merge 'phantome' branch

git-svn-id: https://callirhoe.googlecode.com/svn/trunk@233 81c8bb96-aa45-f2e2-0eef-c4fa4a15c6df
This commit is contained in:
geortz@gmail.com 2014-11-06 17:09:07 +00:00
commit 923470b2b9
11 changed files with 329 additions and 147 deletions

View File

@ -12,7 +12,7 @@ George M. Tzoumas <geortz@gmail.com>
CONTRIBUTORS
------------
Neels Hofmeyr <neels@hofmeyr.de>
Nick Kavalieris <ph4ntome@gmail.com>
LANGUAGE DEFINITIONS
--------------------

View File

@ -1,3 +1,10 @@
*******************************
* Version 0.4.1 (Nov 2014) *
*******************************
+ packaging support for Python/Arch Linux {Nick}
! calmagick: fixed bug regarding --min-size being greater than --max-size
* refactored some common code between callirhoe and calmagick
*******************************
* Version 0.4.0 (Oct 2014) *
*******************************

70
INSTALL
View File

@ -6,6 +6,12 @@
QUICK INSTALLATION GUIDE
CONTENTS
1) FROM COMPRESSED ARCHIVE
2) FROM SVN
3) INSTALLING INTO BINARY PATH
4) INSTALLATION FOR ARCH LINUX
(rough installation guide for novice users...)
1) FROM COMPRESSED ARCHIVE
@ -13,8 +19,9 @@ QUICK INSTALLATION GUIDE
Download the latest version from the project's page,
or directly from:
https://callirhoe.googlecode.com/svn/wiki/releases/
You end up with a file named callirhoe-X.Y.Z.7z where X.Y.Z the version number (for example 0.4.0).
You end up with a file named callirhoe-X.Y.Z.7z where X.Y.Z the version
number (for example 0.4.0).
Extract the contents of the archive
$ 7z x callirhoe-X.Y.Z.7z
@ -26,13 +33,8 @@ Now you can launch the program, e.g.
$ ./callirhoe.py foo.pdf
You may want to add a link to your path, $HOME/bin or /usr/local/bin:
$ ln -s `pwd`/callirhoe.py $HOME/bin/callirhoe
You can do the same with calmagick.py. You may also install it system-wide,
for example in /opt. In this case, keep in mind, that ~/.callirhoe/ is also
searched for additional definitions, styles etc.
See section 3 for how to install callirhoe so that it lies in your
executable path.
2) FROM SVN
@ -51,3 +53,53 @@ $ svn up
You can launch the program as usual:
$ ./callirhoe.py foo.pdf
3) INSTALLING INTO BINARY PATH
You can add a link to your path, $HOME/bin or /usr/local/bin:
$ ln -s `pwd`/callirhoe.py $HOME/bin/callirhoe
You can do the same with calmagick.py. You may also install it
system-wide, for example in /opt. In this case, keep in mind, that
~/.callirhoe/ is also searched for additional definitions, styles etc.
If you do not plan to mess with the source, you may create a binary
python package. This is not exactly a binary, it is a zip archive
containing compiled python bytecode, which is quite compact. To do so,
simply run:
$ make
This will create two executables, 'callirhoe' and 'calmagick'. Now you
can install them into your binary path as follows:
$ make install
this will typically install to /usr/local/bin (and the holiday files into
/usr/local/share/callirhoe/holidays). You can specify another prefix:
$ make install DESTDIR=/my/other/dir
Now you can remove the source dir, as it is no longer needed.
4) INSTALLATION FOR ARCH LINUX
There is a PKGBUILD file you can use to install. Normally you get just
the PKGBUILD from the webpage from AUR (<INSERT LINK HERE>). It is also
included in the source distribution, but this is a bit redundant (see
below).
Place the PKGBUILD into a directory and run:
$ makepkg -si
( -s will automatically install missing depedencies; also note that this
will redownload the source from the svn )
Arch will do the rest for you.
In the unlikely event that you don't have "makepkg" already installed
you can find information about it's installation here:
https://wiki.archlinux.org/index.php/Arch_User_Repository

