7016: new cmdline switches -y/--yes and -q/--quiet
reapply r22916 from trunk testing on gramps40: UT svn: r22932
This commit is contained in:
parent
4bfb2e082c
commit
0afdb2e361
@ -54,7 +54,6 @@ from .clidbman import CLIDbManager, NAME_FILE, find_locker_name
|
||||
from gramps.gen.plug import BasePluginManager
|
||||
from gramps.gen.plug.report import CATEGORY_BOOK, CATEGORY_CODE, BookList
|
||||
from .plug import cl_report, cl_book
|
||||
from .user import User
|
||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||
_ = glocale.translation.gettext
|
||||
|
||||
@ -153,10 +152,15 @@ class ArgHandler(object):
|
||||
|
||||
def __init__(self, dbstate, parser, sessionmanager,
|
||||
errorfunc=None, gui=False):
|
||||
from .user import User
|
||||
|
||||
self.dbstate = dbstate
|
||||
self.sm = sessionmanager
|
||||
self.errorfunc = errorfunc
|
||||
self.gui = gui
|
||||
self.user = User(error=self.__error,
|
||||
auto_accept=parser.auto_accept,
|
||||
quiet=parser.quiet)
|
||||
if self.gui:
|
||||
self.actions = []
|
||||
self.list = False
|
||||
@ -283,19 +287,12 @@ class ArgHandler(object):
|
||||
else:
|
||||
fullpath = os.path.abspath(os.path.expanduser(fname))
|
||||
if os.path.exists(fullpath):
|
||||
self.__error(_("WARNING: Output file already exists!\n"
|
||||
"WARNING: It will be overwritten:\n %s") %
|
||||
fullpath)
|
||||
try:
|
||||
if sys.version_info[0] < 3:
|
||||
ask = raw_input
|
||||
else:
|
||||
ask = input
|
||||
answer = ask(_('OK to overwrite? (yes/no) '))
|
||||
except EOFError:
|
||||
print()
|
||||
sys.exit(0)
|
||||
if answer.upper() in ('Y', 'YES', _('YES').upper()):
|
||||
message = _("WARNING: Output file already exists!\n"
|
||||
"WARNING: It will be overwritten:\n %s"
|
||||
) % fullpath
|
||||
accepted = self.user.prompt(_('OK to overwrite?'), message,
|
||||
_('yes'), _('no'))
|
||||
if accepted:
|
||||
self.__error(_("Will overwrite the existing file: %s")
|
||||
% fullpath)
|
||||
else:
|
||||
@ -551,7 +548,7 @@ class ArgHandler(object):
|
||||
for plugin in pmgr.get_import_plugins():
|
||||
if family_tree_format == plugin.get_extension():
|
||||
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 self.imp_db_path:
|
||||
@ -573,7 +570,7 @@ class ArgHandler(object):
|
||||
for plugin in pmgr.get_export_plugins():
|
||||
if family_tree_format == plugin.get_extension():
|
||||
export_function = plugin.get_export_function()
|
||||
export_function(self.dbstate.db, filename, User(error=self.__error))
|
||||
export_function(self.dbstate.db, filename, self.user)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
@ -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
|
||||
_ = glocale.translation.gettext
|
||||
|
||||
# Note: Make sure to edit const.py.in POPT_TABLE too!
|
||||
_HELP = _("""
|
||||
Usage: gramps.py [OPTION...]
|
||||
--load-modules=MODULE1,MODULE2,... Dynamic modules to load
|
||||
@ -77,6 +76,8 @@ Application options
|
||||
-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
|
||||
""")
|
||||
|
||||
@ -183,6 +184,8 @@ class ArgParser(object):
|
||||
self.force_unlock = False
|
||||
self.create = None
|
||||
self.runqml = False
|
||||
self.quiet = False
|
||||
self.auto_accept = False
|
||||
|
||||
self.errors = []
|
||||
self.parse_args()
|
||||
@ -198,19 +201,21 @@ class ArgParser(object):
|
||||
|
||||
Possible:
|
||||
1/ Just the family tree (name or database dir)
|
||||
2/ -O, --open: Open of a family tree
|
||||
3/ -i, --import: Import a family tree of any format understood by
|
||||
2/ -O --open: Open of a family tree
|
||||
3/ -i --import: Import a family tree of any format understood by
|
||||
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
|
||||
5/ -f, --format=FORMAT : format after a -i or -e option
|
||||
6/ -a, --action: An action (possible: 'report', 'tool')
|
||||
7/ -p, --options=OPTIONS_STRING : specify options
|
||||
8/ -u, --force-unlock: A locked database can be unlocked by giving
|
||||
5/ -f --format=FORMAT : format after a -i or -e option
|
||||
6/ -a --action: An action (possible: 'report', 'tool')
|
||||
7/ -p --options=OPTIONS_STRING : specify options
|
||||
8/ -u --force-unlock: A locked database can be unlocked by giving
|
||||
this argument when opening it
|
||||
9/ -s --show : Show config settings
|
||||
10/ -c --config=config.setting:value : Set config.setting and start
|
||||
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:
|
||||
@ -259,23 +264,23 @@ class ArgParser(object):
|
||||
need_to_quit = False
|
||||
for opt_ix in range(len(options)):
|
||||
option, value = options[opt_ix]
|
||||
if option in ( '-O', '--open'):
|
||||
if option in ['-O', '--open']:
|
||||
self.open = value
|
||||
elif option in ( '-C', '--create'):
|
||||
elif option in ['-C', '--create']:
|
||||
self.create = value
|
||||
elif option in ( '-i', '--import'):
|
||||
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 ( '-e', '--export' ):
|
||||
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]
|
||||
self.exports.append((value, family_tree_format))
|
||||
elif option in ( '-a', '--action' ):
|
||||
elif option in ['-a', '--action']:
|
||||
action = value
|
||||
if action not in ('report', 'tool', 'book'):
|
||||
print(_("Unknown action: %s. Ignoring.") % action,
|
||||
@ -286,18 +291,18 @@ class ArgParser(object):
|
||||
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'):
|
||||
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'):
|
||||
elif option in ['-l']:
|
||||
self.list = True
|
||||
elif option in ('-L'):
|
||||
elif option in ['-L']:
|
||||
self.list_more = True
|
||||
elif option in ('-t'):
|
||||
elif option in ['-t']:
|
||||
self.list_table = True
|
||||
elif option in ('-s','--show'):
|
||||
elif option in ['-s','--show']:
|
||||
print(_("Gramps config settings from %s:")
|
||||
% config.filename)
|
||||
for section in config.data:
|
||||
@ -307,7 +312,7 @@ class ArgParser(object):
|
||||
repr(config.data[section][setting])))
|
||||
print()
|
||||
sys.exit(0)
|
||||
elif option in ('-c', '--config'):
|
||||
elif option in ['-c', '--config']:
|
||||
setting_name = value
|
||||
set_value = False
|
||||
if setting_name:
|
||||
@ -339,14 +344,18 @@ class ArgParser(object):
|
||||
% setting_name, file=sys.stderr)
|
||||
need_to_quit = True
|
||||
cleandbg += [opt_ix]
|
||||
elif option in ('-h', '-?', '--help'):
|
||||
elif option in ['-h', '-?', '--help']:
|
||||
self.help = True
|
||||
elif option in ('-u', '--force-unlock'):
|
||||
elif option in ['-u', '--force-unlock']:
|
||||
self.force_unlock = True
|
||||
elif option in ('--usage'):
|
||||
elif option in ['--usage']:
|
||||
self.usage = True
|
||||
elif option in ('--qml'):
|
||||
elif option in ['--qml']:
|
||||
self.runqml = True
|
||||
elif option in ['-y', '--yes']:
|
||||
self.auto_accept = True
|
||||
elif option in ['-q', '--quiet']:
|
||||
self.quiet = True
|
||||
|
||||
#clean options list
|
||||
cleandbg.reverse()
|
||||
|
88
gramps/cli/test/argparser_test.py
Normal file
88
gramps/cli/test/argparser_test.py
Normal 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()
|
@ -32,9 +32,9 @@ import sys
|
||||
|
||||
try:
|
||||
if sys.version_info < (3,3):
|
||||
from mock import Mock
|
||||
from mock import Mock, NonCallableMock
|
||||
else:
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, NonCallableMock
|
||||
|
||||
MOCKING = True
|
||||
|
||||
@ -47,8 +47,8 @@ class TestUser_prompt(unittest.TestCase):
|
||||
self.real_user = user.User()
|
||||
if MOCKING:
|
||||
self.user = user.User()
|
||||
self.user._fileout = Mock()
|
||||
self.user._input = Mock()
|
||||
self.user._fileout = Mock(spec=sys.stderr)
|
||||
self.user._input = Mock(spec=input)
|
||||
|
||||
def test_default_fileout_has_write(self):
|
||||
assert hasattr(self.real_user._fileout, 'write')
|
||||
@ -100,10 +100,45 @@ class TestUser_prompt(unittest.TestCase):
|
||||
self.assert_prompt_contains_text(TestUser.REJECT)
|
||||
|
||||
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(
|
||||
TestUser.TITLE, TestUser.MSG, TestUser.ACCEPT, TestUser.REJECT)
|
||||
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__":
|
||||
unittest.main()
|
||||
|
@ -58,7 +58,7 @@ class User(user.User):
|
||||
This class provides a means to interact with the user via CLI.
|
||||
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.
|
||||
|
||||
@ -70,6 +70,15 @@ class User(user.User):
|
||||
self.current_step = 0;
|
||||
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):
|
||||
"""
|
||||
Start showing a progress indicator to the user.
|
||||
@ -127,13 +136,17 @@ class User(user.User):
|
||||
@returns: the user's answer to the question
|
||||
@rtype: bool
|
||||
"""
|
||||
text = "{t} {m} ({y}/{n}): ".format(
|
||||
text = "{t}\n{m} ([{y}]/{n}): ".format(
|
||||
t = title,
|
||||
m = message,
|
||||
y = accept_label,
|
||||
n = reject_label)
|
||||
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=""):
|
||||
"""
|
||||
|
@ -303,8 +303,10 @@ LONGOPTS = [
|
||||
"usage",
|
||||
"version",
|
||||
"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')
|
||||
|
@ -69,10 +69,13 @@ class User():
|
||||
else:
|
||||
self.callback_function(percentage)
|
||||
else:
|
||||
if text is None:
|
||||
self._fileout.write("\r%02d%%" % percentage)
|
||||
else:
|
||||
self._fileout.write("\r%02d%% %s" % (percentage, text))
|
||||
self._default_callback(percentage, text)
|
||||
|
||||
def _default_callback(self, percentage, text):
|
||||
if text is None:
|
||||
self._fileout.write("\r%02d%%" % percentage)
|
||||
else:
|
||||
self._fileout.write("\r%02d%% %s" % (percentage, text))
|
||||
|
||||
def end_progress(self):
|
||||
"""
|
||||
|
@ -16,7 +16,7 @@
|
||||
TOP_DIR=`dirname $PWD`
|
||||
TEST_DIR=$TOP_DIR/test
|
||||
SRC_DIR=$TOP_DIR/gramps
|
||||
PRG="python ../Gramps.py"
|
||||
PRG="python ../Gramps.py --yes --quiet"
|
||||
EXAMPLE_XML=$TOP_DIR/example/gramps/example.gramps
|
||||
|
||||
OUT_FMT="csv ged gramps gpkg wft gw vcs vcf"
|
||||
|
Loading…
Reference in New Issue
Block a user