mirror of
https://github.com/oDinZu/callirhoe.git
synced 2025-02-22 00:04:52 -05:00
311 lines
12 KiB
Python
Executable File
311 lines
12 KiB
Python
Executable File
#!/usr/bin/env python2.7
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# callirhoe - high quality calendar rendering
|
|
# Copyright (C) 2012-2015 George M. Tzoumas
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see http://www.gnu.org/licenses/
|
|
|
|
"""high quality calendar rendering"""
|
|
|
|
# TODO:
|
|
|
|
# fix auto-measure rendering (cairo)
|
|
# fix plugin loading (without global vars)
|
|
# week markers selectable
|
|
# test layouts
|
|
|
|
# allow to change background color (fill), other than white
|
|
# page spec parse errors
|
|
# mobile themes (e.g. 800x480)
|
|
# photo support (like ImageMagick's polaroid effect)
|
|
# .callirhoe/config : default values for plugins (styles/layouts/lang...) and cmdline
|
|
|
|
# MAYBE-TODO:
|
|
# implement various data sources
|
|
# auto-landscape? should aim for matrix or bars?
|
|
# allow /usr/bin/date-like formatting %x...
|
|
# improve file matching with __init__ when lang known
|
|
# styles and geometries could be merged, css-like
|
|
# then we can apply a chain of --style a --style b ...
|
|
# and b inherits from a and so on
|
|
# however, this would require dynamically creating a class that inherits from others...
|
|
|
|
# CANNOT UPGRADE TO argparse !!! -- how to handle [[month] year] form?
|
|
|
|
import calendar
|
|
import sys
|
|
import time
|
|
import optparse
|
|
import lib.xcairo as xcairo
|
|
import lib.holiday as holiday
|
|
import lib
|
|
|
|
from lib.plugin import *
|
|
|
|
|
|
# TODO: SEE IF IT CAN BE MOVED INTO lib.plugin ...
|
|
def import_plugin(plugin_paths, cat, longcat, longcat2, listopt, preset):
|
|
"""import a plugin making it visible
|
|
|
|
I{Example:}
|
|
|
|
>>> Language = import_plugin(get_plugin_paths(), "lang", "language", "languages", "--list-languages", "EN")
|
|
|
|
@param plugin_paths: list of plugin search paths
|
|
@param cat: short category name (for folder name)
|
|
@param longcat: long category name
|
|
@param longcat2: long category name in plural form
|
|
@param listopt: option name
|
|
@param preset: default value
|
|
@rtype: module
|
|
|
|
@note: Aimed for internal use with I{lang}, I{style}, I{geom}, I{layouts}.
|
|
"""
|
|
try:
|
|
found = []
|
|
for path in plugin_paths:
|
|
found += available_files(path, cat, preset)
|
|
if len(found) == 0: raise IOError
|
|
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,
|
|
preset,listopt))
|
|
except ImportError:
|
|
sys.exit("callirhoe: error loading %s definition '%s'" % (longcat, preset))
|
|
|
|
def print_examples():
|
|
"""print usage examples"""
|
|
print """Examples:
|
|
|
|
Create a calendar of the current year (by default in a 4x3 grid):
|
|
$ callirhoe my_calendar.pdf
|
|
|
|
Same as above, but in landscape mode (3x4) (for printing):
|
|
$ callirhoe --landscape my_calendar.pdf
|
|
|
|
Landscape via rotation (for screen):
|
|
$ callirhoe --paper=a4w --rows=3 my_calendar.pdf
|
|
|
|
Let's try with bars instead of boxes:
|
|
$ callirhoe -t bars my_calendar.pdf
|
|
|
|
In landscape mode, one row only looks quite good:
|
|
$ callirhoe -t bars --landscape --rows=1 my_calendar.pdf
|
|
|
|
How about a more flat look?
|
|
$ callirhoe -t sparse -s bw_sparse --rows=1 --cols=3 my_calendar.pdf
|
|
|
|
Calendar of 24 consecutive months, starting from current month:
|
|
$ callirhoe 0:24 0 my_calendar.pdf
|
|
|
|
Create a 600-dpi PNG file so that we can edit it with some effects in order to print an A3 poster:
|
|
$ callirhoe my_poster.png --paper=a3 --dpi=600 --opaque
|
|
|
|
Create a calendar as a full-hd wallpaper (1920x1080):
|
|
$ callirhoe wallpaper.png --paper=-1920:-1080 --opaque --rows=3 --no-shadow -s rainbow-gfs
|
|
and do some magic with ImageMagick! ;)
|
|
$ convert wallpaper.png -negate fancy.png
|
|
|
|
"""
|
|
|
|
|
|
def add_list_option(parser, opt):
|
|
"""add a --list-I{plugins} option to parser
|
|
|
|
@note: To be used with I{languages}, I{layouts}, I{styles} and I{geometries}.
|
|
"""
|
|
parser.add_option("--list-%s" % opt, action="store_true", dest="list_%s" % opt, default=False,
|
|
help="list available %s" % opt)
|
|
|
|
|
|
def get_parser():
|
|
"""get the argument parser object
|
|
|
|
@rtype: optparse.OptionParser
|
|
"""
|
|
parser = optparse.OptionParser(usage="usage: %prog [options] [[MONTH[-MONTH2|:SPAN]] YEAR] FILE",
|
|
description="High quality calendar rendering with vector graphics. "
|
|
"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 " + 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",
|
|
help="choose layout [%default]")
|
|
parser.add_option("-?", "--layout-help", dest="layouthelp", action="store_true", default=False,
|
|
help="show layout-specific help")
|
|
parser.add_option("--examples", dest="examples", action="store_true",
|
|
help="display some usage examples")
|
|
parser.add_option("-s", "--style", dest="style", default="default",
|
|
help="choose style [%default]")
|
|
parser.add_option("-g", "--geometry", dest="geom", default="default",
|
|
help="choose geometry [%default]")
|
|
parser.add_option("--landscape", action="store_true", dest="landscape", default=False,
|
|
help="landscape mode")
|
|
parser.add_option("--dpi", type="float", default=72.0,
|
|
help="set DPI (for raster output) [%default]")
|
|
parser.add_option("--paper", default="a4",
|
|
help="set paper type; PAPER can be an ISO paper type (a0..a9 or a0w..a9w) or of the "
|
|
"form W:H; positive values correspond to W or H mm, negative values correspond to "
|
|
"-W or -H pixels; 'w' suffix swaps width & height [%default]")
|
|
parser.add_option("--border", type="float", default=3,
|
|
help="set border size (in mm) [%default]")
|
|
parser.add_option("-H", "--with-holidays", action="append", dest="holidays",
|
|
help="load holiday file (can be used multiple times)")
|
|
parser.add_option("--short-monthnames", action="store_true", default=False,
|
|
help="user the short version of month names (defined in language file) [%default]")
|
|
parser.add_option("--long-daynames", action="store_true", default=False,
|
|
help="user the long version of day names (defined in language file) [%default]")
|
|
parser.add_option("-T", "--terse-holidays", action="store_false", dest="multiday_holidays",
|
|
default=True, help="do not print holiday end markers and omit dots")
|
|
|
|
for x in ["languages", "layouts", "styles", "geometries"]:
|
|
add_list_option(parser, x)
|
|
|
|
parser.add_option("--lang-var", action="append", dest="lang_assign",
|
|
help="modify a language variable")
|
|
parser.add_option("--style-var", action="append", dest="style_assign",
|
|
help="modify a style variable, e.g. dom.frame_thickness=0")
|
|
parser.add_option("--geom-var", action="append", dest="geom_assign",
|
|
help="modify a geometry variable")
|
|
return parser
|
|
|
|
|
|
def main_program():
|
|
parser = get_parser()
|
|
|
|
sys.argv,argv2 = lib.extract_parser_args(sys.argv,parser)
|
|
(options,args) = parser.parse_args()
|
|
|
|
list_and_exit = False
|
|
if options.list_languages:
|
|
for x in plugin_list("lang"): print x[0],
|
|
print
|
|
list_and_exit = True
|
|
if options.list_styles:
|
|
for x in plugin_list("style"): print x[0],
|
|
print
|
|
list_and_exit = True
|
|
if options.list_geometries:
|
|
for x in plugin_list("geom"): print x[0],
|
|
print
|
|
list_and_exit = True
|
|
if options.list_layouts:
|
|
for x in plugin_list("layouts"): print x[0],
|
|
print
|
|
list_and_exit = True
|
|
if list_and_exit: return
|
|
|
|
plugin_paths = get_plugin_paths()
|
|
Language = import_plugin(plugin_paths, "lang", "language", "languages", "--list-languages", options.lang)
|
|
Style = import_plugin(plugin_paths, "style", "style", "styles", "--list-styles", options.style)
|
|
Geometry = import_plugin(plugin_paths, "geom", "geometry", "geometries", "--list-geometries", options.geom)
|
|
Layout = import_plugin(plugin_paths, "layouts", "layout", "layouts", "--list-layouts", options.layout)
|
|
|
|
for x in argv2:
|
|
if '=' in x: x = x[0:x.find('=')]
|
|
if not Layout.parser.has_option(x):
|
|
parser.error("invalid option %s; use --help (-h) or --layout-help (-?) to see available options" % x)
|
|
|
|
(loptions,largs) = Layout.parser.parse_args(argv2)
|
|
|
|
if options.layouthelp:
|
|
#print "Help for layout:", options.layout
|
|
Layout.parser.print_help()
|
|
return
|
|
|
|
if options.examples:
|
|
print_examples()
|
|
return
|
|
|
|
# we can put it separately together with Layout; but we load Layout *after* lang,style,geom
|
|
if len(args) < 1 or len(args) > 3:
|
|
parser.print_help()
|
|
return
|
|
|
|
#if (len(args[-1]) == 4 and args[-1].isdigit()):
|
|
# print "WARNING: file name '%s' looks like a year, writing anyway..." % args[-1]
|
|
|
|
# the usual "beware of exec()" crap applies here... but come on,
|
|
# this is a SCRIPTING language, you can always hack the source code!!!
|
|
if options.lang_assign:
|
|
for x in options.lang_assign: exec "Language.%s" % x
|
|
if options.style_assign:
|
|
for x in options.style_assign: exec "Style.%s" % x
|
|
if options.geom_assign:
|
|
for x in options.geom_assign: exec "Geometry.%s" % x
|
|
|
|
calendar.long_month_name = Language.long_month_name
|
|
calendar.long_day_name = Language.long_day_name
|
|
calendar.short_month_name = Language.short_month_name
|
|
calendar.short_day_name = Language.short_day_name
|
|
|
|
if len(args) == 1:
|
|
Year = time.localtime()[0]
|
|
Month, MonthSpan = 1, 12
|
|
Outfile = args[0]
|
|
elif len(args) == 2:
|
|
Year = lib.parse_year(args[0])
|
|
Month, MonthSpan = 1, 12
|
|
Outfile = args[1]
|
|
elif len(args) == 3:
|
|
Month, MonthSpan = lib.parse_month_range(args[0])
|
|
Year = lib.parse_year(args[1])
|
|
Outfile = args[2]
|
|
|
|
if MonthSpan == 0:
|
|
raise lib.Abort("callirhoe: empty calendar requested, aborting")
|
|
|
|
Geometry.landscape = options.landscape
|
|
xcairo.XDPI = options.dpi
|
|
Geometry.pagespec = options.paper
|
|
Geometry.border = options.border
|
|
|
|
hprovider = holiday.HolidayProvider(Style.dom, Style.dom_weekend,
|
|
Style.dom_holiday, Style.dom_weekend_holiday,
|
|
Style.dom_multi, Style.dom_weekend_multi, options.multiday_holidays)
|
|
|
|
if options.holidays:
|
|
for f in options.holidays:
|
|
hprovider.load_holiday_file(f)
|
|
|
|
if options.long_daynames:
|
|
Language.day_name = Language.long_day_name
|
|
else:
|
|
Language.day_name = Language.short_day_name
|
|
|
|
if options.short_monthnames:
|
|
Language.month_name = Language.short_month_name
|
|
else:
|
|
Language.month_name = Language.long_month_name
|
|
|
|
renderer = Layout.CalendarRenderer(Outfile, Year, Month, MonthSpan,
|
|
(Style,Geometry,Language), hprovider, lib._version, loptions)
|
|
renderer.render()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main_program()
|
|
except lib.Abort as e:
|
|
sys.exit(e.args[0])
|