Skip to content

Commit e59dfe5

Browse files
committed
Improved example handling for parser help
1 parent f73bd1e commit e59dfe5

File tree

6 files changed

+88
-68
lines changed

6 files changed

+88
-68
lines changed

.github/workflows/pypi-publish.yml

-37
This file was deleted.

.github/workflows/python-package.yml

+30
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,33 @@ jobs:
7979
with:
8080
github_token: ${{ secrets.github_token }}
8181
branch: ${{ github.ref }}
82+
deploy:
83+
runs-on: ubuntu-latest
84+
needs: coverage
85+
steps:
86+
- uses: actions/checkout@v3
87+
with:
88+
fetch-depth: 0
89+
- name: Check for version change
90+
uses: dorny/paths-filter@v2
91+
id: filter
92+
with:
93+
filters: |
94+
version:
95+
- '**/VERSION.txt'
96+
- if: steps.filter.outputs.version == 'true'
97+
name: Cleanup README
98+
run: |
99+
sed -ri 's/^(##*)\s*:.*:\s*/\1 /g' README.md
100+
awk '{if (match($0,"## Supporters")) exit; print}' README.md > README
101+
mv -f README README.md
102+
- if: steps.filter.outputs.version == 'true'
103+
name: Build ${{ env.package }} package
104+
run: python3 -m pip install --upgrade build && python3 -m build
105+
- if: steps.filter.outputs.version == 'true'
106+
name: Upload ${{ env.package }} to PyPi
107+
uses: pypa/gh-action-pypi-publish@release/v1
108+
with:
109+
password: ${{ secrets.PYPI_API_TOKEN }}
110+
verbose: true
111+
verify_metadata: false

src/tinyscript/VERSION.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.30.18
1+
1.30.19

src/tinyscript/argreparse.py

+42-17
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,9 @@
3535
'__source__', '__training__']
3636
DUNDERS = BASE_DUNDERS + [
3737
'__date__', '__details__', '__description__', '__doc__', '__docformat__', '__email__', '__examples__',
38-
'__functions__', '__maximum_python_version__', '__minimum_python_version__', '__priority__', '__product__',
39-
'__script__', '__status__', '__version__',
38+
'__example_limit__', '__functions__', '__maximum_python_version__', '__minimum_python_version__', '__priority__',
39+
'__product__', '__requires__', '__script__', '__status__', '__version__',
4040
]
41-
if sys.version_info >= (3, 8):
42-
DUNDERS.append('__requires__')
43-
4441
DEFAULT_MAX_LEN = 20
4542
DEFAULT_LST_MAX_LEN = 10
4643

@@ -158,14 +155,14 @@ def add_parser(self, name, **kwargs):
158155
choice_action.category = category
159156
self._choices_actions.append(choice_action)
160157
# create the parser, but with another formatter and separating the help into an argument group
158+
kwargs.update(name=name, parent_action=self)
161159
parser = self._parser_class(add_help=False, **kwargs)
162-
parser.name = name
163160
# add default extra arguments group
164161
i = parser.add_argument_group("extra arguments")
165162
i.add_argument("-h", action="usage", prefix="show", help=gt("show usage message and exit"))
166163
i.add_argument("--help", action="help", prefix="show", help=gt("show this help message and exit"))
167164
# add it to the map
168-
self._name_parser_map[name] = parser
165+
self._name_parser_map[parser.name] = parser
169166
# make parser available under aliases also
170167
for alias in aliases:
171168
self._name_parser_map[alias] = parser
@@ -316,13 +313,16 @@ def __init__(self, *args, **kwargs):
316313
if not hasattr(ArgumentParser, "_globals_dict"):
317314
ArgumentParser._globals_dict = get_tool_globals()
318315
gd = ArgumentParser._globals_dict
319-
if sys.version_info >= (3, 8):
320-
self._check_requirements(gd.get('__requires__'))
316+
self._check_requirements(gd.get('__requires__'))
321317
configure_docformat(gd)
322318
self._config_parsed = False
323319
self._docfmt = gd.get('__docformat__')
320+
self._parent_action = kwargs.pop('parent_action', None)
321+
self._parent = self._parent_action._parent if self._parent_action else None
324322
self._reparse_args = {'pos': [], 'opt': [], 'sub': []}
323+
self.name = kwargs.pop('name', self.__class__.name)
325324
self.examples = gd.get('__examples__', [])
325+
self.example_limit = gd.get('__example_limit__', {})
326326
script = basename(self.tokens[0])
327327
_stem = lambda p: splitext(p)[0]
328328
if gd.get('__script__') is None:
@@ -344,6 +344,19 @@ def __init__(self, *args, **kwargs):
344344
l = ["{} {}".format(ArgumentParser.prog, e) for e in self.examples]
345345
l = list(filter(lambda x: x.startswith(kwargs['prog']), l))
346346
if len(l) > 0:
347+
dest = self._parent_action.dest if getattr(self, "_parent_action", None) else None
348+
if (n := self.example_limit if isinstance(self.example_limit, int) else \
349+
self.example_limit.get(self.name, self.example_limit.get(dest, 0))):
350+
from random import shuffle
351+
from shlex import split
352+
shuffle(l)
353+
_tmp, new_l = set(), []
354+
for example in l:
355+
token = split(example)[self.depth+1]
356+
if token not in _tmp:
357+
new_l.append(example)
358+
_tmp.add(token)
359+
l = sorted(new_l)
347360
kwargs['epilog'] = txt2title(gt("Usage example{}".format(["", "s"][len(l) > 1])) + ":")
348361
e = '\n'.join(["\n", " "][self._docfmt is None] + txt2paragraph(e) for e in l)
349362
kwargs['epilog'] += "\n" + e
@@ -603,6 +616,11 @@ def _sorted_actions(self):
603616
for a in filter(lambda _: self.is_action(_, 'parsers'), self._actions):
604617
yield a
605618

