py-arguments 1.0.1__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.
- py_arguments-1.0.1/.github/workflows/claude-review.yml +29 -0
- py_arguments-1.0.1/.github/workflows/pre-commit.yml +16 -0
- py_arguments-1.0.1/.github/workflows/publish.yml +26 -0
- py_arguments-1.0.1/.github/workflows/release.yml +33 -0
- py_arguments-1.0.1/.gitignore +61 -0
- py_arguments-1.0.1/.pre-commit-config.yaml +79 -0
- py_arguments-1.0.1/.releaserc.json +45 -0
- py_arguments-1.0.1/CHANGELOG.md +14 -0
- py_arguments-1.0.1/DEVELOPERS.md +46 -0
- py_arguments-1.0.1/Makefile +4 -0
- py_arguments-1.0.1/PKG-INFO +153 -0
- py_arguments-1.0.1/README.md +146 -0
- py_arguments-1.0.1/hooks/format_commit_message.py +114 -0
- py_arguments-1.0.1/py_arguments.egg-info/PKG-INFO +153 -0
- py_arguments-1.0.1/py_arguments.egg-info/SOURCES.txt +22 -0
- py_arguments-1.0.1/py_arguments.egg-info/dependency_links.txt +1 -0
- py_arguments-1.0.1/py_arguments.egg-info/requires.txt +3 -0
- py_arguments-1.0.1/py_arguments.egg-info/scm_file_list.json +19 -0
- py_arguments-1.0.1/py_arguments.egg-info/scm_version.json +8 -0
- py_arguments-1.0.1/py_arguments.egg-info/top_level.txt +1 -0
- py_arguments-1.0.1/pyarg/__init__.py +5 -0
- py_arguments-1.0.1/pyarg/parser.py +118 -0
- py_arguments-1.0.1/pyproject.toml +55 -0
- py_arguments-1.0.1/setup.cfg +4 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Claude PR Review
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened]
|
|
5
|
+
jobs:
|
|
6
|
+
review:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pull-requests: write
|
|
11
|
+
issues: write
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 1
|
|
17
|
+
|
|
18
|
+
- uses: anthropics/claude-code-action@v1
|
|
19
|
+
with:
|
|
20
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
21
|
+
prompt: |
|
|
22
|
+
REPO: ${{ github.repository }}
|
|
23
|
+
PR NUMBER: ${{ github.event.pull_request.number }}
|
|
24
|
+
|
|
25
|
+
Review this PR focusing on bugs, security issues, code performance and readability.
|
|
26
|
+
Be objective and answer in english. Comment only what is really important.
|
|
27
|
+
|
|
28
|
+
claude_args: |
|
|
29
|
+
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: Pre-Commit
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
jobs:
|
|
7
|
+
pre-commit:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
- uses: actions/setup-python@v5
|
|
12
|
+
with:
|
|
13
|
+
python-version: '3.x'
|
|
14
|
+
- uses: pre-commit/action@v3.0.1
|
|
15
|
+
env:
|
|
16
|
+
SKIP: no-commit-to-branch
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
inputs:
|
|
7
|
+
tag:
|
|
8
|
+
description: Tag to be published
|
|
9
|
+
required: true
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
environment: pypi
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
with:
|
|
20
|
+
fetch-depth: 0
|
|
21
|
+
ref: ${{ inputs.tag || github.ref }}
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
- run: pipx run build
|
|
26
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
8
|
+
issues: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
id-token: write
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Generate app token
|
|
16
|
+
id: app-token
|
|
17
|
+
uses: actions/create-github-app-token@v2
|
|
18
|
+
with:
|
|
19
|
+
app-id: ${{ vars.APP_ID }}
|
|
20
|
+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
25
|
+
- uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: 20
|
|
28
|
+
- name: Install semantic-release
|
|
29
|
+
run: npm install -g semantic-release @semantic-release/git @semantic-release/changelog
|
|
30
|
+
- name: Release
|
|
31
|
+
env:
|
|
32
|
+
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
33
|
+
run: npx semantic-release
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Python.
|
|
2
|
+
*.pyc
|
|
3
|
+
__pycache__/
|
|
4
|
+
|
|
5
|
+
# Distribution / packaging.
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
|
|
10
|
+
# Node.
|
|
11
|
+
node_modules/
|
|
12
|
+
npm-debug.*
|
|
13
|
+
yarn-debug.*
|
|
14
|
+
yarn-error.*
|
|
15
|
+
pnpm-debug.log*
|
|
16
|
+
lerna-debug.log*
|
|
17
|
+
*.log
|
|
18
|
+
logs
|
|
19
|
+
|
|
20
|
+
# Vite.
|
|
21
|
+
dist-ssr
|
|
22
|
+
*.local
|
|
23
|
+
|
|
24
|
+
# Expo / React Native.
|
|
25
|
+
.expo/
|
|
26
|
+
web-build/
|
|
27
|
+
expo-env.d.ts
|
|
28
|
+
.metro-health-check*
|
|
29
|
+
|
|
30
|
+
# Native (iOS/Android).
|
|
31
|
+
.kotlin/
|
|
32
|
+
/ios
|
|
33
|
+
/android
|
|
34
|
+
*.jks
|
|
35
|
+
*.p8
|
|
36
|
+
*.p12
|
|
37
|
+
*.key
|
|
38
|
+
*.mobileprovision
|
|
39
|
+
|
|
40
|
+
# TypeScript.
|
|
41
|
+
*.tsbuildinfo
|
|
42
|
+
|
|
43
|
+
# Environment.
|
|
44
|
+
*.env*
|
|
45
|
+
*.venv*
|
|
46
|
+
env/
|
|
47
|
+
venv/
|
|
48
|
+
|
|
49
|
+
# Ruff.
|
|
50
|
+
.ruff_cache/
|
|
51
|
+
|
|
52
|
+
# MyPy.
|
|
53
|
+
.mypy_cache/
|
|
54
|
+
|
|
55
|
+
# IDEs and code editors.
|
|
56
|
+
.idea/
|
|
57
|
+
.vscode/
|
|
58
|
+
|
|
59
|
+
# macOS.
|
|
60
|
+
.DS_Store
|
|
61
|
+
*.pem
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
default_install_hook_types: [pre-commit, commit-msg]
|
|
2
|
+
default_stages: [pre-commit]
|
|
3
|
+
repos:
|
|
4
|
+
# Overall hooks.
|
|
5
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
6
|
+
rev: v6.0.0
|
|
7
|
+
hooks:
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
name: Check for Large Files
|
|
10
|
+
args: [--maxkb=512]
|
|
11
|
+
- id: check-illegal-windows-names
|
|
12
|
+
name: Check for Illegal Windows Names
|
|
13
|
+
- id: check-json
|
|
14
|
+
name: Check JSON Files
|
|
15
|
+
- id: check-symlinks
|
|
16
|
+
name: Check for Symlinks
|
|
17
|
+
- id: check-toml
|
|
18
|
+
name: Check TOML Files
|
|
19
|
+
- id: check-xml
|
|
20
|
+
name: Check XML Files
|
|
21
|
+
- id: check-yaml
|
|
22
|
+
name: Check YAML Files
|
|
23
|
+
args: [--allow-multiple-documents]
|
|
24
|
+
- id: detect-private-key
|
|
25
|
+
name: Detect Private Keys
|
|
26
|
+
- id: end-of-file-fixer
|
|
27
|
+
name: Fix End of Files
|
|
28
|
+
- id: name-tests-test
|
|
29
|
+
name: Ensure Test Files Start with "test"
|
|
30
|
+
args: [--pytest-test-first]
|
|
31
|
+
- id: no-commit-to-branch
|
|
32
|
+
name: Prevent Commit to `main` and `develop` Branch
|
|
33
|
+
args: [--branch=main, --branch=develop]
|
|
34
|
+
- id: pretty-format-json
|
|
35
|
+
name: Pretty Format JSON Files
|
|
36
|
+
args: [--autofix, --indent=4]
|
|
37
|
+
- id: requirements-txt-fixer
|
|
38
|
+
name: Fix Requirements.txt Files
|
|
39
|
+
- id: trailing-whitespace
|
|
40
|
+
name: Trim Trailing Whitespace
|
|
41
|
+
- repo: https://github.com/compilerla/conventional-pre-commit
|
|
42
|
+
rev: v4.4.0
|
|
43
|
+
hooks:
|
|
44
|
+
- id: conventional-pre-commit
|
|
45
|
+
name: Conventional Commit Message Validator
|
|
46
|
+
stages: [commit-msg]
|
|
47
|
+
args: [feat, fix, docs, style, refactor, test, chore, revert, ci, build, perf]
|
|
48
|
+
- repo: local
|
|
49
|
+
hooks:
|
|
50
|
+
- id: format-commit-message
|
|
51
|
+
name: Format Commit Message
|
|
52
|
+
language: python
|
|
53
|
+
stages: [commit-msg]
|
|
54
|
+
entry: python3 hooks/format_commit_message.py
|
|
55
|
+
additional_dependencies: [conventional-pre-commit==4.4.0] # Same version as hook.
|
|
56
|
+
# Python hooks.
|
|
57
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
58
|
+
rev: v0.15.16
|
|
59
|
+
hooks:
|
|
60
|
+
- id: ruff-check
|
|
61
|
+
name: Lint Python Files
|
|
62
|
+
args: [--fix]
|
|
63
|
+
- id: ruff-format
|
|
64
|
+
name: Format Python Files
|
|
65
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
66
|
+
rev: v2.1.0
|
|
67
|
+
hooks:
|
|
68
|
+
- id: mypy
|
|
69
|
+
name: Static Python Type Checker
|
|
70
|
+
additional_dependencies: [pydantic==2.13.5] # Add additional mypy plugins for type validation if needed.
|
|
71
|
+
args: [--config-file=pyproject.toml]
|
|
72
|
+
# Node hooks.
|
|
73
|
+
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
74
|
+
rev: v4.0.0-alpha.8
|
|
75
|
+
hooks:
|
|
76
|
+
- id: prettier
|
|
77
|
+
name: Format JS and CSS Files
|
|
78
|
+
files: \.(js|jsx|ts|tsx|css|html)$
|
|
79
|
+
args: [--write]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"branches": [
|
|
3
|
+
"main"
|
|
4
|
+
],
|
|
5
|
+
"plugins": [
|
|
6
|
+
[
|
|
7
|
+
"@semantic-release/commit-analyzer",
|
|
8
|
+
{
|
|
9
|
+
"parserOpts": {
|
|
10
|
+
"headerCorrespondence": [
|
|
11
|
+
"type",
|
|
12
|
+
"scope",
|
|
13
|
+
"subject"
|
|
14
|
+
],
|
|
15
|
+
"headerPattern": "^(?:.*?\\s)?(\\w*)(?:\\(([\\w$.\\-* ]*)\\))?: (.*)$"
|
|
16
|
+
},
|
|
17
|
+
"preset": "angular"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
[
|
|
21
|
+
"@semantic-release/release-notes-generator",
|
|
22
|
+
{
|
|
23
|
+
"parserOpts": {
|
|
24
|
+
"headerCorrespondence": [
|
|
25
|
+
"type",
|
|
26
|
+
"scope",
|
|
27
|
+
"subject"
|
|
28
|
+
],
|
|
29
|
+
"headerPattern": "^(?:.*?\\s)?(\\w*)(?:\\(([\\w$.\\-* ]*)\\))?: (.*)$"
|
|
30
|
+
},
|
|
31
|
+
"preset": "angular"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"@semantic-release/changelog",
|
|
35
|
+
"@semantic-release/github",
|
|
36
|
+
[
|
|
37
|
+
"@semantic-release/git",
|
|
38
|
+
{
|
|
39
|
+
"assets": [
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## [1.0.1](https://github.com/SmilingTech/pyarg/compare/v1.0.0...v1.0.1) (2026-11-08)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Accept manual publish via UI. ([1396176](https://github.com/SmilingTech/pyarg/commit/139617633f7d533aaad4923a5016af80c2a9a651))
|
|
7
|
+
|
|
8
|
+
# 1.0.0 (2026-11-08)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Add main lib folder and it utilities. ([31bb920](https://github.com/SmilingTech/pyarg/commit/31bb920c5d484b7eb4ad74ef81f94915078be334))
|
|
14
|
+
* Drop all unnecessary files and directories for the project. ([36733f9](https://github.com/SmilingTech/pyarg/commit/36733f9d8085b0fb7e59acd5691c90d4a2bc3ba6))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Developers
|
|
2
|
+
> [!IMPORTANT]
|
|
3
|
+
> Before starting development on this repository, make sure you install all local development requirements.
|
|
4
|
+
```bash
|
|
5
|
+
$ python3 -m venv .env # Create a virtual environment.
|
|
6
|
+
$ source .env/bin/activate # Activate the virtual environment.
|
|
7
|
+
$ make setup-local-development # Install all development requirements.
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### Folder structure.
|
|
11
|
+
> [!WARNING]
|
|
12
|
+
> *This structure can be modified during the development of any project.*
|
|
13
|
+
```txt
|
|
14
|
+
.github/
|
|
15
|
+
ᒪ workflows/
|
|
16
|
+
ᒪ ... # YAML files to be executed in GitHub workflows.
|
|
17
|
+
pyarg/ # Main library directory.
|
|
18
|
+
hooks/
|
|
19
|
+
ᒪ ... # Python scripts to be used as hooks in pre-commit.
|
|
20
|
+
.gitignore # File with all files and folders to be ignored by Git.
|
|
21
|
+
.pre-commit-config.yaml # Configuration file to set up local hooks for pre-commit.
|
|
22
|
+
.releaserc.json # Plugins needed for generate the Release Notes.
|
|
23
|
+
DEVELOPERS.md # Documentation for developers.
|
|
24
|
+
Makefile # Used to define keywords to execute long commands.
|
|
25
|
+
pyproject.toml # Main Python configuration file.
|
|
26
|
+
README.md # Main project documentation.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Conventional Commit Message.
|
|
30
|
+
All projects in this organizations must to use the conventional commit message format based on the following pattern:
|
|
31
|
+
```
|
|
32
|
+
^(feat|fix|docs|styles|refactor|test|chore|revert|ci|build|perf): .+$
|
|
33
|
+
```
|
|
34
|
+
|Type|Description|
|
|
35
|
+
|-|-|
|
|
36
|
+
|`feat`|Add a new functionality to the project.|
|
|
37
|
+
|`fix`|Fix a bug in the project.|
|
|
38
|
+
|`docs`|Changes in documentation. (README, comments, wikis, etc.)|
|
|
39
|
+
|`style`|Formatting, spacing, semicolons. Without changing the logic.|
|
|
40
|
+
|`refactor`|Rewriting/restructuring code without adding feature or fixing bug.|
|
|
41
|
+
|`test`|Add or fix tests. Without touching the main production code.|
|
|
42
|
+
|`chore`|Maintenance tasks: update dependencies, configs, build scripts.|
|
|
43
|
+
|`revert`|Revert a previous commit.|
|
|
44
|
+
|`ci`|Pipeline changes and CI/CD configurations. (GitHub Actions, etc.)|
|
|
45
|
+
|`build`|Changes in build system or external dependencies. (Dockerfile, Makefile, etc.)|
|
|
46
|
+
|`perf`|Code changes that improve performance without changing behavior.|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-arguments
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
Provides-Extra: development
|
|
6
|
+
Requires-Dist: pre-commit; extra == "development"
|
|
7
|
+
|
|
8
|
+
# PyArg
|
|
9
|
+
|
|
10
|
+
A small layer on top of `argparse` to build CLIs with sub-commands by **reusing parser classes**.
|
|
11
|
+
|
|
12
|
+
Each parser is a class. You define its arguments in one method, and then plug that class into another parser as a
|
|
13
|
+
command, without rewriting the arguments.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For development (changes apply without reinstalling):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.10+ and has no dependencies.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Create a class that inherits from `PyArg` and define the arguments in `setup_arguments`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pyarg import PyArg
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Login(PyArg):
|
|
38
|
+
def setup_arguments(self) -> None:
|
|
39
|
+
self.add_argument("--username", "-u")
|
|
40
|
+
self.add_argument("--password", "-p")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
parser = Login(name="Login")
|
|
44
|
+
print(parser.get_arguments())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
$ python main.py -u ana -p 123
|
|
49
|
+
{'username': 'ana', 'password': '123'}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Since `PyArg` inherits from `argparse.ArgumentParser`, everything from `argparse` still works
|
|
53
|
+
(`add_argument`, `add_mutually_exclusive_group`, `type`, `nargs`, `choices`, etc.).
|
|
54
|
+
|
|
55
|
+
## Sub-commands
|
|
56
|
+
|
|
57
|
+
Use `add_command_parser` to attach parsers to a main one. Each command is named after the parser `name`, converted to
|
|
58
|
+
lowercase with spaces replaced by `-` (`"Make Directory"` becomes `make-directory`).
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pyarg import PyArg
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Login(PyArg):
|
|
65
|
+
def setup_arguments(self) -> None:
|
|
66
|
+
self.add_argument("--username", "-u")
|
|
67
|
+
self.add_argument("--password", "-p")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Logout(PyArg):
|
|
71
|
+
def setup_arguments(self) -> None:
|
|
72
|
+
self.add_argument("--all", action="store_true")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class App(PyArg):
|
|
76
|
+
def setup_arguments(self) -> None:
|
|
77
|
+
pass # The main parser can have its own arguments too.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
app = App(name="My App", description="Demo app.")
|
|
81
|
+
app.add_command_parser(Login(name="Login", description="Sign in."))
|
|
82
|
+
app.add_command_parser(Logout(name="Logout"), description="Sign out.")
|
|
83
|
+
|
|
84
|
+
print(app.get_arguments())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```console
|
|
88
|
+
$ python main.py login -u ana -p 123
|
|
89
|
+
{'command': 'login', 'username': 'ana', 'password': '123'}
|
|
90
|
+
|
|
91
|
+
$ python main.py logout --all
|
|
92
|
+
{'command': 'logout', 'all': True}
|
|
93
|
+
|
|
94
|
+
$ python main.py -h
|
|
95
|
+
usage: My App [-h] {login,logout} ...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The chosen command is available under the `command` key.
|
|
99
|
+
|
|
100
|
+
## Reusing arguments
|
|
101
|
+
|
|
102
|
+
Arguments from other parsers can be inherited with `parents`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
class Common(PyArg):
|
|
106
|
+
def setup_arguments(self) -> None:
|
|
107
|
+
self.add_argument("--verbose", "-v", action="store_true")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Build(PyArg):
|
|
111
|
+
def setup_arguments(self) -> None:
|
|
112
|
+
self.add_argument("--target")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
build = Build(name="Build", parents=[Common(name="Common", add_help=False)])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use `add_help=False` on the parent parser so the `-h` option is not registered twice.
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `PyArg(name, description=None, parents=[], **kwargs)`
|
|
123
|
+
|
|
124
|
+
Abstract class. It cannot be instantiated directly, you must inherit from it and implement `setup_arguments`.
|
|
125
|
+
|
|
126
|
+
- `name`: parser name. Used as `prog` in the help text and to build the command name.
|
|
127
|
+
- `description`: text shown in `-h`.
|
|
128
|
+
- `parents`: other parsers whose arguments are copied into this one.
|
|
129
|
+
- `**kwargs`: forwarded to `argparse.ArgumentParser`. The `prog`, `description`, `parents` and `conflict_handler`
|
|
130
|
+
keys are ignored, as `conflict_handler` is always `"resolve"`.
|
|
131
|
+
|
|
132
|
+
### `setup_arguments(self) -> None`
|
|
133
|
+
|
|
134
|
+
Abstract method. Define all the arguments of the parser here. It runs at the end of `__init__`.
|
|
135
|
+
|
|
136
|
+
### `command_name`
|
|
137
|
+
|
|
138
|
+
Read-only property with the command name derived from `name`.
|
|
139
|
+
|
|
140
|
+
### `add_command_parser(parser, description=None, script_entrypoint=None)`
|
|
141
|
+
|
|
142
|
+
Adds `parser` as a sub-command. If `description` is not set, the parser's own description is used.
|
|
143
|
+
|
|
144
|
+
`script_entrypoint` stores a callable on the command, but `get_arguments` does not call it and removes it from the
|
|
145
|
+
result.
|
|
146
|
+
|
|
147
|
+
### `get_arguments() -> dict[str, Any]`
|
|
148
|
+
|
|
149
|
+
Parses `sys.argv` and returns the arguments passed by the user as a dictionary.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
See [DEVELOPERS.md](DEVELOPERS.md).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# PyArg
|
|
2
|
+
|
|
3
|
+
A small layer on top of `argparse` to build CLIs with sub-commands by **reusing parser classes**.
|
|
4
|
+
|
|
5
|
+
Each parser is a class. You define its arguments in one method, and then plug that class into another parser as a
|
|
6
|
+
command, without rewriting the arguments.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install .
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For development (changes apply without reinstalling):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install -e .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Python 3.10+ and has no dependencies.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
Create a class that inherits from `PyArg` and define the arguments in `setup_arguments`:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from pyarg import PyArg
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Login(PyArg):
|
|
31
|
+
def setup_arguments(self) -> None:
|
|
32
|
+
self.add_argument("--username", "-u")
|
|
33
|
+
self.add_argument("--password", "-p")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
parser = Login(name="Login")
|
|
37
|
+
print(parser.get_arguments())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
$ python main.py -u ana -p 123
|
|
42
|
+
{'username': 'ana', 'password': '123'}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Since `PyArg` inherits from `argparse.ArgumentParser`, everything from `argparse` still works
|
|
46
|
+
(`add_argument`, `add_mutually_exclusive_group`, `type`, `nargs`, `choices`, etc.).
|
|
47
|
+
|
|
48
|
+
## Sub-commands
|
|
49
|
+
|
|
50
|
+
Use `add_command_parser` to attach parsers to a main one. Each command is named after the parser `name`, converted to
|
|
51
|
+
lowercase with spaces replaced by `-` (`"Make Directory"` becomes `make-directory`).
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from pyarg import PyArg
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Login(PyArg):
|
|
58
|
+
def setup_arguments(self) -> None:
|
|
59
|
+
self.add_argument("--username", "-u")
|
|
60
|
+
self.add_argument("--password", "-p")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Logout(PyArg):
|
|
64
|
+
def setup_arguments(self) -> None:
|
|
65
|
+
self.add_argument("--all", action="store_true")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class App(PyArg):
|
|
69
|
+
def setup_arguments(self) -> None:
|
|
70
|
+
pass # The main parser can have its own arguments too.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
app = App(name="My App", description="Demo app.")
|
|
74
|
+
app.add_command_parser(Login(name="Login", description="Sign in."))
|
|
75
|
+
app.add_command_parser(Logout(name="Logout"), description="Sign out.")
|
|
76
|
+
|
|
77
|
+
print(app.get_arguments())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```console
|
|
81
|
+
$ python main.py login -u ana -p 123
|
|
82
|
+
{'command': 'login', 'username': 'ana', 'password': '123'}
|
|
83
|
+
|
|
84
|
+
$ python main.py logout --all
|
|
85
|
+
{'command': 'logout', 'all': True}
|
|
86
|
+
|
|
87
|
+
$ python main.py -h
|
|
88
|
+
usage: My App [-h] {login,logout} ...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The chosen command is available under the `command` key.
|
|
92
|
+
|
|
93
|
+
## Reusing arguments
|
|
94
|
+
|
|
95
|
+
Arguments from other parsers can be inherited with `parents`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
class Common(PyArg):
|
|
99
|
+
def setup_arguments(self) -> None:
|
|
100
|
+
self.add_argument("--verbose", "-v", action="store_true")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Build(PyArg):
|
|
104
|
+
def setup_arguments(self) -> None:
|
|
105
|
+
self.add_argument("--target")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
build = Build(name="Build", parents=[Common(name="Common", add_help=False)])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Use `add_help=False` on the parent parser so the `-h` option is not registered twice.
|
|
112
|
+
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
### `PyArg(name, description=None, parents=[], **kwargs)`
|
|
116
|
+
|
|
117
|
+
Abstract class. It cannot be instantiated directly, you must inherit from it and implement `setup_arguments`.
|
|
118
|
+
|
|
119
|
+
- `name`: parser name. Used as `prog` in the help text and to build the command name.
|
|
120
|
+
- `description`: text shown in `-h`.
|
|
121
|
+
- `parents`: other parsers whose arguments are copied into this one.
|
|
122
|
+
- `**kwargs`: forwarded to `argparse.ArgumentParser`. The `prog`, `description`, `parents` and `conflict_handler`
|
|
123
|
+
keys are ignored, as `conflict_handler` is always `"resolve"`.
|
|
124
|
+
|
|
125
|
+
### `setup_arguments(self) -> None`
|
|
126
|
+
|
|
127
|
+
Abstract method. Define all the arguments of the parser here. It runs at the end of `__init__`.
|
|
128
|
+
|
|
129
|
+
### `command_name`
|
|
130
|
+
|
|
131
|
+
Read-only property with the command name derived from `name`.
|
|
132
|
+
|
|
133
|
+
### `add_command_parser(parser, description=None, script_entrypoint=None)`
|
|
134
|
+
|
|
135
|
+
Adds `parser` as a sub-command. If `description` is not set, the parser's own description is used.
|
|
136
|
+
|
|
137
|
+
`script_entrypoint` stores a callable on the command, but `get_arguments` does not call it and removes it from the
|
|
138
|
+
result.
|
|
139
|
+
|
|
140
|
+
### `get_arguments() -> dict[str, Any]`
|
|
141
|
+
|
|
142
|
+
Parses `sys.argv` and returns the arguments passed by the user as a dictionary.
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
See [DEVELOPERS.md](DEVELOPERS.md).
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This script is a custom hook for pre-commit that checks if the commit message follows the expected
|
|
3
|
+
format. Then, it formats the commit message to match the project conventions.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from conventional_pre_commit.format import ConventionalCommit
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Enums.
|
|
15
|
+
class CommitTypeEmoji(Enum):
|
|
16
|
+
"""
|
|
17
|
+
Emoji representations for different commit types.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
FEAT = "🔥"
|
|
21
|
+
FIX = "🐛"
|
|
22
|
+
DOCS = "📝"
|
|
23
|
+
STYLE = "💄"
|
|
24
|
+
REFACTOR = "✨"
|
|
25
|
+
TEST = "🔍"
|
|
26
|
+
CHORE = "⚙️"
|
|
27
|
+
REVERT = "⏪"
|
|
28
|
+
CI = "🚀"
|
|
29
|
+
BUILD = "🚧"
|
|
30
|
+
PERF = "⚡️"
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def types(cls) -> list[str]:
|
|
34
|
+
"""
|
|
35
|
+
Get the list of available commit types.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
list[str]: A list of commit types.
|
|
39
|
+
"""
|
|
40
|
+
return [name.lower() for name in cls.__members__]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Functions.
|
|
44
|
+
def main(log: bool = True) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Entry point for the script.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
log (bool): Whether to log the results to the console. Defaults to True.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
# Utils.
|
|
53
|
+
def logger(message: str) -> None:
|
|
54
|
+
print(message) if log else None
|
|
55
|
+
|
|
56
|
+
def capitalize_after_final_dot(match: re.Match[str]) -> str:
|
|
57
|
+
match_str: str = match.group(0)
|
|
58
|
+
return match_str.capitalize()
|
|
59
|
+
|
|
60
|
+
# Get the commit message from arguments.
|
|
61
|
+
try:
|
|
62
|
+
commit_msg_filepath = sys.argv[1]
|
|
63
|
+
commit_message = Path(commit_msg_filepath).read_text(encoding="utf-8").strip()
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger("Unable to read the commit message file. " + str(e))
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
# Get commit type.
|
|
69
|
+
conventional_commit_pattern = ConventionalCommit().r_types
|
|
70
|
+
commit_type_re = re.compile(pattern=conventional_commit_pattern)
|
|
71
|
+
commit_type_match = commit_type_re.search(string=commit_message)
|
|
72
|
+
if not commit_type_match:
|
|
73
|
+
logger("Could not parse commit type from message.")
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
commit_type: str = commit_type_match.group(0)
|
|
76
|
+
|
|
77
|
+
# Get main commit message.
|
|
78
|
+
main_message_pattern = ConventionalCommit().r_subject
|
|
79
|
+
main_message_re = re.compile(pattern=main_message_pattern)
|
|
80
|
+
main_message_match = main_message_re.search(string=commit_message)
|
|
81
|
+
if not main_message_match:
|
|
82
|
+
logger("Could not parse main commit message from message.")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
main_commit_message: str = main_message_match.group(0).strip()
|
|
85
|
+
|
|
86
|
+
# Capitalize the first letter of the commit description and
|
|
87
|
+
# after any final dot.
|
|
88
|
+
new_main_commit_message = main_commit_message[0].upper() + main_commit_message[1:]
|
|
89
|
+
capitalize_after_final_dot_re = re.compile(pattern=r"(?<=\.\s)(\w)")
|
|
90
|
+
new_main_commit_message_str = capitalize_after_final_dot_re.sub(
|
|
91
|
+
repl=capitalize_after_final_dot, string=new_main_commit_message
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Add only one final dot at the end of the commit description if it doesn't already have one.
|
|
95
|
+
final_dot_re = re.compile(pattern=r"(?<![!?])\.*$")
|
|
96
|
+
new_main_commit_message_str = final_dot_re.sub(repl="", string=new_main_commit_message_str)
|
|
97
|
+
new_main_commit_message_str += "."
|
|
98
|
+
|
|
99
|
+
# Get emoji for commit message.
|
|
100
|
+
if commit_type not in CommitTypeEmoji.types():
|
|
101
|
+
logger("Could not find emoji for the commit type: " + commit_type)
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
commit_type_emoji = CommitTypeEmoji[commit_type.upper()].value
|
|
104
|
+
|
|
105
|
+
# Format final commit message.
|
|
106
|
+
formatted_commit_message = f"{commit_type_emoji} {commit_message}"
|
|
107
|
+
formatted_commit_message = formatted_commit_message.replace(main_commit_message, new_main_commit_message_str, 1)
|
|
108
|
+
|
|
109
|
+
# Write the formatted commit message back to the file.
|
|
110
|
+
Path(commit_msg_filepath).write_text(formatted_commit_message, encoding="utf-8")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
if __name__ == "__main__":
|
|
114
|
+
main()
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-arguments
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
Provides-Extra: development
|
|
6
|
+
Requires-Dist: pre-commit; extra == "development"
|
|
7
|
+
|
|
8
|
+
# PyArg
|
|
9
|
+
|
|
10
|
+
A small layer on top of `argparse` to build CLIs with sub-commands by **reusing parser classes**.
|
|
11
|
+
|
|
12
|
+
Each parser is a class. You define its arguments in one method, and then plug that class into another parser as a
|
|
13
|
+
command, without rewriting the arguments.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install .
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For development (changes apply without reinstalling):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install -e .
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.10+ and has no dependencies.
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Create a class that inherits from `PyArg` and define the arguments in `setup_arguments`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from pyarg import PyArg
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Login(PyArg):
|
|
38
|
+
def setup_arguments(self) -> None:
|
|
39
|
+
self.add_argument("--username", "-u")
|
|
40
|
+
self.add_argument("--password", "-p")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
parser = Login(name="Login")
|
|
44
|
+
print(parser.get_arguments())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```console
|
|
48
|
+
$ python main.py -u ana -p 123
|
|
49
|
+
{'username': 'ana', 'password': '123'}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Since `PyArg` inherits from `argparse.ArgumentParser`, everything from `argparse` still works
|
|
53
|
+
(`add_argument`, `add_mutually_exclusive_group`, `type`, `nargs`, `choices`, etc.).
|
|
54
|
+
|
|
55
|
+
## Sub-commands
|
|
56
|
+
|
|
57
|
+
Use `add_command_parser` to attach parsers to a main one. Each command is named after the parser `name`, converted to
|
|
58
|
+
lowercase with spaces replaced by `-` (`"Make Directory"` becomes `make-directory`).
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pyarg import PyArg
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Login(PyArg):
|
|
65
|
+
def setup_arguments(self) -> None:
|
|
66
|
+
self.add_argument("--username", "-u")
|
|
67
|
+
self.add_argument("--password", "-p")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Logout(PyArg):
|
|
71
|
+
def setup_arguments(self) -> None:
|
|
72
|
+
self.add_argument("--all", action="store_true")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class App(PyArg):
|
|
76
|
+
def setup_arguments(self) -> None:
|
|
77
|
+
pass # The main parser can have its own arguments too.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
app = App(name="My App", description="Demo app.")
|
|
81
|
+
app.add_command_parser(Login(name="Login", description="Sign in."))
|
|
82
|
+
app.add_command_parser(Logout(name="Logout"), description="Sign out.")
|
|
83
|
+
|
|
84
|
+
print(app.get_arguments())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```console
|
|
88
|
+
$ python main.py login -u ana -p 123
|
|
89
|
+
{'command': 'login', 'username': 'ana', 'password': '123'}
|
|
90
|
+
|
|
91
|
+
$ python main.py logout --all
|
|
92
|
+
{'command': 'logout', 'all': True}
|
|
93
|
+
|
|
94
|
+
$ python main.py -h
|
|
95
|
+
usage: My App [-h] {login,logout} ...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The chosen command is available under the `command` key.
|
|
99
|
+
|
|
100
|
+
## Reusing arguments
|
|
101
|
+
|
|
102
|
+
Arguments from other parsers can be inherited with `parents`:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
class Common(PyArg):
|
|
106
|
+
def setup_arguments(self) -> None:
|
|
107
|
+
self.add_argument("--verbose", "-v", action="store_true")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Build(PyArg):
|
|
111
|
+
def setup_arguments(self) -> None:
|
|
112
|
+
self.add_argument("--target")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
build = Build(name="Build", parents=[Common(name="Common", add_help=False)])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use `add_help=False` on the parent parser so the `-h` option is not registered twice.
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `PyArg(name, description=None, parents=[], **kwargs)`
|
|
123
|
+
|
|
124
|
+
Abstract class. It cannot be instantiated directly, you must inherit from it and implement `setup_arguments`.
|
|
125
|
+
|
|
126
|
+
- `name`: parser name. Used as `prog` in the help text and to build the command name.
|
|
127
|
+
- `description`: text shown in `-h`.
|
|
128
|
+
- `parents`: other parsers whose arguments are copied into this one.
|
|
129
|
+
- `**kwargs`: forwarded to `argparse.ArgumentParser`. The `prog`, `description`, `parents` and `conflict_handler`
|
|
130
|
+
keys are ignored, as `conflict_handler` is always `"resolve"`.
|
|
131
|
+
|
|
132
|
+
### `setup_arguments(self) -> None`
|
|
133
|
+
|
|
134
|
+
Abstract method. Define all the arguments of the parser here. It runs at the end of `__init__`.
|
|
135
|
+
|
|
136
|
+
### `command_name`
|
|
137
|
+
|
|
138
|
+
Read-only property with the command name derived from `name`.
|
|
139
|
+
|
|
140
|
+
### `add_command_parser(parser, description=None, script_entrypoint=None)`
|
|
141
|
+
|
|
142
|
+
Adds `parser` as a sub-command. If `description` is not set, the parser's own description is used.
|
|
143
|
+
|
|
144
|
+
`script_entrypoint` stores a callable on the command, but `get_arguments` does not call it and removes it from the
|
|
145
|
+
result.
|
|
146
|
+
|
|
147
|
+
### `get_arguments() -> dict[str, Any]`
|
|
148
|
+
|
|
149
|
+
Parses `sys.argv` and returns the arguments passed by the user as a dictionary.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
See [DEVELOPERS.md](DEVELOPERS.md).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
.pre-commit-config.yaml
|
|
3
|
+
.releaserc.json
|
|
4
|
+
CHANGELOG.md
|
|
5
|
+
DEVELOPERS.md
|
|
6
|
+
Makefile
|
|
7
|
+
README.md
|
|
8
|
+
pyproject.toml
|
|
9
|
+
.github/workflows/claude-review.yml
|
|
10
|
+
.github/workflows/pre-commit.yml
|
|
11
|
+
.github/workflows/publish.yml
|
|
12
|
+
.github/workflows/release.yml
|
|
13
|
+
hooks/format_commit_message.py
|
|
14
|
+
py_arguments.egg-info/PKG-INFO
|
|
15
|
+
py_arguments.egg-info/SOURCES.txt
|
|
16
|
+
py_arguments.egg-info/dependency_links.txt
|
|
17
|
+
py_arguments.egg-info/requires.txt
|
|
18
|
+
py_arguments.egg-info/scm_file_list.json
|
|
19
|
+
py_arguments.egg-info/scm_version.json
|
|
20
|
+
py_arguments.egg-info/top_level.txt
|
|
21
|
+
pyarg/__init__.py
|
|
22
|
+
pyarg/parser.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
".github/workflows/claude-review.yml",
|
|
4
|
+
".github/workflows/pre-commit.yml",
|
|
5
|
+
".github/workflows/publish.yml",
|
|
6
|
+
".github/workflows/release.yml",
|
|
7
|
+
".gitignore",
|
|
8
|
+
".pre-commit-config.yaml",
|
|
9
|
+
".releaserc.json",
|
|
10
|
+
"CHANGELOG.md",
|
|
11
|
+
"DEVELOPERS.md",
|
|
12
|
+
"Makefile",
|
|
13
|
+
"README.md",
|
|
14
|
+
"hooks/format_commit_message.py",
|
|
15
|
+
"pyarg/__init__.py",
|
|
16
|
+
"pyarg/parser.py",
|
|
17
|
+
"pyproject.toml"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyarg
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import re
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Callable, Iterable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Classes.
|
|
9
|
+
class PyArg(ABC, argparse.ArgumentParser):
|
|
10
|
+
"""
|
|
11
|
+
**PyArg** is an abstract class that uses `argparse.ArgumentParser` class that allows you to set up easily sub
|
|
12
|
+
parsers/commands from a parent parser.
|
|
13
|
+
|
|
14
|
+
Creates a class inhenrance from **PyArg**, set up all arguments in `setup_arguments` method, and then, you can
|
|
15
|
+
re-use the same parser definition to build your complex structure of parser arguments and CLI commands.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
```
|
|
19
|
+
class Forms(PyArg):
|
|
20
|
+
def setup_arguments(self):
|
|
21
|
+
self.add_argument("--username", "-u")
|
|
22
|
+
self.add_argument("--password", "-p")
|
|
23
|
+
|
|
24
|
+
parser = Forms(name="Forms")
|
|
25
|
+
arguments = parser.get_arguments()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Extra commands that trigger a function:
|
|
29
|
+
```
|
|
30
|
+
class Application(PyArg):
|
|
31
|
+
def setup_arguments(self):
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
class MakeDir(PyArg):
|
|
35
|
+
@staticmethod
|
|
36
|
+
def create_directory(location, name_list):
|
|
37
|
+
for name in name_list:
|
|
38
|
+
os.makedirs(os.path.join(location, name))
|
|
39
|
+
|
|
40
|
+
def setup_arguments(self):
|
|
41
|
+
self.add_argument("--location", "-l")
|
|
42
|
+
self.add_argument("--name", "-n", type=str, nargs="+")
|
|
43
|
+
|
|
44
|
+
class DeleteDir(PyArg):
|
|
45
|
+
@staticmethod
|
|
46
|
+
def delete_directory(path):
|
|
47
|
+
shutil.rmtree(path)
|
|
48
|
+
|
|
49
|
+
def setup_arguments(self):
|
|
50
|
+
self.add_argument("--path", "-p")
|
|
51
|
+
|
|
52
|
+
make_dir_parser = MakeDir(name="Make Directory")
|
|
53
|
+
delete_dir_parser = DeleteDir(name="Delete Directory")
|
|
54
|
+
|
|
55
|
+
app = Application(name="Directory Manager")
|
|
56
|
+
app.add_command_parser(parser=make_dir_parser, script_entrypoint=MakeDir.create_directory)
|
|
57
|
+
app.add_command_parser(parser=delete_dir_parser, script_entrypoint=DeleteDir.delete_directory)
|
|
58
|
+
app.get_arguments()
|
|
59
|
+
```
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# Special methods.
|
|
63
|
+
def __init__(
|
|
64
|
+
self, name: str, description: str | None = None, parents: Iterable["PyArg"] | None = None, **kwargs: Any
|
|
65
|
+
) -> None:
|
|
66
|
+
# Remove parameters.
|
|
67
|
+
for to_remove in ("prog", "description", "parents", "conflict_handler"):
|
|
68
|
+
kwargs.pop(to_remove, None)
|
|
69
|
+
parents = [] if parents is None else parents
|
|
70
|
+
super().__init__(prog=name, description=description, parents=parents, conflict_handler="resolve", **kwargs)
|
|
71
|
+
|
|
72
|
+
# Attributes.
|
|
73
|
+
self.__command_name: str = self.__get_program_name(name=name)
|
|
74
|
+
|
|
75
|
+
# Set up extra sub parsers.
|
|
76
|
+
self.__subparser: None | argparse._SubParsersAction[argparse.ArgumentParser] = None
|
|
77
|
+
|
|
78
|
+
# Set up arguments.
|
|
79
|
+
self.setup_arguments()
|
|
80
|
+
|
|
81
|
+
# Properties.
|
|
82
|
+
@property
|
|
83
|
+
def command_name(self) -> str:
|
|
84
|
+
return self.__command_name
|
|
85
|
+
|
|
86
|
+
# Abstract methods.
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def setup_arguments(self) -> None:
|
|
89
|
+
"""Main method used to define all parameters of the parser."""
|
|
90
|
+
|
|
91
|
+
# Private methods.
|
|
92
|
+
def __get_program_name(self, name: str) -> str:
|
|
93
|
+
"""Convert the parser name to a CLI command name."""
|
|
94
|
+
name = name.lower()
|
|
95
|
+
name = re.sub(pattern=r"\s+", repl="-", string=name.strip())
|
|
96
|
+
return name
|
|
97
|
+
|
|
98
|
+
# Methods.
|
|
99
|
+
def add_command_parser(
|
|
100
|
+
self, parser: "PyArg", description: str | None = None, script_entrypoint: Callable[..., Any] | None = None
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Add an extra parser to a parent **PyArg** class to be called by `name`."""
|
|
103
|
+
if self.__subparser is None:
|
|
104
|
+
self.__subparser = self.add_subparsers(
|
|
105
|
+
dest="command", parser_class=argparse.ArgumentParser, help="Available commands."
|
|
106
|
+
)
|
|
107
|
+
subparser: argparse.ArgumentParser = self.__subparser.add_parser(
|
|
108
|
+
parents=[parser], name=parser.command_name, description=description or parser.description, add_help=False
|
|
109
|
+
)
|
|
110
|
+
# Add script.
|
|
111
|
+
if script_entrypoint:
|
|
112
|
+
subparser.set_defaults(_entrypoint=script_entrypoint)
|
|
113
|
+
|
|
114
|
+
def get_arguments(self) -> dict[str, Any]:
|
|
115
|
+
"""Get the CLI arguments."""
|
|
116
|
+
args: dict[str, Any] = vars(self.parse_args())
|
|
117
|
+
args.pop("_entrypoint", None)
|
|
118
|
+
return args
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Build system.
|
|
2
|
+
[build-system]
|
|
3
|
+
requires = ["setuptools>=64", "setuptools-scm>=8"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
# Project metadata.
|
|
7
|
+
[project]
|
|
8
|
+
name = "py-arguments"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = []
|
|
11
|
+
dynamic = ["version"]
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
# Dependencies.
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
development = [
|
|
17
|
+
"pre-commit"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[tool.setuptools]
|
|
21
|
+
packages = ["pyarg"]
|
|
22
|
+
|
|
23
|
+
# Version comes from the git tag created by semantic-release.
|
|
24
|
+
[tool.setuptools_scm]
|
|
25
|
+
|
|
26
|
+
# Ruff.
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
line-length = 120
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
select = [
|
|
32
|
+
"E", # All pycodestyle errors
|
|
33
|
+
"F", # Pyflakes
|
|
34
|
+
"I", # All isort errors
|
|
35
|
+
"B", # All flake8-bugbear errors
|
|
36
|
+
"C4", # flake8-comprehensions
|
|
37
|
+
"UP", # All pyupgrade errors
|
|
38
|
+
"SIM", # flake8-simplify
|
|
39
|
+
]
|
|
40
|
+
ignore = ["B008"] # Valid for FastAPI `Depends` call.
|
|
41
|
+
|
|
42
|
+
[tool.ruff.format]
|
|
43
|
+
quote-style = "double"
|
|
44
|
+
indent-style = "space"
|
|
45
|
+
line-ending = "auto"
|
|
46
|
+
|
|
47
|
+
# Mypy.
|
|
48
|
+
[tool.mypy]
|
|
49
|
+
mypy_path = "."
|
|
50
|
+
strict = true
|
|
51
|
+
ignore_missing_imports = true
|
|
52
|
+
explicit_package_bases = true
|
|
53
|
+
disallow_untyped_decorators = false
|
|
54
|
+
disallow_subclassing_any = false
|
|
55
|
+
plugins = ["pydantic.mypy"]
|