diff --git a/byexample/cmdline.py b/byexample/cmdline.py index ad603dff..959d9245 100644 --- a/byexample/cmdline.py +++ b/byexample/cmdline.py @@ -4,6 +4,9 @@ from .log_level import str_to_level from .prof import profile +''' +>>> from byexample.cmdline import ByexampleArgumentParser +''' class _CSV(argparse.Action): @@ -181,6 +184,88 @@ def _expand_glob_patterns(gpatterns): return list(set(fnames)) +class ByexampleArgumentParser(argparse.ArgumentParser): + def convert_arg_line_to_args(self, arg_line): + ''' Return a list with the arguments read from a line. + + If in the line there is a flag/argument with one or more + values the flag may be separated from its value(s) with + a space and this method will replace it with an '='. + + This is in order to produce a single argument for each + line as it is expected by argparse.ArgumentParser. + + >>> parser = ByexampleArgumentParser() + >>> parser.convert_arg_line_to_args('--skip=foo') + ['--skip=foo'] + + >>> parser.convert_arg_line_to_args('--skip foo') + ['--skip=foo'] + + >>> parser.convert_arg_line_to_args('--skip=foo bar') + ['--skip=foo bar'] + + >>> parser.convert_arg_line_to_args('--skip foo bar') + ['--skip=foo bar'] + + >>> parser.convert_arg_line_to_args('--skip=') + ['--skip='] + + >>> parser.convert_arg_line_to_args('--skip ') + ['--skip '] + + >>> parser.convert_arg_line_to_args('--skip') + ['--skip'] + + >>> parser.convert_arg_line_to_args('foo') + ['foo'] + + >>> parser.convert_arg_line_to_args('foo bar') + ['foo bar'] + + Empty lines or lines that starts with a # are ignored. + + >>> parser.convert_arg_line_to_args(' ') + [] + + >>> parser.convert_arg_line_to_args(' # foo ') + [] + ''' + arg_line = arg_line.lstrip() + if arg_line and arg_line[0] in self.prefix_chars: + flag, _, value = arg_line.partition(' ') + value = value.lstrip() + if not value: + # the flag is argumentless or it is using '=' + # to paste the flag with its argument, + # return the whole line then + # + # Ex: + # -foo + # -bar=32 + # -zaz= + return [arg_line] + else: + if '=' in flag: + # the line already has the '=' to paste the + # flag with the argument, leave them as they are + # + # Ex: + # -bar=32 42 + return [arg_line] + + # Paste the flag with its value (or values) with a '=' + return [flag + '=' + value] + + if arg_line and arg_line[0] == '#': + return [] + + if not arg_line: + return [] + + return [arg_line] + + @profile def parse_args(args=None): '''Parse the arguments args and return the them. @@ -192,7 +277,7 @@ def parse_args(args=None): ) python_version = sys.version.split(' ', 1)[0] - parser = argparse.ArgumentParser( + parser = ByexampleArgumentParser( fromfile_prefix_chars='@', add_help=False, formatter_class=HelpExtraFormatter, diff --git a/docs/basic/options.md b/docs/basic/options.md index 6bee6133..8a93a843 100644 --- a/docs/basic/options.md +++ b/docs/basic/options.md @@ -64,22 +64,104 @@ Other languages and concerns may add their owns. If the amount of options is a little overwhelming for you, you can write them down to a file and let ``byexample`` load them for you. -The only convention that you need to follow is to write one option -per line and use ``=`` for the arguments. +The only convention that you need to follow is to write **one** option +per line. If the option receives one argument you can separate them by +`=` or by a space but if there is more than one argument you will have +to write the option and the arguments one by one in its own line. ```shell $ cat test/ds/options_file --l=python ---options="+norm-ws" +# Options and their arguments are separated by a = or by a space +-l python +--options=+norm-ws +<...> +# But if the option receives more than one argument, all of them +# must be in its own line +--skip +test/ds/pkg/foo1.py +test/ds/pkg/foo2.py +<...> +# This wouldn't work: +#--skip test/ds/pkg/foo1.py test/ds/pkg/foo2.py +<...> ``` Then load it with ``@`` and the file; you can use multiple files and combine them with more options from the command line: ```shell -$ byexample @test/ds/options_file test/ds/python-tutorial.v2.md +$ byexample @test/ds/options_file -- test/ds/python-tutorial.v2.md <...> File test/ds/python-tutorial.v2.md, 4/4 test ran in <...> seconds [PASS] Pass: 4 Fail: 0 Skip: 0 ``` +> **Note:** before `10.5.2` the options in the file required to be followed by an +> `=` like `-l=python`; spaces were not allowed. + +## File pattern expansions + +Consider the following: + +```shell +$ byexample -l python test/ds/pkg/*.py | grep pkg | sort # byexample: +timeout=8 +File test/ds/pkg/bar1.py, 1/1 test ran in <...> seconds +File test/ds/pkg/foo1.py, 1/1 test ran in <...> seconds +File test/ds/pkg/foo2.py, 1/1 test ran in <...> seconds +``` + +Your *shell* usually expands `test/ds/pkg/*.py` into a list of files: +`test/ds/pkg/bar1.py`, `test/ds/pkg/foo1.py` and `test/ds/pkg/foo2.py` + +The same happens with the argument list for `--skip`, it is expanded by +your *shell*. + +```shell +$ byexample -l python --skip test/ds/pkg/foo* -- test/ds/pkg/*.py | grep pkg | sort # byexample: +timeout=8 +File test/ds/pkg/bar1.py, 1/1 test ran in <...> seconds +``` + +Since `10.0.3`, `byexample` does the same expansion even if you shell does not. +This is in particular useful if the list of files is in a file (where your shell +never ever see). + +```shell +$ cat test/ds/pkg/bopts +--skip=test/ds/pkg/foo*.py +-- +test/ds/pkg/*.py + +$ byexample -l python @test/ds/pkg/bopts | grep pkg | sort # byexample: +timeout=8 +File test/ds/pkg/bar1.py, 1/1 test ran in <...> seconds +``` + + diff --git a/test/ds/options_file b/test/ds/options_file index 1c9beff0..cdf8b7a9 100644 --- a/test/ds/options_file +++ b/test/ds/options_file @@ -1,2 +1,12 @@ --l=python ---options="+norm-ws" +# Options and their arguments are separated by a = or by a space +-l python +--options=+norm-ws + +# But if the option receives more than one argument, all of them +# must be in its own line +--skip +test/ds/pkg/foo1.py +test/ds/pkg/foo2.py + +# This wouldn't work: +#--skip test/ds/pkg/foo1.py test/ds/pkg/foo2.py diff --git a/test/ds/pkg/bar1.py b/test/ds/pkg/bar1.py new file mode 100644 index 00000000..687bd03d --- /dev/null +++ b/test/ds/pkg/bar1.py @@ -0,0 +1,4 @@ +''' +>>> print("bar1") +bar1 +''' diff --git a/test/ds/pkg/bopts b/test/ds/pkg/bopts new file mode 100644 index 00000000..4f3c56ca --- /dev/null +++ b/test/ds/pkg/bopts @@ -0,0 +1,3 @@ +--skip=test/ds/pkg/foo*.py +-- +test/ds/pkg/*.py diff --git a/test/ds/pkg/bopts2 b/test/ds/pkg/bopts2 new file mode 100644 index 00000000..4ca1c9bd --- /dev/null +++ b/test/ds/pkg/bopts2 @@ -0,0 +1,7 @@ +--skip +test/ds/pkg/foo1.py +test/ds/pkg/foo2.py +-- +test/ds/pkg/foo1.py +test/ds/pkg/foo2.py +test/ds/pkg/bar1.py diff --git a/test/ds/pkg/bopts3 b/test/ds/pkg/bopts3 new file mode 100644 index 00000000..f44d2219 --- /dev/null +++ b/test/ds/pkg/bopts3 @@ -0,0 +1,3 @@ +--skip=test/ds/pkg/foo1.py test/ds/pkg/foo2.py +-- +test/ds/pkg/*.py diff --git a/test/ds/pkg/foo1.py b/test/ds/pkg/foo1.py new file mode 100644 index 00000000..a2ef0b27 --- /dev/null +++ b/test/ds/pkg/foo1.py @@ -0,0 +1,4 @@ +''' +>>> print("foo1") +foo1 +''' diff --git a/test/ds/pkg/foo2.py b/test/ds/pkg/foo2.py new file mode 100644 index 00000000..472f998f --- /dev/null +++ b/test/ds/pkg/foo2.py @@ -0,0 +1,4 @@ +''' +>>> print("foo2") +foo2 +'''