16
Makefile Normal file
View File

@ -0,0 +1,16 @@
DESTDIR=/usr/local
all:
cd scripts && ./make_pkg
install: install-package
install-package:
mkdir -p $(DESTDIR)/bin
mkdir -p $(DESTDIR)/share/callirhoe/holidays
install -m755 callirhoe $(DESTDIR)/bin/callirhoe
install -m755 calmagick $(DESTDIR)/bin/calmagick
install -m644 holidays/* $(DESTDIR)/share/callirhoe/holidays/
clean:
rm -f callirhoe calmagick

View File

@ -44,21 +44,13 @@
# CANNOT UPGRADE TO argparse !!! -- how to handle [[month] year] form?
_version = "0.4.0"
_copyright = """Copyright (C) 2012-2014 George M. Tzoumas
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."""
import calendar
import sys
import time
import optparse
import lib.xcairo as xcairo
import lib.holiday as holiday
class Abort(Exception):
pass
import lib
from lib.plugin import *
# TODO: SEE IF IT CAN BE MOVED INTO lib.plugin ...
@ -80,12 +72,16 @@ def import_plugin(plugin_paths, cat, longcat, longcat2, listopt, preset):
@note: Aimed for internal use with I{lang}, I{style}, I{geom}, I{layouts}.
"""
try:
found = available_files(plugin_paths[0], cat, preset) + available_files(plugin_paths[1], cat, preset)
found = []
for path in plugin_paths:
found += available_files(path, cat, preset)
if len(found) == 0: raise IOError
old = sys.path[0];
sys.path[0] = found[0][1]
m = __import__("%s.%s" % (cat,preset), globals(), locals(), [ "*" ])
sys.path[0] = old
if found[0][1] == "resource:":
m = __import__("%s.%s" % (cat,preset), globals(), locals(), [ "*" ])
else:
sys.path.insert(0, found[0][1])
m = __import__("%s.%s" % (cat,preset), globals(), locals(), [ "*" ])
sys.path.pop(0)
return m
except IOError:
sys.exit("callirhoe: %s definition '%s' not found, use %s to see available definitions" % (longcat,
@ -136,103 +132,6 @@ def add_list_option(parser, opt):
parser.add_option("--list-%s" % opt, action="store_true", dest="list_%s" % opt, default=False,
help="list available %s" % opt)
def atoi(s, lower_bound=None, upper_bound=None, prefix=''):
"""convert string to integer, exiting on error (for cmdline parsing)
@param lower_bound: perform additional check so that value >= I{lower_bound}
@param upper_bound: perform additional check so that value <= I{upper_bound}
@param prefix: output prefix for error reporting
@rtype: int
"""
try:
k = int(s);
if lower_bound is not None:
if k < lower_bound:
raise Abort(prefix + "value '" + s +"' out of range: should not be less than %d" % lower_bound)
if upper_bound is not None:
if k > upper_bound:
raise Abort(prefix + "value '" + s +"' out of range: should not be greater than %d" % upper_bound)
except ValueError as e:
raise Abort(prefix + "invalid integer value '" + s +"'")
return k
def _parse_month(mstr):
"""get a month value (0-12) from I{mstr}, exiting on error (for cmdline parsing)
@rtype: int
"""
m = atoi(mstr,lower_bound=0,upper_bound=12,prefix='month: ')
if m == 0: m = time.localtime()[1]
return m
def parse_month_range(s):
"""return (Month,Span) by parsing range I{Month}, I{Month1}-I{Month2} or I{Month}:I{Span}
@rtype: (int,int)
"""
if ':' in s:
t = s.split(':')
if len(t) != 2: raise Abort("invalid month range '" + s + "'")
Month = _parse_month(t[0])
MonthSpan = atoi(t[1],lower_bound=0,prefix='month span: ')
elif '-' in s:
t = s.split('-')
if len(t) != 2: raise Abort("invalid month range '" + s + "'")
Month = _parse_month(t[0])
MonthSpan = atoi(t[1],lower_bound=Month+1,prefix='month range: ') - Month + 1
else:
Month = _parse_month(s)
MonthSpan = 1
return (Month,MonthSpan)
def parse_year(ystr):
"""get a year value (>=0) from I{ystr}, exiting on error (for cmdline parsing)
@rtype: int
"""
y = atoi(ystr,lower_bound=0,prefix='year: ')
if y == 0: y = time.localtime()[0]
return y
def extract_parser_args(arglist, parser, pos = -1):
"""extract options belonging to I{parser} along with I{pos} positional arguments
@param arglist: argument list to extract
@param parser: parser object to be used for extracting
@param pos: number of positional options to be extracted
if I{pos}<0 then all positional arguments are extracted, otherwise,
only I{pos} arguments are extracted. arglist[0] (usually sys.argv[0]) is also positional
argument!
@rtype: ([str,...],[str,...])
@return: tuple (argv1,argv2) with extracted argument list and remaining argument list
"""
argv = [[],[]]
posc = 0
push_value = None
for x in arglist:
if push_value:
push_value.append(x)
push_value = None
continue
# get option name (long options stop at '=')
y = x[0:x.find('=')] if '=' in x else x
if x[0] == '-':
if parser.has_option(y):
argv[0].append(x)
if not x.startswith('--') and parser.get_option(y).takes_value():
push_value = argv[0]
else:
argv[1].append(x)
else:
if pos < 0:
argv[0].append(x)
else:
argv[posc >= pos].append(x)
posc += 1
return tuple(argv)
def get_parser():
"""get the argument parser object
@ -243,7 +142,7 @@ def get_parser():
"By default, a calendar of the current year in pdf format is written to FILE. "
"Alternatively, you can select a specific YEAR (0=current), "
"and a month range from MONTH (0-12, 0=current) to MONTH2 or for SPAN months.",
version="callirhoe " + _version + '\n' + _copyright)
version="callirhoe " + lib._version + '\n' + lib._copyright)
parser.add_option("-l", "--lang", dest="lang", default="EN",
help="choose language [%default]")
parser.add_option("-t", "--layout", dest="layout", default="classic",
@ -289,7 +188,7 @@ def get_parser():
def main_program():
parser = get_parser()
sys.argv,argv2 = extract_parser_args(sys.argv,parser)
sys.argv,argv2 = lib.extract_parser_args(sys.argv,parser)
(options,args) = parser.parse_args()
list_and_exit = False
@ -360,16 +259,16 @@ def main_program():
Month, MonthSpan = 1, 12
Outfile = args[0]
elif len(args) == 2:
Year = parse_year(args[0])
Year = lib.parse_year(args[0])
Month, MonthSpan = 1, 12
Outfile = args[1]
elif len(args) == 3:
Month, MonthSpan = parse_month_range(args[0])
Year = parse_year(args[1])
Month, MonthSpan = lib.parse_month_range(args[0])
Year = lib.parse_year(args[1])
Outfile = args[2]
if MonthSpan == 0:
raise Abort("callirhoe: empty calendar requested, aborting")
raise lib.Abort("callirhoe: empty calendar requested, aborting")
Geometry.landscape = options.landscape
xcairo.XDPI = options.dpi
@ -395,11 +294,11 @@ def main_program():
Language.month_name = Language.long_month_name
renderer = Layout.CalendarRenderer(Outfile, Year, Month, MonthSpan,
(Style,Geometry,Language), hprovider, _version, loptions)
(Style,Geometry,Language), hprovider, lib._version, loptions)
renderer.render()
if __name__ == "__main__":
try:
main_program()
except Abort as e:
except lib.Abort as e:
sys.exit(e.args[0])

View File

@ -34,7 +34,7 @@ import optparse
import Queue
import threading
from callirhoe import extract_parser_args, parse_month_range, parse_year, atoi, Abort, _version, _copyright
import lib
from lib.geom import rect_rel_scale
# MAYBE-TODO
@ -198,7 +198,7 @@ def get_parser():
If IMAGE is a single file, then a calendar of the current month is overlayed. If IMAGE contains wildcards,
then every month is generated according to the --range option, advancing one month for every photo file.
Photos will be reused in a round-robin fashion if more calendar
months are requested.""", version="callirhoe.CalMagick " + _version + '\n' + _copyright)
months are requested.""", version="callirhoe.CalMagick " + lib._version + '\n' + lib._copyright)
parser.add_option("--outdir", default=".",
help="set directory for the output image(s); directory will be created if it does not already exist [%default]")
parser.add_option("--outfile", default=None,
@ -298,13 +298,15 @@ photos.""")
def check_parsed_options(options):
"""set (remaining) default values and check validity of various option combinations"""
if options.min_size is None:
options.min_size = 0.333 if options.placement in ['min','max','random'] else 0.05
options.min_size = min(0.333,options.max_size) if options.placement in ['min','max','random'] else min(0.05,options.max_size)
if options.min_size > options.max_size:
raise lib.Abort("calmagick: --min-size should not be greater than --max-size")
if options.sample is not None and not options.range:
raise Abort("calmagick: --sample requested without --range")
raise lib.Abort("calmagick: --sample requested without --range")
if options.outfile is not None and options.range:
raise Abort("calmagick: you cannot specify both --outfile and --range options")
raise lib.Abort("calmagick: you cannot specify both --outfile and --range options")
if options.sample is not None and options.shuffle:
raise Abort("calmagick: you cannot specify both --shuffle and --sample options")
raise lib.Abort("calmagick: you cannot specify both --shuffle and --sample options")
if options.shuffle:
options.sample = 0
if options.sample is None:
@ -370,7 +372,7 @@ def get_outfile(infile, outdir, base_prefix, format, hint=None):
if format: ext = '.' + format
outfile = os.path.join(outdir,base_prefix+base+ext)
if os.path.exists(outfile) and os.path.samefile(infile, outfile):
if hint: raise Abort("calmagick: --outfile same as input, aborting")
if hint: raise lib.Abort("calmagick: --outfile same as input, aborting")
outfile = os.path.join(outdir,base_prefix+base+'_calmagick'+ext)
return outfile
@ -523,7 +525,7 @@ def compose_calendar(img, outimg, options, callirhoe_args, magick_args, stats=No
if '/' in options.ratio:
tmp = options.ratio.split('/')
calratio = float(atoi(tmp[0],1))/atoi(tmp[1],1)
calratio = float(lib.atoi(tmp[0],1))/lib.atoi(tmp[1],1)
else:
calratio = float(options.ratio)
if options.placement == 'min' or options.placement == 'max':
@ -596,9 +598,9 @@ def parse_range(s,hint=None):
"""
if '/' in s:
t = s.split('/')
month,span = parse_month_range(t[0])
month,span = lib.parse_month_range(t[0])
if hint and span == 0: span = hint
year = parse_year(t[1])
year = lib.parse_year(t[1])
margs = []
for m in xrange(span):
margs += [(month,year)]
@ -606,7 +608,7 @@ def parse_range(s,hint=None):
if month > 12: month = 1; year += 1
return margs
else:
raise Abort("calmagick: invalid range format '%s'" % options.range)
raise lib.Abort("calmagick: invalid range format '%s'" % options.range)
def range_worker(q,ev,i):
"""worker thread for a (I{Month,Year}) tuple
@ -638,7 +640,7 @@ def main_program():
parser = get_parser()
magick_args = parse_magick_args()
sys.argv,argv2 = extract_parser_args(sys.argv,parser,2)
sys.argv,argv2 = lib.extract_parser_args(sys.argv,parser,2)
(options,args) = parser.parse_args()
check_parsed_options(options)
@ -682,12 +684,12 @@ def main_program():
else:
img = args[0]
if not os.path.isfile(img):
raise Abort("calmagick: input image '%s' does not exist" % img)
raise lib.Abort("calmagick: input image '%s' does not exist" % img)
outimg = get_outfile(img,options.outdir,'',options.format,options.outfile)
compose_calendar(img, outimg, options, argv2, magick_args)
if __name__ == '__main__':
try:
main_program()
except Abort as e:
except lib.Abort as e:
sys.exit(e.args[0])

View File

@ -0,0 +1,108 @@
import time
_version = "0.4.1"
_copyright = """Copyright (C) 2012-2014 George M. Tzoumas
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."""
class Abort(Exception):
pass
def extract_parser_args(arglist, parser, pos = -1):
"""extract options belonging to I{parser} along with I{pos} positional arguments
@param arglist: argument list to extract
@param parser: parser object to be used for extracting
@param pos: number of positional options to be extracted
if I{pos}<0 then all positional arguments are extracted, otherwise,
only I{pos} arguments are extracted. arglist[0] (usually sys.argv[0]) is also positional
argument!
@rtype: ([str,...],[str,...])
@return: tuple (argv1,argv2) with extracted argument list and remaining argument list
"""
argv = [[],[]]
posc = 0
push_value = None
for x in arglist:
if push_value:
push_value.append(x)
push_value = None
continue
# get option name (long options stop at '=')
y = x[0:x.find('=')] if '=' in x else x
if x[0] == '-':
if parser.has_option(y):
argv[0].append(x)
if not x.startswith('--') and parser.get_option(y).takes_value():
push_value = argv[0]
else:
argv[1].append(x)
else:
if pos < 0:
argv[0].append(x)
else:
argv[posc >= pos].append(x)
posc += 1
return tuple(argv)
def atoi(s, lower_bound=None, upper_bound=None, prefix=''):
"""convert string to integer, exiting on error (for cmdline parsing)
@param lower_bound: perform additional check so that value >= I{lower_bound}
@param upper_bound: perform additional check so that value <= I{upper_bound}
@param prefix: output prefix for error reporting
@rtype: int
"""
try:
k = int(s);
if lower_bound is not None:
if k < lower_bound:
raise Abort(prefix + "value '" + s +"' out of range: should not be less than %d" % lower_bound)
if upper_bound is not None:
if k > upper_bound:
raise Abort(prefix + "value '" + s +"' out of range: should not be greater than %d" % upper_bound)
except ValueError as e:
raise Abort(prefix + "invalid integer value '" + s +"'")
return k
def _parse_month(mstr):
"""get a month value (0-12) from I{mstr}, exiting on error (for cmdline parsing)
@rtype: int
"""
m = atoi(mstr,lower_bound=0,upper_bound=12,prefix='month: ')
if m == 0: m = time.localtime()[1]
return m
def parse_month_range(s):
"""return (Month,Span) by parsing range I{Month}, I{Month1}-I{Month2} or I{Month}:I{Span}
@rtype: (int,int)
"""
if ':' in s:
t = s.split(':')
if len(t) != 2: raise Abort("invalid month range '" + s + "'")
Month = _parse_month(t[0])
MonthSpan = atoi(t[1],lower_bound=0,prefix='month span: ')
elif '-' in s:
t = s.split('-')
if len(t) != 2: raise Abort("invalid month range '" + s + "'")
Month = _parse_month(t[0])
MonthSpan = atoi(t[1],lower_bound=Month+1,prefix='month range: ') - Month + 1
else:
Month = _parse_month(s)
MonthSpan = 1
return (Month,MonthSpan)
def parse_year(ystr):
"""get a year value (>=0) from I{ystr}, exiting on error (for cmdline parsing)
@rtype: int
"""
y = atoi(ystr,lower_bound=0,prefix='year: ')
if y == 0: y = time.localtime()[0]
return y

View File

@ -26,6 +26,11 @@ import sys
import os.path
import glob
try:
import resources
except:
resources = None
def available_files(parent, dir, fmatch = None):
"""find parent/dir/*.py files to be used for plugins
@ -38,7 +43,7 @@ def available_files(parent, dir, fmatch = None):
good = False
res = []
pattern = parent + "/" + dir + "/*.py"
for x in glob.glob(pattern):
for x in glob.glob(pattern) if not parent.startswith('resource:') else resources.resource_list[dir]:
basex = os.path.basename(x)
if basex == "__init__.py": good = True
elif basex.startswith('_'):
@ -56,7 +61,10 @@ def plugin_list(cat):
@rtype: [str,...]
"""
plugin_paths = get_plugin_paths()
return available_files(plugin_paths[0], cat) + available_files(plugin_paths[1], cat)
result = []
for path in plugin_paths:
result += available_files(path, cat)
return result
# cat = lang (category)
# longcat = language
@ -67,6 +75,9 @@ def plugin_list(cat):
def get_plugin_paths():
"""return the plugin search paths
@rtype: [str,str]
@rtype: [str,str,..]
"""
return [ os.path.expanduser("~/.callirhoe"), sys.path[0] if sys.path[0] else "." ]
result = [ os.path.expanduser("~/.callirhoe"), sys.path[0] if sys.path[0] else "." ]
if resources:
result.append("resource:")
return result

34
scripts/PKGBUILD Normal file
View File

@ -0,0 +1,34 @@
# Maintainer: Nick Kavalieris <ph4ntome[at]gmail[dot]com>
# Upstream Author : George Tzoumas <geotz[at]gmail[dot]com>
# For contributors check the AUTHORS file
# This file is also included in the source tree for purposes of completeness
# The normal way of aqusition is from AUR: <INSERT LINK HERE>
pkgname=callirhoe
pkgver=228
pkgrel=1
pkgdesc="PDF Calendar creator with high quality vector graphics"
url="https://code.google.com/p/callirhoe/"
arch=('any')
license=('GPLv3')
depends=('python2' 'imagemagick' 'python2-cairo' 'subversion')
source=("$pkgname::svn+https://callirhoe.googlecode.com/svn/branches/phantome")
md5sums=('SKIP')
pkgver() {
cd "$pkgname"
svnversion | tr -d [A-z] | sed 's/ *//g'
}
build() {
cd "${srcdir}/${pkgname}"
make
}
package() {
cd "${srcdir}/${pkgname}"
install -Dm644 COPYING "$pkgdir/usr/share/licenses/$pkgname/COPYING"
make DESTDIR="$pkgdir/usr" install
}