619+
def add_subparsers(self, **kwargs):
620+
action = super(ArgumentParser, self).add_subparsers(**kwargs)
621+
action._parent = self
622+
return action
623+
606624
def config_args(self, section="main"):
607625
""" Additional method for feeding input arguments from a config file. """
608626
if self._config_parsed:
@@ -730,19 +748,26 @@ def print_usage(self, file=None):
730748
self._print_message(txt_terminal_render(self.format_usage()), file or sys.stdout)
731749

732750
@property
733-
def tokens(self):
751+
def depth(self):
752+
p, d = self, 0
753+
while (p := getattr(p, "_parent", None)):
754+
d += 1
755+
return d
756+
757+
@property
758+
def root(self):
734759
p = self
735-
while hasattr(p, "_parent") and p._parent is not None:
760+
while getattr(p, "_parent", None):
736761
p = p._parent
737-
if hasattr(p, "_tokens"):
738-
return p._tokens
762+
return p
763+
764+
@property
765+
def tokens(self):
766+
return getattr(self.root, "_tokens", None)
739767

740768
@tokens.setter
741769
def tokens(self, command):
742-
p = self
743-
while hasattr(p, "_parent") and p._parent is not None:
744-
p = p._parent
745-
if hasattr(p, "_tokens"):
770+
if hasattr(p := self.root, "_tokens"):
746771
return
747772
p._tokens = sys.argv if command is None else command
748773
if isinstance(p._tokens, str):

src/tinyscript/helpers/parser.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212

1313

1414
def get_parser(tool, logger=None, **kwargs):
15-
return [p for p in get_parsers(tool, logger=logger, **kwargs).values() if isinstance(p, ArgumentParser) and \
16-
not hasattr(p, "_parent")][0]
15+
for p in get_parsers(tool, logger=logger, **kwargs).values():
16+
if p.name == ArgumentParser.name:
17+
return p
1718

1819

1920
def get_parsers(tool, logger=None, **kwargs):

tests/utils.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,22 @@
2525

2626

2727
FIXTURES = ArgumentParser._globals_dict = {
28-
'__author__': "John Doe",
29-
'__contributors__': [
28+
'__author__': "John Doe",
29+
'__contributors__': [
3030
{'author': "James McAdams", 'email': "j.mcadams@hotmail.com"},
3131
{'author': "h4x0r1234", 'reason': "for his kind testing"},
3232
{'reason': "won't be displayed (no author and no email specified)"},
3333
],
34-
'__copyright__': "test",
35-
'__credits__': "Thanks to Bob for his contribution",
36-
'__doc__': "test tool",
37-
'__details__': "some more information",
38-
'__email__': "john.doe@example.com",
39-
'__examples__': ["-v"],
40-
'__license__': "agpl-v3.0",
41-
'__status__': "beta",
42-
'__version__': "1.2.3",
34+
'__copyright__': "test",
35+
'__credits__': "Thanks to Bob for his contribution",
36+
'__doc__': "test tool",
37+
'__details__': "some more information",
38+
'__email__': "john.doe@example.com",
39+
'__examples__': ["-v"],
40+
'__example_limit__': {'main': 1},
41+
'__license__': "agpl-v3.0",
42+
'__status__': "beta",
43+
'__version__': "1.2.3",
4344
}
4445

4546

0 commit comments

Comments
 (0)