532 lines
23 KiB
Python
532 lines
23 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2000-2006 Donald N. Allingham, A. Roitman
|
|
# Copyright (C) 2007-2009 B. Malengier
|
|
# Copyright (C) 2008 Lukasz Rymarczyk
|
|
# Copyright (C) 2008 Raphael Ackermann
|
|
# Copyright (C) 2008 Brian G. Matherly
|
|
# Copyright (C) 2012 Doug Blank
|
|
# Copyright (C) 2012-2013 Paul Franklin
|
|
# Copyright (C) 2017 Serge Noiraud
|
|
#
|
|
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
"""
|
|
Module responsible for handling the command line arguments for Gramps.
|
|
"""
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Standard python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import sys
|
|
import os
|
|
import getopt
|
|
import logging
|
|
import shutil
|
|
from glob import glob
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# gramps modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from gramps.gen.const import (LONGOPTS, SHORTOPTS, USER_PLUGINS, VERSION_DIR,
|
|
HOME_DIR, TEMP_DIR, THUMB_DIR, ENV_DIR, USER_CSS)
|
|
from gramps.gen.utils.cast import get_type_converter
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.gettext
|
|
|
|
_HELP = _("""
|
|
Usage: gramps.py [OPTION...]
|
|
--load-modules=MODULE1,MODULE2,... Dynamic modules to load
|
|
|
|
Help options
|
|
-?, --help Show this help message
|
|
--usage Display brief usage message
|
|
|
|
Application options
|
|
-O, --open=FAMILY_TREE Open Family Tree
|
|
-U, --username=USERNAME Database username
|
|
-P, --password=PASSWORD Database password
|
|
-C, --create=FAMILY_TREE Create on open if new Family Tree
|
|
-i, --import=FILENAME Import file
|
|
-e, --export=FILENAME Export file
|
|
-r, --remove=FAMILY_TREE_PATTERN Remove matching Family Tree(s) (use regular expressions)
|
|
-f, --format=FORMAT Specify Family Tree format
|
|
-a, --action=ACTION Specify action
|
|
-p, --options=OPTIONS_STRING Specify options
|
|
-d, --debug=LOGGER_NAME Enable debug logs
|
|
-l [FAMILY_TREE_PATTERN...] List Family Trees
|
|
-L [FAMILY_TREE_PATTERN...] List Family Trees in Detail
|
|
-t [FAMILY_TREE_PATTERN...] List Family Trees, tab delimited
|
|
-u, --force-unlock Force unlock of Family Tree
|
|
-s, --show Show config settings
|
|
-c, --config=[config.setting[:value]] Set config setting(s) and start Gramps
|
|
-y, --yes Don't ask to confirm dangerous actions (non-GUI mode only)
|
|
-q, --quiet Suppress progress indication output (non-GUI mode only)
|
|
-v, --version Show versions
|
|
-S, --safe Start Gramps in 'Safe mode'
|
|
(temporarily use default settings)
|
|
-D, --default=[APXFE] Reset settings to default;
|
|
A - addons are cleared
|
|
P - Preferences to default
|
|
X - Books are cleared, reports and tool settings to default
|
|
F - filters are cleared
|
|
E - Everything is set to default or cleared
|
|
""")
|
|
|
|
_USAGE = _("""
|
|
Example of usage of Gramps command line interface
|
|
|
|
1. To import four databases (whose formats can be determined from their names)
|
|
and then check the resulting database for errors, one may type:
|
|
gramps -i file1.ged -i file2.gpkg -i ~/db3.gramps -i file4.wft -a tool -p name=check.
|
|
|
|
2. To explicitly specify the formats in the above example, append filenames with appropriate -f options:
|
|
gramps -i file1.ged -f gedcom -i file2.gpkg -f gramps-pkg -i ~/db3.gramps -f gramps-xml -i file4.wft -f wft -a tool -p name=check.
|
|
|
|
3. To record the database resulting from all imports, supply -e flag
|
|
(use -f if the filename does not allow Gramps to guess the format):
|
|
gramps -i file1.ged -i file2.gpkg -e ~/new-package -f gramps-pkg
|
|
|
|
4. To save any error messages of the above example into files outfile and errfile, run:
|
|
gramps -i file1.ged -i file2.dpkg -e ~/new-package -f gramps-pkg >outfile 2>errfile
|
|
|
|
5. To import three databases and start interactive Gramps session with the result:
|
|
gramps -i file1.ged -i file2.gpkg -i ~/db3.gramps
|
|
|
|
6. To open a database and, based on that data, generate timeline report in PDF format
|
|
putting the output into the my_timeline.pdf file:
|
|
gramps -O 'Family Tree 1' -a report -p name=timeline,off=pdf,of=my_timeline.pdf
|
|
|
|
7. To generate a summary of a database:
|
|
gramps -O 'Family Tree 1' -a report -p name=summary
|
|
|
|
8. Listing report options
|
|
Use the name=timeline,show=all to find out about all available options for the timeline report.
|
|
To find out details of a particular option, use show=option_name , e.g. name=timeline,show=off string.
|
|
To learn about available report names, use name=show string.
|
|
|
|
9. To convert a Family Tree on the fly to a .gramps xml file:
|
|
gramps -O 'Family Tree 1' -e output.gramps -f gramps-xml
|
|
|
|
10. To generate a web site into an other locale (in german):
|
|
LANGUAGE=de_DE; LANG=de_DE.UTF-8 gramps -O 'Family Tree 1' -a report -p name=navwebpage,target=/../de
|
|
|
|
11. Finally, to start normal interactive session type:
|
|
gramps
|
|
|
|
Note: These examples are for bash shell.
|
|
Syntax may be different for other shells and for Windows.
|
|
""")
|
|
|
|
#-------------------------------------------------------------------------
|
|
# ArgParser
|
|
#-------------------------------------------------------------------------
|
|
class ArgParser:
|
|
"""
|
|
This class is responsible for parsing the command line arguments (if any)
|
|
given to gramps, and determining if a GUI or a CLI session must be started.
|
|
A filename and/or options may be specified as arguments.
|
|
|
|
The valid options are:
|
|
|
|
-O, --open=FAMILY_TREE Open Family Tree
|
|
-U, --username=USERNAME Database username
|
|
-P, --password=PASSWORD Database password
|
|
-C, --create=FAMILY_TREE Create on open if new Family Tree
|
|
-i, --import=FILENAME Import file
|
|
-e, --export=FILENAME Export file
|
|
-r, --remove=PATTERN Remove matching Family Tree(s)
|
|
-f, --format=FORMAT Specify Family Tree format
|
|
-a, --action=ACTION Specify action
|
|
-p, --options=OPTIONS_STRING Specify options
|
|
-d, --debug=LOGGER_NAME Enable debug logs
|
|
-l [FAMILY_TREE...] List Family Trees
|
|
-L [FAMILY_TREE...] List Family Trees in Detail
|
|
-t [FAMILY_TREE...] List Family Trees, tab delimited
|
|
-u, --force-unlock Force unlock of Family Tree
|
|
-s, --show Show config settings
|
|
-c, --config=SETTINGS Set config setting(s) and start Gramps
|
|
-y, --yes Don't ask to confirm dangerous actions
|
|
-q, --quiet Suppress progress indication output
|
|
-v, --version Show versions
|
|
-h, --help Display the help
|
|
--usage Display usage information
|
|
|
|
If the filename (no options) is specified, the interactive session is
|
|
launched using data from filename. In this mode (filename, no options), the
|
|
rest of the arguments are ignored. This is a mode suitable by default for
|
|
GUI launchers, mime type handlers, and the like.
|
|
|
|
If no filename or -i option is given, a new interactive session (empty
|
|
database) is launched, since no data is given anyway.
|
|
|
|
If -O or -i option is given, but no -e or -a options are given, an
|
|
interactive session is launched with the ``FILENAME`` (specified with -i).
|
|
|
|
If both input (-O or -i) and processing (-e or -a) options are given,
|
|
interactive session will not be launched.
|
|
|
|
When using import or export options (-i or -e), the -f option may be
|
|
specified to indicate the family tree format.
|
|
|
|
Possible values for ``ACTION`` are: 'report', 'book' and 'tool'.
|
|
|
|
Configuration ``SETTINGS`` may be specified using the -c option. The
|
|
settings are of the form config.setting[:value]. If used without a value,
|
|
the setting is shown.
|
|
|
|
If the -y option is given, the user's acceptance of any CLI prompt is
|
|
assumed. (see :meth:`.cli.user.User.prompt`)
|
|
|
|
If the -q option is given, extra noise on sys.stderr, such as progress
|
|
indicators, is suppressed.
|
|
"""
|
|
|
|
def __init__(self, args):
|
|
"""
|
|
Pass the command line arguments on creation.
|
|
"""
|
|
self.args = args
|
|
|
|
self.open_gui = None
|
|
self.open = None
|
|
self.username = None
|
|
self.password = None
|
|
self.exports = []
|
|
self.actions = []
|
|
self.imports = []
|
|
self.removes = []
|
|
self.imp_db_path = None
|
|
self.list = False
|
|
self.list_more = False
|
|
self.list_table = False
|
|
self.database_names = None
|
|
self.help = False
|
|
self.usage = False
|
|
self.force_unlock = False
|
|
self.create = None
|
|
self.quiet = False
|
|
self.auto_accept = False
|
|
|
|
self.errors = []
|
|
self.parse_args()
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Argument parser: sorts out given arguments
|
|
#-------------------------------------------------------------------------
|
|
def parse_args(self):
|
|
"""
|
|
Fill in lists with open, exports, imports, and actions options.
|
|
|
|
Any errors are added to self.errors
|
|
"""
|
|
try:
|
|
options, leftargs = getopt.getopt(self.args[1:],
|
|
SHORTOPTS, LONGOPTS)
|
|
except getopt.GetoptError as msg:
|
|
# Extract the arguments in the list.
|
|
# The % operator replaces the list elements
|
|
# with repr() of the list elements
|
|
# which is OK for latin characters,
|
|
# but not for non latin characters in list elements
|
|
cliargs = "[ "
|
|
for arg in range(len(self.args) - 1):
|
|
cliargs += self.args[arg + 1] + " "
|
|
cliargs += "]"
|
|
# Must first do str() of the msg object.
|
|
msg = str(msg)
|
|
self.errors += [(_('Error parsing the arguments'),
|
|
msg + '\n' +
|
|
_("Error parsing the arguments: %s \n"
|
|
"Type gramps --help for an overview of "
|
|
"commands, or read the manual pages."
|
|
) % cliargs)]
|
|
return
|
|
|
|
# Some args can work on a list of databases:
|
|
if leftargs:
|
|
for opt_ix in range(len(options)):
|
|
option, value = options[opt_ix]
|
|
if option in ['-L', '-l', '-t']:
|
|
self.database_names = leftargs
|
|
leftargs = []
|
|
|
|
if leftargs:
|
|
# if there were an argument without option,
|
|
# use it as a file to open and return
|
|
self.open_gui = leftargs[0]
|
|
print(_("Trying to open: %s ..."
|
|
) % leftargs[0],
|
|
file=sys.stderr)
|
|
#see if force open is on
|
|
for opt_ix in range(len(options)):
|
|
option, value = options[opt_ix]
|
|
if option in ('-u', '--force-unlock'):
|
|
self.force_unlock = True
|
|
break
|
|
return
|
|
|
|
# Go over all given option and place them into appropriate lists
|
|
cleandbg = []
|
|
need_to_quit = False
|
|
for opt_ix in range(len(options)):
|
|
option, value = options[opt_ix]
|
|
if option in ['-O', '--open']:
|
|
self.open = value
|
|
elif option in ['-C', '--create']:
|
|
self.create = value
|
|
elif option in ['-U', '--username']:
|
|
self.username = value
|
|
elif option in ['-P', '--password']:
|
|
self.password = value
|
|
elif option in ['-i', '--import']:
|
|
family_tree_format = None
|
|
if (opt_ix < len(options) - 1
|
|
and options[opt_ix + 1][0] in ('-f', '--format')):
|
|
family_tree_format = options[opt_ix + 1][1]
|
|
self.imports.append((value, family_tree_format))
|
|
elif option in ['-r', '--remove']:
|
|
self.removes.append(value)
|
|
elif option in ['-e', '--export']:
|
|
family_tree_format = None
|
|
if (opt_ix < len(options) - 1
|
|
and options[opt_ix + 1][0] in ('-f', '--format')):
|
|
family_tree_format = options[opt_ix + 1][1]
|
|
abs_name = os.path.abspath(os.path.expanduser(value))
|
|
if not os.path.exists(abs_name):
|
|
# The file doesn't exists, try to create it.
|
|
try:
|
|
open(abs_name, 'w').close()
|
|
os.unlink(abs_name)
|
|
except OSError as e:
|
|
message = _("WARNING: %(strerr)s "
|
|
"(errno=%(errno)s):\n"
|
|
"WARNING: %(name)s\n") % {
|
|
'strerr' : e.strerror,
|
|
'errno' : e.errno,
|
|
'name' : e.filename}
|
|
print(message)
|
|
sys.exit(1)
|
|
self.exports.append((value, family_tree_format))
|
|
elif option in ['-a', '--action']:
|
|
action = value
|
|
if action not in ('report', 'tool', 'book'):
|
|
print(_("Unknown action: %s. Ignoring."
|
|
) % action,
|
|
file=sys.stderr)
|
|
continue
|
|
options_str = ""
|
|
if (opt_ix < len(options)-1
|
|
and options[opt_ix+1][0] in ('-p', '--options')):
|
|
options_str = options[opt_ix+1][1]
|
|
self.actions.append((action, options_str))
|
|
elif option in ['-d', '--debug']:
|
|
print(_('setup debugging'), value, file=sys.stderr)
|
|
logger = logging.getLogger(value)
|
|
logger.setLevel(logging.DEBUG)
|
|
cleandbg += [opt_ix]
|
|
elif option in ['-l']:
|
|
self.list = True
|
|
elif option in ['-L']:
|
|
self.list_more = True
|
|
elif option in ['-t']:
|
|
self.list_table = True
|
|
elif option in ['-s', '--show']:
|
|
from gramps.gen.config import config
|
|
print(_("Gramps config settings from %s:"
|
|
) % config.filename)
|
|
for sect in config.data:
|
|
for setting in config.data[sect]:
|
|
print("%s.%s=%s" % (sect, setting,
|
|
repr(config.data[sect][setting])))
|
|
print()
|
|
sys.exit(0)
|
|
elif option in ['-c', '--config']:
|
|
from gramps.gen.config import config
|
|
cfg_name = value
|
|
set_value = False
|
|
if cfg_name:
|
|
if ":" in cfg_name:
|
|
cfg_name, new_value = cfg_name.split(":", 1)
|
|
set_value = True
|
|
if config.has_default(cfg_name):
|
|
setting_value = config.get(cfg_name)
|
|
print(_("Current Gramps config setting: "
|
|
"%(name)s:%(value)s"
|
|
) % {'name' : cfg_name,
|
|
'value' : repr(setting_value)},
|
|
file=sys.stderr)
|
|
if set_value:
|
|
# does a user want the default config value?
|
|
if new_value in ("DEFAULT", _("DEFAULT")):
|
|
new_value = config.get_default(cfg_name)
|
|
else:
|
|
converter = get_type_converter(setting_value)
|
|
new_value = converter(new_value)
|
|
config.set(cfg_name, new_value)
|
|
# translators: indent "New" to match "Current"
|
|
print(_(" New Gramps config setting: "
|
|
"%(name)s:%(value)s"
|
|
) % {'name' : cfg_name,
|
|
'value' : repr(config.get(cfg_name))},
|
|
file=sys.stderr)
|
|
else:
|
|
need_to_quit = True
|
|
else:
|
|
print(_("Gramps: no such config setting: '%s'"
|
|
) % cfg_name,
|
|
file=sys.stderr)
|
|
need_to_quit = True
|
|
cleandbg += [opt_ix]
|
|
elif option in ['-h', '-?', '--help']:
|
|
self.help = True
|
|
elif option in ['-u', '--force-unlock']:
|
|
self.force_unlock = True
|
|
elif option in ['--usage']:
|
|
self.usage = True
|
|
elif option in ['-y', '--yes']:
|
|
self.auto_accept = True
|
|
elif option in ['-q', '--quiet']:
|
|
self.quiet = True
|
|
elif option in ['-S', '--safe']:
|
|
cleandbg += [opt_ix]
|
|
elif option in ['-D', '--default']:
|
|
def rmtree(path):
|
|
if os.path.isdir(path):
|
|
shutil.rmtree(path, ignore_errors=True)
|
|
|
|
if 'E' in value or 'A' in value: # clear addons
|
|
rmtree(USER_PLUGINS)
|
|
if 'E' in value or 'P' in value: # clear ini preferences
|
|
for fil in glob(os.path.join(VERSION_DIR, "*.*")):
|
|
if "custom_filters.xml" in fil:
|
|
continue
|
|
os.remove(fil)
|
|
# create gramps.ini so config won't load the one from an
|
|
# older version of Gramps.
|
|
with open(os.path.join(VERSION_DIR, 'gramps.ini'), 'w'):
|
|
pass
|
|
if 'E' in value or 'F' in value: # clear filters
|
|
fil = os.path.join(VERSION_DIR, "custom_filters.xml")
|
|
if os.path.isfile(fil):
|
|
os.remove(fil)
|
|
if 'E' in value or 'X' in value: # clear xml reports/tools
|
|
for fil in glob(os.path.join(HOME_DIR, "*.xml")):
|
|
os.remove(fil)
|
|
if 'E' in value or 'Z' in value: # clear upgrade zips
|
|
for fil in glob(os.path.join(HOME_DIR, "*.zip")):
|
|
os.remove(fil)
|
|
if 'E' in value: # Everything else
|
|
rmtree(TEMP_DIR)
|
|
rmtree(THUMB_DIR)
|
|
rmtree(USER_CSS)
|
|
rmtree(ENV_DIR)
|
|
rmtree(os.path.join(HOME_DIR, "maps"))
|
|
for fil in glob(os.path.join(HOME_DIR, "*")):
|
|
if os.path.isfile(fil):
|
|
os.remove(fil)
|
|
sys.exit(0) # Done with Default
|
|
|
|
#clean options list
|
|
cleandbg.reverse()
|
|
for ind in cleandbg:
|
|
del options[ind]
|
|
|
|
if (len(options) > 0
|
|
and self.open is None
|
|
and self.imports == []
|
|
and self.removes == []
|
|
and not (self.list
|
|
or self.list_more
|
|
or self.list_table
|
|
or self.help)):
|
|
# Extract and convert to unicode the arguments in the list.
|
|
# The % operator replaces the list elements with repr() of
|
|
# the list elements, which is OK for latin characters
|
|
# but not for non-latin characters in list elements
|
|
cliargs = "[ "
|
|
for arg in range(len(self.args) - 1):
|
|
cliargs += self.args[arg + 1] + ' '
|
|
cliargs += "]"
|
|
self.errors += [(_('Error parsing the arguments'),
|
|
_("Error parsing the arguments: %s \n"
|
|
"To use in the command-line mode, supply at "
|
|
"least one input file to process."
|
|
) % cliargs)]
|
|
if need_to_quit:
|
|
sys.exit(0)
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Determine the need for GUI
|
|
#-------------------------------------------------------------------------
|
|
def need_gui(self):
|
|
"""
|
|
Determine whether we need a GUI session for the given tasks.
|
|
"""
|
|
if self.errors:
|
|
#errors in argument parsing ==> give cli error, no gui needed
|
|
return False
|
|
|
|
if len(self.removes) > 0:
|
|
return False
|
|
|
|
if self.list or self.list_more or self.list_table or self.help:
|
|
return False
|
|
|
|
if self.open_gui:
|
|
# No-option argument, definitely GUI
|
|
return True
|
|
|
|
# If we have data to work with:
|
|
if self.open or self.imports:
|
|
if self.exports or self.actions:
|
|
# have both data and what to do with it => no GUI
|
|
return False
|
|
elif self.create:
|
|
if self.open: # create an empty DB, open a GUI to fill it
|
|
return True
|
|
else: # create a DB, then do the import, with no GUI
|
|
self.open = self.create
|
|
return False
|
|
else:
|
|
# data given, but no action/export => GUI
|
|
return True
|
|
|
|
# No data, can only do GUI here
|
|
return True
|
|
|
|
def print_help(self):
|
|
"""
|
|
If the user gives the --help or -h option, print the output to terminal.
|
|
"""
|
|
if self.help:
|
|
print(_HELP)
|
|
sys.exit(0)
|
|
|
|
def print_usage(self):
|
|
"""
|
|
If the user gives the --usage print the output to terminal.
|
|
"""
|
|
if self.usage:
|
|
print(_USAGE)
|
|
sys.exit(0)
|