zparser2 0.0.4__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.
- zparser2-0.0.4/LICENSE +11 -0
- zparser2-0.0.4/PKG-INFO +152 -0
- zparser2-0.0.4/README.md +132 -0
- zparser2-0.0.4/pyproject.toml +34 -0
- zparser2-0.0.4/setup.cfg +4 -0
- zparser2-0.0.4/zparser2/__init__.py +556 -0
- zparser2-0.0.4/zparser2.egg-info/PKG-INFO +152 -0
- zparser2-0.0.4/zparser2.egg-info/SOURCES.txt +8 -0
- zparser2-0.0.4/zparser2.egg-info/dependency_links.txt +1 -0
- zparser2-0.0.4/zparser2.egg-info/top_level.txt +1 -0
zparser2-0.0.4/LICENSE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Copyright 2025 Marcos Diez
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
4
|
+
|
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
6
|
+
|
|
7
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
8
|
+
|
|
9
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
10
|
+
|
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
zparser2-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zparser2
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: ZParser Cli Argument Library. Nothing comes after Z
|
|
5
|
+
Author: Marcos Diez, Benjamin Port
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/marcosdiez/zparser2
|
|
8
|
+
Project-URL: Issues, https://github.com/marcosdiez/zparser2/issues
|
|
9
|
+
Keywords: cli,argument,parsing
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.6
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
ZParser2 Cli Argument Parsing Library
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
`zparser2` is probably the simplest most opinionated argument parsing library. Lot's of conventions, zero configuration!
|
|
25
|
+
|
|
26
|
+
If you add the `@z.task` notation to a function, it automatically become available to the CLI.
|
|
27
|
+
The function's parameters are what the CLI will expect. If notations are given, type will be enforced.
|
|
28
|
+
The file in which the function is located will be the module in which the function will be available.
|
|
29
|
+
|
|
30
|
+
The downside is that you can only have two layers in your cli. That being said, more than that would be too complex and less than that you don't really need a library.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Example
|
|
34
|
+
-------
|
|
35
|
+
|
|
36
|
+
Let's say you have 3 files:
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
math_functions.py
|
|
40
|
+
```
|
|
41
|
+
"""here we do math"""
|
|
42
|
+
from zparser2 import z
|
|
43
|
+
|
|
44
|
+
@z.task
|
|
45
|
+
def duplicate_number(x: float):
|
|
46
|
+
"""returns twice the value of x"""
|
|
47
|
+
return 2*x
|
|
48
|
+
|
|
49
|
+
@z.task
|
|
50
|
+
def triple_number(x: float):
|
|
51
|
+
"""returns 3 times the value of x"""
|
|
52
|
+
return 3*x
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
string_functions.py
|
|
56
|
+
```
|
|
57
|
+
"""string processing"""
|
|
58
|
+
from zparser2 import z
|
|
59
|
+
|
|
60
|
+
@z.task
|
|
61
|
+
def add_square_brackets_to_string(x: str):
|
|
62
|
+
"""x -> [x]"""
|
|
63
|
+
return f"[{x}]"
|
|
64
|
+
|
|
65
|
+
@z.task
|
|
66
|
+
def first_word(x: str):
|
|
67
|
+
"""returns the first word of a string"""
|
|
68
|
+
return x.split(" ")[0]
|
|
69
|
+
|
|
70
|
+
@z.task
|
|
71
|
+
def last_word(x: str):
|
|
72
|
+
"""returns the first word of a string"""
|
|
73
|
+
return x.split(" ")[-1]
|
|
74
|
+
|
|
75
|
+
@z.task
|
|
76
|
+
def another_task(somestring: str, some_int: int, workdir=None, root_url=None):
|
|
77
|
+
"""description of the task"""
|
|
78
|
+
print(f"somestring={somestring}")
|
|
79
|
+
print(f"some_int={some_int}")
|
|
80
|
+
print(f"workdir={workdir}")
|
|
81
|
+
print(f"root_url={root_url}")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
mycli.py
|
|
86
|
+
```
|
|
87
|
+
#!/usr/bin/env python3
|
|
88
|
+
import zparser2
|
|
89
|
+
|
|
90
|
+
import math_functions
|
|
91
|
+
import string_functions
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
zparser2.init()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Output
|
|
98
|
+
------
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py
|
|
102
|
+
./mycli.py <plugin_name> <task>
|
|
103
|
+
Plugin list:
|
|
104
|
+
math_functions - here we do math
|
|
105
|
+
string_functions - string processing
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions
|
|
111
|
+
You need to specify a task
|
|
112
|
+
--------------------------------------------------------------------------------
|
|
113
|
+
string processing
|
|
114
|
+
./mycli.py string_functions <task>
|
|
115
|
+
Plugin alias: []
|
|
116
|
+
Tasks:
|
|
117
|
+
add_square_brackets_to_string - x -> [x]
|
|
118
|
+
another_task - description of the task
|
|
119
|
+
first_word - returns the first word of a string
|
|
120
|
+
last_word - returns the first word of a string
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions another_task
|
|
125
|
+
You need to specify the required arguments [somestring, some_int]
|
|
126
|
+
--------------------------------------------------------------------------------
|
|
127
|
+
description of the task
|
|
128
|
+
Usage:
|
|
129
|
+
./mycli.py string_functions another_task somestring some_int [--workdir workdir] [--root_url root_url]
|
|
130
|
+
Positional arguments:
|
|
131
|
+
somestring - <class 'str'>
|
|
132
|
+
some_int - <class 'int'>
|
|
133
|
+
Optional arguments:
|
|
134
|
+
--workdir (Default: None) -
|
|
135
|
+
--root_url (Default: None) -
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions another_task blah 42 --root_url https://blah.com
|
|
140
|
+
somestring=blah
|
|
141
|
+
some_int=42
|
|
142
|
+
workdir=None
|
|
143
|
+
root_url=https://blah.com
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
How to build & publish
|
|
148
|
+
----------------------
|
|
149
|
+
|
|
150
|
+
* `python3 -m build`
|
|
151
|
+
* `python3 -m twine upload --repository testpypi dist/*`
|
|
152
|
+
|
zparser2-0.0.4/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
ZParser2 Cli Argument Parsing Library
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
`zparser2` is probably the simplest most opinionated argument parsing library. Lot's of conventions, zero configuration!
|
|
5
|
+
|
|
6
|
+
If you add the `@z.task` notation to a function, it automatically become available to the CLI.
|
|
7
|
+
The function's parameters are what the CLI will expect. If notations are given, type will be enforced.
|
|
8
|
+
The file in which the function is located will be the module in which the function will be available.
|
|
9
|
+
|
|
10
|
+
The downside is that you can only have two layers in your cli. That being said, more than that would be too complex and less than that you don't really need a library.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Example
|
|
14
|
+
-------
|
|
15
|
+
|
|
16
|
+
Let's say you have 3 files:
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
math_functions.py
|
|
20
|
+
```
|
|
21
|
+
"""here we do math"""
|
|
22
|
+
from zparser2 import z
|
|
23
|
+
|
|
24
|
+
@z.task
|
|
25
|
+
def duplicate_number(x: float):
|
|
26
|
+
"""returns twice the value of x"""
|
|
27
|
+
return 2*x
|
|
28
|
+
|
|
29
|
+
@z.task
|
|
30
|
+
def triple_number(x: float):
|
|
31
|
+
"""returns 3 times the value of x"""
|
|
32
|
+
return 3*x
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
string_functions.py
|
|
36
|
+
```
|
|
37
|
+
"""string processing"""
|
|
38
|
+
from zparser2 import z
|
|
39
|
+
|
|
40
|
+
@z.task
|
|
41
|
+
def add_square_brackets_to_string(x: str):
|
|
42
|
+
"""x -> [x]"""
|
|
43
|
+
return f"[{x}]"
|
|
44
|
+
|
|
45
|
+
@z.task
|
|
46
|
+
def first_word(x: str):
|
|
47
|
+
"""returns the first word of a string"""
|
|
48
|
+
return x.split(" ")[0]
|
|
49
|
+
|
|
50
|
+
@z.task
|
|
51
|
+
def last_word(x: str):
|
|
52
|
+
"""returns the first word of a string"""
|
|
53
|
+
return x.split(" ")[-1]
|
|
54
|
+
|
|
55
|
+
@z.task
|
|
56
|
+
def another_task(somestring: str, some_int: int, workdir=None, root_url=None):
|
|
57
|
+
"""description of the task"""
|
|
58
|
+
print(f"somestring={somestring}")
|
|
59
|
+
print(f"some_int={some_int}")
|
|
60
|
+
print(f"workdir={workdir}")
|
|
61
|
+
print(f"root_url={root_url}")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
mycli.py
|
|
66
|
+
```
|
|
67
|
+
#!/usr/bin/env python3
|
|
68
|
+
import zparser2
|
|
69
|
+
|
|
70
|
+
import math_functions
|
|
71
|
+
import string_functions
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
zparser2.init()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Output
|
|
78
|
+
------
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py
|
|
82
|
+
./mycli.py <plugin_name> <task>
|
|
83
|
+
Plugin list:
|
|
84
|
+
math_functions - here we do math
|
|
85
|
+
string_functions - string processing
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions
|
|
91
|
+
You need to specify a task
|
|
92
|
+
--------------------------------------------------------------------------------
|
|
93
|
+
string processing
|
|
94
|
+
./mycli.py string_functions <task>
|
|
95
|
+
Plugin alias: []
|
|
96
|
+
Tasks:
|
|
97
|
+
add_square_brackets_to_string - x -> [x]
|
|
98
|
+
another_task - description of the task
|
|
99
|
+
first_word - returns the first word of a string
|
|
100
|
+
last_word - returns the first word of a string
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions another_task
|
|
105
|
+
You need to specify the required arguments [somestring, some_int]
|
|
106
|
+
--------------------------------------------------------------------------------
|
|
107
|
+
description of the task
|
|
108
|
+
Usage:
|
|
109
|
+
./mycli.py string_functions another_task somestring some_int [--workdir workdir] [--root_url root_url]
|
|
110
|
+
Positional arguments:
|
|
111
|
+
somestring - <class 'str'>
|
|
112
|
+
some_int - <class 'int'>
|
|
113
|
+
Optional arguments:
|
|
114
|
+
--workdir (Default: None) -
|
|
115
|
+
--root_url (Default: None) -
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions another_task blah 42 --root_url https://blah.com
|
|
120
|
+
somestring=blah
|
|
121
|
+
some_int=42
|
|
122
|
+
workdir=None
|
|
123
|
+
root_url=https://blah.com
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
How to build & publish
|
|
128
|
+
----------------------
|
|
129
|
+
|
|
130
|
+
* `python3 -m build`
|
|
131
|
+
* `python3 -m twine upload --repository testpypi dist/*`
|
|
132
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zparser2"
|
|
7
|
+
license = "BSD-3-Clause"
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Marcos Diez"},
|
|
10
|
+
{name = "Benjamin Port"},
|
|
11
|
+
]
|
|
12
|
+
keywords = ["cli", "argument", "parsing"]
|
|
13
|
+
description="ZParser Cli Argument Library. Nothing comes after Z"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Topic :: Software Development :: Libraries"
|
|
21
|
+
]
|
|
22
|
+
readme = "README.md"
|
|
23
|
+
dynamic = ["version"]
|
|
24
|
+
requires-python = ">=3.6"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools]
|
|
27
|
+
packages = ["zparser2"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.dynamic]
|
|
30
|
+
version = {attr = "zparser2.__version__"}
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/marcosdiez/zparser2"
|
|
34
|
+
Issues = "https://github.com/marcosdiez/zparser2/issues"
|
zparser2-0.0.4/setup.cfg
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import importlib
|
|
5
|
+
from copy import copy
|
|
6
|
+
from inspect import getfullargspec
|
|
7
|
+
|
|
8
|
+
__version__ = "0.0.4"
|
|
9
|
+
|
|
10
|
+
def extracted_arg_name(arg):
|
|
11
|
+
if arg.startswith('--'):
|
|
12
|
+
return arg[2:]
|
|
13
|
+
elif arg.startswith('-'):
|
|
14
|
+
return arg[1:]
|
|
15
|
+
else:
|
|
16
|
+
return arg
|
|
17
|
+
|
|
18
|
+
def is_help(value):
|
|
19
|
+
return value in ('h', 'help')
|
|
20
|
+
|
|
21
|
+
def is_setting(value):
|
|
22
|
+
return value in ('s', 'setting')
|
|
23
|
+
|
|
24
|
+
def is_quiet(value):
|
|
25
|
+
return value in ('q', 'quiet')
|
|
26
|
+
|
|
27
|
+
def is_optional(arg):
|
|
28
|
+
return arg.startswith('-')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
ENDC = '\033[0m'
|
|
32
|
+
BOLD = '\033[1m'
|
|
33
|
+
RED = '\033[91m'
|
|
34
|
+
|
|
35
|
+
RST_PARAM_RE = re.compile(r"^([\t ]*):param (.*?): (.*\n(\1[ \t]+.*\n*)*)", re.MULTILINE)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ZExitException(Exception):
|
|
39
|
+
def __init__(self, exit_code):
|
|
40
|
+
self.exit_code = exit_code
|
|
41
|
+
|
|
42
|
+
def zexit(exit_code):
|
|
43
|
+
raise ZExitException(exit_code)
|
|
44
|
+
|
|
45
|
+
class ArgumentException(Exception):
|
|
46
|
+
def __init__(self, error_msg=None):
|
|
47
|
+
self.error_msg = error_msg
|
|
48
|
+
|
|
49
|
+
class Helper:
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.help = ""
|
|
52
|
+
|
|
53
|
+
def usage(self):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def print_help(self, error_msg=None):
|
|
57
|
+
if error_msg:
|
|
58
|
+
print(BOLD + RED + error_msg + ENDC)
|
|
59
|
+
print('-'*80)
|
|
60
|
+
self.usage()
|
|
61
|
+
if error_msg:
|
|
62
|
+
zexit(1)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def short_help(self):
|
|
66
|
+
try:
|
|
67
|
+
return self.help.strip().splitlines()[0]
|
|
68
|
+
except IndexError:
|
|
69
|
+
return ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ZParser(Helper):
|
|
73
|
+
def __init__(self, plugin_module=None):
|
|
74
|
+
if plugin_module is None:
|
|
75
|
+
plugin_module = []
|
|
76
|
+
|
|
77
|
+
super(ZParser, self).__init__()
|
|
78
|
+
|
|
79
|
+
self.plugin_module = None
|
|
80
|
+
self.plugins = {}
|
|
81
|
+
self.set_plugin_module(plugin_module)
|
|
82
|
+
self.settings = {}
|
|
83
|
+
self.quiet = False
|
|
84
|
+
self.runner = None
|
|
85
|
+
|
|
86
|
+
def __repr__(self):
|
|
87
|
+
return self.plugins
|
|
88
|
+
|
|
89
|
+
def set_plugin_module(self, plugin_module):
|
|
90
|
+
self.plugin_module = plugin_module
|
|
91
|
+
|
|
92
|
+
for module in self.plugin_module:
|
|
93
|
+
loaded = importlib.import_module(module)
|
|
94
|
+
if loaded.__file__ is None:
|
|
95
|
+
print("bin")
|
|
96
|
+
print(module)
|
|
97
|
+
continue
|
|
98
|
+
plugin_dir = os.path.dirname(loaded.__file__)
|
|
99
|
+
plugin_list = [filename[:-3] for filename in os.listdir(plugin_dir) if filename[-3:] == '.py'
|
|
100
|
+
and filename != '__init__.py']
|
|
101
|
+
# create dict entry before loading them
|
|
102
|
+
for plugin_name in plugin_list:
|
|
103
|
+
self.plugins[plugin_name] = Plugin(plugin_name)
|
|
104
|
+
|
|
105
|
+
for plugin_name in plugin_list:
|
|
106
|
+
# loaded_plugin = importlib.import_module("{}.{}".format(module, plugin))
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
loaded_plugin = importlib.import_module("{}.{}".format(module, plugin_name))
|
|
110
|
+
except ImportError as e:
|
|
111
|
+
del self.plugins[plugin_name]
|
|
112
|
+
print("Failed to load plugin {}.{} [{}]".format(module, plugin_name, e))
|
|
113
|
+
raise
|
|
114
|
+
else:
|
|
115
|
+
try:
|
|
116
|
+
self.plugins[plugin_name].alias = loaded_plugin.alias
|
|
117
|
+
except AttributeError:
|
|
118
|
+
self.plugins[plugin_name].alias = []
|
|
119
|
+
self.plugins[plugin_name].help = loaded_plugin.__doc__ or ""
|
|
120
|
+
return self
|
|
121
|
+
|
|
122
|
+
def task(self, function=None, name=None, overwrite=None, short=None):
|
|
123
|
+
if overwrite is None:
|
|
124
|
+
overwrite = {}
|
|
125
|
+
|
|
126
|
+
if short is None:
|
|
127
|
+
short = {}
|
|
128
|
+
|
|
129
|
+
if not function:
|
|
130
|
+
return lambda function: self.task(function, name=name, overwrite=overwrite, short=short)
|
|
131
|
+
|
|
132
|
+
plugin_name = function.__module__.split('.')[-1]
|
|
133
|
+
self._register(plugin_name, function, name, overwrite, short)
|
|
134
|
+
return function
|
|
135
|
+
|
|
136
|
+
def _register(self, plugin_name, function, name=None, overwrite=None, short=None):
|
|
137
|
+
if overwrite is None:
|
|
138
|
+
overwrite = {}
|
|
139
|
+
|
|
140
|
+
if short is None:
|
|
141
|
+
short = {}
|
|
142
|
+
|
|
143
|
+
task = Task(function, name, overwrite, short)
|
|
144
|
+
if plugin_name not in self.plugins:
|
|
145
|
+
self.plugins[plugin_name] = Plugin(plugin_name)
|
|
146
|
+
self.plugins[plugin_name].alias = sys.modules[function.__module__].__dict__.get("alias", [])
|
|
147
|
+
self.plugins[plugin_name].help = sys.modules[function.__module__].__doc__ or ""
|
|
148
|
+
self.plugins[plugin_name].add_task(task)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def usage(self):
|
|
152
|
+
print("{} <plugin_name> <task>".format(self.prog_name))
|
|
153
|
+
print("Plugin list:")
|
|
154
|
+
for plugin in [value for (key, value) in sorted(self.plugins.items())]:
|
|
155
|
+
print(" {:20} - {}".format(plugin.name, plugin.short_help))
|
|
156
|
+
|
|
157
|
+
def parse(self, argv=None, prog_name=None):
|
|
158
|
+
if argv is None:
|
|
159
|
+
argv = sys.argv
|
|
160
|
+
|
|
161
|
+
self.prog_name = prog_name if prog_name else argv[0]
|
|
162
|
+
|
|
163
|
+
argv = argv[1:]
|
|
164
|
+
if not argv or (argv and is_optional(argv[0]) and is_help(extracted_arg_name(argv[0]))):
|
|
165
|
+
self.print_help()
|
|
166
|
+
zexit(0)
|
|
167
|
+
|
|
168
|
+
# parse global argument
|
|
169
|
+
argv = self.parse_global(argv)
|
|
170
|
+
|
|
171
|
+
if is_optional(argv[0]):
|
|
172
|
+
self.print_help("This argument is unexpected {}".format(argv[0]))
|
|
173
|
+
|
|
174
|
+
plugin = self._load_plugin_from_arg(argv[0])
|
|
175
|
+
self.runner = plugin.parse(argv[1:])
|
|
176
|
+
return self.runner
|
|
177
|
+
|
|
178
|
+
def _load_plugin_from_arg(self, arg):
|
|
179
|
+
for plugin in self.plugins.values():
|
|
180
|
+
if arg == plugin.name or arg in plugin.alias:
|
|
181
|
+
return plugin
|
|
182
|
+
return self.print_help("Plugin with name {} doesn't exist".format(arg))
|
|
183
|
+
|
|
184
|
+
def parse_global(self, argv=None):
|
|
185
|
+
pos = 0
|
|
186
|
+
need_setting_key = False
|
|
187
|
+
current_setting = None
|
|
188
|
+
for arg in argv:
|
|
189
|
+
if need_setting_key:
|
|
190
|
+
if is_optional(arg):
|
|
191
|
+
self.print_help("Need setting key")
|
|
192
|
+
else:
|
|
193
|
+
if current_setting:
|
|
194
|
+
self.settings[current_setting] = arg
|
|
195
|
+
need_setting_key = False
|
|
196
|
+
current_setting = None
|
|
197
|
+
else:
|
|
198
|
+
current_setting = arg
|
|
199
|
+
pos += 1
|
|
200
|
+
|
|
201
|
+
else:
|
|
202
|
+
if is_optional(arg):
|
|
203
|
+
if is_quiet(extracted_arg_name(arg)):
|
|
204
|
+
self.quiet = True
|
|
205
|
+
pos += 1
|
|
206
|
+
elif is_setting(extracted_arg_name(arg)):
|
|
207
|
+
pos += 1
|
|
208
|
+
need_setting_key = True
|
|
209
|
+
else:
|
|
210
|
+
self.print_help("Unexpected argument {}".format(arg))
|
|
211
|
+
else:
|
|
212
|
+
break
|
|
213
|
+
return argv[pos:]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class Plugin(Helper):
|
|
217
|
+
def __init__(self, name):
|
|
218
|
+
super(Plugin, self).__init__()
|
|
219
|
+
self.name = name
|
|
220
|
+
self.alias = []
|
|
221
|
+
self.tasks = {}
|
|
222
|
+
self.help = ""
|
|
223
|
+
|
|
224
|
+
def add_task(self, task):
|
|
225
|
+
self.tasks[task.name] = task
|
|
226
|
+
|
|
227
|
+
def __repr__(self):
|
|
228
|
+
return "{}".format(self.tasks)
|
|
229
|
+
|
|
230
|
+
def usage(self):
|
|
231
|
+
print(self.help)
|
|
232
|
+
|
|
233
|
+
print("{} {} <task>".format(z.prog_name, self.name))
|
|
234
|
+
print("Plugin alias: {}".format(self.alias))
|
|
235
|
+
print("Tasks:")
|
|
236
|
+
for task in [value for (key, value) in sorted(self.tasks.items())]:
|
|
237
|
+
print(" {:20} - {}".format(task.name, task.short_help))
|
|
238
|
+
|
|
239
|
+
def parse(self, argv=None):
|
|
240
|
+
if not argv:
|
|
241
|
+
self.print_help("You need to specify a task")
|
|
242
|
+
zexit(0)
|
|
243
|
+
|
|
244
|
+
arg = argv[0]
|
|
245
|
+
if is_optional(arg):
|
|
246
|
+
if is_help(extracted_arg_name(arg)):
|
|
247
|
+
self.print_help()
|
|
248
|
+
zexit(0)
|
|
249
|
+
else:
|
|
250
|
+
self.print_help("We don't expect this option here: {}".format(arg))
|
|
251
|
+
else:
|
|
252
|
+
task = self._load_task_from_arg(arg)
|
|
253
|
+
task.parse(argv[1:])
|
|
254
|
+
return task
|
|
255
|
+
|
|
256
|
+
def _load_task_from_arg(self, arg):
|
|
257
|
+
for task in self.tasks.values():
|
|
258
|
+
if arg == task.name:
|
|
259
|
+
return task
|
|
260
|
+
self.print_help("Task with name {} doesn't exist".format(arg))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class Task(Helper):
|
|
264
|
+
def __init__(self, function, name, overwrite, short):
|
|
265
|
+
super(Task, self).__init__()
|
|
266
|
+
if name is None:
|
|
267
|
+
name = function.__name__
|
|
268
|
+
self.function = function
|
|
269
|
+
self.name = name
|
|
270
|
+
self.overwrite = overwrite
|
|
271
|
+
self.short = short
|
|
272
|
+
self.args = []
|
|
273
|
+
self.optional_args = []
|
|
274
|
+
self.varargs = None
|
|
275
|
+
self.annotations = {}
|
|
276
|
+
self._init_args()
|
|
277
|
+
self._use_docstring()
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def all_args(self):
|
|
281
|
+
return self.args + self.optional_args
|
|
282
|
+
|
|
283
|
+
def _use_docstring(self):
|
|
284
|
+
docstring = self.function.__doc__ or ""
|
|
285
|
+
for match in RST_PARAM_RE.finditer(docstring):
|
|
286
|
+
name = match.group(2)
|
|
287
|
+
value = match.group(3)
|
|
288
|
+
for arg in self.all_args:
|
|
289
|
+
if arg.arg_python == name:
|
|
290
|
+
arg.help = value
|
|
291
|
+
docstring = RST_PARAM_RE.sub("", docstring)
|
|
292
|
+
self.help = docstring
|
|
293
|
+
|
|
294
|
+
def _init_args(self):
|
|
295
|
+
argdata = getfullargspec(self.function)
|
|
296
|
+
|
|
297
|
+
args = argdata.args
|
|
298
|
+
defaults = argdata.defaults
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
args2 = copy(args)
|
|
302
|
+
if defaults:
|
|
303
|
+
for arg, default in zip(args[0 - len(defaults):], defaults):
|
|
304
|
+
args2.remove(arg)
|
|
305
|
+
short = self.short.get(arg, None)
|
|
306
|
+
name = self.overwrite.get(arg, None)
|
|
307
|
+
self.optional_args.append(ArgumentOptional(arg, default, name, short))
|
|
308
|
+
for arg in args2:
|
|
309
|
+
self.args.append(Argument(arg))
|
|
310
|
+
|
|
311
|
+
if argdata.varargs:
|
|
312
|
+
self.varargs = Varargs(argdata.varargs)
|
|
313
|
+
|
|
314
|
+
if argdata.annotations:
|
|
315
|
+
self.annotations = argdata.annotations
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _clean_args(self):
|
|
319
|
+
for arg in self.optional_args:
|
|
320
|
+
arg.is_set = False
|
|
321
|
+
if self.varargs:
|
|
322
|
+
self.varargs.value = []
|
|
323
|
+
|
|
324
|
+
def parse(self, argv=None):
|
|
325
|
+
self._clean_args()
|
|
326
|
+
if argv and is_optional(argv[0]) and is_help(extracted_arg_name(argv[0])):
|
|
327
|
+
self.print_help()
|
|
328
|
+
zexit(0)
|
|
329
|
+
arg_pos = 0
|
|
330
|
+
if not argv and not self.args:
|
|
331
|
+
pass
|
|
332
|
+
elif len(argv) < len(self.args):
|
|
333
|
+
self.print_help("You need to specify the required arguments {}".format(self.args))
|
|
334
|
+
else:
|
|
335
|
+
current_arg = None
|
|
336
|
+
in_optional = False
|
|
337
|
+
for value in argv:
|
|
338
|
+
if arg_pos < len(self.all_args):
|
|
339
|
+
valid = False
|
|
340
|
+
if not is_optional(value):
|
|
341
|
+
if current_arg:
|
|
342
|
+
current_arg.value = value
|
|
343
|
+
current_arg = None
|
|
344
|
+
else:
|
|
345
|
+
if in_optional:
|
|
346
|
+
self.print_help("Error we don't expect a positional argument after an optional "
|
|
347
|
+
"declaration")
|
|
348
|
+
self.all_args[arg_pos].value = value
|
|
349
|
+
arg_pos += 1
|
|
350
|
+
valid = True
|
|
351
|
+
else:
|
|
352
|
+
if current_arg:
|
|
353
|
+
self.print_help("Except value for: {}".format(current_arg.name))
|
|
354
|
+
arg_name = extracted_arg_name(value)
|
|
355
|
+
for argument in self.optional_args:
|
|
356
|
+
if argument.name == arg_name or argument.short == arg_name:
|
|
357
|
+
if not isinstance(argument.default, bool):
|
|
358
|
+
current_arg = argument
|
|
359
|
+
valid = True
|
|
360
|
+
else:
|
|
361
|
+
argument.value = not argument.default
|
|
362
|
+
valid = True
|
|
363
|
+
in_optional = True
|
|
364
|
+
if not valid:
|
|
365
|
+
self.print_help("Invalid argument {}".format(value))
|
|
366
|
+
else:
|
|
367
|
+
if self.varargs:
|
|
368
|
+
self.varargs.value.append(value)
|
|
369
|
+
else:
|
|
370
|
+
self.print_help("too many arguments")
|
|
371
|
+
if arg_pos < len(self.args):
|
|
372
|
+
self.print_help("You need to give data for each positional argument")
|
|
373
|
+
|
|
374
|
+
# set optional argument not set to default
|
|
375
|
+
for arg in self.optional_args:
|
|
376
|
+
arg.use_default()
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def plugin(self):
|
|
380
|
+
return self.function.__module__.split('.')[-1]
|
|
381
|
+
|
|
382
|
+
def usage(self):
|
|
383
|
+
print(self.help)
|
|
384
|
+
print("Usage:")
|
|
385
|
+
parameters = []
|
|
386
|
+
for arg in self.all_args:
|
|
387
|
+
if isinstance(arg, ArgumentOptional):
|
|
388
|
+
if isinstance(arg.default, bool):
|
|
389
|
+
parameter = "[--{p}]"
|
|
390
|
+
else:
|
|
391
|
+
parameter = "[--{p} {p}]"
|
|
392
|
+
else:
|
|
393
|
+
parameter = "{p}"
|
|
394
|
+
parameters.append(parameter.format(p=arg.name))
|
|
395
|
+
if self.varargs:
|
|
396
|
+
parameters.append("[{p}, [{p}...]".format(p=self.varargs.name))
|
|
397
|
+
|
|
398
|
+
print(" {} {} {} {}".format(z.prog_name, self.plugin, self.name, " ".join(parameters)))
|
|
399
|
+
if self.args:
|
|
400
|
+
print("Positional arguments:")
|
|
401
|
+
for arg in self.args:
|
|
402
|
+
print(" {} - {} {}".format(arg.name, arg.short_help, self.annotations.get(arg.name, "")))
|
|
403
|
+
|
|
404
|
+
if self.optional_args:
|
|
405
|
+
print("Optional arguments:")
|
|
406
|
+
for arg in self.optional_args:
|
|
407
|
+
arg_name = "--{}".format(arg.name)
|
|
408
|
+
if arg.short:
|
|
409
|
+
arg_name = "{}/-{}".format(arg_name, arg.short)
|
|
410
|
+
print(" {} (Default: {}) {} - {}".format(arg_name, arg.default, self.annotations.get(arg.name, ""), arg.short_help))
|
|
411
|
+
|
|
412
|
+
if self.varargs:
|
|
413
|
+
print(" {} - {}".format(self.varargs.name, self.varargs.short_help))
|
|
414
|
+
|
|
415
|
+
def _args_value(self):
|
|
416
|
+
only_string_parameters = [arg.value for arg in self.all_args] + ([] if not self.varargs else self.varargs.value)
|
|
417
|
+
parsed_paramenters = self._parse_floats_and_ints_in_a_list(only_string_parameters)
|
|
418
|
+
self._enforce_variable_annotations(parsed_paramenters)
|
|
419
|
+
return parsed_paramenters
|
|
420
|
+
|
|
421
|
+
def _enforce_variable_annotations(self, parsed_paramenters):
|
|
422
|
+
if len(parsed_paramenters) <= len(self.all_args):
|
|
423
|
+
for i in range(len(parsed_paramenters)):
|
|
424
|
+
param_name = str(self.all_args[i])
|
|
425
|
+
param_class = self.annotations.get(param_name)
|
|
426
|
+
if param_class is not None:
|
|
427
|
+
param_value = parsed_paramenters[i]
|
|
428
|
+
if isinstance(param_value, param_class) or param_value.__class__ == int and param_class == float:
|
|
429
|
+
continue
|
|
430
|
+
else:
|
|
431
|
+
raise ArgumentException(f"Invalid value for paramter {param_name}. A {param_class} is expected, not {param_value.__class__}")
|
|
432
|
+
|
|
433
|
+
def _parse_floats_and_ints_in_a_list(self, the_list):
|
|
434
|
+
size = len(the_list)
|
|
435
|
+
for i in range(0, size):
|
|
436
|
+
elem = the_list[i]
|
|
437
|
+
if isinstance(elem, str):
|
|
438
|
+
if "." in elem:
|
|
439
|
+
try:
|
|
440
|
+
new_elem = float(elem)
|
|
441
|
+
except ValueError:
|
|
442
|
+
continue
|
|
443
|
+
else:
|
|
444
|
+
try:
|
|
445
|
+
new_elem = int(elem)
|
|
446
|
+
except ValueError:
|
|
447
|
+
continue
|
|
448
|
+
the_list[i] = new_elem
|
|
449
|
+
|
|
450
|
+
return the_list
|
|
451
|
+
|
|
452
|
+
def run(self):
|
|
453
|
+
try:
|
|
454
|
+
result = self.function(*self._args_value())
|
|
455
|
+
except ArgumentException as e:
|
|
456
|
+
self.print_help(e.error_msg)
|
|
457
|
+
else:
|
|
458
|
+
if result or result is False:
|
|
459
|
+
if isinstance(result, list):
|
|
460
|
+
if isinstance(result[0], str):
|
|
461
|
+
x = []
|
|
462
|
+
for r in result:
|
|
463
|
+
if " " in r:
|
|
464
|
+
x.append('"{}"'.format(r))
|
|
465
|
+
else:
|
|
466
|
+
x.append(r)
|
|
467
|
+
output = " ".join(x)
|
|
468
|
+
print(output)
|
|
469
|
+
else:
|
|
470
|
+
for r in result:
|
|
471
|
+
print(r)
|
|
472
|
+
elif isinstance(result, dict):
|
|
473
|
+
for key, value in result.iteritems():
|
|
474
|
+
print("{}\t{}".format(key, value))
|
|
475
|
+
else:
|
|
476
|
+
print(result)
|
|
477
|
+
|
|
478
|
+
def __repr__(self):
|
|
479
|
+
return self.name
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class Argument(Helper):
|
|
483
|
+
def __init__(self, arg_python, name=None, short=None):
|
|
484
|
+
super(Argument, self).__init__()
|
|
485
|
+
self.arg_python = arg_python
|
|
486
|
+
self.name = name or arg_python
|
|
487
|
+
self.short = short
|
|
488
|
+
self._value = None
|
|
489
|
+
|
|
490
|
+
@property
|
|
491
|
+
def value(self):
|
|
492
|
+
return self._value
|
|
493
|
+
|
|
494
|
+
@value.setter
|
|
495
|
+
def value(self, value):
|
|
496
|
+
self._value = value
|
|
497
|
+
|
|
498
|
+
def __repr__(self):
|
|
499
|
+
return self.name
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class ArgumentOptional(Argument):
|
|
503
|
+
def __init__(self, arg_python, default, name=None, short=None):
|
|
504
|
+
if isinstance(default, bool) and default:
|
|
505
|
+
if not name:
|
|
506
|
+
name = "no-{}".format(arg_python)
|
|
507
|
+
|
|
508
|
+
super(ArgumentOptional, self).__init__(arg_python, name, short)
|
|
509
|
+
self.default = default
|
|
510
|
+
self.is_set = False
|
|
511
|
+
|
|
512
|
+
def use_default(self):
|
|
513
|
+
if not self.is_set:
|
|
514
|
+
self._value = self.default
|
|
515
|
+
|
|
516
|
+
@Argument.value.getter
|
|
517
|
+
def value(self):
|
|
518
|
+
if type(self._value) == type(self.default):
|
|
519
|
+
return self._value
|
|
520
|
+
if isinstance(self.default, bool):
|
|
521
|
+
if self._value.lower() in ['true', '1']:
|
|
522
|
+
return True
|
|
523
|
+
elif self._value.lower() in ['false', '0']:
|
|
524
|
+
return False
|
|
525
|
+
else:
|
|
526
|
+
self.print_help("Invalid data, expect boolean for arg: {}".format(self.name))
|
|
527
|
+
elif isinstance(self.default, int):
|
|
528
|
+
return int(self._value)
|
|
529
|
+
elif isinstance(self.default, list):
|
|
530
|
+
result = []
|
|
531
|
+
for i in self._value.split(','):
|
|
532
|
+
result.append(i)
|
|
533
|
+
return result
|
|
534
|
+
return self._value
|
|
535
|
+
|
|
536
|
+
@value.setter
|
|
537
|
+
def value(self, value):
|
|
538
|
+
self.is_set = True
|
|
539
|
+
self._value = value
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
class Varargs(Argument):
|
|
543
|
+
def __init__(self, arg_python, name=None, short=None):
|
|
544
|
+
super(Varargs, self).__init__(arg_python, name, short)
|
|
545
|
+
self._value = []
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def init(plugin_list: list=[]) -> int:
|
|
549
|
+
global z
|
|
550
|
+
try:
|
|
551
|
+
z.set_plugin_module(plugin_list).parse().run()
|
|
552
|
+
except ZExitException as exit_exception:
|
|
553
|
+
sys.exit(exit_exception.exit_code)
|
|
554
|
+
sys.exit(0)
|
|
555
|
+
|
|
556
|
+
z = ZParser()
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zparser2
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: ZParser Cli Argument Library. Nothing comes after Z
|
|
5
|
+
Author: Marcos Diez, Benjamin Port
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/marcosdiez/zparser2
|
|
8
|
+
Project-URL: Issues, https://github.com/marcosdiez/zparser2/issues
|
|
9
|
+
Keywords: cli,argument,parsing
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.6
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
ZParser2 Cli Argument Parsing Library
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
`zparser2` is probably the simplest most opinionated argument parsing library. Lot's of conventions, zero configuration!
|
|
25
|
+
|
|
26
|
+
If you add the `@z.task` notation to a function, it automatically become available to the CLI.
|
|
27
|
+
The function's parameters are what the CLI will expect. If notations are given, type will be enforced.
|
|
28
|
+
The file in which the function is located will be the module in which the function will be available.
|
|
29
|
+
|
|
30
|
+
The downside is that you can only have two layers in your cli. That being said, more than that would be too complex and less than that you don't really need a library.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Example
|
|
34
|
+
-------
|
|
35
|
+
|
|
36
|
+
Let's say you have 3 files:
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
math_functions.py
|
|
40
|
+
```
|
|
41
|
+
"""here we do math"""
|
|
42
|
+
from zparser2 import z
|
|
43
|
+
|
|
44
|
+
@z.task
|
|
45
|
+
def duplicate_number(x: float):
|
|
46
|
+
"""returns twice the value of x"""
|
|
47
|
+
return 2*x
|
|
48
|
+
|
|
49
|
+
@z.task
|
|
50
|
+
def triple_number(x: float):
|
|
51
|
+
"""returns 3 times the value of x"""
|
|
52
|
+
return 3*x
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
string_functions.py
|
|
56
|
+
```
|
|
57
|
+
"""string processing"""
|
|
58
|
+
from zparser2 import z
|
|
59
|
+
|
|
60
|
+
@z.task
|
|
61
|
+
def add_square_brackets_to_string(x: str):
|
|
62
|
+
"""x -> [x]"""
|
|
63
|
+
return f"[{x}]"
|
|
64
|
+
|
|
65
|
+
@z.task
|
|
66
|
+
def first_word(x: str):
|
|
67
|
+
"""returns the first word of a string"""
|
|
68
|
+
return x.split(" ")[0]
|
|
69
|
+
|
|
70
|
+
@z.task
|
|
71
|
+
def last_word(x: str):
|
|
72
|
+
"""returns the first word of a string"""
|
|
73
|
+
return x.split(" ")[-1]
|
|
74
|
+
|
|
75
|
+
@z.task
|
|
76
|
+
def another_task(somestring: str, some_int: int, workdir=None, root_url=None):
|
|
77
|
+
"""description of the task"""
|
|
78
|
+
print(f"somestring={somestring}")
|
|
79
|
+
print(f"some_int={some_int}")
|
|
80
|
+
print(f"workdir={workdir}")
|
|
81
|
+
print(f"root_url={root_url}")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
mycli.py
|
|
86
|
+
```
|
|
87
|
+
#!/usr/bin/env python3
|
|
88
|
+
import zparser2
|
|
89
|
+
|
|
90
|
+
import math_functions
|
|
91
|
+
import string_functions
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
zparser2.init()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Output
|
|
98
|
+
------
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py
|
|
102
|
+
./mycli.py <plugin_name> <task>
|
|
103
|
+
Plugin list:
|
|
104
|
+
math_functions - here we do math
|
|
105
|
+
string_functions - string processing
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions
|
|
111
|
+
You need to specify a task
|
|
112
|
+
--------------------------------------------------------------------------------
|
|
113
|
+
string processing
|
|
114
|
+
./mycli.py string_functions <task>
|
|
115
|
+
Plugin alias: []
|
|
116
|
+
Tasks:
|
|
117
|
+
add_square_brackets_to_string - x -> [x]
|
|
118
|
+
another_task - description of the task
|
|
119
|
+
first_word - returns the first word of a string
|
|
120
|
+
last_word - returns the first word of a string
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions another_task
|
|
125
|
+
You need to specify the required arguments [somestring, some_int]
|
|
126
|
+
--------------------------------------------------------------------------------
|
|
127
|
+
description of the task
|
|
128
|
+
Usage:
|
|
129
|
+
./mycli.py string_functions another_task somestring some_int [--workdir workdir] [--root_url root_url]
|
|
130
|
+
Positional arguments:
|
|
131
|
+
somestring - <class 'str'>
|
|
132
|
+
some_int - <class 'int'>
|
|
133
|
+
Optional arguments:
|
|
134
|
+
--workdir (Default: None) -
|
|
135
|
+
--root_url (Default: None) -
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
(env2) mdiez@batman:~/code/zparser2/example$ ./mycli.py string_functions another_task blah 42 --root_url https://blah.com
|
|
140
|
+
somestring=blah
|
|
141
|
+
some_int=42
|
|
142
|
+
workdir=None
|
|
143
|
+
root_url=https://blah.com
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
How to build & publish
|
|
148
|
+
----------------------
|
|
149
|
+
|
|
150
|
+
* `python3 -m build`
|
|
151
|
+
* `python3 -m twine upload --repository testpypi dist/*`
|
|
152
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zparser2
|