git-project 0.0.27__tar.gz → 0.0.28__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. git_project-0.0.28/.#pyproject.toml +1 -0
  2. git_project-0.0.28/.git +1 -0
  3. {git_project-0.0.27 → git_project-0.0.28}/PKG-INFO +1 -1
  4. {git_project-0.0.27 → git_project-0.0.28}/_version.py +9 -4
  5. git_project-0.0.28/git_project-0.0.28.dev1+g2184a37-py3-none-any.whl +0 -0
  6. git_project-0.0.28/git_project-0.0.28.dev1+g2184a37.tar.gz +0 -0
  7. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/substitutable.py +13 -0
  8. git_project-0.0.28/test.out +131 -0
  9. {git_project-0.0.27 → git_project-0.0.28}/tests/test_substitutable.py +40 -0
  10. git_project-0.0.27/.git +0 -1
  11. git_project-0.0.27/build/lib/git_project/__init__.py +0 -33
  12. git_project-0.0.27/build/lib/git_project/commandline.py +0 -164
  13. git_project-0.0.27/build/lib/git_project/configobj.py +0 -300
  14. git_project-0.0.27/build/lib/git_project/exception.py +0 -30
  15. git_project-0.0.27/build/lib/git_project/git.py +0 -873
  16. git_project-0.0.27/build/lib/git_project/gitproject.py +0 -61
  17. git_project-0.0.27/build/lib/git_project/main.py +0 -63
  18. git_project-0.0.27/build/lib/git_project/parsermanager.py +0 -360
  19. git_project-0.0.27/build/lib/git_project/plugin.py +0 -118
  20. git_project-0.0.27/build/lib/git_project/pluginmanager.py +0 -54
  21. git_project-0.0.27/build/lib/git_project/project.py +0 -197
  22. git_project-0.0.27/build/lib/git_project/runnable.py +0 -71
  23. git_project-0.0.27/build/lib/git_project/scopedobj.py +0 -207
  24. git_project-0.0.27/build/lib/git_project/shell.py +0 -119
  25. git_project-0.0.27/build/lib/git_project/substitutable.py +0 -139
  26. git_project-0.0.27/test.out +0 -18
  27. {git_project-0.0.27 → git_project-0.0.28}/.gitignore +0 -0
  28. {git_project-0.0.27 → git_project-0.0.28}/CONTRIBUTORS +0 -0
  29. {git_project-0.0.27 → git_project-0.0.28}/COPYING +0 -0
  30. {git_project-0.0.27 → git_project-0.0.28}/MANIFEST.in +0 -0
  31. {git_project-0.0.27 → git_project-0.0.28}/docs/Makefile +0 -0
  32. {git_project-0.0.27 → git_project-0.0.28}/docs/authors.rst +0 -0
  33. {git_project-0.0.27 → git_project-0.0.28}/docs/changelog.rst +0 -0
  34. {git_project-0.0.27 → git_project-0.0.28}/docs/conf.py +0 -0
  35. {git_project-0.0.27 → git_project-0.0.28}/docs/index.rst +0 -0
  36. {git_project-0.0.27 → git_project-0.0.28}/docs/intro.rst +0 -0
  37. {git_project-0.0.27 → git_project-0.0.28}/docs/license.rst +0 -0
  38. {git_project-0.0.27 → git_project-0.0.28}/docs/make.bat +0 -0
  39. {git_project-0.0.27 → git_project-0.0.28}/pyproject.toml +0 -0
  40. {git_project-0.0.27 → git_project-0.0.28}/requirements.txt +0 -0
  41. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/__init__.py +0 -0
  42. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/commandline.py +0 -0
  43. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/configobj.py +0 -0
  44. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/exception.py +0 -0
  45. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/git.py +0 -0
  46. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/gitproject.py +0 -0
  47. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/main.py +0 -0
  48. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/parsermanager.py +0 -0
  49. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/plugin.py +0 -0
  50. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/pluginmanager.py +0 -0
  51. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/project.py +0 -0
  52. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/runnable.py +0 -0
  53. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/scopedobj.py +0 -0
  54. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/shell.py +0 -0
  55. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/test_support/__init__.py +0 -0
  56. {git_project-0.0.27 → git_project-0.0.28}/src/git_project/test_support/common.py +0 -0
  57. {git_project-0.0.27 → git_project-0.0.28}/tests/conftest.py +0 -0
  58. {git_project-0.0.27 → git_project-0.0.28}/tests/test_confobj.py +0 -0
  59. {git_project-0.0.27 → git_project-0.0.28}/tests/test_git.py +0 -0
  60. {git_project-0.0.27 → git_project-0.0.28}/tests/test_gitproject.py +0 -0
  61. {git_project-0.0.27 → git_project-0.0.28}/tests/test_main.py +0 -0
  62. {git_project-0.0.27 → git_project-0.0.28}/tests/test_project.py +0 -0
  63. {git_project-0.0.27 → git_project-0.0.28}/tests/test_runnable.py +0 -0
  64. {git_project-0.0.27 → git_project-0.0.28}/tests/test_scopedobj.py +0 -0
