7016: new cmdline switches -y/--yes and -q/--quiet

reapply r22916 from trunk

testing on gramps40: UT

svn: r22932
This commit is contained in:
Vassilii Khachaturov 2013-08-29 12:41:31 +00:00
parent 4bfb2e082c
commit 0afdb2e361
8 changed files with 200 additions and 53 deletions

View File

@ -54,7 +54,6 @@ from .clidbman import CLIDbManager, NAME_FILE, find_locker_name
from gramps.gen.plug import BasePluginManager from gramps.gen.plug import BasePluginManager
from gramps.gen.plug.report import CATEGORY_BOOK, CATEGORY_CODE, BookList from gramps.gen.plug.report import CATEGORY_BOOK, CATEGORY_CODE, BookList
from .plug import cl_report, cl_book from .plug import cl_report, cl_book
from .user import User
from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = glocale.translation.gettext
@ -153,10 +152,15 @@ class ArgHandler(object):
def __init__(self, dbstate, parser, sessionmanager, def __init__(self, dbstate, parser, sessionmanager,
errorfunc=None, gui=False): errorfunc=None, gui=False):
from .user import User
self.dbstate = dbstate self.dbstate = dbstate
self.sm = sessionmanager self.sm = sessionmanager
self.errorfunc = errorfunc self.errorfunc = errorfunc
self.gui = gui self.gui = gui
self.user = User(error=self.__error,
auto_accept=parser.auto_accept,
quiet=parser.quiet)
if self.gui: if self.gui:
self.actions = [] self.actions = []
self.list = False self.list = False
@ -283,19 +287,12 @@ class ArgHandler(object):
else: else:
fullpath = os.path.abspath(os.path.expanduser(fname)) fullpath = os.path.abspath(os.path.expanduser(fname))
if os.path.exists(fullpath): if os.path.exists(fullpath):
self.__error(_("WARNING: Output file already exists!\n" message = _("WARNING: Output file already exists!\n"
"WARNING: It will be overwritten:\n %s") % "WARNING: It will be overwritten:\n %s"
fullpath) ) % fullpath
try: accepted = self.user.prompt(_('OK to overwrite?'), message,
if sys.version_info[0] < 3: _('yes'), _('no'))
ask = raw_input if accepted:
else:
ask = input
answer = ask(_('OK to overwrite? (yes/no) '))
except EOFError:
print()
sys.exit(0)
if answer.upper() in ('Y', 'YES', _('YES').upper()):
self.__error(_("Will overwrite the existing file: %s") self.__error(_("Will overwrite the existing file: %s")
% fullpath) % fullpath)
else: else:
@ -551,7 +548,7 @@ class ArgHandler(object):
for plugin in pmgr.get_import_plugins(): for plugin in pmgr.get_import_plugins():
if family_tree_format == plugin.get_extension(): if family_tree_format == plugin.get_extension():
import_function = plugin.get_import_function() import_function = plugin.get_import_function()
import_function(self.dbstate.db, filename, User()) import_function(self.dbstate.db, filename, self.user)
if not self.cl: if not self.cl:
if self.imp_db_path: if self.imp_db_path:
@ -573,7 +570,7 @@ class ArgHandler(object):
for plugin in pmgr.get_export_plugins(): for plugin in pmgr.get_export_plugins():
if family_tree_format == plugin.get_extension(): if family_tree_format == plugin.get_extension():
export_function = plugin.get_export_function() export_function = plugin.get_export_function()
export_function(self.dbstate.db, filename, User(error=self.__error)) export_function(self.dbstate.db, filename, self.user)
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #

View File

