Author: laukpe
Date: Tue Nov 25 16:34:48 2008
New Revision: 1121
Modified:
trunk/src/robot/utils/argumentparser.py
trunk/utest/utils/test_argumentparser.py
Log:
1) Read tool name from the first line of the usage, 2) Replace <VERSION>
and <--ESCAPES--> in usage automatically with given version and available
escapes, 3) No need to have AP.check_args or get_escapes in public API
anymore, 4) Enhanced doc
Modified: trunk/src/robot/utils/argumentparser.py
==============================================================================
--- trunk/src/robot/utils/argumentparser.py (original)
+++ trunk/src/robot/utils/argumentparser.py Tue Nov 25 16:34:48 2008
@@ -24,6 +24,7 @@
from misc import seq2str, plural_or_not
from robottypes import is_list, is_boolean
+from text import wrap
ESCAPES =
{ 'space' : ' ', 'apos' : "'", 'quot' : '"', 'lt' : '<',
@@ -35,12 +36,6 @@
'bslash' : '\\' }
-def get_escapes():
- names = ESCAPES.keys()
- names.sort()
- return [ '%s (%s)' % (name, ESCAPES[name]) for name in names ]
-
-
class ArgumentParser:
_short_opt_chars = '-?a-zA-Z'
@@ -64,12 +59,18 @@
\s*$ #
''', re.VERBOSE | re.IGNORECASE)
- def __init__(self, usage, version='No version information available'):
- """Initialization is done using a usage doc explaining options.
-
- See for example 'runner.py' and 'rebot.py' for usage examples.
+ def __init__(self, usage, version=None):
+ """Available options and tool name are read from the usage.
+
+ Tool name is got from the first row of the usage. It is either the
+ whole row or anything before first ' -- '.
+
+ See for example 'runner.py' and 'rebot.py' for examples.
"""
+ if not usage:
+ raise FrameworkError('Usage cannot be empty')
self._usage = usage
+ self._name = usage.splitlines()[0].split(' -- ')[0].strip()
self._version = version
self._short_opts = ''
self._long_opts = []
@@ -91,7 +92,8 @@
are given multiple times the last value is used) or None if the
option
is not used at all. Value for options that can be given multiple
times
(denoted with '*' in the usage) is a list which contains all the
given
- values and is empty if options are not used.
+ values and is empty if options are not used. Options not taken
+ arguments have value False when they are not set and True
otherwise.
Positional arguments are returned as a list in the order they are
given.
@@ -110,10 +112,23 @@
be added into 'sys.path'. Value can be either a string containing
the
name of the long option used for this purpose or a list containing
all such long options (i.e. the latter format allows aliases).
+
+ 'help' and 'version' make it possible to automatically generate
help
+ and version messages. Version is generated based on the tool name
+ and version -- see __init__ for information how to set them. Help
+ contains the whole usage given to __init__. Possible <VERSION> text
+ in the usage is replaced with the given version. Possible
<--ESCAPES-->
+ is replaced with available escapes so that they are wrapped to
multiple
+ lines but take the same amount of horizontal space as
<---ESCAPES--->.
+ The numer of hyphens can be used to contrl the horizontal space.
Both
+ help and version are wrapped to Information exception.
If 'check_args' is True, this method will automatically check that
- correct number of arguments (as parsed from the usage line) is
given.
- If wrong number of arguments is given DataError is risen in that
case.
+ correct number of arguments, as parsed from the usage line, are
given.
+ If the last argument in the usage line ends with the character 's',
+ the maximum number of arguments is infinite.
+
+ Possible errors in processing arguments are reported using
DataError.
"""
if argfile:
args_list = self._add_args_from_file(args_list, argfile)
@@ -121,13 +136,13 @@
if unescape:
opts, args = self._unescape_opts_and_args(opts, args, unescape)
if help and opts[help]:
- raise Information(self._usage)
+ self._raise_help()
if version and opts[version]:
- raise Information(self._version)
+ self._raise_version()
if pythonpath:
sys.path = self._get_pythonpath(opts[pythonpath]) + sys.path
if check_args:
- self.check_args(args)
+ self._check_args(args)
return opts, args
def _parse_args(self, args):
@@ -138,7 +153,7 @@
raise DataError(err)
return self._process_opts(opts), self._glob_args(args)
- def check_args(self, args):
+ def _check_args(self, args):
if len(args) == len(self._expected_args):
return
elif len(args) < len(self._expected_args):
@@ -207,8 +222,8 @@
try:
escapes[value] = ESCAPES[name.lower()]
except KeyError:
- av = seq2str(get_escapes(), quote='', lastsep=', ')
- raise DataError("Invalid escape '%s'. Available: %s" %
(name, av))
+ raise DataError("Invalid escape '%s'. Available: %s"
+ % (name, self._get_available_escapes()))
return escapes
def _unescape(self, value, escapes):
@@ -352,6 +367,26 @@
if drive:
ret.append(drive)
return ret
+
+ def _get_available_escapes(self):
+ names = ESCAPES.keys()
+ names.sort()
+ return ', '.join([ '%s (%s)' % (n, ESCAPES[n]) for n in names ])
+
+ def _raise_help(self):
+ msg = self._usage
+ if self._version:
+ msg = msg.replace('<VERSION>', self._version)
+ def replace_escapes(res):
+ escapes = 'Available escapes:\n' +
self._get_available_escapes()
+ return wrap(escapes, len(res.group(2)), len(res.group(1)))
+ msg = re.sub('( *)(<-+ESCAPES-+>)', replace_escapes, msg)
+ raise Information(msg)
+
+ def _raise_version(self):
+ if not self._version:
+ raise FrameworkError('Version not set')
+ raise Information('%s %s' % (self._name, self._version))
def _raise_option_multiple_times_in_usage(self, opt):
raise FrameworkError("Option '%s' multiple times in usage" % opt)
Modified: trunk/utest/utils/test_argumentparser.py
==============================================================================
--- trunk/utest/utils/test_argumentparser.py (original)
+++ trunk/utest/utils/test_argumentparser.py Tue Nov 25 16:34:48 2008
@@ -3,13 +3,16 @@
from robot.utils.argumentparser import ArgumentParser
from robot.utils.asserts import *
-from robot.errors import *
+from robot.errors import Information, DataError, FrameworkError
-USAGE = """
-usage: robot.py [options] datafile
+USAGE = """Example Tool -- Stuff before hyphens is considered name
-options:
+Usage: robot.py [options] datafile
+
+Version: <VERSION>
+
+Options:
-d --reportdir dir Explanation
-r --reportfile file This explanation continues ............... 78
........... to multiple lines.
@@ -31,12 +34,13 @@
* denotes options that can be set multiple times
"""
-USAGE2 = """
+USAGE2 = """Just Name Here
usage: robot.py [options] arg1 arg2
options:
-v --variable name=value
- -x --var-able name=v1,v2 Explanation
+ -x --var-able name=v1,v2 Explanation
+ --42
"""
@@ -120,26 +124,26 @@
def test_non_ascii_chars(self):
ap = ArgumentParser(USAGE2)
inargs = '-x foo=bar --variable a=1,2,3 arg1 arg2'.split()
- exp_opts = { 'var-able':'foo=bar', 'variable':'a=1,2,3' }
- exp_args = [ 'arg1', 'arg2' ]
+ exp_opts = {'var-able':'foo=bar', 'variable':'a=1,2,3', '42':
False}
+ exp_args = ['arg1', 'arg2']
opts, args = ap.parse_args(inargs)
assert_equals(opts, exp_opts)
assert_equals(args, exp_args)
def test_check_args_with_correct_args(self):
for args in [ ('hello',), ('hello world',) ]:
- self.ap.check_args(args)
+ self.ap.parse_args(args, check_args=True)
def test_check_args_with_wrong_number_of_args(self):
for args in [ (), ('arg1','arg2','arg3') ]:
- assert_raises(DataError, self.ap.check_args, args)
+ assert_raises(DataError, self.ap._check_args, args)
def test_check_variable_number_of_args(self):
ap = ArgumentParser('usage: robot.py [options] args')
- ap.check_args(['one_is_ok'])
- ap.check_args(['two', 'ok'])
- ap.check_args(['this', 'should', 'also', 'work', 'pretty', 'well'])
- assert_raises(DataError, ap.check_args, [])
+ ap.parse_args(['one_is_ok'], check_args=True)
+ ap.parse_args(['two', 'ok'], check_args=True)
+ ap.parse_args(['this', 'should', 'also', 'work', '!'],
check_args=True)
+ assert_raises(DataError, ap._check_args, [])
def test_unescape_options(self):
cli = '--escape quot:Q -E space:SP -E lt:LT -E gt:GT ' \
@@ -150,7 +154,7 @@
assert_equals(args, ['source with spaces'])
def test_split_pythonpath(self):
- ap = ArgumentParser('')
+ ap = ArgumentParser('ignored')
data = [ (['path'], ['path']),
(['path1','path2'], ['path1','path2']),
(['path1:path2'], ['path1','path2']),
@@ -165,7 +169,7 @@
assert_equals(ap._split_pythonpath(inp), exp)
def test_get_pythonpath(self):
- ap = ArgumentParser('')
+ ap = ArgumentParser('ignored')
p1 = os.path.abspath('.')
p2 = os.path.abspath('..')
assert_equals(ap._get_pythonpath(p1), [p1])
@@ -187,20 +191,46 @@
class TestPrintHelpAndVersion(unittest.TestCase):
def setUp(self):
- self.ap = ArgumentParser(USAGE, version='testing 1.0')
+ self.ap = ArgumentParser(USAGE, version='1.0 alpha')
+ self.ap2 = ArgumentParser(USAGE2)
def test_print_help(self):
- assert_raises_with_msg(Information, USAGE,
- self.ap.parse_args, ['--help'], help='help')
+ assert_raises_with_msg(Information, USAGE2,
+ self.ap2.parse_args, ['--42'], help='42')
+
+ def test_name_is_got_from_first_line_of_the_usage(self):
+ assert_equals(self.ap._name, 'Example Tool')
+ assert_equals(self.ap2._name, 'Just Name Here')
def test_print_version(self):
- assert_raises_with_msg(Information, 'testing 1.0',
+ assert_raises_with_msg(Information, 'Example Tool 1.0 alpha',
self.ap.parse_args, ['--version'],
version='version')
def test_print_version_when_version_not_set(self):
- ap = ArgumentParser(USAGE)
- assert_raises_with_msg(Information, "No version information
available",
- ap.parse_args, ['--version'],
version='version')
+ assert_raises(FrameworkError, self.ap2.parse_args, ['--42', '-x
a'], version='42')
+
+ def test_version_is_replaced_in_help(self):
+ assert_raises_with_msg(Information,
USAGE.replace('<VERSION>', '1.0 alpha'),
+ self.ap.parse_args, ['--help'], help='help')
+
+ def test_escapes_are_replaced_in_help(self):
+ usage = """Name
+ --escape x:y blaa
blaa .............................................. end
+
<-----------------------ESCAPES---------------------------->
+ -- next line --
+ --he"""
+ expected = """Name
+ --escape x:y blaa
blaa .............................................. end
+ Available escapes:
+ amp (&), apos ('), at (@), bslash (\), colon (:), comma
(,),
+ curly1 ({), curly2 (}), dollar ($), exclam (!), gt (>),
hash
+ (#), lt (<), paren1 ((), paren2 ()), percent (%), pipe
(|),
+ quest (?), quot ("), semic (;), slash (/), space ( ),
+ square1 ([), square2 (]), star (*)
+ -- next line --
+ --he"""
+ assert_raises_with_msg(Information, expected,
+ ArgumentParser(usage).parse_args, ['--he'],
help='he')
if __name__ == "__main__":