@@ -0,0 +1 @@
1
+ dag@basie.2617:1746908671
@@ -0,0 +1 @@
1
+ gitdir: /home/dag/src/git-project/.git/worktrees/dev/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-project
3
- Version: 0.0.27
3
+ Version: 0.0.28
4
4
  Summary: The extensible stupid project manager
5
5
  Author-email: "David A. Greene" <dag@obbligato.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.0.27'
16
- __version_tuple__ = version_tuple = (0, 0, 27)
20
+ __version__ = version = '0.0.28'
21
+ __version_tuple__ = version_tuple = (0, 0, 28)
@@ -119,7 +119,17 @@ class SubstitutableConfigObject(ConfigObject):
119
119
  value = scope.get_ident()
120
120
  formats[key] = value
121
121
 
122
+ # Allow escaping braces by surrounding each brace with braces. This is
123
+ # not valid f-string syntax and is unlikely to be used in ordinary
124
+ # config values. Replace with an equally unlikely sequence, then
125
+ # substitute on that string.
126
+
127
+ escaped_braces = False
122
128
  while True:
129
+ escaped_braces = escaped_braces or '{{}' in string or '{}}' in string
130
+ if escaped_braces:
131
+ string = string.replace('{{}', '[[[')
132
+ string = string.replace('{}}', ']]]')
123
133
  try:
124
134
  newstring = try_format(string, formats)
125
135
  except KeyError as exception:
@@ -140,6 +150,9 @@ class SubstitutableConfigObject(ConfigObject):
140
150
  changed = False if newstring == string else True
141
151
  string = newstring
142
152
  if not changed:
153
+ if escaped_braces:
154
+ string = string.replace('[[[', '{')
155
+ string = string.replace(']]]', '}')
143
156
  break
144
157
 
145
158
  return string