@ -53,7 +53,6 @@ from gramps.gen.utils.file import get_unicode_path_from_env_var
from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = glocale.translation.gettext
# Note: Make sure to edit const.py.in POPT_TABLE too!
_HELP = _(""" _HELP = _("""
Usage: gramps.py [OPTION...] Usage: gramps.py [OPTION...]
--load-modules=MODULE1,MODULE2,... Dynamic modules to load --load-modules=MODULE1,MODULE2,... Dynamic modules to load
@ -77,6 +76,8 @@ Application options
-u, --force-unlock Force unlock of Family Tree -u, --force-unlock Force unlock of Family Tree
-s, --show Show config settings -s, --show Show config settings
-c, --config=[config.setting[:value]] Set config setting(s) and start Gramps -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 -v, --version Show versions
""") """)
@ -183,6 +184,8 @@ class ArgParser(object):
self.force_unlock = False self.force_unlock = False
self.create = None self.create = None
self.runqml = False self.runqml = False
self.quiet = False
self.auto_accept = False
self.errors = [] self.errors = []
self.parse_args() self.parse_args()
@ -198,19 +201,21 @@ class ArgParser(object):
Possible: Possible:
1/ Just the family tree (name or database dir) 1/ Just the family tree (name or database dir)
2/ -O, --open: Open of a family tree 2/ -O --open: Open of a family tree
3/ -i, --import: Import a family tree of any format understood by 3/ -i --import: Import a family tree of any format understood by
an importer, optionally provide -f to indicate format an importer, optionally provide -f to indicate format
4/ -e, --export: export a family tree in required format, optionally 4/ -e --export: export a family tree in required format, optionally
provide -f to indicate format provide -f to indicate format
5/ -f, --format=FORMAT : format after a -i or -e option 5/ -f --format=FORMAT : format after a -i or -e option
6/ -a, --action: An action (possible: 'report', 'tool') 6/ -a --action: An action (possible: 'report', 'tool')
7/ -p, --options=OPTIONS_STRING : specify options 7/ -p --options=OPTIONS_STRING : specify options
8/ -u, --force-unlock: A locked database can be unlocked by giving 8/ -u --force-unlock: A locked database can be unlocked by giving
this argument when opening it this argument when opening it
9/ -s --show : Show config settings 9/ -s --show : Show config settings
10/ -c --config=config.setting:value : Set config.setting and start 10/ -c --config=config.setting:value : Set config.setting and start
Gramps without :value, the actual config.setting is shown Gramps without :value, the actual config.setting is shown
11/ -y --yes: assume user's acceptance of any CLI prompt (see cli.user.User.prompt)
12/ -q --quiet: suppress extra noise on sys.stderr, such as progress indicators
""" """
try: try:
@ -259,23 +264,23 @@ class ArgParser(object):
need_to_quit = False need_to_quit = False
for opt_ix in range(len(options)): for opt_ix in range(len(options)):
option, value = options[opt_ix] option, value = options[opt_ix]
if option in ( '-O', '--open'): if option in ['-O', '--open']:
self.open = value self.open = value
elif option in ( '-C', '--create'): elif option in ['-C', '--create']:
self.create = value self.create = value
elif option in ( '-i', '--import'): elif option in ['-i', '--import']:
family_tree_format = None family_tree_format = None
if opt_ix < len(options) - 1 \ if opt_ix < len(options) - 1 \
and options[opt_ix + 1][0] in ( '-f', '--format'): and options[opt_ix + 1][0] in ( '-f', '--format'):
family_tree_format = options[opt_ix + 1][1] family_tree_format = options[opt_ix + 1][1]
self.imports.append((value, family_tree_format)) self.imports.append((value, family_tree_format))
elif option in ( '-e', '--export' ): elif option in ['-e', '--export']:
family_tree_format = None family_tree_format = None
if opt_ix < len(options) - 1 \ if opt_ix < len(options) - 1 \
and options[opt_ix + 1][0] in ( '-f', '--format'): and options[opt_ix + 1][0] in ( '-f', '--format'):
family_tree_format = options[opt_ix + 1][1] family_tree_format = options[opt_ix + 1][1]
self.exports.append((value, family_tree_format)) self.exports.append((value, family_tree_format))
elif option in ( '-a', '--action' ): elif option in ['-a', '--action']:
action = value action = value
if action not in ('report', 'tool', 'book'): if action not in ('report', 'tool', 'book'):
print(_("Unknown action: %s. Ignoring.") % action, print(_("Unknown action: %s. Ignoring.") % action,
@ -286,18 +291,18 @@ class ArgParser(object):
and options[opt_ix+1][0] in ( '-p', '--options' ): and options[opt_ix+1][0] in ( '-p', '--options' ):
options_str = options[opt_ix+1][1] options_str = options[opt_ix+1][1]
self.actions.append((action, options_str)) self.actions.append((action, options_str))
elif option in ('-d', '--debug'): elif option in ['-d', '--debug']:
print(_('setup debugging'), value, file=sys.stderr) print(_('setup debugging'), value, file=sys.stderr)
logger = logging.getLogger(value) logger = logging.getLogger(value)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
cleandbg += [opt_ix] cleandbg += [opt_ix]
elif option in ('-l'): elif option in ['-l']:
self.list = True self.list = True
elif option in ('-L'): elif option in ['-L']:
self.list_more = True self.list_more = True
elif option in ('-t'): elif option in ['-t']:
self.list_table = True self.list_table = True
elif option in ('-s','--show'): elif option in ['-s','--show']:
print(_("Gramps config settings from %s:") print(_("Gramps config settings from %s:")
% config.filename) % config.filename)
for section in config.data: for section in config.data:
@ -307,7 +312,7 @@ class ArgParser(object):
repr(config.data[section][setting]))) repr(config.data[section][setting])))
print() print()
sys.exit(0) sys.exit(0)
elif option in ('-c', '--config'): elif option in ['-c', '--config']:
setting_name = value setting_name = value
set_value = False set_value = False
if setting_name: if setting_name:
@ -339,14 +344,18 @@ class ArgParser(object):
% setting_name, file=sys.stderr) % setting_name, file=sys.stderr)
need_to_quit = True need_to_quit = True
cleandbg += [opt_ix] cleandbg += [opt_ix]
elif option in ('-h', '-?', '--help'): elif option in ['-h', '-?', '--help']:
self.help = True self.help = True
elif option in ('-u', '--force-unlock'): elif option in ['-u', '--force-unlock']:
self.force_unlock = True self.force_unlock = True
elif option in ('--usage'): elif option in ['--usage']:
self.usage = True self.usage = True
elif option in ('--qml'): elif option in ['--qml']:
self.runqml = True self.runqml = True
elif option in ['-y', '--yes']:
self.auto_accept = True
elif option in ['-q', '--quiet']:
self.quiet = True
#clean options list #clean options list
cleandbg.reverse() cleandbg.reverse()