42
scripts/make_pkg Executable file
View File

@ -0,0 +1,42 @@
#!/bin/bash
make_python_zip() {
base="$1"
tempdir="$2"
curdir=`pwd`
for i in `find $tempdir -type f -name "*.py" | grep -v "__init__"`; do
python2.7 -m py_compile $i
rm $i
done
cd $tempdir
zip -q -r $curdir/$base.zip *
cd $curdir
rm -rf $tempdir
echo '#!/usr/bin/env python2.7' | cat - $base.zip > $base
chmod 755 $base
rm -f $base.zip
}
create_callirhoe_package() {
DIR=`mktemp -d -t callirhoe.XXX`
tar c {geom,lang,layouts,lib,style}/*.py | tar x -C "$DIR"
cp callirhoe.py "$DIR/__main__.py"
python2.7 scripts/make_resources_list.py > "$DIR/lib/resources.py"
make_python_zip callirhoe "$DIR"
}
create_calmagick_package() {
# Create Calmagick package
DIR=`mktemp -d -t callirhoe.XXX`
tar c lib/{__init__,geom}.py | tar x -C "$DIR"
cp calmagick.py "$DIR/__main__.py"
make_python_zip calmagick "$DIR"
}
set -e
cd ..
[[ -x callirhoe ]] && echo "callirhoe package seems to exist, skipping; rm 'calliroe' to recreate" || create_callirhoe_package
[[ -x calmagick ]] && echo "calmagick package seems to exist, skipping; rm 'calmagick' to recreate" || create_calmagick_package

View File

@ -0,0 +1,11 @@
#!/usr/bin/env python2.7
import glob
res = dict()
for x in ['lang', 'style', 'layouts', 'geom']:
res[x] = glob.glob('%s/*.py' % x)
print 'resource_list = {}'
for x in res.keys():
print 'resource_list["%s"] = %s' % (x, str(res[x]))