Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 226 make options slash args file easier #227

Merged
merged 4 commits into from
May 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion byexample/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

from .log_level import str_to_level
from .prof import profile
'''
>>> from byexample.cmdline import ByexampleArgumentParser
'''


class _CSV(argparse.Action):
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand Down
92 changes: 87 additions & 5 deletions docs/basic/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

<!--

Extra test checking that options in a file with multiple spaces are
interpreted correctly now that we support separate the flag from its
value with a space (before an '=' was always required)

So the following lines are equivalent
-skip=foo bar
-skip foo bar

$ cat test/ds/pkg/bopts2
<...>skip
test/ds/pkg/foo1.py
test/ds/pkg/foo2.py
<...>

$ byexample -l python @test/ds/pkg/bopts2 | grep pkg | wc -l # byexample: +timeout=8
1

$ cat test/ds/pkg/bopts3
<...>skip=test/ds/pkg/foo1.py test/ds/pkg/foo2.py
<...>

$ byexample -l python @test/ds/pkg/bopts2 | grep pkg | wc -l # byexample: +timeout=8
1

$ byexample -l python @test/ds/pkg/bopts | grep pkg | wc -l # byexample: +timeout=8
1

-->
14 changes: 12 additions & 2 deletions test/ds/options_file
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions test/ds/pkg/bar1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'''
>>> print("bar1")
bar1
'''
3 changes: 3 additions & 0 deletions test/ds/pkg/bopts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--skip=test/ds/pkg/foo*.py
--
test/ds/pkg/*.py
7 changes: 7 additions & 0 deletions test/ds/pkg/bopts2
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions test/ds/pkg/bopts3
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--skip=test/ds/pkg/foo1.py test/ds/pkg/foo2.py
--
test/ds/pkg/*.py
4 changes: 4 additions & 0 deletions test/ds/pkg/foo1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'''
>>> print("foo1")
foo1
'''
4 changes: 4 additions & 0 deletions test/ds/pkg/foo2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'''
>>> print("foo2")
foo2
'''