View File

@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2013 Vassilii Khachaturov <vassilii@tarunz.org>
#
# 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
#
# $Id$
""" Unittest for argparser.py """
from __future__ import print_function
import unittest
from ..argparser import ArgParser
import sys
try:
if sys.version_info < (3,3):
from mock import Mock
else:
from unittest.mock import Mock
MOCKING = True
except:
MOCKING = False
print ("Mocking disabled", sys.exc_info()[0:2])
class TestArgParser(unittest.TestCase):
def setUp(self):
pass
def create_parser(*self_and_args):
return ArgParser(list(self_and_args))
def triggers_option_error(self, option):
ap = self.create_parser(option)
return (str(ap.errors).find("option "+option)>=0, ap)
def test_wrong_argument_triggers_option_error(self):
bad,ap = self.triggers_option_error('--I-am-a-wrong-argument')
assert bad, ap.__dict__
def test_y_shortopt_sets_auto_accept(self):
bad,ap = self.triggers_option_error('-y')
assert not bad, ap.errors
assert ap.auto_accept
def test_yes_longopt_sets_auto_accept(self):
bad,ap = self.triggers_option_error('--yes')
assert not bad, ap.errors
assert ap.auto_accept
def test_q_shortopt_sets_quiet(self):
bad,ap = self.triggers_option_error('-q')
assert not bad, ap.errors
assert ap.quiet
def test_quiet_longopt_sets_quiet(self):
bad,ap = self.triggers_option_error('--quiet')
assert not bad, ap.errors
assert ap.quiet
def test_quiet_exists_by_default(self):
ap = self.create_parser()
assert hasattr(ap,'quiet')
def test_auto_accept_unset_by_default(self):
ap = self.create_parser()
assert not ap.auto_accept
if __name__ == "__main__":
unittest.main()

View File

