gramps/src/cli/plug/__init__.py
Nick Hall da75a38762 5326: Revert r18842
svn: r18848
2012-02-10 19:57:57 +00:00

654 lines
27 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001-2007 Donald N. Allingham
# Copyright (C) 2008 Lukasz Rymarczyk
# Copyright (C) 2008 Raphael Ackermann
# Copyright (C) 2008-2011 Brian G. Matherly
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2011 Paul Franklin
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
# cli.plug.__init__
#
# $Id$
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
from gen.ggettext import gettext as _
import traceback
import os
import sys
import logging
log = logging.getLogger(".")
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
import Utils
from gen.plug import BasePluginManager
from gen.plug.docgen import (StyleSheet, StyleSheetList, PaperStyle,
PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc)
from gen.plug.menu import (FamilyOption, PersonOption, NoteOption,
MediaOption, PersonListOption, NumberOption,
BooleanOption, DestinationOption, StringOption,
TextOption, EnumeratedListOption, Option)
from gen.display.name import displayer as name_displayer
from Errors import ReportError
from gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK,
CATEGORY_GRAPHVIZ, CATEGORY_CODE)
from gen.plug.report._paper import paper_sizes
import const
import DbState
from cli.grampscli import CLIManager
import cli.user
#------------------------------------------------------------------------
#
# Private Functions
#
#------------------------------------------------------------------------
def _convert_str_to_match_type(str_val, type_val):
"""
Returns a value representing str_val that is the same type as type_val.
"""
str_val = str_val.strip()
ret_type = type(type_val)
if ret_type in (str, unicode):
if ( str_val.startswith("'") and str_val.endswith("'") ) or \
( str_val.startswith('"') and str_val.endswith('"') ):
# Remove enclosing quotes
return unicode(str_val[1:-1])
else:
return unicode(str_val)
elif ret_type == int:
if str_val.isdigit():
return int(str_val)
else:
print "'%s' is not an integer number" % str_val
return 0
elif ret_type == float:
try:
return float(str_val)
except ValueError:
print "'%s' is not a decimal number" % str_val
return 0.0
elif ret_type == bool:
if str_val == str(True):
return True
elif str_val == str(False):
return False
else:
print "'%s' is not a boolean-- try 'True' or 'False'" % str_val
return False
elif ret_type == list:
ret_val = []
if not ( str_val.startswith("[") and str_val.endswith("]") ):
print "'%s' is not a list-- try: [%s]" % (str_val, str_val)
return ret_val
entry = ""
quote_type = None
# Search through characters between the brackets
for char in str_val[1:-1]:
if (char == "'" or char == '"') and quote_type == None:
# This character starts a string
quote_type = char
elif char == quote_type:
# This character ends a string
quote_type = None
elif quote_type == None and char == ",":
# This character ends an entry
ret_val.append(entry.strip())
entry = ""
quote_type = None
else:
entry += char
if entry != "":
# Add the last entry
ret_val.append(entry.strip())
return ret_val
def _validate_options(options, dbase):
"""
Validate all options by making sure that their values are consistent with
the database.
menu: The Menu class
dbase: the database the options will be applied to
"""
if not hasattr(options, "menu"):
return
menu = options.menu
for name in menu.get_all_option_names():
option = menu.get_option_by_name(name)
if isinstance(option, PersonOption):
pid = option.get_value()
person = dbase.get_person_from_gramps_id(pid)
if not person:
person = dbase.get_default_person()
if not person:
try:
phandle = dbase.iter_person_handles().next()
except StopIteration:
phandle = None
person = dbase.get_person_from_handle(phandle)
if not person:
print _("ERROR: Please specify a person")
if person:
option.set_value(person.get_gramps_id())
elif isinstance(option, FamilyOption):
fid = option.get_value()
family = dbase.get_family_from_gramps_id(fid)
if not family:
person = dbase.get_default_person()
family_list = []
family_handle = None
if person:
family_list = person.get_family_handle_list()
if family_list:
family_handle = family_list[0]
else:
try:
family_handle = dbase.iter_family_handles().next()
except StopIteration:
family_handle = None
if family_handle:
family = dbase.get_family_from_handle(family_handle)
option.set_value(family.get_gramps_id())
else:
print _("ERROR: Please specify a family")
#------------------------------------------------------------------------
#
# Command-line report
#
#------------------------------------------------------------------------
class CommandLineReport(object):
"""
Provide a way to generate report from the command line.
"""
def __init__(self, database, name, category, option_class, options_str_dict,
noopt=False):
pmgr = BasePluginManager.get_instance()
self.__textdoc_plugins = []
self.__drawdoc_plugins = []
self.__bookdoc_plugins = []
for plugin in pmgr.get_docgen_plugins():
if plugin.get_text_support() and plugin.get_extension():
self.__textdoc_plugins.append(plugin)
if plugin.get_draw_support() and plugin.get_extension():
self.__drawdoc_plugins.append(plugin)
if plugin.get_text_support() and \
plugin.get_draw_support() and \
plugin.get_extension():
self.__bookdoc_plugins.append(plugin)
self.database = database
self.category = category
self.format = None
self.option_class = option_class(name, database)
if category == CATEGORY_GRAPHVIZ:
# Need to include GraphViz options
self.__gvoptions = graphdoc.GVOptions()
menu = self.option_class.menu
self.__gvoptions.add_menu_options(menu)
for name in menu.get_all_option_names():
if name not in self.option_class.options_dict:
self.option_class.options_dict[name] = \
menu.get_option_by_name(name).get_value()
self.option_class.load_previous_values()
_validate_options(self.option_class, database)
self.show = options_str_dict.pop('show', None)
self.options_str_dict = options_str_dict
self.init_standard_options(noopt)
self.init_report_options()
self.parse_options()
self.show_options()
def init_standard_options(self, noopt):
"""
Initialize the options that are hard-coded into the report system.
"""
self.options_dict = {
'of' : self.option_class.handler.module_name,
'off' : self.option_class.handler.get_format_name(),
'style' : \
self.option_class.handler.get_default_stylesheet_name(),
'papers' : self.option_class.handler.get_paper_name(),
'papero' : self.option_class.handler.get_orientation(),
'paperml' : self.option_class.handler.get_margins()[0],
'papermr' : self.option_class.handler.get_margins()[1],
'papermt' : self.option_class.handler.get_margins()[2],
'papermb' : self.option_class.handler.get_margins()[3],
'css' : self.option_class.handler.get_css_filename(),
}
self.options_help = {
'of' : ["=filename", "Output file name. MANDATORY", ""],
'off' : ["=format", "Output file format.", []],
'style' : ["=name", "Style name.", ""],
'papers' : ["=name", "Paper size name.", ""],
'papero' : ["=num", "Paper orientation number.", ""],
'paperml' : ["=num", "Left paper margin", "Size in cm"],
'papermr' : ["=num", "Right paper margin", "Size in cm"],
'papermt' : ["=num", "Top paper margin", "Size in cm"],
'papermb' : ["=num", "Bottom paper margin", "Size in cm"],
'css' : ["=css filename", "CSS filename to use, html format"
" only", ""],
}
if noopt:
return
self.options_help['of'][2] = os.path.join(const.USER_HOME,
"whatever_name")
if self.category == CATEGORY_TEXT:
for plugin in self.__textdoc_plugins:
self.options_help['off'][2].append(
plugin.get_extension() + "\t" + plugin.get_description() )
elif self.category == CATEGORY_DRAW:
for plugin in self.__drawdoc_plugins:
self.options_help['off'][2].append(
plugin.get_extension() + "\t" + plugin.get_description() )
elif self.category == CATEGORY_BOOK:
for plugin in self.__bookdoc_plugins:
self.options_help['off'][2].append(
plugin.get_extension() + "\t" + plugin.get_description() )
elif self.category == CATEGORY_GRAPHVIZ:
for graph_format in graphdoc.FORMATS:
self.options_help['off'][2].append(
graph_format["ext"] + "\t" + graph_format["descr"] )
else:
self.options_help['off'][2] = "NA"
self.options_help['papers'][2] = \
[ paper.get_name() for paper in paper_sizes
if paper.get_name() != _("Custom Size") ]
self.options_help['papero'][2] = [
"%d\tPortrait" % PAPER_PORTRAIT,
"%d\tLandscape" % PAPER_LANDSCAPE ]
self.options_help['css'][2] = os.path.join(const.USER_HOME,
"whatever_name.css")
if self.category in (CATEGORY_TEXT, CATEGORY_DRAW):
default_style = StyleSheet()
self.option_class.make_default_style(default_style)
# Read all style sheets available for this item
style_file = self.option_class.handler.get_stylesheet_savefile()
self.style_list = StyleSheetList(style_file, default_style)
self.options_help['style'][2] = self.style_list.get_style_names()
def init_report_options(self):
"""
Initialize the options that are defined by each report.
"""
if self.category == CATEGORY_BOOK: # a Book Report has no "menu"
for key in self.option_class.options_dict:
self.options_dict[key] = self.option_class.options_dict[key]
self.options_help[key] = \
self.option_class.options_help[key][:3]
# a Book Report can't have HTML output so "css" is meaningless
self.options_dict.pop('css')
if not hasattr(self.option_class, "menu"):
return
menu = self.option_class.menu
for name in menu.get_all_option_names():
option = menu.get_option_by_name(name)
self.options_dict[name] = option.get_value()
self.options_help[name] = [ "", option.get_help() ]
if isinstance(option, PersonOption):
id_list = []
for person_handle in self.database.get_person_handles(True):
person = self.database.get_person_from_handle(person_handle)
id_list.append("%s\t%s" % (
person.get_gramps_id(),
name_displayer.display(person)))
self.options_help[name].append(id_list)
elif isinstance(option, FamilyOption):
id_list = []
for family in self.database.iter_families():
mname = ""
fname = ""
mhandle = family.get_mother_handle()
if mhandle:
mother = self.database.get_person_from_handle(mhandle)
if mother:
mname = name_displayer.display(mother)
fhandle = family.get_father_handle()
if fhandle:
father = self.database.get_person_from_handle(fhandle)
if father:
fname = name_displayer.display(father)
text = "%s:\t%s, %s" % \
(family.get_gramps_id(), fname, mname)
id_list.append(text)
self.options_help[name].append(id_list)
elif isinstance(option, NoteOption):
id_list = []
for nhandle in self.database.get_note_handles():
note = self.database.get_note_from_handle(nhandle)
id_list.append(note.get_gramps_id())
self.options_help[name].append(id_list)
elif isinstance(option, MediaOption):
id_list = []
for mhandle in self.database.get_media_object_handles():
mobject = self.database.get_object_from_handle(mhandle)
id_list.append(mobject.get_gramps_id())
self.options_help[name].append(id_list)
elif isinstance(option, PersonListOption):
self.options_help[name].append("")
elif isinstance(option, NumberOption):
self.options_help[name].append("A number")
elif isinstance(option, BooleanOption):
self.options_help[name].append(["False", "True"])
elif isinstance(option, DestinationOption):
self.options_help[name].append("A file system path")
elif isinstance(option, StringOption):
self.options_help[name].append("Any text")
elif isinstance(option, TextOption):
self.options_help[name].append(
"A list of text values. Each entry in the list "
"represents one line of text." )
elif isinstance(option, EnumeratedListOption):
ilist = []
for (value, description) in option.get_items():
ilist.append("%s\t%s" % (value, description))
self.options_help[name].append(ilist)
elif isinstance(option, Option):
self.options_help[name].append(option.get_help())
else:
print _("Unknown option: %s") % option
print _(" Valid options are:"), ", ".join(
self.options_dict.keys())
print (_(" Use '%(donottranslate)s' to see description "
"and acceptable values") %
{'donottranslate' : "show=option"})
def parse_options(self):
"""
Load the options that the user has entered.
"""
if not hasattr(self.option_class, "menu"):
menu = None
else:
menu = self.option_class.menu
menu_opt_names = menu.get_all_option_names()
for opt in self.options_str_dict:
if opt in self.options_dict:
self.options_dict[opt] = \
_convert_str_to_match_type(self.options_str_dict[opt],
self.options_dict[opt])
self.option_class.handler.options_dict[opt] = \
self.options_dict[opt]
if menu and opt in menu_opt_names:
option = menu.get_option_by_name(opt)
option.set_value(self.options_dict[opt])
else:
print _("Ignoring unknown option: %s") % opt
print _(" Valid options are:"), ", ".join(
self.options_dict.keys())
print (_(" Use '%(donottranslate)s' to see description "
"and acceptable values") %
{'donottranslate' : "show=option"})
self.option_class.handler.output = self.options_dict['of']
self.css_filename = None
_chosen_format = None
if self.category == CATEGORY_TEXT:
for plugin in self.__textdoc_plugins:
if plugin.get_extension() == self.options_dict['off']:
self.format = plugin.get_basedoc()
self.css_filename = self.options_dict['css']
if self.format is None:
# Pick the first one as the default.
self.format = self.__textdoc_plugins[0].get_basedoc()
_chosen_format = self.__textdoc_plugins[0].get_extension()
elif self.category == CATEGORY_DRAW:
for plugin in self.__drawdoc_plugins:
if plugin.get_extension() == self.options_dict['off']:
self.format = plugin.get_basedoc()
if self.format is None:
# Pick the first one as the default.
self.format = self.__drawdoc_plugins[0].get_basedoc()
_chosen_format = self.__drawdoc_plugins[0].get_extension()
elif self.category == CATEGORY_BOOK:
for plugin in self.__bookdoc_plugins:
if plugin.get_extension() == self.options_dict['off']:
self.format = plugin.get_basedoc()
if self.format is None:
# Pick the first one as the default.
self.format = self.__bookdoc_plugins[0].get_basedoc()
_chosen_format = self.__bookdoc_plugins[0].get_extension()
elif self.category == CATEGORY_GRAPHVIZ:
for graph_format in graphdoc.FORMATS:
if graph_format['ext'] == self.options_dict['off']:
if not self.format: # choose the first one, not the last
self.format = graph_format["class"]
if self.format is None:
# Pick the first one as the default.
self.format = graphdoc.FORMATS[0]["class"]
_chosen_format = graphdoc.FORMATS[0]["ext"]
else:
self.format = None
if _chosen_format and self.options_str_dict.has_key('off'):
print (_("Ignoring '%(notranslate1)s=%(notranslate2)s' "
"and using '%(notranslate1)s=%(notranslate3)s'.") %
{'notranslate1' : "off",
'notranslate2' : self.options_dict['off'],
'notranslate3' : _chosen_format})
print (_("Use '%(notranslate)s' to see valid values.") %
{'notranslate' : "show=off"})
for paper in paper_sizes:
if paper.get_name() == self.options_dict['papers']:
self.paper = paper
self.option_class.handler.set_paper(self.paper)
self.orien = self.options_dict['papero']
self.marginl = self.options_dict['paperml']
self.marginr = self.options_dict['papermr']
self.margint = self.options_dict['papermt']
self.marginb = self.options_dict['papermb']
if self.category in (CATEGORY_TEXT, CATEGORY_DRAW):
default_style = StyleSheet()
self.option_class.make_default_style(default_style)
# Read all style sheets available for this item
style_file = self.option_class.handler.get_stylesheet_savefile()
self.style_list = StyleSheetList(style_file, default_style)
# Get the selected stylesheet
style_name = self.option_class.handler.get_default_stylesheet_name()
self.selected_style = self.style_list.get_style_sheet(style_name)
def show_options(self):
"""
Print available options on the CLI.
"""
if not self.show:
return
elif self.show == 'all':
print _(" Available options:")
for key in sorted(self.options_dict.keys()):
if key in self.options_help:
opt = self.options_help[key]
# Make the output nicer to read, assume a tab has 8 spaces
tabs = '\t\t' if len(key) < 10 else '\t'
optmsg = " %s%s%s (%s)" % (key, tabs, opt[1], opt[0])
print optmsg.encode(sys.getfilesystemencoding())
else:
optmsg = " %s" % key
print optmsg.encode(sys.getfilesystemencoding())
print (_(" Use '%(donottranslate)s' to see description "
"and acceptable values") %
{'donottranslate' : "show=option"})
elif self.show in self.options_help:
opt = self.options_help[self.show]
tabs = '\t\t' if len(self.show) < 10 else '\t'
print ' %s%s%s (%s)' % (self.show, tabs, opt[1], opt[0])
print _(" Available values are:")
vals = opt[2]
if isinstance(vals, (list, tuple)):
for val in vals:
optmsg = " %s" % val
print optmsg.encode(sys.getfilesystemencoding())
else:
optmsg = " %s" % opt[2]
print optmsg.encode(sys.getfilesystemencoding())
else:
#there was a show option given, but the option is invalid
print (_("option '%(optionname)s' not valid. "
"Use '%(donottranslate)s' to see all valid options.") %
{'optionname' : self.show, 'donottranslate' : "show=all"})
#------------------------------------------------------------------------
#
# Command-line report generic task
#
#------------------------------------------------------------------------
def cl_report(database, name, category, report_class, options_class,
options_str_dict):
err_msg = _("Failed to write report. ")
clr = CommandLineReport(database, name, category, options_class,
options_str_dict)
# Exit here if show option was given
if clr.show:
return
# write report
try:
if category in [CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK]:
clr.option_class.handler.doc = clr.format(
clr.selected_style,
PaperStyle(clr.paper,clr.orien,clr.marginl,
clr.marginr,clr.margint,clr.marginb))
elif category == CATEGORY_GRAPHVIZ:
clr.option_class.handler.doc = clr.format(
clr.option_class,
PaperStyle(clr.paper,clr.orien,clr.marginl,
clr.marginr,clr.margint,clr.marginb))
if clr.css_filename is not None and \
hasattr(clr.option_class.handler.doc, 'set_css_filename'):
clr.option_class.handler.doc.set_css_filename(clr.css_filename)
MyReport = report_class(database, clr.option_class, cli.user.User())
MyReport.doc.init()
MyReport.begin_report()
MyReport.write_report()
MyReport.end_report()
return clr
except ReportError, msg:
(m1, m2) = msg.messages()
print err_msg
print m1
if m2:
print m2
except:
if len(log.handlers) > 0:
log.error(err_msg, exc_info=True)
else:
print >> sys.stderr, err_msg
## Something seems to eat the exception above.
## Hack to re-get the exception:
try:
raise
except:
traceback.print_exc()
def run_report(db, name, **options_str_dict):
"""
Given a database, run a given report.
db is a Db database
name is the name of a report
options_str_dict is the same kind of options
given at the command line. For example:
>>> run_report(db, "ancestor_report", off="txt",
of="ancestor-007.txt", pid="I37")
returns CommandLineReport (clr) if successfully runs the report,
None otherwise.
You can see:
options and values used in clr.option_class.options_dict
filename in clr.option_class.get_output()
"""
dbstate = DbState.DbState()
climanager = CLIManager(dbstate, False) # don't load db
climanager.do_reg_plugins(dbstate, None)
pmgr = BasePluginManager.get_instance()
cl_list = pmgr.get_reg_reports()
clr = None
for pdata in cl_list:
if name == pdata.id:
mod = pmgr.load_plugin(pdata)
if not mod:
#import of plugin failed
return clr
category = pdata.category
report_class = getattr(mod, pdata.reportclass)
options_class = getattr(mod, pdata.optionclass)
if category in (CATEGORY_BOOK, CATEGORY_CODE):
options_class(db, name, category,
options_str_dict)
else:
clr = cl_report(db, name, category,
report_class, options_class,
options_str_dict)
return clr
return clr