@@ -0,0 +1,131 @@
1
+ ============================= test session starts ==============================
2
+ platform linux -- Python 3.11.9, pytest-8.2.2, pluggy-1.5.0
3
+ rootdir: /home/dag/src/git-project/dev
4
+ configfile: pyproject.toml
5
+ plugins: anyio-4.3.0, console-scripts-1.4.1
6
+ collected 106 items
7
+
8
+ tests/test_confobj.py ............ [ 11%]
9
+ tests/test_git.py .......................FF............................ [ 61%]
10
+ tests/test_gitproject.py .. [ 63%]
11
+ tests/test_main.py . [ 64%]
12
+ tests/test_project.py ........... [ 74%]
13
+ tests/test_runnable.py ........ [ 82%]
14
+ tests/test_scopedobj.py ..... [ 86%]
15
+ tests/test_substitutable.py .............. [100%]
16
+
17
+ =================================== FAILURES ===================================
18
+ ________________________________ test_git_clone ________________________________
19
+
20
+ reset_directory = None
21
+ remote_repository = pygit2.Repository('/tmp/pytest-of-dag/pytest-14/local_remote_test_git_clone.git0/')
22
+ tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ff199ba1e50>, _basetemp=PosixPath('/tmp/pytest-of-dag/pytest-14'), _retention_count=3, _retention_policy='all')
23
+
24
+ def test_git_clone(reset_directory, remote_repository, tmp_path_factory):
25
+ path = tmp_path_factory.mktemp('clone-workdir')
26
+
27
+ os.chdir(path)
28
+
29
+ git = git_project.Git()
30
+
31
+ remote_url = 'file://' + remote_repository.path
32
+
33
+ > clone_path = git.clone(remote_url)
34
+
35
+ /home/dag/src/git-project/dev/tests/test_git.py:441:
36
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
37
+ /home/dag/src/git-project/dev/src/git_project/git.py:744: in clone
38
+ self._repo = pygit2.clone_repository(url, target_path, bare, callbacks=callbacks)
39
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/__init__.py:217: in clone_repository
40
+ payload.check_error(err)
41
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:93: in check_error
42
+ raise self._stored_exception
43
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:424: in wrapper
44
+ return f(*args)
45
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:552: in _transfer_progress_cb
46
+ transfer_progress(TransferProgress(stats_ptr))
47
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
48
+
49
+ self = <git_project.git.Git.RemoteCallbacks object at 0x7ff199a06950>
50
+ stats = <pygit2.remotes.TransferProgress object at 0x7ff199a9f790>
51
+
52
+ def transfer_progress(self, stats):
53
+ if not self.started_transfer:
54
+ self.started_transfer = True
55
+ print(f'Receiving objects ({stats.total_objects})...')
56
+ > self.progress.start(max_value=stats.total_objects)
57
+ E TypeError: ProgressBar.start() got an unexpected keyword argument 'max_value'
58
+
59
+ /home/dag/src/git-project/dev/src/git_project/git.py:77: TypeError
60
+ ----------------------------- Captured stdout call -----------------------------
61
+ Remote: Counting objects 1
62
+ Remote: Counting objects 9
63
+ Remote: Counting objects 14
64
+ Remote: Counting objects 14
65
+
66
+ Remote: Compressing objects: 0% (0/14)
67
+ Remote: Compressing objects: 100% (14/14), done
68
+
69
+ Receiving objects (14)...
70
+ ___________________________ test_git_clone_with_path ___________________________
71
+
72
+ reset_directory = None
73
+ remote_repository = pygit2.Repository('/tmp/pytest-of-dag/pytest-14/local_remote_test_git_clone_with_path.git0/')
74
+ tmp_path_factory = TempPathFactory(_given_basetemp=None, _trace=<pluggy._tracing.TagTracerSub object at 0x7ff199ba1e50>, _basetemp=PosixPath('/tmp/pytest-of-dag/pytest-14'), _retention_count=3, _retention_policy='all')
75
+
76
+ def test_git_clone_with_path(reset_directory,
77
+ remote_repository,
78
+ tmp_path_factory):
79
+ path = tmp_path_factory.mktemp('clone-workdir')
80
+ os.chdir(path)
81
+
82
+ git = git_project.Git()
83
+
84
+ remote_url = 'file://' + remote_repository.path
85
+
86
+ path = Path.cwd() / 'foo' / 'bar'
87
+
88
+ os.makedirs(path)
89
+
90
+ path = path / 'test-clone'
91
+
92
+ > clone_path = git.clone(remote_url, path)
93
+
94
+ /home/dag/src/git-project/dev/tests/test_git.py:482:
95
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
96
+ /home/dag/src/git-project/dev/src/git_project/git.py:744: in clone
97
+ self._repo = pygit2.clone_repository(url, target_path, bare, callbacks=callbacks)
98
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/__init__.py:217: in clone_repository
99
+ payload.check_error(err)
100
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:93: in check_error
101
+ raise self._stored_exception
102
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:424: in wrapper
103
+ return f(*args)
104
+ /home/dag/.virtualenvs/dev/lib/python3.11/site-packages/pygit2/callbacks.py:552: in _transfer_progress_cb
105
+ transfer_progress(TransferProgress(stats_ptr))
106
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
107
+
108
+ self = <git_project.git.Git.RemoteCallbacks object at 0x7ff198a8e350>
109
+ stats = <pygit2.remotes.TransferProgress object at 0x7ff198a8eb10>
110
+
111
+ def transfer_progress(self, stats):
112
+ if not self.started_transfer:
113
+ self.started_transfer = True
114
+ print(f'Receiving objects ({stats.total_objects})...')
115
+ > self.progress.start(max_value=stats.total_objects)
116
+ E TypeError: ProgressBar.start() got an unexpected keyword argument 'max_value'
117
+
118
+ /home/dag/src/git-project/dev/src/git_project/git.py:77: TypeError
119
+ ----------------------------- Captured stdout call -----------------------------
120
+ Remote: Counting objects 1
121
+ Remote: Counting objects 14
122
+ Remote: Counting objects 14
123
+
124
+ Remote: Compressing objects: 0% (0/14)
125
+ Remote: Compressing objects: 100% (14/14), done
126
+
127
+ Receiving objects (14)...
128
+ =========================== short test summary info ============================
129
+ FAILED tests/test_git.py::test_git_clone - TypeError: ProgressBar.start() got...
130
+ FAILED tests/test_git.py::test_git_clone_with_path - TypeError: ProgressBar.s...
131
+ ======================== 2 failed, 104 passed in 2.74s =========================
@@ -502,3 +502,43 @@ def test_substitutable_substitute_git_workdir(reset_directory, git):
502
502
  substitutable.command)