@ -32,9 +32,9 @@ import sys
try: try:
if sys.version_info < (3,3): if sys.version_info < (3,3):
from mock import Mock from mock import Mock, NonCallableMock
else: else:
from unittest.mock import Mock from unittest.mock import Mock, NonCallableMock
MOCKING = True MOCKING = True
@ -47,8 +47,8 @@ class TestUser_prompt(unittest.TestCase):
self.real_user = user.User() self.real_user = user.User()
if MOCKING: if MOCKING:
self.user = user.User() self.user = user.User()
self.user._fileout = Mock() self.user._fileout = Mock(spec=sys.stderr)
self.user._input = Mock() self.user._input = Mock(spec=input)
def test_default_fileout_has_write(self): def test_default_fileout_has_write(self):
assert hasattr(self.real_user._fileout, 'write') assert hasattr(self.real_user._fileout, 'write')
@ -100,10 +100,45 @@ class TestUser_prompt(unittest.TestCase):
self.assert_prompt_contains_text(TestUser.REJECT) self.assert_prompt_contains_text(TestUser.REJECT)
if not MOCKING: #don't use SKIP, to avoid counting a skipped test if not MOCKING: #don't use SKIP, to avoid counting a skipped test
def testManualRun(self): def test_manual_run(self):
b = self.real_user.prompt( b = self.real_user.prompt(
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT) TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT)
print ("Returned: {}".format(b)) print ("Returned: {}".format(b))
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
def test_auto_accept_accepts_without_prompting(self):
u = user.User(auto_accept=True)
u._fileout = Mock(spec=sys.stderr)
assert u.prompt(
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT
), "True expected!"
assert len(u._fileout.method_calls) == 0, list(u._fileout.method_calls)
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
def test_EOFError_in_prompt_caught_as_False(self):
self.user._input.configure_mock(
side_effect = EOFError,
return_value = TestUser.REJECT)
assert not self.user.prompt(
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT
), "False expected!"
self.user._input.assert_called_once_with()
@unittest.skipUnless(MOCKING, "Requires unittest.mock to run")
class TestUser_quiet(unittest.TestCase):
def setUp(self):
self.user = user.User(quiet=True)
self.user._fileout = Mock(spec=sys.stderr)
def test_progress_can_begin_step_end(self):
self.user.begin_progress("Foo", "Bar", 0)
for i in range(10):
self.user.step_progress()
self.user.end_progress()
def tearDown(self):
assert len(self.user._fileout.method_calls
) == 0, list(self.user._fileout.method_calls)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -58,7 +58,7 @@ class User(user.User):
This class provides a means to interact with the user via CLI. This class provides a means to interact with the user via CLI.
It implements the interface in gramps.gen.user.User() It implements the interface in gramps.gen.user.User()
""" """
def __init__(self, callback=None, error=None): def __init__(self, callback=None, error=None, auto_accept=False, quiet=False):
""" """
Init. Init.
@ -70,6 +70,15 @@ class User(user.User):
self.current_step = 0; self.current_step = 0;
self._input = raw_input if sys.version_info[0] < 3 else input self._input = raw_input if sys.version_info[0] < 3 else input
def yes(*args):
return True
if auto_accept:
self.prompt = yes
if quiet:
self.begin_progress = self.end_progress = self.step_progress = \
self._default_callback = yes
def begin_progress(self, title, message, steps): def begin_progress(self, title, message, steps):
""" """
Start showing a progress indicator to the user. Start showing a progress indicator to the user.
@ -127,13 +136,17 @@ class User(user.User):
@returns: the user's answer to the question @returns: the user's answer to the question
@rtype: bool @rtype: bool
""" """
text = "{t} {m} ({y}/{n}): ".format( text = "{t}\n{m} ([{y}]/{n}): ".format(
t = title, t = title,
m = message, m = message,
y = accept_label, y = accept_label,
n = reject_label) n = reject_label)
print (text, file = self._fileout) # TODO python3 add flush=True print (text, file = self._fileout) # TODO python3 add flush=True
return self._input() == accept_label try:
reply = self._input()
return reply == "" or reply == accept_label
except EOFError:
return False
def warn(self, title, warning=""): def warn(self, title, warning=""):
""" """

View File

@ -303,8 +303,10 @@ LONGOPTS = [
"usage", "usage",
"version", "version",
"qml", "qml",
"yes",
"quiet",
] ]
SHORTOPTS = "O:C:i:e:f:a:p:d:c:lLthuv?s" SHORTOPTS = "O:C:i:e:f:a:p:d:c:lLthuv?syq"
GRAMPS_UUID = uuid.UUID('516cd010-5a41-470f-99f8-eb22f1098ad6') GRAMPS_UUID = uuid.UUID('516cd010-5a41-470f-99f8-eb22f1098ad6')

View File

@ -69,6 +69,9 @@ class User():
else: else:
self.callback_function(percentage) self.callback_function(percentage)
else: else:
self._default_callback(percentage, text)
def _default_callback(self, percentage, text):
if text is None: if text is None:
self._fileout.write("\r%02d%%" % percentage) self._fileout.write("\r%02d%%" % percentage)
else: else:

View File

@ -16,7 +16,7 @@
TOP_DIR=`dirname $PWD` TOP_DIR=`dirname $PWD`
TEST_DIR=$TOP_DIR/test TEST_DIR=$TOP_DIR/test
SRC_DIR=$TOP_DIR/gramps SRC_DIR=$TOP_DIR/gramps
PRG="python ../Gramps.py" PRG="python ../Gramps.py --yes --quiet"
EXAMPLE_XML=$TOP_DIR/example/gramps/example.gramps EXAMPLE_XML=$TOP_DIR/example/gramps/example.gramps
OUT_FMT="csv ged gramps gpkg wft gw vcs vcf" OUT_FMT="csv ged gramps gpkg wft gw vcs vcf"