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__":

Reply via email to