503
503
 
504
504
  assert command == f'cd {git.get_working_copy_root()}/path/to/build/{git.get_current_branch()} && make {project.target}'
505
+
506
+ def test_substitutable_substitute_escaped_braces(reset_directory, git):
507
+ class MyProject(git_project.ScopedConfigObject):
508
+ def __init__(self):
509
+ super().__init__(git,
510
+ 'project',
511
+ None,
512
+ 'myproject',
513
+ builddir='{git_workdir}/{{}path{}}/to/build',
514
+ target='debug')
515
+
516
+ substitutable = MySubstitutable.get(git, 'project', 'test')
517
+
518
+ project = MyProject()
519
+
520
+ command = substitutable.substitute_value(git,
521
+ project,
522
+ substitutable.command)
523
+
524
+ assert command == f'cd {git.get_working_copy_root()}/{{path}}/to/build/{git.get_current_branch()} && make {project.target}'
525
+
526
+ def test_substitutable_substitute_preserve_escaped_braces(reset_directory, git):
527
+ class MyProject(git_project.ScopedConfigObject):
528
+ def __init__(self):
529
+ super().__init__(git,
530
+ 'project',
531
+ None,
532
+ 'myproject',
533
+ builddir='{git_workdir}/{{}{{}{}}path{{}{}}{}}/to/build',
534
+ target='debug')
535
+
536
+ substitutable = MySubstitutable.get(git, 'project', 'test')
537
+
538
+ project = MyProject()
539
+
540
+ command = substitutable.substitute_value(git,
541
+ project,
542
+ substitutable.command)
543
+
544
+ assert command == f'cd {git.get_working_copy_root()}/{{{{}}path{{}}}}/to/build/{git.get_current_branch()} && make {project.target}'
git_project-0.0.27/.git DELETED
@@ -1 +0,0 @@
1
- gitdir: /Users/dag/projects/git-project/.git/worktrees/master/
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env python3
2
- #
3
- # Copyright 2020 David A. Greene
4
- #
5
- # This program is free software: you can redistribute it and/or modify it under
6
- # the terms of the GNU General Public License as published by the Free Software
7
- # Foundation, either version 3 of the License, or (at your option) any later
8
- # version.
9
- #
10
- # This program is distributed in the hope that it will be useful, but WITHOUT
11
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
- # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
- # details.
14
- #
15
- # You should have received a copy of the GNU General Public License along with
16
- # this program. If not, see <https://www.gnu.org/licenses/>.
17
- #
18
-
19
- from .commandline import parse_arguments, add_top_level_command
20
- from .commandline import get_or_add_top_level_command
21
- from .configobj import ConfigObject
22
- from .exception import GitProjectException
23
- from .git import Git
24
- from .gitproject import GitProject
25
- from .main import main_impl
26
- from .parsermanager import ParserManager
27
- from .plugin import Plugin
28
- from .pluginmanager import PluginManager
29
- from .project import Project
30
- from .runnable import RunnableConfigObject
31
- from .scopedobj import ScopedConfigObject
32
- from .substitutable import SubstitutableConfigObject
33
- from .shell import run_command_with_shell, iter_command, capture_command
@@ -1,164 +0,0 @@
1
- #!/usr/bin/env python3
2
- #
3
- # Copyright 2020 David A. Greene
4
- #
5
- # This program is free software: you can redistribute it and/or modify it under
6
- # the terms of the GNU General Public License as published by the Free Software
7
- # Foundation, either version 3 of the License, or (at your option) any later
8
- # version.
9
- #
10
- # This program is distributed in the hope that it will be useful, but WITHOUT
11
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12
- # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13
- # details.
14
- #
15
- # You should have received a copy of the GNU General Public License along with
16
- # this program. If not, see <https://www.gnu.org/licenses/>.
17
- #
18
- """git-project: Manage project work.
19
-
20
- Summary:
21
-
22
- git project <command> [<options>]
23
-
24
- Commands are registered by plugins and show with 'git project --help.' Each
25
- command also has a --help option to explore workings of th command.
26
-
27
- Users may link to git-project with a name appropriate to the project (for
28
- example, git-fizzbin to invoke as 'git fizzbin <command> [<options>]).
29
- git-project will then place all configuration in a section named after the link
30
- ('fizzbin' in this case). This way one may have multiple projects active in a
31
- single repository by invoking git-project via various links.
32
-
33
- """
34
-
35
- import argparse
36
- import getpass
37
- import io
38
- import os
39
- from pathlib import Path
40
- import pygit2
41
- import re
42
- import shlex
43
- import subprocess
44
- import sys
45
- import urllib.parse
46
-
47
- try:
48
- from importlib import metadata
49
- except ImportError:
50
- # Running on pre-3.8 Python; use importlib-metadata package
51
- import importlib_metadata as metadata
52
-
53
- from .parsermanager import ParserManager
54
-
55
- def get_version_string(package):
56
- version = metadata.version(package)
57
-
58
- return f'{package} version {version}'
59
-
60
- def add_version_argument(parser, package):
61
- parser.add_argument('--version',
62
- action='version',
63
- version=get_version_string(package),
64
- help='Print version')
65
-
66
- def parse_arguments(git, gitproject, project, plugin_manager, args):
67
- """Register plugin comand-line arguments. Parse command-link arguments and
68
- invoke command functions.
69
-
70
- git: Git object to examine the repository.
71
-
72
- gitproject: GitProject object to provide cross-project configuration.
73
-
74
- project: Project object to provide project-specific configuration.
75
-
76
- plugin_manager: A manager holding available plugins.
77
-
78
- """
79
- parser_manager = ParserManager(gitproject, project)
80
-
81
- parser = parser_manager.find_parser('__main__')
82
-
83
- # --version
84
- add_version_argument(parser, 'git-project')
85
-
86
- command_subparser = parser_manager.add_subparser(parser,
87
- 'command',
88
- dest='command',
89
- help='commands')
90
- command_subparser.required = True
91
-
92
- for plugin in plugin_manager.iterplugins():
93
- plugin.add_arguments(git,
94
- gitproject,
95
- project,
96
- parser_manager,
97
- plugin_manager)
98
-
99
- # Once all plugins have added arguments, give them a chance to modify
100
- # arguments other plugsin may have added.
101
- for plugin in plugin_manager.iterplugins():
102
- plugin.modify_arguments(git,
103
- gitproject,
104
- project,
105
- parser_manager,
106
- plugin_manager)
107
-
108
- clargs = parser_manager.parse_args(args)
109
-
110
- if not hasattr(clargs, 'func'):
111
- parser_manager.error()
112
- sys.exit(1)
113
-
114
- return clargs
115
-
116
- def add_top_level_command(parser_manager,
117
- name,
118
- key,
119
- **kwargs):
120
- """Create a top-level command. Throw an exception if the command already
121
- exists.
122
-
123
- parser_manager - The active ParserManager.
124
-
125
- name - The name of the command to be invoked.
126
-
127
- key - A unique identifier for the created parser.
128
-
129
- kwargs - Passthrough to argparse parser.
130
-
131
- """
132
-
133
- command_subparser = parser_manager.find_subparser('command')
134
- assert command_subparser
135
- parser = parser_manager.add_parser(command_subparser,
136
- name,
137
- key,
138
- **kwargs)
139
-
140
- return parser
141
-
142
- def get_or_add_top_level_command(parser_manager,
143
- name,
144
- key,
145
- **kwargs):
146
- """Retrieve a top-level command parser or create one if none exists.
147
-
148
- parser_manager - The active ParserManager.
149
-
150
- name - The name of the command to be invoked.
151
-
152
- key - A unique identifier for the created parser.
153
-
154
- kwargs - Passthrough to argparse parser.
155
-
156
- """
157
-
158
- parser = parser_manager.find_parser(key)
159
- if not parser:
160
- parser = add_top_level_command(parser_manager,
161
- name,
162
- key,
163
- **kwargs)
164
- return parser