argumentor-gardehal 0.0.0__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.
- argumentor_gardehal-0.0.0/.gitattributes +2 -0
- argumentor_gardehal-0.0.0/.github/workflows/publish.yml +60 -0
- argumentor_gardehal-0.0.0/.gitignore +183 -0
- argumentor_gardehal-0.0.0/.vscode/settings.json +13 -0
- argumentor_gardehal-0.0.0/Argumentor/Argument.py +72 -0
- argumentor_gardehal-0.0.0/Argumentor/ArgumentValidation.py +225 -0
- argumentor_gardehal-0.0.0/Argumentor/Argumentor.py +153 -0
- argumentor_gardehal-0.0.0/Argumentor/BoolFlag.py +30 -0
- argumentor_gardehal-0.0.0/Argumentor/Command.py +66 -0
- argumentor_gardehal-0.0.0/Argumentor/Flag.py +49 -0
- argumentor_gardehal-0.0.0/Argumentor/Result.py +60 -0
- argumentor_gardehal-0.0.0/Argumentor/__init__.py +7 -0
- argumentor_gardehal-0.0.0/LICENSE +21 -0
- argumentor_gardehal-0.0.0/PKG-INFO +103 -0
- argumentor_gardehal-0.0.0/README.md +90 -0
- argumentor_gardehal-0.0.0/argumentor_gardehal.egg-info/PKG-INFO +103 -0
- argumentor_gardehal-0.0.0/argumentor_gardehal.egg-info/SOURCES.txt +24 -0
- argumentor_gardehal-0.0.0/argumentor_gardehal.egg-info/dependency_links.txt +1 -0
- argumentor_gardehal-0.0.0/argumentor_gardehal.egg-info/top_level.txt +1 -0
- argumentor_gardehal-0.0.0/pyproject.toml +19 -0
- argumentor_gardehal-0.0.0/setup.cfg +4 -0
- argumentor_gardehal-0.0.0/tests/ArgumentorTests.py +505 -0
- argumentor_gardehal-0.0.0/tests/ExampleAdvanced.py +106 -0
- argumentor_gardehal-0.0.0/tests/ExampleBasic.py +39 -0
- argumentor_gardehal-0.0.0/tests/enums/CommandHitValues.py +6 -0
- argumentor_gardehal-0.0.0/tests/enums/Measurement.py +5 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
name: Build Argumentor ${{github.ref_name}}
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
outputs:
|
|
17
|
+
version: ${{steps.get_version.outputs.version}}
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
uses: actions/setup-python@v6
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.x"
|
|
28
|
+
|
|
29
|
+
- name: Install build dependencies
|
|
30
|
+
run: python -m pip install --upgrade build setuptools_scm twine
|
|
31
|
+
|
|
32
|
+
- name: Build distributions
|
|
33
|
+
run: python -m build
|
|
34
|
+
|
|
35
|
+
- name: Upload artifact
|
|
36
|
+
uses: actions/upload-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: argumentor
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
- name: List built files
|
|
42
|
+
run: ls -la dist/
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
name: Publish to PyPI
|
|
46
|
+
needs: build
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
environment:
|
|
49
|
+
name: pypi
|
|
50
|
+
url: https://pypi.org/project/argumentor-gardehal/
|
|
51
|
+
permissions:
|
|
52
|
+
id-token: write
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/download-artifact@v4
|
|
55
|
+
with:
|
|
56
|
+
name: argumentor
|
|
57
|
+
path: dist/
|
|
58
|
+
|
|
59
|
+
- name: Publish to PyPI
|
|
60
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
#.idea/
|
|
169
|
+
|
|
170
|
+
# Ruff stuff:
|
|
171
|
+
.ruff_cache/
|
|
172
|
+
|
|
173
|
+
# PyPI configuration file
|
|
174
|
+
.pypirc
|
|
175
|
+
|
|
176
|
+
# Cursor
|
|
177
|
+
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
|
|
178
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
179
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
180
|
+
.cursorignore
|
|
181
|
+
.cursorindexingignore
|
|
182
|
+
|
|
183
|
+
Main.py
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from typing import TypeVar, Type, Callable
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
class Argument():
|
|
8
|
+
name: str
|
|
9
|
+
alias: list[str]
|
|
10
|
+
typeT: Type[T]
|
|
11
|
+
optional: bool
|
|
12
|
+
castFunc: Callable[[str], T]
|
|
13
|
+
validateFunc: Callable[[T], bool]
|
|
14
|
+
useDefaultValue: bool
|
|
15
|
+
defaultValue: T
|
|
16
|
+
description: str
|
|
17
|
+
|
|
18
|
+
def __init__(self, name: str,
|
|
19
|
+
alias: list[str],
|
|
20
|
+
typeT: Type[T],
|
|
21
|
+
optional: bool = False,
|
|
22
|
+
castFunc: Callable[[str], T] = None,
|
|
23
|
+
validateFunc: Callable[[T], bool] = None,
|
|
24
|
+
useDefaultValue: bool = False,
|
|
25
|
+
defaultValue: T = None,
|
|
26
|
+
description: str = None):
|
|
27
|
+
"""
|
|
28
|
+
Designates values input as arguments after commands
|
|
29
|
+
eg. height in
|
|
30
|
+
$ -dimensions height:100
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
name (str): Name of argument, key for dictionary in Return
|
|
34
|
+
alias (list[str]): Alias of argument.
|
|
35
|
+
typeT (Type[T]): Type of argument, str, int, bool, enum, etc.
|
|
36
|
+
optional (bool, optional): Argument is optional/nullable (from input). Defaults to False. Note that this implies the argument can be None in result, unless useDefaultValue and defaultValue are both set.
|
|
37
|
+
castFunc (Callable[[str], T], optional): Optional function for custom casting of input to typeT. Must take in 1 argument: str and return typeT. Defaults to None.
|
|
38
|
+
validateFunc (Callable[[T], bool], optional): Optional function for custom validation. Must take in 1 argument: typeT and return bool. Defaults to None.
|
|
39
|
+
useDefaultValue (bool, optional): Use a default value if casting and validation fails. Defaults to False.
|
|
40
|
+
defaultValue (T, optional): The default value to use if casting and validation fails, and useDefaultValue is True. Must be typeT. Defaults to None.
|
|
41
|
+
description (str, optional): Explaining what the argument is for. Defaults to None.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
invalidCharactersRegex = r"\W"
|
|
45
|
+
invalidNames = [e for e in alias + [name] if (re.search(invalidCharactersRegex, e))]
|
|
46
|
+
if(invalidNames):
|
|
47
|
+
raise AttributeError(f"Argument \"{name}\" name or alias ({invalidNames}) contain invalid characters, must be alphanumeric.")
|
|
48
|
+
|
|
49
|
+
self.name = name
|
|
50
|
+
self.alias = alias
|
|
51
|
+
self.typeT = typeT
|
|
52
|
+
self.optional = optional
|
|
53
|
+
self.castFunc = castFunc
|
|
54
|
+
self.validateFunc = validateFunc
|
|
55
|
+
self.useDefaultValue = useDefaultValue
|
|
56
|
+
self.defaultValue = defaultValue
|
|
57
|
+
self.description = description
|
|
58
|
+
|
|
59
|
+
def getFormattedDescription(self) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Get the description of arguments with formatting.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
str: String description.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
optionalDisplayString = "optional" if self.optional else "required"
|
|
68
|
+
typeDisplayString = f", type: {self.typeT.__name__}"
|
|
69
|
+
aliasDisplayString = f", alias: {", ".join(self.alias)}" if self.alias else ""
|
|
70
|
+
defaultDisplayString = f", default: {str(self.defaultValue)}" if self.useDefaultValue else ""
|
|
71
|
+
return f"* Argument {self.name} ({optionalDisplayString}{typeDisplayString}{defaultDisplayString}{aliasDisplayString}): \
|
|
72
|
+
\n\t{self.description}"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from .Argument import Argument
|
|
2
|
+
from .Command import Command
|
|
3
|
+
from .Flag import Flag
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
class ArgumentValidation():
|
|
8
|
+
isValid: bool
|
|
9
|
+
namedArguments: dict[str, str]
|
|
10
|
+
validatedArguments: dict[str, str]
|
|
11
|
+
finalizedArguments: dict[str, object]
|
|
12
|
+
messages: list[str]
|
|
13
|
+
|
|
14
|
+
namedInputRegex: str
|
|
15
|
+
flagInputRegex: str
|
|
16
|
+
|
|
17
|
+
def __init__(self, inputList: list[str], command: Command, namedArgDelim: str, flagPrefix: str):
|
|
18
|
+
"""
|
|
19
|
+
Internal validation in Argumentor.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
inputList (list[str]): List of inputs from user
|
|
23
|
+
command (Command): Command to validate input against
|
|
24
|
+
namedArgDelim (str): Delimiter used for named input, e.g. ":" in key:value
|
|
25
|
+
flagPrefix (str): Prefix for flags, e.g. "--updateexternal".
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
self.isValid = False
|
|
29
|
+
self.namedArguments = {}
|
|
30
|
+
self.validatedArguments = {}
|
|
31
|
+
self.finalizedArguments = {}
|
|
32
|
+
self.messages = []
|
|
33
|
+
|
|
34
|
+
self.namedInputRegex = fr"^\w+{namedArgDelim}\S+"
|
|
35
|
+
self.flagInputRegex = fr"^{flagPrefix}\w+"
|
|
36
|
+
|
|
37
|
+
if(command.arguments):
|
|
38
|
+
self.__populateNamedArguments(inputList, namedArgDelim)
|
|
39
|
+
self.__validateNamedArguments(command.arguments)
|
|
40
|
+
self.__addPositionalArguments(inputList, command)
|
|
41
|
+
self.__castAndValidateArguments(command)
|
|
42
|
+
else:
|
|
43
|
+
self.isValid = True
|
|
44
|
+
|
|
45
|
+
if(command.flags):
|
|
46
|
+
self.__addFlags(inputList, flagPrefix, command.flags)
|
|
47
|
+
|
|
48
|
+
def toString(self) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Returns string with class properties.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: String of class properties.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
return f""" \
|
|
57
|
+
isValid: {self.isValid},
|
|
58
|
+
namedArguments: {self.namedArguments},
|
|
59
|
+
validatedArguments: {self.validatedArguments},
|
|
60
|
+
finalizedArguments: {self.finalizedArguments},
|
|
61
|
+
messages: {self.messages},
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __populateNamedArguments(self, inputList: list[str], namedArgDelim: str):
|
|
65
|
+
namedInputs = [e for e in inputList if(namedArgDelim in e)]
|
|
66
|
+
namedArguments = {}
|
|
67
|
+
for input in namedInputs:
|
|
68
|
+
namedSplit = input.split(namedArgDelim)
|
|
69
|
+
key = namedSplit[0]
|
|
70
|
+
value = namedArgDelim.join(namedSplit[1:])
|
|
71
|
+
namedArguments[key] = value
|
|
72
|
+
|
|
73
|
+
self.namedArguments = namedArguments
|
|
74
|
+
|
|
75
|
+
def __validateNamedArguments(self, arguments: list[Argument]):
|
|
76
|
+
argumentAliasMap = {}
|
|
77
|
+
for argument in arguments:
|
|
78
|
+
argumentAliasMap[argument.name] = argument.name
|
|
79
|
+
for alias in argument.alias:
|
|
80
|
+
argumentAliasMap[alias] = argument.name
|
|
81
|
+
|
|
82
|
+
for key in self.namedArguments.keys():
|
|
83
|
+
if(key not in argumentAliasMap.keys()):
|
|
84
|
+
self.messages.append(self.__formatArgumentError(key, "Not a valid argument alias"))
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
if(key in self.validatedArguments.keys()):
|
|
88
|
+
self.messages.append(self.__formatArgumentError(key, "Alias was already added"))
|
|
89
|
+
continue
|
|
90
|
+
|
|
91
|
+
self.validatedArguments[argumentAliasMap[key]] = self.namedArguments[key]
|
|
92
|
+
|
|
93
|
+
def __addPositionalArguments(self, inputList: list[str], command: Command):
|
|
94
|
+
unnamedInput = [e for e in inputList if(not re.search(self.namedInputRegex, e) and not re.search(self.flagInputRegex, e))]
|
|
95
|
+
remainingArgument = [e for e in command.arguments if(e.name not in self.validatedArguments.keys())]
|
|
96
|
+
|
|
97
|
+
for i in range(len(unnamedInput)):
|
|
98
|
+
if(i >= len(remainingArgument)):
|
|
99
|
+
self.messages.append(f"Received more positional arguments ({len(unnamedInput)}) than expected ({len(remainingArgument)})")
|
|
100
|
+
for extraArg in unnamedInput[i:]:
|
|
101
|
+
self.messages.append(f"{extraArg} not added, exceeds expected Arguments length")
|
|
102
|
+
|
|
103
|
+
break # unnamedInput loop
|
|
104
|
+
|
|
105
|
+
unnamedArg = unnamedInput[i]
|
|
106
|
+
positionalArg = remainingArgument[i]
|
|
107
|
+
if(positionalArg.name in self.validatedArguments.keys()):
|
|
108
|
+
self.messages.append(self.__formatArgumentError(positionalArg.name, f"Already added as named argument {unnamedArg}"))
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
self.validatedArguments[positionalArg.name] = unnamedArg
|
|
112
|
+
|
|
113
|
+
def __castAndValidateArguments(self, command: Command):
|
|
114
|
+
inputIsValid = True
|
|
115
|
+
|
|
116
|
+
# Adding optional arguments not in input
|
|
117
|
+
# TODO new method?
|
|
118
|
+
for argument in command.arguments:
|
|
119
|
+
if(argument.optional and argument.name not in self.validatedArguments):
|
|
120
|
+
self.validatedArguments[argument.name] = None
|
|
121
|
+
|
|
122
|
+
for key in self.validatedArguments.keys():
|
|
123
|
+
argument = [e for e in command.arguments if e.name is key][0]
|
|
124
|
+
if(argument is None):
|
|
125
|
+
self.messages.append(self.__formatArgumentError(key, "Critical error! No Argument object found"))
|
|
126
|
+
inputIsValid = False
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
value = self.validatedArguments[key]
|
|
130
|
+
if(value is None):
|
|
131
|
+
if(argument.useDefaultValue):
|
|
132
|
+
self.messages.append(self.__formatArgumentError(key, f"Value was None and not optional, default value {argument.defaultValue} was applied"))
|
|
133
|
+
castValue = argument.defaultValue
|
|
134
|
+
continue
|
|
135
|
+
elif(argument.optional):
|
|
136
|
+
self.finalizedArguments[key] = None
|
|
137
|
+
continue
|
|
138
|
+
else:
|
|
139
|
+
self.messages.append(self.__formatArgumentError(key, f"Critical error! Value was None, and Argument is not optional"))
|
|
140
|
+
inputIsValid = False
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
castSuccess = False
|
|
144
|
+
castValue = None
|
|
145
|
+
try:
|
|
146
|
+
if(argument.castFunc):
|
|
147
|
+
castValue = argument.castFunc(value)
|
|
148
|
+
else:
|
|
149
|
+
castValue = (argument.typeT)(value)
|
|
150
|
+
|
|
151
|
+
if(castValue is None and not argument.optional):
|
|
152
|
+
if(argument.useDefaultValue):
|
|
153
|
+
self.messages.append(self.__formatArgumentError(key, f"Value was None but argument was not optional, default value {argument.defaultValue} was applied"))
|
|
154
|
+
castValue = argument.defaultValue
|
|
155
|
+
continue
|
|
156
|
+
else:
|
|
157
|
+
self.messages.append(self.__formatArgumentError(key, f"Critical error! Value was None, not optional, and no default was given")) # Remember useDefaultValue
|
|
158
|
+
inputIsValid = False
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
castSuccess = True
|
|
162
|
+
except Exception as ex:
|
|
163
|
+
if(argument.useDefaultValue):
|
|
164
|
+
self.messages.append(self.__formatArgumentError(key, f"{value} could not be cast, default value {argument.defaultValue} was applied"))
|
|
165
|
+
castValue = argument.defaultValue
|
|
166
|
+
continue
|
|
167
|
+
else:
|
|
168
|
+
self.messages.append(self.__formatArgumentError(key, f"Critical error! {value} could not be cast to {argument.typeT.__name__}"))
|
|
169
|
+
inputIsValid = False
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
if(castSuccess and argument.validateFunc):
|
|
173
|
+
try:
|
|
174
|
+
resultValid = argument.validateFunc(castValue)
|
|
175
|
+
if(not resultValid):
|
|
176
|
+
if(argument.useDefaultValue):
|
|
177
|
+
self.messages.append(self.__formatArgumentError(key, f"{value} did not pass validation, default value {argument.defaultValue} was applied"))
|
|
178
|
+
castValue = argument.defaultValue
|
|
179
|
+
continue
|
|
180
|
+
else:
|
|
181
|
+
self.messages.append(self.__formatArgumentError(key, f"Critical error! {value} did not pass validation"))
|
|
182
|
+
inputIsValid = False
|
|
183
|
+
continue
|
|
184
|
+
except Exception as ex:
|
|
185
|
+
if(argument.useDefaultValue):
|
|
186
|
+
self.messages.append(self.__formatArgumentError(key, f"{value} validation raised an exception, default value {argument.defaultValue} was applied"))
|
|
187
|
+
castValue = argument.defaultValue
|
|
188
|
+
continue
|
|
189
|
+
else:
|
|
190
|
+
self.messages.append(self.__formatArgumentError(key, f"Critical error! {value} validation raised an exception and no defaults were given"))
|
|
191
|
+
inputIsValid = False
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
self.finalizedArguments[key] = castValue
|
|
195
|
+
|
|
196
|
+
requiredArgumentNames = [e.name for e in command.arguments if not e.optional]
|
|
197
|
+
if(len(self.finalizedArguments.keys())) < len(requiredArgumentNames):
|
|
198
|
+
self.messages.append(f"Critical error! Required arguments are missing (got {len(self.finalizedArguments.keys())}/{len(requiredArgumentNames)})")
|
|
199
|
+
inputIsValid = False
|
|
200
|
+
|
|
201
|
+
if(inputIsValid):
|
|
202
|
+
for argument in command.arguments:
|
|
203
|
+
if(argument.name not in self.finalizedArguments.keys() and argument.useDefaultValue):
|
|
204
|
+
self.finalizedArguments[argument.name] = argument.defaultValue
|
|
205
|
+
|
|
206
|
+
self.isValid = inputIsValid
|
|
207
|
+
|
|
208
|
+
def __addFlags(self, inputList: list[str], flagPrefix: str, flags: list[Flag]):
|
|
209
|
+
flagInputs = [e.removeprefix(flagPrefix) for e in inputList if(re.search(self.flagInputRegex, e))]
|
|
210
|
+
for flag in flags:
|
|
211
|
+
intersections = list(set(flagInputs) & set(flag.alias + [flag.name]))
|
|
212
|
+
if(intersections):
|
|
213
|
+
self.finalizedArguments[flag.name] = flag.value
|
|
214
|
+
for intersection in intersections:
|
|
215
|
+
flagInputs.remove(intersection)
|
|
216
|
+
else:
|
|
217
|
+
self.finalizedArguments[flag.name] = flag.defaultValue
|
|
218
|
+
|
|
219
|
+
if(flagInputs):
|
|
220
|
+
self.messages.append(self.__formatArgumentError(", ".join(flagInputs), f"No such flag(s)"))
|
|
221
|
+
|
|
222
|
+
def __formatArgumentError(self, arg: str, error: str) -> str:
|
|
223
|
+
return f"{arg} error: {error}"
|
|
224
|
+
|
|
225
|
+
|