mirror of
https://github.com/koloideal/Argenta.git
synced 2026-06-10 18:15:28 +03:00
Compare commits
334 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b54ae9a330 | |||
| 552e1db6fd | |||
| c06ea6e196 | |||
| a72ddf61b7 | |||
| eb5830ec1b | |||
| da06099990 | |||
| 8ff9944d37 | |||
| c07ee92371 | |||
| a21570e779 | |||
| f1034ff447 | |||
| fcff6f4263 | |||
| 417e0e2905 | |||
| 31dc49a1bf | |||
| f859451069 | |||
| 24aa75eb37 | |||
| 5f6b3368e1 | |||
| d03ce5061b | |||
| 107530a4b1 | |||
| 9aa99352fe | |||
| 2a9281a421 | |||
| 0567a3f4a3 | |||
| dfe482c545 | |||
| 46c1ec02fd | |||
| 70bbbd76ce | |||
| 19bbaab1ee | |||
| 1c54f11f31 | |||
| 2ad86dbedd | |||
| f27f7b135b | |||
| f9a85da430 | |||
| 2f557b99b0 | |||
| 63c4443ff6 | |||
| 30761749b4 | |||
| 0c3048266a | |||
| 088c1720c4 | |||
| 838f33db87 | |||
| b8e9fdcb9c | |||
| 295e260a46 | |||
| 3fa7b17de9 | |||
| a174e0d5ab | |||
| 9d250fde9c | |||
| e7d064908f | |||
| ba9a7b5539 | |||
| 69e871a639 | |||
| 1648a8206a | |||
| 2e5b19f4d8 | |||
| 6aa6b0f179 | |||
| 00cb94ffe2 | |||
| 285ea39fa9 | |||
| 30e5fd6ebe | |||
| 18f62d3e7c | |||
| 3cd74fc186 | |||
| 9bde1321e1 | |||
| 0f8b1c05fc | |||
| cd3dd10d11 | |||
| b8d8c44bdd | |||
| 4957de95d3 | |||
| c5dab43c87 | |||
| 56189be6ab | |||
| 725a1f2e40 | |||
| 22970f7115 | |||
| 6598fb7fa6 | |||
| 183f069766 | |||
| 75b1efb259 | |||
| cb6549452d | |||
| 425342c059 | |||
| 9423034a08 | |||
| dee328525d | |||
| 1d2ab6f6bb | |||
| a2ef2652ed | |||
| 2423f57000 | |||
| 913e7f16ca | |||
| 20b638c421 | |||
| b0e6127c1e | |||
| 67bc8960e7 | |||
| 6b9cb586d5 | |||
| ce7e24b924 | |||
| 5c933ab656 | |||
| e1327c278c | |||
| a57ea45c6f | |||
| baebeca55b | |||
| 6be0a94ba9 | |||
| 087c76fed3 | |||
| 723ed2210f | |||
| c6fd01b7bc | |||
| 9179eb8468 | |||
| be083bb64d | |||
| eeb5a752ec | |||
| 9d420af0c5 | |||
| d4129e27e7 | |||
| 974d35fb82 | |||
| da1ee9269a | |||
| dca320297e | |||
| 08660f81d7 | |||
| 52139e5405 | |||
| 2a0ec209c3 | |||
| cd3095f42e | |||
| 2800a7ffc2 | |||
| e6645730f0 | |||
| eae8cdbb58 | |||
| 7c20bf296b | |||
| 2ff47398ba | |||
| 19906c1b1b | |||
| 2a96dfcabe | |||
| 47fda23431 | |||
| d3bf5e703d | |||
| be178b10c7 | |||
| 1eaf2b6333 | |||
| e5be7b5d99 | |||
| 2e76f68d4a | |||
| 8edd59c1b8 | |||
| 16e7cc21fb | |||
| 4da876b774 | |||
| 7b85b0f08d | |||
| 270e91f705 | |||
| 767d742060 | |||
| b37096d790 | |||
| ad8c3af532 | |||
| 02b02793d0 | |||
| f0a18e89c8 | |||
| 6f90712a17 | |||
| 239e582241 | |||
| 4967ec3d7f | |||
| 7e02694cb0 | |||
| 9c58c10152 | |||
| 64c984a704 | |||
| 15f97eab61 | |||
| df7313912c | |||
| acddb1fbc6 | |||
| 9b28ef17ff | |||
| e4a5c6d398 | |||
| 0598f6e7a5 | |||
| c3395a3922 | |||
| b0924a9e89 | |||
| 1f15b4c093 | |||
| bc6cb583a7 | |||
| 7b40fff4c5 | |||
| fc8597504f | |||
| 667aeccc38 | |||
| c88c1ac1cc | |||
| ec42e63d80 | |||
| 4f5481fa70 | |||
| f38da15bdb | |||
| 90e80d3454 | |||
| 8a8a1739e7 | |||
| bf6fe0f9ee | |||
| 9ac24926af | |||
| 6fa431a27a | |||
| 87d232e835 | |||
| 1314b11827 | |||
| d146fc6b6b | |||
| 9ff00ee785 | |||
| 5d54375f14 | |||
| 02bc775148 | |||
| af73b9067d | |||
| 67ac8c53ca | |||
| dcbe03d08a | |||
| 5b5668593c | |||
| f52932dc2e | |||
| b6e5c57b8a | |||
| 8324d3e5a2 | |||
| a5af8337f3 | |||
| a2ac6a608f | |||
| e4bff84c38 | |||
| cd58f2a5d3 | |||
| 182467502d | |||
| db04a5bba7 | |||
| d37688392a | |||
| a25ede147f | |||
| c87a0ce547 | |||
| 4e30aae916 | |||
| 051b6f8b50 | |||
| 5c14f9b8bc | |||
| c38d539e33 | |||
| cbf9c11a63 | |||
| dfec91c509 | |||
| eb18730789 | |||
| 19716bdb5e | |||
| 02687f5acd | |||
| 7f5234b67a | |||
| a4543cee92 | |||
| 074ace7d54 | |||
| e68a6c48ac | |||
| fba2021114 | |||
| 51d54ee9c8 | |||
| 6359479b5b | |||
| 4db3573292 | |||
| 0cb41628dd | |||
| 648f9da2c6 | |||
| 79e768f943 | |||
| 2275ec1d00 | |||
| bd37fab1ce | |||
| 6e3da8b4e4 | |||
| f696805bc6 | |||
| 0fca3af35d | |||
| a5e72161ef | |||
| 36b4d16610 | |||
| 4a895df52c | |||
| 35707e078e | |||
| 078cbdc4a8 | |||
| 462a8088e9 | |||
| e2753ef904 | |||
| 8dfb95ec91 | |||
| e67d08970f | |||
| 1e5b220a22 | |||
| 6f4f8c407a | |||
| 2b76bea318 | |||
| e481ee8775 | |||
| 0a1d462090 | |||
| b3b5e2e8a8 | |||
| 0bdc3f07c2 | |||
| 77416cf22c | |||
| b6c84f1a91 | |||
| c2d235e576 | |||
| f7f5db58aa | |||
| 73303b1c08 | |||
| 22f1171192 | |||
| a844095fdc | |||
| a7c6a14705 | |||
| cfdb37330e | |||
| aef6a9ca38 | |||
| c8e0729be8 | |||
| c2bbc5f15d | |||
| 0acbf54e44 | |||
| c3d9541330 | |||
| f6561de9b3 | |||
| bebd84969b | |||
| 365347ea7f | |||
| 33cb528b1d | |||
| fd287c5da0 | |||
| 45f410e3e8 | |||
| 8b06e9cd39 | |||
| c38fe10006 | |||
| 03cbc64f48 | |||
| cbf7d3c578 | |||
| ea2d068022 | |||
| 5991851207 | |||
| f628c3b5b5 | |||
| 05379712f4 | |||
| ed1cbf0fcf | |||
| 471f05369b | |||
| 13f7e33db1 | |||
| 9a78aa9263 | |||
| 58ccd6b26d | |||
| 73144f7ba4 | |||
| 650f4c9036 | |||
| 393f5c7d81 | |||
| 9eb2bb6c46 | |||
| 79b275eac7 | |||
| 07ac2af71e | |||
| c4b3aa7db8 | |||
| 61ef6a6466 | |||
| 477f3a7dec | |||
| adf3431388 | |||
| 83955aa046 | |||
| 5a17e916eb | |||
| 1159dda16e | |||
| 315508a36e | |||
| 9d6598c4e0 | |||
| eb43806da6 | |||
| e076dbf84f | |||
| 2f090b6b47 | |||
| c9dbf2bbae | |||
| e768c1bd2c | |||
| 408450ec12 | |||
| 106ca058be | |||
| b5ddfb3b35 | |||
| 61e4502e41 | |||
| 9b2fc87e33 | |||
| 89f09c42f8 | |||
| 5bcae8fe68 | |||
| ca58008431 | |||
| 30974f48eb | |||
| df4ba080b0 | |||
| f93930d712 | |||
| 036c17ec9a | |||
| 7281fdeabf | |||
| 051ec6df28 | |||
| 00a1e11fc1 | |||
| 584df9ba69 | |||
| a649022f1d | |||
| 26a9d8a6da | |||
| 9522b0161a | |||
| e189f8d9aa | |||
| 3ef8707cfa | |||
| a5fdcab862 | |||
| ba035881ee | |||
| 34ebe55531 | |||
| 01c9d2dc6d | |||
| a6db733204 | |||
| 8506e4ffcf | |||
| 04d3329572 | |||
| 5bfdde4bd9 | |||
| e2dd7e4aea | |||
| d1d644d422 | |||
| 8b496aa782 | |||
| 592d128ef6 | |||
| b44ee227fd | |||
| 0dce4a0d9e | |||
| ca6634c6f0 | |||
| c1805af420 | |||
| 0e308ce77f | |||
| ab1d335f8e | |||
| 1a2e9d1487 | |||
| 76bcba9340 | |||
| ae9795bd53 | |||
| 7540728f1b | |||
| 54da63dd03 | |||
| 8e08d0fe09 | |||
| 55b88f7c8a | |||
| 1cd616336f | |||
| 253790fe2e | |||
| 1c6f896b73 | |||
| 8810e12551 | |||
| 285007a59a | |||
| 6edd17646a | |||
| 154ee25dde | |||
| 30cf3cfd06 | |||
| 0d98d80919 | |||
| 54992e55cb | |||
| cc8135b733 | |||
| 5c6fa5151a | |||
| 2918bc9f81 | |||
| 6e2fbc23e9 | |||
| 1ec8ea53b4 | |||
| 4256d67789 | |||
| 0246ff4b22 | |||
| 956febc757 | |||
| beafdd0afd | |||
| b172e2cdc3 | |||
| baaf0e25f3 | |||
| 09c40978a1 | |||
| 0f4f48555e | |||
| d30515c1a2 | |||
| 5a6fc1d8ca |
@@ -0,0 +1,31 @@
|
||||
name: mypy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install uv
|
||||
uv sync --group typecheckers
|
||||
|
||||
- name: Run type checker
|
||||
run: uv run python -m mypy -p argenta
|
||||
@@ -0,0 +1,31 @@
|
||||
name: ruff
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install uv
|
||||
uv sync --group linters
|
||||
|
||||
- name: Run linter
|
||||
run: uv run python -m ruff check ./src
|
||||
@@ -0,0 +1,34 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.12", "3.13", "3.14"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install uv
|
||||
uv sync --group tests
|
||||
|
||||
- name: Run tests
|
||||
run: uv run python -m pytest tests
|
||||
+323
-5
@@ -1,6 +1,324 @@
|
||||
.venv
|
||||
.idea
|
||||
dist
|
||||
poetry.lock
|
||||
*__pycache__/
|
||||
#### joe made this: http://goel.io/joe
|
||||
|
||||
metrics/reports/diagrams
|
||||
|
||||
#### python ####
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
#### joe made this: http://goel.io/joe
|
||||
|
||||
#### visualstudiocode ####
|
||||
.vscode/
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
!*.code-workspace
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
#### joe made this: http://goel.io/joe
|
||||
|
||||
#### jetbrains ####
|
||||
# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based HTTP Client
|
||||
.idea/httpRequests
|
||||
http-client.private.env.json
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
# Apifox Helper cache
|
||||
.idea/.cache/.Apifox_Helper
|
||||
.idea/ApifoxUploaderProjectSetting.xml
|
||||
|
||||
.zed
|
||||
@@ -0,0 +1,17 @@
|
||||
version: 2
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
|
||||
jobs:
|
||||
post_install:
|
||||
- sphinx-intl build --locale-dir docs/locales
|
||||
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
@@ -0,0 +1,20 @@
|
||||
|
||||
<a id='changelog-1.2.0'></a>
|
||||
## 1.2.0 — 2026-02-07
|
||||
|
||||
### Added
|
||||
|
||||
- 100% coverage of the code base with tests
|
||||
- 100% coverage with typhints
|
||||
- 100% coverage of public API documentation in two languages - Russian and English
|
||||
- cli attributes: highlighting valid commands, redesigned input history with auto-completion, interactive autocomplete selection menu for multiple candidates
|
||||
- a metrics module that allows you to test the performance of various library units
|
||||
- implementing a dependency injection pattern through an ioc container
|
||||
- implementation of a context object for transferring data between handlers within a session
|
||||
- adding a changelog
|
||||
|
||||
### Changed
|
||||
|
||||
- increased performance by several times (there will be real numbers in the next releases)
|
||||
- reworking the internal API, highlighting different layers and reducing connectivity
|
||||
- reworking the README and adding a translation for it
|
||||
@@ -0,0 +1,66 @@
|
||||
# Code of Conduct - Argenta
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, level of experience, education,
|
||||
socio-economic status, nationality, personal appearance, race and religion.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behaviour that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologising to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behaviour include:
|
||||
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying and enforcing our standards of
|
||||
acceptable behaviour and will take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behaviour.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, or to ban
|
||||
temporarily or permanently any contributor for other behaviours that they deem
|
||||
inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behaviour may be
|
||||
reported to the community leaders responsible for enforcement at .
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org/), version
|
||||
[1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/code_of_conduct.md) and
|
||||
[2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/code_of_conduct.md)
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
<!-- omit in toc -->
|
||||
# Contributing to Argenta
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||
|
||||
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||
> - Star the project
|
||||
> - Tweet about it
|
||||
> - Refer this project in your project's readme
|
||||
> - Mention the project at local meetups and tell your friends/colleagues
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
- [Your First Code Contribution](#your-first-code-contribution)
|
||||
- [Improving The Documentation](#improving-the-documentation)
|
||||
- [Styleguides](#styleguides)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Join The Project Team](#join-the-project-team)
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the
|
||||
[Argenta Code of Conduct](https://github.com/koloideal/Argenta/blob/main/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||
to .
|
||||
|
||||
-----
|
||||
|
||||
## I Have a Question
|
||||
|
||||
> If you want to ask a question, we assume that you have read the available [Documentation](https://argenta.readthedocs.io).
|
||||
|
||||
Before you ask a question, it is best to search for existing [Issues](https://github.com/koloideal/Argenta/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
||||
|
||||
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||
|
||||
- Open an [Issue](https://github.com/koloideal/Argenta/issues/new).
|
||||
- Provide as much context as you can about what you're running into.
|
||||
- Provide project and platform versions (cpython, pip, etc), depending on what seems relevant.
|
||||
|
||||
We will then take care of the issue as soon as possible.
|
||||
|
||||
<!--
|
||||
You might want to create a separate issue tag for questions and include it in this description. People should then tag their issues accordingly.
|
||||
|
||||
Depending on how large the project is, you may want to outsource the questioning, e.g. to Stack Overflow or Gitter. You may add additional contact and information possibilities:
|
||||
- IRC
|
||||
- Slack
|
||||
- Gitter
|
||||
- Stack Overflow tag
|
||||
- Blog
|
||||
- FAQ
|
||||
- Roadmap
|
||||
- E-Mail List
|
||||
- Forum
|
||||
-->
|
||||
|
||||
-----
|
||||
|
||||
## I Want To Contribute
|
||||
|
||||
> ### Legal Notice <!-- omit in toc -->
|
||||
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence.
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting a Bug Report
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://argenta.readthedocs.io). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/koloideal/Argenta/issues?q=label%3Abug).
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Possibly your input and the output
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Bug Report?
|
||||
|
||||
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
|
||||
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
|
||||
|
||||
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||
|
||||
- Open an [Issue](https://github.com/koloideal/Argenta/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||
- Explain the behavior you would expect and the actual behavior.
|
||||
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||
- Provide the information you collected in the previous section.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
|
||||
|
||||
<!-- You might want to create an issue template for bugs and errors that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
|
||||
|
||||
-----
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for Argenta, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting an Enhancement
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Read the [documentation](https://argenta.readthedocs.io) carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](https://github.com/koloideal/Argenta/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/koloideal/Argenta/issues).
|
||||
|
||||
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||
- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. <!-- this should only be included if the project has a GUI -->
|
||||
- **Explain why this enhancement would be useful** to most Argenta users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||
|
||||
<!-- You might want to create an issue template for enhancement suggestions that can be used as a guide and that defines the structure of the information to be included. If you do so, reference it here in the description. -->
|
||||
|
||||
-----
|
||||
|
||||
### Your First Code Contribution
|
||||
|
||||
Unsure where to begin contributing to Argenta? You can start by looking through `good first issue` and `help wanted` issues on our GitHub repository. These are issues that are well-suited for new contributors.
|
||||
|
||||
To get started with your first code contribution, please follow these steps to set up your local development environment.
|
||||
|
||||
1. Fork the `Argenta` repository on GitHub.
|
||||
2. Clone your forked repository to your local machine:
|
||||
```bash
|
||||
git clone https://github.com/<YOUR_USERNAME>/Argenta.git
|
||||
cd Argenta
|
||||
```
|
||||
3. Create and activate a Python virtual environment.
|
||||
```bash
|
||||
# For macOS/Linux
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# For Windows
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
4. Install the project dependencies, including the development tools.
|
||||
```bash
|
||||
pip install -e .[dev]
|
||||
```
|
||||
5. Create a new branch for your feature or bug fix. Use a descriptive name, like `fix/login-bug` or `feat/new-widget`.
|
||||
```bash
|
||||
git checkout -b your-new-branch-name
|
||||
```
|
||||
6. Make your changes! Write your code, and don't forget to add or update tests for your changes.
|
||||
7. Run the test suite to ensure everything is working correctly.
|
||||
```bash
|
||||
python -m pytest tests
|
||||
```
|
||||
8. Commit your changes following our commit message styleguide and push them to your fork.
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(widget): add the new super widget"
|
||||
git push origin your-new-branch-name
|
||||
```
|
||||
9. Open a Pull Request from your forked branch to the `main` branch of the official Argenta repository. Provide a clear description of the problem and your solution. Include the relevant issue number if applicable.
|
||||
|
||||
-----
|
||||
|
||||
### Improving The Documentation
|
||||
|
||||
Good documentation is crucial for any project. We use Sphinx to generate our documentation from source files located in the `docs/` directory. We welcome any improvements, from fixing a simple typo to writing a whole new section.
|
||||
|
||||
We support documentation in two languages: Russian and English
|
||||
|
||||
To improve the documentation, you can follow a similar workflow as for code contributions:
|
||||
|
||||
1. Ensure your development environment is set up as described in the "Your First Code Contribution" section.
|
||||
2. Navigate to the documentation directory.
|
||||
```bash
|
||||
cd docs
|
||||
```
|
||||
3. Make the necessary changes to the **Russian** version of the documentation - ``docs/index.rst`` and ``docs/root/*``
|
||||
4. To build the documentation locally and see your changes, run:
|
||||
```bash
|
||||
make live-ru
|
||||
```
|
||||
5. Open `127.0.0.1:8000` in your web browser to preview the generated documentation.
|
||||
6. Make your desired changes to the `.rst` or `.md` files in the `docs/source` directory.
|
||||
7. After completing the work on the Russian documentation, it is necessary to create an English translation:
|
||||
|
||||
```bash
|
||||
make update-langs
|
||||
```
|
||||
8. After updating the translation template, update the necessary translation files located at ``docs/locales/en/LC_MESSAGES``
|
||||
8. Once you are happy with your changes, commit them and open a Pull Request. Use the `docs:` prefix in your commit message.
|
||||
|
||||
---
|
||||
|
||||
## Styleguides
|
||||
|
||||
### Commit Messages
|
||||
|
||||
We follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for our commit messages. This leads to more readable messages that are easy to follow when looking through the project history and allows for automated changelog generation.
|
||||
|
||||
Each commit message consists of a **header**, a **body**, and a **footer**.
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
The `<type>` must be one of the following:
|
||||
|
||||
* **feat**: A new feature for the user.
|
||||
* **fix**: A bug fix for the user.
|
||||
* **docs**: Documentation only changes.
|
||||
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, etc).
|
||||
* **refactor**: A code change that neither fixes a bug nor adds a feature.
|
||||
* **perf**: A code change that improves performance.
|
||||
* **test**: Adding missing tests or correcting existing tests.
|
||||
* **chore**: Changes to the build process or auxiliary tools and libraries.
|
||||
|
||||
#### Examples
|
||||
|
||||
A simple fix:
|
||||
``fix: correct typo in user authentication flow``
|
||||
|
||||
A new feature with a scope:
|
||||
``feat(api): add new endpoint for user profiles``
|
||||
|
||||
---
|
||||
|
||||
## Join The Project Team
|
||||
|
||||
We are always looking for enthusiastic and dedicated people to join our project team. If you are a regular contributor and have shown a deep understanding of the project's goals and architecture, you might be a good candidate to become a maintainer.
|
||||
|
||||
Active members of the community can become team members. This typically involves:
|
||||
|
||||
* Consistently contributing high-quality code and documentation.
|
||||
* Helping other users by answering questions and triaging issues.
|
||||
* Reviewing Pull Requests from other contributors with constructive feedback.
|
||||
|
||||
If you are interested in becoming a more permanent part of the team, the best way to start is by being an active and helpful member of the community. The existing maintainers will notice your efforts and may reach out with an invitation to join the team.
|
||||
@@ -1,444 +1,105 @@
|
||||
# Argenta
|
||||

|
||||
|
||||
**Argenta** is a simple and elegant framework for building modular CLI applications. It provides a clean and intuitive way to create context-aware command-line tools with isolated command scopes.
|
||||
|
||||
Argenta is the **"Simplest"**, **"Most Modular"**, and **"Most Elegant"** way to build interactive CLI applications in Python.
|
||||
|
||||
📖 **Read the full documentation:** [argenta.readthedocs.io](https://argenta.readthedocs.io/)<br>
|
||||
🌍 **Other languages:** [RU](https://github.com/koloideal/Argenta/blob/main/README.ru.md)
|
||||
|
||||
---
|
||||
|
||||
## Описание
|
||||
**Argenta** — Python library for creating custom shells
|
||||

|
||||
|
||||
---
|
||||
**Argenta** allows you to build interactive CLI applications incredibly easily. There's no need to manually parse complex command structures or manage state transitions — just use routers and commands!
|
||||
|
||||
# Установка
|
||||
```bash
|
||||
pip install argenta
|
||||
```
|
||||
or
|
||||
```bash
|
||||
poetry add argenta
|
||||
## ✨ Installing Argenta
|
||||
|
||||
Argenta is available on ``PyPI``:
|
||||
|
||||
```console
|
||||
$ python -m pip install argenta
|
||||
```
|
||||
|
||||
---
|
||||
or using ``uv``:
|
||||
|
||||
# Быстрый старт
|
||||
```console
|
||||
$ uv add argenta
|
||||
```
|
||||
|
||||
Argenta officially supports Python 3.12+.
|
||||
|
||||
## 🚀 Supported Features & Best Practices
|
||||
|
||||
Argenta is ready for the demands of building scalable, robust and maintainable CLI applications.
|
||||
|
||||
- **Interactive Sessions**: Unlike traditional CLI tools, ``Argenta`` creates cyclic sessions, allowing users to execute commands sequentially without restarting the application.
|
||||
- **Declarative Syntax**: Commands and their handlers are declared using simple decorators, making the code intuitive and allowing you to focus on "what" you want to do, not "how".
|
||||
- **Native DI**: Thanks to integration with [dishka](https://dishka.readthedocs.io/en/stable/), you can easily inject dependencies directly into command handlers, simplifying testing, avoiding mutable globals, and much more.
|
||||
- **Automatic Validation and Parsing**: The library handles command-line flags and arguments processing, including parsing, validation, and type conversion.
|
||||
- **Flexible Configuration**: You can easily customize system messages, output formatting, create custom handlers for non-standard behavior, and more.
|
||||
|
||||
Need something more? Create an **issue**, we're open to suggestions.
|
||||
|
||||
## 📝 Why did we create this?
|
||||
|
||||
Building complex CLI applications often requires managing different contexts and command scopes. For example, when creating a utility similar to the Metasploit Framework, users need to enter specific scopes (like selecting a scanning module) and then access commands specific only to that context.
|
||||
|
||||
Traditional CLI frameworks don't provide an elegant way to handle this kind of modular, context-aware architecture. Argenta was built to solve this problem by providing a simple and concise way to encapsulate CLI functionality in isolated, abstracted environments.
|
||||
|
||||
We believe that building CLI applications should be as pleasant as building web applications with modern frameworks. Argenta brings the router pattern and clean separation of concerns to the CLI world.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
Here's a simple example to get you started:
|
||||
|
||||
Пример простейшей оболочки с командой без зарегистрированных флагов
|
||||
```python
|
||||
# routers.py
|
||||
from argenta.router import Router
|
||||
from argenta.command import Command
|
||||
|
||||
from argenta import Router, Response
|
||||
from argenta.command import Flag, Command
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.command(Command("hello"))
|
||||
def handler():
|
||||
print("Hello, world!")
|
||||
def handler(response: Response):
|
||||
"""A simple hello world command"""
|
||||
print("Hello, world!")
|
||||
|
||||
@router.command(Command("greet", flags=Flag('name')))
|
||||
def greet_handler(response: Response):
|
||||
"""Greet a user by name"""
|
||||
name_flag = response.input_flags.get_flag_by_name('name')
|
||||
if name_flag:
|
||||
print(f"Hello, {name_flag.input_value}!")
|
||||
else:
|
||||
print("Hello, Stranger!")
|
||||
```
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from argenta.app import App
|
||||
from routers import router
|
||||
from argenta import App, Orchestrator
|
||||
from .routers import router
|
||||
|
||||
app: App = App()
|
||||
app = App()
|
||||
orchestrator = Orchestrator()
|
||||
|
||||
def main() -> None:
|
||||
# Include your routers
|
||||
app.include_router(router)
|
||||
app.start_polling()
|
||||
|
||||
# Start the interactive CLI
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
Пример оболочки с командой, у которой зарегистрированы флаги
|
||||
|
||||
```python
|
||||
# routers.py
|
||||
import re
|
||||
from argenta.router import Router
|
||||
from argenta.command import Command
|
||||
from argenta.command.params.flag import FlagsGroup, Flag
|
||||
That's it! You now have a fully functional interactive CLI application.
|
||||
|
||||
router = Router()
|
||||
## 📚 Documentation
|
||||
|
||||
registered_flags = FlagsGroup([
|
||||
Flag(flag_name='host',
|
||||
flag_prefix='--',
|
||||
possible_flag_values=re.compile(r'^192.168.\d{1,3}.\d{1,3}$')),
|
||||
Flag('port', '---', re.compile(r'^[0-9]{1,4}$'))
|
||||
])
|
||||
|
||||
|
||||
@router.command(Command("hello"))
|
||||
def handler():
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
@router.command(Command(trigger="ssh",
|
||||
description='connect via ssh',
|
||||
flags=registered_flags))
|
||||
def handler_with_flags(flags: dict):
|
||||
for flag in flags:
|
||||
print(f'Flag name: {flag['name']}\n'
|
||||
f'Flag value: {flag['value']}')
|
||||
```
|
||||
Full documentation is available at [argenta.readthedocs.io](https://argenta.readthedocs.io/)
|
||||
|
||||
---
|
||||
|
||||
# *classes* :
|
||||
|
||||
---
|
||||
|
||||
## *class* : : `App`
|
||||
Класс, определяющий поведение и состояние оболочки
|
||||
|
||||
### Конструктор
|
||||
```python
|
||||
App(prompt: str = 'Enter a command',
|
||||
initial_greeting: str = '\nHello, I am Argenta\n',
|
||||
farewell_message: str = '\nGoodBye\n',
|
||||
exit_command: str = 'Q',
|
||||
exit_command_description: str = 'Exit command',
|
||||
system_points_title: str = 'System points:',
|
||||
ignore_exit_command_register: bool = True,
|
||||
ignore_command_register: bool = False,
|
||||
line_separate: str = '',
|
||||
command_group_description_separate: str = '',
|
||||
repeat_command_groups: bool = True,
|
||||
print_func: Callable[[str], None] = print)
|
||||
```
|
||||
**Аргументы:**
|
||||
- **name : mean**
|
||||
- `prompt` (`str`): Сообщение перед вводом команды.
|
||||
- `initial_greeting` (`str`): Приветственное сообщение при запуске.
|
||||
- `farewell_message` (`str`): Сообщение при выходе.
|
||||
- `exit_command` (`str`): Команда выхода (по умолчанию `'Q'`).
|
||||
- `exit_command_description` (`str`): Описание команды выхода.
|
||||
- `system_points_title` (`str`): Заголовок перед списком системных команд.
|
||||
- `ignore_exit_command_register` (`bool`): Игнорировать регистр команды выхода.
|
||||
- `ignore_command_register` (`bool`): Игнорировать регистр всех команд.
|
||||
- `line_separate` (`str`): Разделительная строка между командами.
|
||||
- `command_group_description_separate` (`str`): Разделитель между группами команд.
|
||||
- `repeat_command_groups` (`bool`): Повторять описание команд перед вводом.
|
||||
- `print_func` (`Callable[[str], None]`): Функция вывода текста в терминал (по умолчанию `print`).
|
||||
|
||||
---
|
||||
|
||||
### ***methods***
|
||||
|
||||
---
|
||||
|
||||
#### **.start_polling() -> `None`**
|
||||
|
||||
*method mean* **::** запускает цикл обработки ввода
|
||||
|
||||
---
|
||||
|
||||
#### **.include_router(router: Router) -> `None`**
|
||||
|
||||
*param* `router: Router` **::** регистрируемый роутер
|
||||
*required* **::** True
|
||||
|
||||
*method mean* **::** регистрирует роутер в оболочке
|
||||
|
||||
---
|
||||
|
||||
#### **.set_initial_message(message: str) -> `None`**
|
||||
|
||||
*param* `message: str` **::** устанавливаемое приветственное сообщение
|
||||
*required* **::** True
|
||||
*example* **::** `"Hello, I'm a example app"`
|
||||
|
||||
*method mean* **::** устанавливает сообщение, которое будет отображено при запуске программы
|
||||
|
||||
---
|
||||
|
||||
#### **.set_farewell_message(message: str) -> `None`**
|
||||
|
||||
*param* `message: str` **::** устанавливаемое сообщение при выходе
|
||||
*required* **::** True
|
||||
*example* **::** `"GoodBye !"`
|
||||
|
||||
*method mean* **::** устанавливает сообщение, которое будет отображено при выходе
|
||||
|
||||
---
|
||||
|
||||
#### **.set_description_message_pattern(pattern: str) -> `None`**
|
||||
|
||||
*param* `pattern: str` **::** паттерн описания команды при её выводе в консоль
|
||||
*required* **::** True
|
||||
*example* **::** `"[{command}] *=*=* {description}"`
|
||||
|
||||
*method mean* **::** устанавливает паттерн описания команд, который будет использован
|
||||
при выводе в консоль
|
||||
|
||||
---
|
||||
<a name="custom_handler"></a>
|
||||
#### **.set_repeated_input_flags_handler(handler: Callable[[str], None]) -> `None`**
|
||||
|
||||
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||
вводе юзером повторяющихся флагов
|
||||
*required* **::** True
|
||||
*example* **::** `lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')`
|
||||
|
||||
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||
вводе юзером повторяющихся флагов
|
||||
|
||||
---
|
||||
|
||||
#### **.set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> `None`**
|
||||
|
||||
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||
вводе юзером команды с некорректным синтаксисом флагов
|
||||
*required* **::** True
|
||||
*example* **::** `lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')`
|
||||
|
||||
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||
вводе юзером команды с некорректным синтаксисом флагов
|
||||
|
||||
---
|
||||
|
||||
#### **.set_unknown_command_handler(self, handler: Callable[[str], None]) -> `None`**
|
||||
|
||||
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||
вводе юзером неизвестной команды
|
||||
*required* **::** True
|
||||
*example* **::** `lambda command: print_func(f"Unknown command: {command.get_string_entity()}")`
|
||||
|
||||
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||
вводе юзером неизвестной команды
|
||||
|
||||
---
|
||||
|
||||
#### **.set_empty_command_handler(self, handler: Callable[[str], None]) -> `None`**
|
||||
|
||||
*param* `handler: Callable[[str], None]` **::** функция или лямбда функция, которой будет передано управление при
|
||||
вводе юзером пустой команды
|
||||
*required* **::** True
|
||||
*example* **::** `lambda: print_func(f'Empty input command')`
|
||||
|
||||
*method mean* **::** устанавливает функцию, которой будет передано управление при
|
||||
вводе юзером пустой команды
|
||||
|
||||
---
|
||||
|
||||
### Примечания
|
||||
|
||||
- В устанавливаемом паттерне сообщения описания команды необходимы быть два ключевых слова:
|
||||
`command` и `description`, каждое из которых должно быть заключено в фигурные скобки, после обработки
|
||||
паттерна на места этих ключевых слов будут подставлены соответствующие атрибуты команды, при отсутствии
|
||||
этих двух ключевых слов будет вызвано исключение `InvalidDescriptionMessagePatternException`
|
||||
|
||||
- Команды оболочки не должны повторяться, при значении атрибута `ignore_command_register` равным `True`
|
||||
допускается создание обработчиков для разных регистров одинаковых символов в команде, для примера `u` и `U`,
|
||||
при значении атрибута `ignore_command_register` класса `App` равным `False` тот же пример вызывает исключение
|
||||
`RepeatedCommandInDifferentRoutersException`. Исключение вызывается только при наличии пересекающихся команд
|
||||
у __<u>разных</u>__ роутеров
|
||||
|
||||
|
||||
|
||||
|
||||
### Исключения
|
||||
|
||||
- `InvalidRouterInstanceException` — Переданный объект в метод `App().include_router()` не является экземпляром класса `Router`.
|
||||
- `InvalidDescriptionMessagePatternException` — Неправильный формат паттерна описания команд.
|
||||
- `IncorrectNumberOfHandlerArgsException` — У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент).
|
||||
- `NoRegisteredRoutersException` — Отсутствуют зарегистрированные роутеры.
|
||||
- `NoRegisteredHandlersException` — У роутера нет ни одного обработчика команд.
|
||||
- `RepeatedCommandInDifferentRoutersException` — Одна и та же команда зарегистрирована в разных роутерах.
|
||||
|
||||
---
|
||||
|
||||
## *class* :: `Router`
|
||||
Класс, который определяет и конфигурирует обработчики команд
|
||||
|
||||
### Конструктор
|
||||
```python
|
||||
Router(title: str = 'Commands group title:',
|
||||
name: str = 'Default')
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Аргументы:**
|
||||
- **name : mean**
|
||||
- `title` (`str`): Заголовок группы команд.
|
||||
- `name` (`str`): Персональное название роутера
|
||||
|
||||
---
|
||||
|
||||
### ***methods***
|
||||
|
||||
---
|
||||
|
||||
#### **command(command: Command)**
|
||||
|
||||
*param* `command: Command` **::** экземпляр класса `Command`, который определяет строковый триггер команды,
|
||||
допустимые флаги команды и другое
|
||||
*required* **::** True
|
||||
*example* **::** `Command(command='ssh', description='connect via ssh')`
|
||||
|
||||
*method mean* **::** декоратор, который регистрирует функцию как обработчик команды
|
||||
|
||||
---
|
||||
|
||||
#### **.get_name() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает установленное название роутера
|
||||
|
||||
---
|
||||
|
||||
#### **.get_title() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает установленный заголовок группы команд данного роутера
|
||||
|
||||
---
|
||||
|
||||
#### **.get_all_commands() -> `list[str]`**
|
||||
|
||||
*method mean* **::** возвращает все зарегистрированные команды для данного роутера
|
||||
|
||||
---
|
||||
|
||||
### Исключения
|
||||
- `RepeatedCommandException` - Одна и та же команда зарегистрирована в одном роутере
|
||||
- `RepeatedFlagNameException` - Повторяющиеся зарегистрированные флаги в команде
|
||||
- `TooManyTransferredArgsException` - Слишком много зарегистрированных аргументов у обработчика команды
|
||||
- `RequiredArgumentNotPassedException` - Не зарегистрирован обязательный аргумент у обработчика команды(аргумент, через который будут переданы флаги введённой команды)
|
||||
- `IncorrectNumberOfHandlerArgsException` - У обработчика нестандартного поведения зарегистрировано неверное количество аргументов(в большинстве случаев у него должен быть один аргумент)
|
||||
- `TriggerCannotContainSpacesException` - У регистрируемой команды в триггере содержатся пробелы
|
||||
|
||||
---
|
||||
|
||||
## *class* :: `Command`
|
||||
Класс, экземпляр которого определяет строковый триггер хэндлера и конфигурирует его атрибуты
|
||||
|
||||
### Конструктор
|
||||
```python
|
||||
Command(trigger: str,
|
||||
description: str = None,
|
||||
flags: Flag | FlagsGroup = None)
|
||||
```
|
||||
|
||||
**Аргументы:**
|
||||
- **name : mean**
|
||||
- `trigger` (`str`): Строковый триггер
|
||||
- `description` (`str`): Описание команды, которое будет выведено в консоль при запуске оболочки
|
||||
- `flags` (`Flag | FlagsGroup`): Флаги, которые будут обработаны при их наличии во вводе юзера
|
||||
|
||||
---
|
||||
|
||||
#### **.get_trigger() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает строковый триггер экземпляра
|
||||
|
||||
---
|
||||
|
||||
#### **.get_description() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает описание команды
|
||||
|
||||
---
|
||||
|
||||
#### **.get_registered_flags() -> `FlagsGroup | None`**
|
||||
|
||||
*method mean* **::** возвращает зарегистрированные флаги экземпляра
|
||||
|
||||
---
|
||||
|
||||
### Исключения
|
||||
- `UnprocessedInputFlagException` - Некорректный синтаксис ввода команды
|
||||
- `RepeatedInputFlagsException` - Повторяющиеся флаги во введённой команде
|
||||
- `EmptyInputCommandException` - Введённая команда является пустой(не содержит символов)
|
||||
|
||||
**Примечание**
|
||||
Все вышеуказанные исключения класса `Command` вызываются в рантайме запущенным экземпляром класса
|
||||
`App`, а также по дефолту обрабатываются, при желании можно задать пользовательские
|
||||
обработчики для этих исключений ([подробнее см.](#custom_handler))
|
||||
|
||||
---
|
||||
|
||||
## *class* :: `Flag`
|
||||
Класс, экземпляры которого в большинстве случаев передаются при создании
|
||||
экземпляра класса `Command` для регистрации допустимого флага при вводе юзером команды
|
||||
|
||||
### Конструктор
|
||||
```python
|
||||
Flag(flag_name: str,
|
||||
flag_prefix: typing.Literal['-', '--', '---'] = '-',
|
||||
ignore_flag_value_register: bool = False,
|
||||
possible_flag_values: list[str] | typing.Pattern[str] = False)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Аргументы:**
|
||||
- **name : mean**
|
||||
- `flag_name` (`str`): Имя флага
|
||||
- `flag_prefix` (`Literal['-', '--', '---']`): Префикс команды, допустимым значением является от одного до трёх минусов
|
||||
- `ignore_flag_value_register` (`bool`): Будет ли игнорироваться регистр значения введённого флага
|
||||
- `possible_flag_values` (`list[str] | Pattern[str]`): Множество допустимых значений флага, может быть задано
|
||||
списком с допустимыми значениями или регулярным выражением (рекомендуется `re.compile(r'example exspression')`)
|
||||
|
||||
---
|
||||
|
||||
### ***methods***
|
||||
|
||||
---
|
||||
|
||||
#### **.get_string_entity() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает строковое представление флага(префикс + имя)
|
||||
|
||||
---
|
||||
|
||||
#### **.get_flag_name() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает имя флага
|
||||
|
||||
---
|
||||
|
||||
#### **.get_flag_prefix() -> `str`**
|
||||
|
||||
*method mean* **::** возвращает префикс флага
|
||||
|
||||
---
|
||||
|
||||
## *class* :: `FlagsGroup`
|
||||
Класс, объединяющий список флагов в один объект, используется в качестве
|
||||
передаваемого аргумента `flags` экземпляру класса `Command`, при регистрации
|
||||
хэндлера
|
||||
|
||||
### Конструктор
|
||||
```python
|
||||
FlagsGroup(flags: list[Flag] = None)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Аргументы:**
|
||||
- **name : mean**
|
||||
- `flags` (`list[Flag]`): Список флагов, которые будут объединены в одну группу
|
||||
|
||||
---
|
||||
|
||||
### ***methods***
|
||||
|
||||
---
|
||||
|
||||
#### **.get_flags() -> `list[Flag]`**
|
||||
|
||||
*method mean* **::** возвращает зарегистрированные флаги
|
||||
|
||||
---
|
||||
|
||||
# Тесты
|
||||
|
||||
Запуск тестов:
|
||||
|
||||
```bash
|
||||
python -m unittest discover
|
||||
```
|
||||
or
|
||||
```bash
|
||||
python -m unittest discover -v
|
||||
```
|
||||
MIT 2025 kolo | made by [kolo](https://t.me/kolo_id)
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||

|
||||
|
||||
**Argenta** — это простой и элегантный фреймворк для создания модульных CLI-приложений. Он предоставляет чистый и интуитивный способ создания контекстно-зависимых инструментов командной строки с изолированными областями команд.
|
||||
|
||||
Argenta — это **"Самый простой"**, **"Самый модульный"** и **"Самый элегантный"** способ создания интерактивных CLI-приложений на Python.
|
||||
|
||||
📖 **Читайте полную документацию:** [argenta.readthedocs.io](https://argenta.readthedocs.io/)<br>
|
||||
🌍 **Другие языки:** [EN](https://github.com/koloideal/Argenta/blob/main/README.md)
|
||||
|
||||
---
|
||||
|
||||

|
||||
|
||||
**Argenta** позволяет создавать интерактивные CLI-приложения невероятно легко. Не нужно вручную парсить сложные структуры команд или управлять переходами состояний — просто используйте роутеры и команды!
|
||||
|
||||
## ✨ Установка Argenta
|
||||
|
||||
Argenta доступна на ``PyPI``:
|
||||
|
||||
```console
|
||||
$ python -m pip install argenta
|
||||
```
|
||||
|
||||
или с использованием ``uv``:
|
||||
|
||||
```console
|
||||
$ uv add argenta
|
||||
```
|
||||
|
||||
Argenta официально поддерживает Python 3.12+.
|
||||
|
||||
## 🚀 Поддерживаемые возможности и лучшие практики
|
||||
|
||||
Argenta готова к требованиям создания масштабируемых, надежных и поддерживаемых CLI-приложений.
|
||||
|
||||
- **Интерактивные сессии**: В отличие от традиционных CLI-инструментов, ``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение.
|
||||
- **Декларативный синтаксис**: Команды и их обработчики объявляются с помощью простых декораторов, что делает код интуитивно понятным и позволяет сосредоточиться на том, "что" вы хотите сделать, а не "как".
|
||||
- **Нативный DI**: Благодаря интеграции с [dishka](https://dishka.readthedocs.io/en/stable/), вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование, позволяет избежать мутабельных глобалов и многое другое.
|
||||
- **Автоматическая валидация и парсинг**: Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов.
|
||||
- **Гибкая настройка**: Вы можете легко кастомизировать системные сообщения, форматирование вывода, ссоздавать кастомные обработчики нестандартного поведения и т.д.
|
||||
|
||||
Нужно что-то еще? Создайте **issue**, мы открыты к предложениям.
|
||||
|
||||
## 📝 Зачем мы это создали?
|
||||
|
||||
Создание сложных CLI-приложений часто требует управления различными контекстами и областями команд. Например, при создании утилиты, подобной Metasploit Framework, пользователям нужно входить в определенные области (например, выбирать модуль сканирования), а затем получать доступ к командам, специфичным только для этого контекста.
|
||||
|
||||
Традиционные CLI-фреймворки не предоставляют элегантного способа обработки такой модульной, контекстно-зависимой архитектуры. Argenta была создана для решения этой проблемы, предоставляя простой и лаконичный способ инкапсуляции CLI-функциональности в изолированные, абстрагированные среды.
|
||||
|
||||
Мы считаем, что создание CLI-приложений должно быть таким же приятным, как создание веб-приложений с современными фреймворками. Argenta привносит паттерн роутеров и чистое разделение ответственности в мир CLI.
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
Вот простой пример для начала работы:
|
||||
|
||||
```python
|
||||
# routers.py
|
||||
from argenta import Router, Response
|
||||
from argenta.command import Flag, Command
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.command(Command("hello"))
|
||||
def handler(response: Response):
|
||||
"""Простая команда hello world"""
|
||||
print("Привет, мир!")
|
||||
|
||||
@router.command(Command("greet", flags=Flag('name')))
|
||||
def greet_handler(response: Response):
|
||||
"""Поприветствовать пользователя по имени"""
|
||||
name_flag = response.input_flags.get_flag_by_name('name')
|
||||
if name_flag:
|
||||
print(f"Привет, {name_flag.input_value}!")
|
||||
else:
|
||||
print("Привет, Незнакомец!")
|
||||
```
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from argenta import App, Orchestrator
|
||||
from .routers import router
|
||||
|
||||
app = App()
|
||||
orchestrator = Orchestrator()
|
||||
|
||||
def main() -> None:
|
||||
# Подключите ваши роутеры
|
||||
app.include_router(router)
|
||||
|
||||
# Запустите интерактивный CLI
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
Вот и всё! Теперь у вас есть полностью функциональное интерактивное CLI-приложение.
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
Полная документация доступна на [argenta.readthedocs.io](https://argenta.readthedocs.io/)
|
||||
|
||||
---
|
||||
|
||||
MIT 2025 kolo | made by [kolo](https://t.me/kolo_id)
|
||||
|
||||
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
# Security Policy
|
||||
|
||||
The Argenta team takes security seriously. We appreciate your efforts to responsibly disclose your findings, and we will make every effort to acknowledge your contributions.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This table shows the versions of Argenta that are currently supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
|---------|-----------------|
|
||||
| 1.1.x | ✅ |
|
||||
| < 1.1 | ❌ |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability in Argenta, please report it to us through one of the following methods:
|
||||
|
||||
- **Email**: Send a detailed report to `kolo.is.main@gmailcom`.
|
||||
- **GitHub Security Advisories**: You can create a new security advisory directly in the Argenta repository.
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
### What to Include
|
||||
|
||||
To help us understand and resolve the issue quickly, please include the following information in your report:
|
||||
|
||||
- A clear and descriptive title.
|
||||
- The affected version(s) of Argenta.
|
||||
- A detailed description of the vulnerability.
|
||||
- Step-by-step instructions to reproduce the issue (a Proof-of-Concept).
|
||||
- The potential impact of the vulnerability.
|
||||
- Any suggested mitigations or fixes, if you have any.
|
||||
|
||||
You can expect a response from us within 48 hours to acknowledge receipt of your report.
|
||||
|
||||
## Disclosure Process
|
||||
|
||||
1. Upon receiving a vulnerability report, we will assign it to a team member and begin our investigation.
|
||||
2. We will confirm the vulnerability and determine its severity.
|
||||
3. We will work on a patch to address the issue.
|
||||
4. Once the patch is ready, we will coordinate with you to schedule a release and a public disclosure. We prefer to disclose vulnerabilities through a GitHub Security Advisory.
|
||||
5. We will credit you for your discovery in the advisory, unless you prefer to remain anonymous.
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
As a user of Argenta, we recommend the following best practices to keep your application secure:
|
||||
|
||||
- Always use the latest version of Argenta.
|
||||
- Regularly scan your project's dependencies for known vulnerabilities.
|
||||
- If your application handles sensitive data, ensure it is stored and transmitted securely.
|
||||
|
||||
## Bug Bounty Program
|
||||
|
||||
Currently, we do not have a formal bug bounty program. However, we deeply appreciate the work of security researchers and may offer non-monetary recognition for significant contributions.
|
||||
@@ -1 +0,0 @@
|
||||
from .entity import App
|
||||
@@ -1,258 +0,0 @@
|
||||
from typing import Callable
|
||||
from inspect import getfullargspec
|
||||
import re
|
||||
|
||||
from ..command.entity import Command
|
||||
from ..router.entity import Router
|
||||
from ..command.exceptions import (UnprocessedInputFlagException,
|
||||
RepeatedInputFlagsException,
|
||||
EmptyInputCommandException)
|
||||
from .exceptions import (InvalidRouterInstanceException,
|
||||
InvalidDescriptionMessagePatternException,
|
||||
NoRegisteredRoutersException,
|
||||
NoRegisteredHandlersException,
|
||||
RepeatedCommandInDifferentRoutersException,
|
||||
IncorrectNumberOfHandlerArgsException)
|
||||
|
||||
|
||||
class App:
|
||||
def __init__(self,
|
||||
prompt: str = 'Enter a command',
|
||||
initial_message: str = '\nHello, I am Argenta\n',
|
||||
farewell_message: str = '\nGoodBye\n',
|
||||
invalid_input_flags_message: str = 'Invalid input flags',
|
||||
exit_command: str = 'Q',
|
||||
exit_command_description: str = 'Exit command',
|
||||
system_points_title: str = 'System points:',
|
||||
ignore_exit_command_register: bool = True,
|
||||
ignore_command_register: bool = False,
|
||||
line_separate: str = '',
|
||||
command_group_description_separate: str = '',
|
||||
repeat_command_groups: bool = True,
|
||||
print_func: Callable[[str], None] = print) -> None:
|
||||
self.prompt = prompt
|
||||
self.print_func = print_func
|
||||
self.exit_command = exit_command
|
||||
self.exit_command_description = exit_command_description
|
||||
self.system_points_title = system_points_title
|
||||
self.ignore_exit_command_register = ignore_exit_command_register
|
||||
self.farewell_message = farewell_message
|
||||
self.initial_message = initial_message
|
||||
self.invalid_input_flags_message = invalid_input_flags_message
|
||||
self.line_separate = line_separate
|
||||
self.command_group_description_separate = command_group_description_separate
|
||||
self.ignore_command_register = ignore_command_register
|
||||
self.repeat_command_groups = repeat_command_groups
|
||||
|
||||
self._routers: list[Router] = []
|
||||
self._description_message_pattern: str = '[{command}] *=*=* {description}'
|
||||
self._registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = []
|
||||
self._invalid_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'Incorrect flag syntax: "{raw_command}"')
|
||||
self._repeated_input_flags_handler: Callable[[str], None] = lambda raw_command: print_func(f'Repeated input flags: "{raw_command}"')
|
||||
self._empty_input_command_handler: Callable[[], None] = lambda: print_func(f'Empty input command')
|
||||
self._unknown_command_handler: Callable[[Command], None] = lambda command: print_func(f"Unknown command: {command.get_string_entity()}")
|
||||
|
||||
|
||||
def start_polling(self) -> None:
|
||||
self._validate_number_of_routers()
|
||||
self._validate_included_routers()
|
||||
self._validate_all_router_commands()
|
||||
|
||||
self.print_func(self.initial_message)
|
||||
|
||||
if not self.repeat_command_groups:
|
||||
self._print_command_group_description()
|
||||
self.print_func(self.prompt)
|
||||
|
||||
while True:
|
||||
if self.repeat_command_groups:
|
||||
self._print_command_group_description()
|
||||
self.print_func(self.prompt)
|
||||
|
||||
raw_command: str = input()
|
||||
|
||||
try:
|
||||
input_command: Command = Command.parse_input_command(raw_command=raw_command)
|
||||
except UnprocessedInputFlagException:
|
||||
self.print_func(self.line_separate)
|
||||
self._invalid_input_flags_handler(raw_command)
|
||||
self.print_func(self.line_separate)
|
||||
|
||||
if not self.repeat_command_groups:
|
||||
self.print_func(self.prompt)
|
||||
continue
|
||||
|
||||
except RepeatedInputFlagsException:
|
||||
self.print_func(self.line_separate)
|
||||
self._repeated_input_flags_handler(raw_command)
|
||||
self.print_func(self.line_separate)
|
||||
|
||||
if not self.repeat_command_groups:
|
||||
self.print_func(self.prompt)
|
||||
continue
|
||||
|
||||
except EmptyInputCommandException:
|
||||
self.print_func(self.line_separate)
|
||||
self._empty_input_command_handler()
|
||||
self.print_func(self.line_separate)
|
||||
|
||||
if not self.repeat_command_groups:
|
||||
self.print_func(self.prompt)
|
||||
continue
|
||||
|
||||
self._check_command_for_exit_command(input_command.get_trigger())
|
||||
|
||||
self.print_func(self.line_separate)
|
||||
is_unknown_command: bool = self._check_is_command_unknown(input_command)
|
||||
|
||||
if is_unknown_command:
|
||||
self.print_func(self.line_separate)
|
||||
self.print_func(self.command_group_description_separate)
|
||||
if not self.repeat_command_groups:
|
||||
self.print_func(self.prompt)
|
||||
continue
|
||||
|
||||
for router in self._routers:
|
||||
router.input_command_handler(input_command)
|
||||
|
||||
self.print_func(self.line_separate)
|
||||
self.print_func(self.command_group_description_separate)
|
||||
if not self.repeat_command_groups:
|
||||
self.print_func(self.prompt)
|
||||
|
||||
|
||||
def set_initial_message(self, message: str) -> None:
|
||||
self.initial_message: str = message
|
||||
|
||||
|
||||
def set_farewell_message(self, message: str) -> None:
|
||||
self.farewell_message: str = message
|
||||
|
||||
|
||||
def set_description_message_pattern(self, pattern: str) -> None:
|
||||
first_check = re.match(r'.*{command}.*', pattern)
|
||||
second_check = re.match(r'.*{description}.*', pattern)
|
||||
|
||||
if bool(first_check) and bool(second_check):
|
||||
self._description_message_pattern: str = pattern
|
||||
else:
|
||||
raise InvalidDescriptionMessagePatternException(pattern)
|
||||
|
||||
|
||||
def set_invalid_input_flags_handler(self, handler: Callable[[str], None]) -> None:
|
||||
args = getfullargspec(handler).args
|
||||
if len(args) != 1:
|
||||
raise IncorrectNumberOfHandlerArgsException()
|
||||
else:
|
||||
self._invalid_input_flags_handler = handler
|
||||
|
||||
|
||||
def set_repeated_input_flags_handler(self, handler: Callable[[str], None]) -> None:
|
||||
args = getfullargspec(handler).args
|
||||
if len(args) != 1:
|
||||
raise IncorrectNumberOfHandlerArgsException()
|
||||
else:
|
||||
self._repeated_input_flags_handler = handler
|
||||
|
||||
|
||||
def set_unknown_command_handler(self, handler: Callable[[str], None]) -> None:
|
||||
args = getfullargspec(handler).args
|
||||
if len(args) != 1:
|
||||
raise IncorrectNumberOfHandlerArgsException()
|
||||
else:
|
||||
self._unknown_command_handler = handler
|
||||
|
||||
|
||||
def set_empty_command_handler(self, handler: Callable[[str], None]) -> None:
|
||||
args = getfullargspec(handler).args
|
||||
if len(args) != 1:
|
||||
raise IncorrectNumberOfHandlerArgsException()
|
||||
else:
|
||||
self._empty_input_command_handler = handler
|
||||
|
||||
|
||||
def include_router(self, router: Router) -> None:
|
||||
if not isinstance(router, Router):
|
||||
raise InvalidRouterInstanceException()
|
||||
|
||||
router.set_ignore_command_register(self.ignore_command_register)
|
||||
self._routers.append(router)
|
||||
|
||||
command_entities: list[dict[str, Callable[[], None] | Command]] = router.get_command_entities()
|
||||
self._registered_router_entities.append({'name': router.get_name(),
|
||||
'title': router.get_title(),
|
||||
'entity': router,
|
||||
'commands': command_entities})
|
||||
|
||||
|
||||
def _validate_number_of_routers(self) -> None:
|
||||
if not self._routers:
|
||||
raise NoRegisteredRoutersException()
|
||||
|
||||
|
||||
def _validate_included_routers(self) -> None:
|
||||
for router in self._routers:
|
||||
if not router.get_command_entities():
|
||||
raise NoRegisteredHandlersException(router.get_name())
|
||||
|
||||
|
||||
def _validate_all_router_commands(self) -> None:
|
||||
for idx in range(len(self._registered_router_entities)):
|
||||
current_router: Router = self._registered_router_entities[idx]['entity']
|
||||
routers_without_current_router = self._registered_router_entities.copy()
|
||||
routers_without_current_router.pop(idx)
|
||||
|
||||
current_router_all_commands: list[str] = current_router.get_all_commands()
|
||||
|
||||
for router_entity in routers_without_current_router:
|
||||
if len(set(current_router_all_commands).intersection(set(router_entity['entity'].get_all_commands()))) > 0:
|
||||
raise RepeatedCommandInDifferentRoutersException()
|
||||
if self.ignore_command_register:
|
||||
if len(set([x.lower() for x in current_router_all_commands]).intersection(set([x.lower() for x in router_entity['entity'].get_all_commands()]))) > 0:
|
||||
raise RepeatedCommandInDifferentRoutersException()
|
||||
|
||||
|
||||
def _check_command_for_exit_command(self, command: str):
|
||||
if command.lower() == self.exit_command.lower():
|
||||
if self.ignore_exit_command_register:
|
||||
self.print_func(self.farewell_message)
|
||||
exit(0)
|
||||
else:
|
||||
if command == self.exit_command:
|
||||
self.print_func(self.farewell_message)
|
||||
exit(0)
|
||||
|
||||
|
||||
def _check_is_command_unknown(self, command: Command):
|
||||
registered_router_entities: list[dict[str, str | list[dict[str, Callable[[], None] | Command]] | Router]] = self._registered_router_entities
|
||||
for router_entity in registered_router_entities:
|
||||
for command_entity in router_entity['commands']:
|
||||
if command_entity['command'].get_trigger().lower() == command.get_trigger().lower():
|
||||
if self.ignore_command_register:
|
||||
return False
|
||||
else:
|
||||
if command_entity['command'].get_trigger() == command.get_trigger():
|
||||
return False
|
||||
self._unknown_command_handler(command)
|
||||
return True
|
||||
|
||||
|
||||
def _print_command_group_description(self):
|
||||
for router_entity in self._registered_router_entities:
|
||||
self.print_func(router_entity['title'])
|
||||
for command_entity in router_entity['commands']:
|
||||
self.print_func(self._description_message_pattern.format(
|
||||
command=command_entity['command'].get_trigger(),
|
||||
description=command_entity['command'].get_description()
|
||||
)
|
||||
)
|
||||
self.print_func(self.command_group_description_separate)
|
||||
|
||||
self.print_func(self.system_points_title)
|
||||
self.print_func(self._description_message_pattern.format(
|
||||
command=self.exit_command,
|
||||
description=self.exit_command_description
|
||||
)
|
||||
)
|
||||
self.print_func(self.command_group_description_separate)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
class InvalidRouterInstanceException(Exception):
|
||||
def __str__(self):
|
||||
return "Invalid Router Instance"
|
||||
|
||||
|
||||
class InvalidDescriptionMessagePatternException(Exception):
|
||||
def __init__(self, pattern: str):
|
||||
self.pattern = pattern
|
||||
def __str__(self):
|
||||
return ("Invalid Description Message Pattern\n"
|
||||
"Correct pattern example: [{command}] *=*=* {description}\n"
|
||||
"The pattern must contain two variables: `command` and `description` - description of the command\n"
|
||||
f"Your pattern: {self.pattern}")
|
||||
|
||||
|
||||
class NoRegisteredRoutersException(Exception):
|
||||
def __str__(self):
|
||||
return "No Registered Router Found"
|
||||
|
||||
|
||||
class NoRegisteredHandlersException(Exception):
|
||||
def __init__(self, router_name):
|
||||
self.router_name = router_name
|
||||
def __str__(self):
|
||||
return f"No Registered Handlers Found For '{self.router_name}'"
|
||||
|
||||
|
||||
class RepeatedCommandInDifferentRoutersException(Exception):
|
||||
def __str__(self):
|
||||
return "Commands in different handlers cannot be repeated"
|
||||
|
||||
|
||||
class IncorrectNumberOfHandlerArgsException(Exception):
|
||||
def __str__(self):
|
||||
return "Incorrect Input Flags Handler has incorrect number of arguments"
|
||||
@@ -1 +0,0 @@
|
||||
from .entity import Command
|
||||
@@ -1,108 +0,0 @@
|
||||
from .params.flag.entity import Flag
|
||||
from .params.flag.flags_group.entity import FlagsGroup
|
||||
from .exceptions import (UnprocessedInputFlagException,
|
||||
RepeatedInputFlagsException,
|
||||
EmptyInputCommandException)
|
||||
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class Command(Generic[T]):
|
||||
def __init__(self, trigger: str,
|
||||
description: str = None,
|
||||
flags: Flag | FlagsGroup = None):
|
||||
self._trigger = trigger
|
||||
self._description = f'description for "{self._trigger}" command' if not description else description
|
||||
self._registered_flags: FlagsGroup | None = flags if isinstance(flags, FlagsGroup) else FlagsGroup([flags]) if isinstance(flags, Flag) else flags
|
||||
|
||||
self._input_flags: FlagsGroup | None = None
|
||||
|
||||
|
||||
def get_trigger(self) -> str:
|
||||
return self._trigger
|
||||
|
||||
|
||||
def get_description(self) -> str:
|
||||
return self._description
|
||||
|
||||
|
||||
def get_registered_flags(self) -> FlagsGroup | None:
|
||||
return self._registered_flags
|
||||
|
||||
|
||||
def validate_input_flag(self, flag: Flag):
|
||||
registered_flags: FlagsGroup | None = self.get_registered_flags()
|
||||
if registered_flags:
|
||||
if isinstance(registered_flags, Flag):
|
||||
if registered_flags.get_string_entity() == flag.get_string_entity():
|
||||
is_valid = registered_flags.validate_input_flag_value(flag.get_value())
|
||||
if is_valid:
|
||||
return True
|
||||
else:
|
||||
for registered_flag in registered_flags:
|
||||
if registered_flag.get_string_entity() == flag.get_string_entity():
|
||||
is_valid = registered_flag.validate_input_flag_value(flag.get_value())
|
||||
if is_valid:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _set_input_flags(self, input_flags: FlagsGroup):
|
||||
self._input_flags = input_flags
|
||||
|
||||
def get_input_flags(self) -> FlagsGroup:
|
||||
return self._input_flags
|
||||
|
||||
@staticmethod
|
||||
def parse_input_command(raw_command: str) -> 'Command[T]':
|
||||
if not raw_command:
|
||||
raise EmptyInputCommandException()
|
||||
list_of_tokens = raw_command.split()
|
||||
command = list_of_tokens[0]
|
||||
list_of_tokens.pop(0)
|
||||
|
||||
flags: FlagsGroup = FlagsGroup()
|
||||
current_flag_name = None
|
||||
current_flag_value = None
|
||||
for _ in list_of_tokens:
|
||||
if _.startswith('-'):
|
||||
flag_prefix_last_symbol_index = _.rfind('-')
|
||||
if current_flag_name or len(_) < 2 or len(_[:flag_prefix_last_symbol_index]) > 3:
|
||||
raise UnprocessedInputFlagException()
|
||||
else:
|
||||
current_flag_name = _
|
||||
else:
|
||||
if not current_flag_name:
|
||||
raise UnprocessedInputFlagException()
|
||||
else:
|
||||
current_flag_value = _
|
||||
if current_flag_name and current_flag_value:
|
||||
flag_prefix_last_symbol_index = current_flag_name.rfind('-')
|
||||
flag_prefix = current_flag_name[:flag_prefix_last_symbol_index+1]
|
||||
flag_name = current_flag_name[flag_prefix_last_symbol_index+1:]
|
||||
input_flag = Flag(flag_name=flag_name,
|
||||
flag_prefix=flag_prefix)
|
||||
input_flag.set_value(current_flag_value)
|
||||
|
||||
all_flags = [x.get_string_entity() for x in flags.get_flags()]
|
||||
if input_flag.get_string_entity() not in all_flags:
|
||||
flags.add_flag(input_flag)
|
||||
else:
|
||||
raise RepeatedInputFlagsException(input_flag)
|
||||
|
||||
current_flag_name = None
|
||||
current_flag_value = None
|
||||
if any([current_flag_name, current_flag_value]):
|
||||
raise UnprocessedInputFlagException()
|
||||
if len(flags.get_flags()) == 0:
|
||||
return Command(trigger=command)
|
||||
else:
|
||||
input_command = Command(trigger=command)
|
||||
input_command._set_input_flags(flags)
|
||||
return input_command
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
from .params.flag.entity import Flag
|
||||
|
||||
|
||||
class UnprocessedInputFlagException(Exception):
|
||||
def __str__(self):
|
||||
return "Unprocessed Input Flags"
|
||||
|
||||
|
||||
class RepeatedInputFlagsException(Exception):
|
||||
def __init__(self, flag: Flag):
|
||||
self.flag = flag
|
||||
def __str__(self):
|
||||
return ("Repeated Input Flags\n"
|
||||
f"Duplicate flag was detected in the input: '{self.flag.get_string_entity()}'")
|
||||
|
||||
|
||||
class EmptyInputCommandException(Exception):
|
||||
def __str__(self):
|
||||
return "Input Command is empty"
|
||||
@@ -1,2 +0,0 @@
|
||||
from .entity import Flag
|
||||
from .flags_group.entity import FlagsGroup
|
||||
@@ -1,51 +0,0 @@
|
||||
from typing import Literal, Pattern
|
||||
|
||||
|
||||
class Flag:
|
||||
def __init__(self, flag_name: str,
|
||||
flag_prefix: Literal['-', '--', '---'] = '--',
|
||||
ignore_flag_value_register: bool = False,
|
||||
possible_flag_values: list[str] | Pattern[str] = False):
|
||||
self._flag_name = flag_name
|
||||
self._flag_prefix = flag_prefix
|
||||
self.possible_flag_values = possible_flag_values
|
||||
self.ignore_flag_value_register = ignore_flag_value_register
|
||||
|
||||
self._flag_value = None
|
||||
|
||||
def get_string_entity(self):
|
||||
string_entity: str = self._flag_prefix + self._flag_name
|
||||
return string_entity
|
||||
|
||||
def get_flag_name(self):
|
||||
return self._flag_name
|
||||
|
||||
def get_flag_prefix(self):
|
||||
return self._flag_prefix
|
||||
|
||||
def get_value(self):
|
||||
return self._flag_value
|
||||
|
||||
def set_value(self, value):
|
||||
self._flag_value = value
|
||||
|
||||
def validate_input_flag_value(self, input_flag_value: str):
|
||||
if isinstance(self.possible_flag_values, Pattern):
|
||||
is_valid = bool(self.possible_flag_values.match(input_flag_value))
|
||||
if bool(is_valid):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if isinstance(self.possible_flag_values, list):
|
||||
if self.ignore_flag_value_register:
|
||||
if input_flag_value.lower() in [x.lower() for x in self.possible_flag_values]:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
if input_flag_value in self.possible_flag_values:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
@@ -1,35 +0,0 @@
|
||||
from argenta.command.params.flag.entity import Flag
|
||||
|
||||
|
||||
class FlagsGroup:
|
||||
def __init__(self, flags: list[Flag] = None):
|
||||
self._flags: list[Flag] = [] if not flags else flags
|
||||
|
||||
def get_flags(self) -> list[Flag]:
|
||||
return self._flags
|
||||
|
||||
def add_flag(self, flag: Flag):
|
||||
self._flags.append(flag)
|
||||
|
||||
def add_flags(self, flags: list[Flag]):
|
||||
self._flags.extend(flags)
|
||||
|
||||
def unparse_to_dict(self):
|
||||
result_dict: dict[str, dict] = {}
|
||||
for flag in self._flags:
|
||||
result_dict[flag.get_flag_name()] = {
|
||||
'name': flag.get_flag_name(),
|
||||
'string_entity': flag.get_string_entity(),
|
||||
'prefix': flag.get_flag_prefix(),
|
||||
'value': flag.get_value()
|
||||
}
|
||||
return result_dict
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._flags)
|
||||
|
||||
def __next__(self):
|
||||
return next(iter(self))
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self._flags[item]
|
||||
@@ -1 +0,0 @@
|
||||
from .entity import Router
|
||||
@@ -1,124 +0,0 @@
|
||||
from typing import Callable, Any
|
||||
from inspect import getfullargspec
|
||||
|
||||
from ..command.entity import Command
|
||||
from ..command.params.flag.entity import Flag
|
||||
from ..command.params.flag.flags_group.entity import FlagsGroup
|
||||
from ..router.exceptions import (RepeatedCommandException,
|
||||
RepeatedFlagNameException,
|
||||
TooManyTransferredArgsException,
|
||||
RequiredArgumentNotPassedException,
|
||||
IncorrectNumberOfHandlerArgsException,
|
||||
TriggerCannotContainSpacesException)
|
||||
|
||||
|
||||
class Router:
|
||||
def __init__(self,
|
||||
title: str = 'Commands group title:',
|
||||
name: str = 'Default'):
|
||||
|
||||
self._title = title
|
||||
self._name = name
|
||||
|
||||
self._command_entities: list[dict[str, Callable[[], None] | Command]] = []
|
||||
self._ignore_command_register: bool = False
|
||||
|
||||
self._not_valid_flag_handler: Callable[[Flag], None] = lambda flag: print(f"Undefined or incorrect input flag: '{flag.get_string_entity()} {flag.get_value()}'")
|
||||
|
||||
|
||||
def command(self, command: Command) -> Callable[[Any], Any]:
|
||||
self._validate_command(command)
|
||||
|
||||
def command_decorator(func):
|
||||
Router._validate_func_args(command, func)
|
||||
self._command_entities.append({'handler_func': func,
|
||||
'command': command})
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
return command_decorator
|
||||
|
||||
def set_invalid_input_flag_handler(self, func):
|
||||
processed_args = getfullargspec(func).args
|
||||
if len(processed_args) != 1:
|
||||
raise IncorrectNumberOfHandlerArgsException()
|
||||
else:
|
||||
self._not_valid_flag_handler = func
|
||||
|
||||
|
||||
def input_command_handler(self, input_command: Command):
|
||||
input_command_name: str = input_command.get_trigger()
|
||||
input_command_flags: FlagsGroup = input_command.get_input_flags()
|
||||
for command_entity in self._command_entities:
|
||||
if input_command_name.lower() == command_entity['command'].get_trigger().lower():
|
||||
if command_entity['command'].get_registered_flags():
|
||||
if input_command_flags:
|
||||
for flag in input_command_flags:
|
||||
is_valid = command_entity['command'].validate_input_flag(flag)
|
||||
if not is_valid:
|
||||
self._not_valid_flag_handler(flag)
|
||||
return
|
||||
return command_entity['handler_func'](input_command_flags.unparse_to_dict())
|
||||
else:
|
||||
return command_entity['handler_func']({})
|
||||
else:
|
||||
if input_command_flags:
|
||||
self._not_valid_flag_handler(input_command_flags[0])
|
||||
return
|
||||
else:
|
||||
return command_entity['handler_func']()
|
||||
|
||||
|
||||
def _validate_command(self, command: Command):
|
||||
command_name: str = command.get_trigger()
|
||||
if command_name.find(' ') != -1:
|
||||
raise TriggerCannotContainSpacesException()
|
||||
if command_name in self.get_all_commands():
|
||||
raise RepeatedCommandException()
|
||||
if self._ignore_command_register:
|
||||
if command_name.lower() in [x.lower() for x in self.get_all_commands()]:
|
||||
raise RepeatedCommandException()
|
||||
|
||||
flags: FlagsGroup = command.get_registered_flags()
|
||||
if flags:
|
||||
flags_name: list = [x.get_string_entity().lower() for x in flags]
|
||||
if len(set(flags_name)) < len(flags_name):
|
||||
raise RepeatedFlagNameException()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _validate_func_args(command: Command, func: Callable):
|
||||
registered_args = command.get_registered_flags()
|
||||
transferred_args = getfullargspec(func).args
|
||||
if registered_args and transferred_args:
|
||||
if len(transferred_args) != 1:
|
||||
raise TooManyTransferredArgsException()
|
||||
elif registered_args and not transferred_args:
|
||||
raise RequiredArgumentNotPassedException()
|
||||
elif not registered_args and transferred_args:
|
||||
raise TooManyTransferredArgsException()
|
||||
|
||||
|
||||
def set_ignore_command_register(self, ignore_command_register: bool):
|
||||
self._ignore_command_register = ignore_command_register
|
||||
|
||||
|
||||
def get_command_entities(self) -> list[dict[str, Callable[[], None] | Command]]:
|
||||
return self._command_entities
|
||||
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self._name
|
||||
|
||||
|
||||
def get_title(self) -> str:
|
||||
return self._title
|
||||
|
||||
|
||||
def get_all_commands(self) -> list[str]:
|
||||
all_commands: list[str] = []
|
||||
for command_entity in self._command_entities:
|
||||
all_commands.append(command_entity['command'].get_trigger())
|
||||
|
||||
return all_commands
|
||||
@@ -1,28 +0,0 @@
|
||||
class RepeatedCommandException(Exception):
|
||||
def __str__(self):
|
||||
return "Commands in handler cannot be repeated"
|
||||
|
||||
|
||||
class RepeatedFlagNameException(Exception):
|
||||
def __str__(self):
|
||||
return "Repeated flag name in register command"
|
||||
|
||||
|
||||
class TooManyTransferredArgsException(Exception):
|
||||
def __str__(self):
|
||||
return "Too many transferred arguments"
|
||||
|
||||
|
||||
class RequiredArgumentNotPassedException(Exception):
|
||||
def __str__(self):
|
||||
return "Required argument not passed"
|
||||
|
||||
|
||||
class IncorrectNumberOfHandlerArgsException(Exception):
|
||||
def __str__(self):
|
||||
return "Handler has incorrect number of arguments"
|
||||
|
||||
|
||||
class TriggerCannotContainSpacesException(Exception):
|
||||
def __str__(self):
|
||||
return "Command trigger cannot contain spaces"
|
||||
@@ -0,0 +1,3 @@
|
||||
_build/
|
||||
_static/
|
||||
*.mo
|
||||
@@ -0,0 +1,35 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
LANGUAGES = en ru
|
||||
|
||||
build-all:
|
||||
sphinx-build -b html -D language=ru $(SOURCEDIR) $(BUILDDIR)/html/ru
|
||||
sphinx-build -b html -D language=en $(SOURCEDIR) $(BUILDDIR)/html/en
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
live-ru:
|
||||
sphinx-autobuild -b html . _build/html/ru -D language=ru
|
||||
|
||||
live-en:
|
||||
sphinx-autobuild -b html . _build/html/en -D language=en
|
||||
|
||||
update-langs:
|
||||
sphinx-build -b gettext . _build/gettext
|
||||
sphinx-intl update -p _build/gettext -l en
|
||||
|
||||
.PHONY: help Makefile serve-all
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,17 @@
|
||||
from argenta import App, Orchestrator
|
||||
from argenta.orchestrator.argparser import ArgParser, BooleanArgument
|
||||
|
||||
arg_parser = ArgParser(
|
||||
processed_args=[
|
||||
BooleanArgument("dev")
|
||||
]
|
||||
)
|
||||
orchestrator = Orchestrator(
|
||||
arg_parser=arg_parser,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if arg_parser.parsed_argspace.get_by_name("dev"):
|
||||
orchestrator.start_polling(App(initial_message="ArgentaDev"))
|
||||
else:
|
||||
orchestrator.start_polling(App())
|
||||
@@ -0,0 +1,29 @@
|
||||
from argenta import App, Orchestrator
|
||||
from argenta.orchestrator.argparser import ArgParser, ValueArgument
|
||||
|
||||
arguments = [
|
||||
ValueArgument("host", help="Server host", is_required=True),
|
||||
ValueArgument("port", help="Server port", is_required=True),
|
||||
]
|
||||
|
||||
argparser = ArgParser(processed_args=arguments, name="WebServer", description="Simple web server")
|
||||
|
||||
app = App()
|
||||
orchestrator = Orchestrator(argparser)
|
||||
|
||||
|
||||
def main():
|
||||
argspace = argparser.parsed_argspace
|
||||
|
||||
host = argspace.get_by_name("host")
|
||||
port = argspace.get_by_name("port")
|
||||
|
||||
print("Server configuration:")
|
||||
print(f" Host: {host.value}")
|
||||
print(f" Port: {port.value}")
|
||||
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,10 @@
|
||||
from argenta import Response, Router
|
||||
from argenta.di import FromDishka
|
||||
from argenta.orchestrator.argparser import ArgSpace
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.command("get_args")
|
||||
async def get_args(response: Response, argspace: FromDishka[ArgSpace]):
|
||||
print(argspace.all_arguments)
|
||||
@@ -0,0 +1,21 @@
|
||||
from argenta import Response, Router
|
||||
from argenta.di import FromDishka
|
||||
from argenta.orchestrator.argparser import ArgSpace, BooleanArgument, ValueArgument
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.command("get_args")
|
||||
def get_args(response: Response, argspace: FromDishka[ArgSpace]):
|
||||
# Get all boolean flags
|
||||
boolean_flags = argspace.get_by_type(BooleanArgument)
|
||||
print(f"Active flags: {[arg.name for arg in boolean_flags if arg.value]}")
|
||||
|
||||
# Get all value arguments
|
||||
value_args = argspace.get_by_type(ValueArgument)
|
||||
for arg in value_args:
|
||||
print(f"{arg.name} = {arg.value}")
|
||||
|
||||
# Count arguments of each type
|
||||
print(f"Boolean arguments: {len(argspace.get_by_type(BooleanArgument))}")
|
||||
print(f"Value arguments: {len(argspace.get_by_type(ValueArgument))}")
|
||||
@@ -0,0 +1,20 @@
|
||||
from argenta import Response, Router
|
||||
from argenta.di import FromDishka
|
||||
from argenta.orchestrator.argparser import ArgSpace
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.command("get_args")
|
||||
def get_args(response: Response, argspace: FromDishka[ArgSpace]):
|
||||
config_arg = argspace.get_by_name("config")
|
||||
if config_arg:
|
||||
print(f"Config path: {config_arg.value}")
|
||||
|
||||
verbose_arg = argspace.get_by_name("verbose")
|
||||
if verbose_arg and verbose_arg.value:
|
||||
print("Verbose mode enabled")
|
||||
|
||||
unknown_arg = argspace.get_by_name("nonexistent")
|
||||
if unknown_arg is None:
|
||||
print("Argument not found")
|
||||
@@ -0,0 +1,20 @@
|
||||
from argenta.orchestrator.argparser import ArgParser, ValueArgument
|
||||
|
||||
# Create arguments
|
||||
config_arg = ValueArgument("config", help="Path to configuration file", default="config.yaml")
|
||||
|
||||
log_level_arg = ValueArgument(
|
||||
"log-level",
|
||||
help="Logging level",
|
||||
possible_values=["DEBUG", "INFO", "WARNING", "ERROR"],
|
||||
default="INFO",
|
||||
)
|
||||
|
||||
host_arg = ValueArgument("host", help="Server host address", is_required=True)
|
||||
|
||||
# Register in ArgParser
|
||||
parser = ArgParser(
|
||||
processed_args=[config_arg, log_level_arg, host_arg],
|
||||
name="MyApp",
|
||||
description="My application with CLI arguments",
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
from argenta.orchestrator.argparser import ArgParser, BooleanArgument
|
||||
|
||||
# Create boolean arguments
|
||||
verbose_arg = BooleanArgument("verbose", help="Enable verbose output")
|
||||
debug_arg = BooleanArgument("debug", help="Enable debug mode")
|
||||
no_cache_arg = BooleanArgument("no-cache", help="Disable caching")
|
||||
|
||||
# Register in ArgParser
|
||||
parser = ArgParser(processed_args=[verbose_arg, debug_arg, no_cache_arg], name="MyApp")
|
||||
@@ -0,0 +1,10 @@
|
||||
from argenta import App
|
||||
from argenta.app import AutoCompleter
|
||||
|
||||
# Setting up autocompletion with saving history to a file
|
||||
my_autocompleter = AutoCompleter(history_filename="argenta_history.txt")
|
||||
|
||||
# Passing the configured autocompleter to the application
|
||||
app = App(autocompleter=my_autocompleter)
|
||||
|
||||
# ... the rest of the application logic
|
||||
@@ -0,0 +1,20 @@
|
||||
from argenta.command import Flag, Flags, Command
|
||||
|
||||
# Simple command without flags
|
||||
hello_cmd = Command("hello", description="Greet the user")
|
||||
|
||||
# Command with description and aliases
|
||||
quit_cmd = Command("quit", description="Exit the application", aliases=["exit", "q"])
|
||||
|
||||
# Command with flags
|
||||
deploy_cmd = Command(
|
||||
"deploy",
|
||||
description="Deploy application to server",
|
||||
flags=Flags(
|
||||
[
|
||||
Flag("env", possible_values=["dev", "prod"]),
|
||||
Flag("force"),
|
||||
]
|
||||
),
|
||||
aliases=["dep"],
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
from argenta import Command, Response, Router
|
||||
|
||||
router = Router(title="User Management")
|
||||
|
||||
|
||||
@router.command(Command("create-user", description="Create a new user account"))
|
||||
def handle_create_user(response: Response):
|
||||
print("Creating new user...")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"delete-user",
|
||||
description="Delete existing user account",
|
||||
aliases=["remove-user", "rm-user"],
|
||||
)
|
||||
)
|
||||
def handle_delete_user(response: Response):
|
||||
print("Deleting user...")
|
||||
@@ -0,0 +1,33 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
|
||||
router = Router(title="Server Management")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"start",
|
||||
description="Start the server",
|
||||
flags=Flags(
|
||||
[
|
||||
Flag("port"),
|
||||
Flag("host"),
|
||||
Flag("debug"),
|
||||
]
|
||||
),
|
||||
aliases=["run"],
|
||||
)
|
||||
)
|
||||
def handle_start(response: Response):
|
||||
input_flags = response.input_flags
|
||||
port_flag = input_flags.get_flag_by_name("port")
|
||||
host_flag = input_flags.get_flag_by_name("host")
|
||||
debug_flag = input_flags.get_flag_by_name("debug")
|
||||
|
||||
host = host_flag.input_value if host_flag else "localhost"
|
||||
port = port_flag.input_value if port_flag else "8080"
|
||||
debug = debug_flag and debug_flag.input_value
|
||||
|
||||
print(f"Starting server on {host}:{port}")
|
||||
if debug:
|
||||
print("Debug mode: ON")
|
||||
@@ -0,0 +1,11 @@
|
||||
from argenta.command import InputCommand
|
||||
|
||||
# Parse command without flags
|
||||
cmd1 = InputCommand.parse("hello")
|
||||
print(cmd1.trigger) # "hello"
|
||||
print(len(cmd1.input_flags)) # 0
|
||||
|
||||
# Parse command with flags
|
||||
cmd2 = InputCommand.parse("deploy --env prod --force")
|
||||
print(cmd2.trigger) # "deploy"
|
||||
print(len(cmd2.input_flags)) # 2
|
||||
@@ -0,0 +1,14 @@
|
||||
from argenta import Router, Command, Response
|
||||
|
||||
router = Router(title="System")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"shutdown",
|
||||
description="Shutdown the system",
|
||||
aliases=["poweroff", "halt", "stop"]
|
||||
)
|
||||
)
|
||||
def handle_shutdown(response: Response):
|
||||
print("Shutting down the system...")
|
||||
@@ -0,0 +1,9 @@
|
||||
from sqlite3 import Connection
|
||||
from argenta import Response, Router
|
||||
from argenta.di import FromDishka
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.command("connect")
|
||||
def connect_handler(response: Response, connection: FromDishka[Connection]):
|
||||
connection.execute("...")
|
||||
@@ -0,0 +1,13 @@
|
||||
import sqlite3
|
||||
from sqlite3 import Connection
|
||||
from typing import Iterable
|
||||
|
||||
from dishka import Provider, Scope, provide
|
||||
|
||||
|
||||
class ConnectionProvider(Provider):
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def new_connection(self) -> Iterable[Connection]:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
yield conn
|
||||
conn.close()
|
||||
@@ -0,0 +1,3 @@
|
||||
from argenta import Orchestrator
|
||||
|
||||
orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
|
||||
@@ -0,0 +1,9 @@
|
||||
from argenta import Response, Router
|
||||
from argenta.di import FromDishka
|
||||
from argenta.orchestrator.argparser import ArgSpace
|
||||
|
||||
router = Router()
|
||||
|
||||
@router.command("info")
|
||||
def connect_handler(response: Response, argspace: FromDishka[ArgSpace]):
|
||||
print(argspace.get_by_name("type"))
|
||||
@@ -0,0 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
def empty_command_handler():
|
||||
print("Empty command handler called")
|
||||
|
||||
app: App = App()
|
||||
app.set_empty_command_handler(empty_command_handler)
|
||||
@@ -0,0 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
def incorrect_input_syntax_handler(raw_command: str):
|
||||
print(f"Incorrect input syntax for command: {raw_command}")
|
||||
|
||||
app: App = App()
|
||||
app.set_incorrect_input_syntax_handler(incorrect_input_syntax_handler)
|
||||
@@ -0,0 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
def repeated_input_flags_handler(raw_command: str):
|
||||
print(f"Repeated input flags: {raw_command}")
|
||||
|
||||
app: App = App()
|
||||
app.set_repeated_input_flags_handler(repeated_input_flags_handler)
|
||||
@@ -0,0 +1,7 @@
|
||||
from argenta import App
|
||||
|
||||
def empty_command_handler():
|
||||
print("Empty input command")
|
||||
|
||||
app: App = App()
|
||||
app.set_empty_command_handler(empty_command_handler)
|
||||
@@ -0,0 +1,8 @@
|
||||
from argenta import App
|
||||
from argenta.command import InputCommand
|
||||
|
||||
def unknown_command_handler(command: InputCommand):
|
||||
print(f"Unknown input command with trigger: {command.trigger}")
|
||||
|
||||
app: App = App()
|
||||
app.set_unknown_command_handler(unknown_command_handler)
|
||||
@@ -0,0 +1,7 @@
|
||||
from argenta import App, Response
|
||||
|
||||
def exit_command_handler(response: Response):
|
||||
print("Exit command handler")
|
||||
|
||||
app: App = App()
|
||||
app.set_exit_command_handler(exit_command_handler)
|
||||
@@ -0,0 +1,36 @@
|
||||
from argenta.command import Flags, PredefinedFlags
|
||||
|
||||
# Using predefined flags when creating a command
|
||||
command_flags = Flags(
|
||||
[
|
||||
PredefinedFlags.HELP,
|
||||
PredefinedFlags.SHORT_HELP,
|
||||
PredefinedFlags.INFO,
|
||||
]
|
||||
)
|
||||
|
||||
# Using Network Flags
|
||||
network_flags = Flags(
|
||||
[
|
||||
PredefinedFlags.HOST,
|
||||
PredefinedFlags.PORT,
|
||||
]
|
||||
)
|
||||
|
||||
# Validating the values of predefined flags
|
||||
print(PredefinedFlags.HOST.validate_input_flag_value("192.168.1.1")) # True
|
||||
print(PredefinedFlags.HOST.validate_input_flag_value("invalid")) # False
|
||||
|
||||
print(PredefinedFlags.PORT.validate_input_flag_value("8080")) # True
|
||||
print(PredefinedFlags.PORT.validate_input_flag_value("99999")) # True
|
||||
print(PredefinedFlags.PORT.validate_input_flag_value("abc")) # False
|
||||
|
||||
# Flags without values
|
||||
print(PredefinedFlags.HELP.validate_input_flag_value(None)) # True
|
||||
print(PredefinedFlags.HELP.validate_input_flag_value("something")) # False
|
||||
|
||||
# Checking string representations
|
||||
print(PredefinedFlags.HELP.string_entity) # --help
|
||||
print(PredefinedFlags.SHORT_HELP.string_entity) # -H
|
||||
print(PredefinedFlags.HOST.string_entity) # --host
|
||||
print(PredefinedFlags.SHORT_PORT.string_entity) # -P
|
||||
@@ -0,0 +1,20 @@
|
||||
import re
|
||||
from argenta.command import Flag, PossibleValues
|
||||
|
||||
# Simple flag with any values
|
||||
verbose_flag = Flag(name="verbose")
|
||||
|
||||
# Flag with short prefix
|
||||
short_flag = Flag(name="v", prefix="-")
|
||||
|
||||
# Flag that does not take a value
|
||||
help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER)
|
||||
|
||||
# Flag with list of possible values
|
||||
format_flag = Flag(name="format", possible_values=["json", "xml", "csv"])
|
||||
|
||||
# Flag with regexp for validation input value
|
||||
email_flag = Flag(
|
||||
name="email",
|
||||
possible_values=re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"),
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
import re
|
||||
|
||||
from argenta.command.flag.models import Flag, PossibleValues
|
||||
|
||||
# Flag with list of allowed values
|
||||
format_flag = Flag(name="format", possible_values=["json", "xml", "csv"])
|
||||
|
||||
# Value validation
|
||||
print(format_flag.validate_input_flag_value("json")) # True
|
||||
print(format_flag.validate_input_flag_value("pdf")) # False
|
||||
|
||||
# Flag without value
|
||||
help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER)
|
||||
print(help_flag.validate_input_flag_value(None)) # True
|
||||
print(help_flag.validate_input_flag_value("value")) # False
|
||||
|
||||
# Flag with regular expression
|
||||
port_flag = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
|
||||
print(port_flag.validate_input_flag_value("8080")) # True
|
||||
print(port_flag.validate_input_flag_value("abc")) # False
|
||||
@@ -0,0 +1,9 @@
|
||||
from argenta.command import Flag
|
||||
|
||||
help_flag = Flag(name="help")
|
||||
version_flag = Flag(name="V", prefix="-")
|
||||
|
||||
print(help_flag) # --help
|
||||
|
||||
message = f"Use {help_flag} to see help"
|
||||
print(message) # Use --help to see help
|
||||
@@ -0,0 +1,12 @@
|
||||
from argenta.command import Flag
|
||||
|
||||
verbose_flag = Flag(name="verbose", prefix="--")
|
||||
short_flag = Flag(name="v", prefix="-")
|
||||
|
||||
# Debug presentation
|
||||
print(repr(verbose_flag)) # Flag<prefix=--, name=verbose>
|
||||
print(repr(short_flag)) # Flag<prefix=-, name=v>
|
||||
|
||||
# In an interactive console or debugger
|
||||
# >>> verbose_flag
|
||||
# Flag<prefix=--, name=verbose>
|
||||
@@ -0,0 +1,21 @@
|
||||
from argenta.command import Flag, PossibleValues
|
||||
|
||||
# Creating two flags with the same name and prefix
|
||||
flag1 = Flag(name="verbose", prefix="--")
|
||||
flag2 = Flag(name="verbose", prefix="--")
|
||||
|
||||
# Flag comparison
|
||||
print(flag1 == flag2) # True
|
||||
|
||||
# Flags with different prefixes are not equal
|
||||
flag3 = Flag(name="verbose", prefix="-")
|
||||
print(flag1 == flag3) # False
|
||||
|
||||
# Flags with different names are not equal
|
||||
flag4 = Flag(name="help", prefix="--")
|
||||
print(flag1 == flag4) # False
|
||||
|
||||
# Different possible_values do not affect equality
|
||||
flag5 = Flag(name="verbose", prefix="--", possible_values=PossibleValues.NEITHER)
|
||||
flag6 = Flag(name="verbose", prefix="--", possible_values=["value1", "value2"])
|
||||
print(flag5 == flag6)
|
||||
@@ -0,0 +1,21 @@
|
||||
from argenta import Router, Response
|
||||
from argenta.command import Command, Flag, PossibleValues
|
||||
from argenta.command.flag import ValidationStatus
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.command(Command("deploy", flags=Flag("verbose", possible_values=PossibleValues.NEITHER)))
|
||||
def deploy_handler(response: Response):
|
||||
# Check for toggle flag presence
|
||||
verbose_flag = response.input_flags.get_flag_by_name("verbose")
|
||||
|
||||
if verbose_flag and verbose_flag.status == ValidationStatus.VALID:
|
||||
print("Deploying with verbose output...")
|
||||
# Detailed logic
|
||||
elif verbose_flag and verbose_flag.status == ValidationStatus.INVALID:
|
||||
print("Incorrect flag value")
|
||||
return
|
||||
else:
|
||||
print("Deploying...")
|
||||
# Normal logic
|
||||
@@ -0,0 +1,16 @@
|
||||
from argenta import Router, Response
|
||||
from argenta.command import Command, Flag
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.command(Command("greet", flags=Flag("name")))
|
||||
def greet_handler(response: Response):
|
||||
# Get flag by name
|
||||
name_flag = response.input_flags.get_flag_by_name("name")
|
||||
|
||||
# Check if flag was passed
|
||||
if name_flag:
|
||||
print(f"Hello, {name_flag.input_value}!")
|
||||
else:
|
||||
print("Hello, stranger!")
|
||||
@@ -0,0 +1,11 @@
|
||||
import re
|
||||
from argenta.command import Command, Flag, Flags
|
||||
|
||||
flags = Flags(
|
||||
[
|
||||
Flag("host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")),
|
||||
Flag("port", possible_values=re.compile(r"^\d{1,5}$")),
|
||||
]
|
||||
)
|
||||
|
||||
cmd = Command("start", description="Start the server", flags=flags)
|
||||
@@ -0,0 +1,9 @@
|
||||
from argenta.command import Flag, Flags
|
||||
|
||||
flags: Flags = Flags()
|
||||
|
||||
flags.add_flag(Flag("config"))
|
||||
flags.add_flag(Flag("debug"))
|
||||
flags.add_flag(Flag("log-level", possible_values=["INFO", "DEBUG", "ERROR"]))
|
||||
|
||||
print(len(flags)) # 3
|
||||
@@ -0,0 +1,15 @@
|
||||
from argenta.command import Flag, Flags
|
||||
from argenta.command.flag.defaults import PredefinedFlags
|
||||
|
||||
flags = Flags([PredefinedFlags.HOST])
|
||||
|
||||
additional_flags = [
|
||||
PredefinedFlags.PORT,
|
||||
Flag("database"),
|
||||
Flag("ssl"),
|
||||
Flag("verbose"),
|
||||
]
|
||||
|
||||
flags.add_flags(additional_flags)
|
||||
|
||||
print(len(flags)) # 5
|
||||
@@ -0,0 +1,13 @@
|
||||
from argenta.command import Flag, Flags
|
||||
from argenta.command.flag.defaults import PredefinedFlags
|
||||
|
||||
|
||||
flags = Flags([PredefinedFlags.HOST, PredefinedFlags.PORT, Flag("verbose")])
|
||||
|
||||
host_flag = flags.get_flag_by_name("host")
|
||||
if host_flag:
|
||||
print(f"Found flag: {host_flag.name}")
|
||||
|
||||
unknown_flag = flags.get_flag_by_name("nonexistent")
|
||||
if unknown_flag is None:
|
||||
print("Flag not found")
|
||||
@@ -0,0 +1,13 @@
|
||||
from argenta.command.flag import InputFlag, ValidationStatus
|
||||
|
||||
flag_with_value = InputFlag(
|
||||
name="output", prefix="--", input_value="result.txt", status=ValidationStatus.VALID
|
||||
)
|
||||
|
||||
flag_without_value = InputFlag(
|
||||
name="help", prefix="-", input_value='', status=ValidationStatus.VALID
|
||||
)
|
||||
|
||||
# String representation includes value
|
||||
print(str(flag_with_value)) # --output result.txt
|
||||
print(str(flag_without_value)) # -help
|
||||
@@ -0,0 +1,12 @@
|
||||
from argenta.command.flag import InputFlag, ValidationStatus
|
||||
|
||||
flag = InputFlag(
|
||||
name="config",
|
||||
prefix="--",
|
||||
input_value="settings.json",
|
||||
status=ValidationStatus.VALID,
|
||||
)
|
||||
|
||||
# Debug representation of the object
|
||||
print(repr(flag))
|
||||
# InputFlag<prefix='--', name='config', value='settings.json', status=ValidationStatus.VALID>
|
||||
@@ -0,0 +1,20 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
|
||||
|
||||
router = Router(title="Example")
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"example",
|
||||
description="Example command with flags",
|
||||
flags=Flags([Flag("name"), Flag("age")]),
|
||||
)
|
||||
)
|
||||
def example_handler(response: Response):
|
||||
input_flags = response.input_flags
|
||||
|
||||
if input_flags:
|
||||
print(f"Received {len(input_flags)} flag(s)")
|
||||
else:
|
||||
print("No flags provided")
|
||||
@@ -0,0 +1,49 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags, InputFlag
|
||||
from argenta.command.flag import ValidationStatus
|
||||
|
||||
router = Router(title="Comprehensive Example")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"validate",
|
||||
description="Validate all flags",
|
||||
flags=Flags(
|
||||
[
|
||||
Flag("format", possible_values=["json", "xml"]),
|
||||
Flag("output"),
|
||||
Flag("force"),
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
def validate_handler(response: Response):
|
||||
input_flags = response.input_flags
|
||||
|
||||
print("Flag validation results:")
|
||||
|
||||
valid_flags: list[InputFlag] = []
|
||||
invalid_flags: list[InputFlag] = []
|
||||
undefined_flags: list[InputFlag] = []
|
||||
|
||||
for flag in input_flags:
|
||||
if flag.status == ValidationStatus.VALID:
|
||||
valid_flags.append(flag)
|
||||
print(f" ✓ {flag.string_entity}: {flag.input_value} (VALID)")
|
||||
elif flag.status == ValidationStatus.INVALID:
|
||||
invalid_flags.append(flag)
|
||||
print(f" ✗ {flag.string_entity}: {flag.input_value} (INVALID)")
|
||||
elif flag.status == ValidationStatus.UNDEFINED:
|
||||
undefined_flags.append(flag)
|
||||
print(f" ? {flag.string_entity}: {flag.input_value} (UNDEFINED)")
|
||||
|
||||
print("\nSummary:")
|
||||
print(f" Valid flags: {len(valid_flags)}")
|
||||
print(f" Invalid flags: {len(invalid_flags)}")
|
||||
print(f" Undefined flags: {len(undefined_flags)}")
|
||||
|
||||
if valid_flags:
|
||||
print("\nProcessing valid flags:")
|
||||
for flag in valid_flags:
|
||||
print(f" Processing {flag.name} = {flag.input_value}")
|
||||
@@ -0,0 +1,32 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
|
||||
router = Router(title="Get Flag Example")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"config",
|
||||
description="Configure settings",
|
||||
flags=Flags([Flag("host"), Flag("port"), Flag("debug")]),
|
||||
)
|
||||
)
|
||||
def config_handler(response: Response):
|
||||
input_flags = response.input_flags
|
||||
|
||||
host_flag = input_flags.get_flag_by_name("host")
|
||||
port_flag = input_flags.get_flag_by_name("port")
|
||||
debug_flag = input_flags.get_flag_by_name("debug")
|
||||
|
||||
if host_flag:
|
||||
print(f"Host: {host_flag.input_value}")
|
||||
|
||||
if port_flag:
|
||||
print(f"Port: {port_flag.input_value}")
|
||||
|
||||
if debug_flag:
|
||||
print("Debug mode enabled")
|
||||
|
||||
missing_flag = input_flags.get_flag_by_name("nonexistent")
|
||||
if missing_flag is None:
|
||||
print("Flag 'nonexistent' not found")
|
||||
@@ -0,0 +1,20 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command.flag import InputFlag, ValidationStatus
|
||||
from argenta.command import InputFlags
|
||||
|
||||
router = Router(title="Add Flag Example")
|
||||
|
||||
|
||||
@router.command(Command("test", description="Test command"))
|
||||
def test_handler(response: Response):
|
||||
# Create new InputFlags collection
|
||||
new_flags = InputFlags()
|
||||
|
||||
# Add one flag
|
||||
test_flag = InputFlag(
|
||||
name="test", prefix="--", input_value="value", status=ValidationStatus.VALID
|
||||
)
|
||||
new_flags.add_flag(test_flag)
|
||||
|
||||
print(f"Flags count: {len(new_flags.flags)}")
|
||||
print(f"First flag: {new_flags.flags[0].name}")
|
||||
@@ -0,0 +1,19 @@
|
||||
from argenta.command.flag import InputFlag, ValidationStatus
|
||||
from argenta.command import InputFlags
|
||||
|
||||
# Create InputFlags collection
|
||||
flags = InputFlags()
|
||||
|
||||
# Create several flags
|
||||
flag1 = InputFlag(name="option1", prefix="--", input_value="value1", status=ValidationStatus.VALID)
|
||||
|
||||
flag2 = InputFlag(name="option2", prefix="--", input_value="value2", status=ValidationStatus.VALID)
|
||||
|
||||
flag3 = InputFlag(name="option3", prefix="---", input_value="value3", status=ValidationStatus.VALID)
|
||||
|
||||
# Add all flags in one call
|
||||
flags.add_flags([flag1, flag2, flag3])
|
||||
|
||||
print(f"Total flags: {len(flags.flags)}")
|
||||
for flag in flags:
|
||||
print(f" - {flag.string_entity}: {flag.input_value}")
|
||||
@@ -0,0 +1,27 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
|
||||
router = Router(title="Bool Check Example")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"action",
|
||||
description="Action with optional flags",
|
||||
flags=Flags([Flag("option1"), Flag("option2")]),
|
||||
)
|
||||
)
|
||||
def action_handler(response: Response):
|
||||
input_flags = response.input_flags
|
||||
|
||||
# Check for flags presence
|
||||
if input_flags:
|
||||
print("Flags were provided:")
|
||||
for flag in input_flags:
|
||||
print(f" - {flag.name}: {flag.input_value}")
|
||||
else:
|
||||
print("No flags provided, using defaults")
|
||||
|
||||
# Alternative way to check
|
||||
has_flags = bool(input_flags)
|
||||
print(f"\nHas flags: {has_flags}")
|
||||
@@ -0,0 +1,33 @@
|
||||
from argenta.command.flag import InputFlag, ValidationStatus
|
||||
from argenta.command import InputFlags
|
||||
|
||||
# Create first collection
|
||||
flags1 = InputFlags(
|
||||
[
|
||||
InputFlag(name="flag1", input_value="value1", status=ValidationStatus.VALID),
|
||||
InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID),
|
||||
]
|
||||
)
|
||||
|
||||
# Create second collection with same flags
|
||||
flags2 = InputFlags(
|
||||
[
|
||||
InputFlag(name="flag1", input_value="value1", status=ValidationStatus.VALID),
|
||||
InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID),
|
||||
]
|
||||
)
|
||||
|
||||
# Create third collection with different values
|
||||
flags3 = InputFlags(
|
||||
[
|
||||
InputFlag(name="flag1", input_value="different", status=ValidationStatus.VALID),
|
||||
InputFlag(name="flag2", input_value="value2", status=ValidationStatus.VALID),
|
||||
]
|
||||
)
|
||||
|
||||
print(f"flags1 == flags2: {flags1 == flags2}") # True (same names)
|
||||
print(f"flags1 == flags3: {flags1 == flags3}") # True (same names, values are not considered)
|
||||
|
||||
# Different collections
|
||||
flags4 = InputFlags([InputFlag(name="flag3", input_value="value3", status=ValidationStatus.VALID)])
|
||||
print(f"flags1 == flags4: {flags1 == flags4}") # False (different flags)
|
||||
@@ -0,0 +1,33 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
from argenta.command.flag import InputFlag
|
||||
|
||||
router = Router(title="Contains Example")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"check",
|
||||
description="Check flags",
|
||||
flags=Flags([Flag("verbose"), Flag("debug"), Flag("quiet")]),
|
||||
)
|
||||
)
|
||||
def check_handler(response: Response):
|
||||
input_flags = response.input_flags
|
||||
|
||||
# Check for specific flag presence
|
||||
verbose_flag = input_flags.get_flag_by_name("verbose")
|
||||
debug_flag = input_flags.get_flag_by_name("debug")
|
||||
|
||||
# Use 'in' operator for checking
|
||||
if verbose_flag and verbose_flag in input_flags:
|
||||
print("Verbose flag is present")
|
||||
|
||||
if debug_flag and debug_flag in input_flags:
|
||||
print("Debug flag is present")
|
||||
|
||||
# You can create a flag for checking (comparison is by name)
|
||||
test_flag = InputFlag(name="verbose", prefix="--", input_value="any", status=None)
|
||||
|
||||
if test_flag in input_flags:
|
||||
print("Verbose flag found using 'in' operator")
|
||||
@@ -0,0 +1,9 @@
|
||||
from metrics.benchmarks.entity import benchmarks
|
||||
|
||||
@benchmarks.register(
|
||||
type_="my_category",
|
||||
description="Description of what is being measured"
|
||||
)
|
||||
def benchmark_my_operation() -> None:
|
||||
# Code whose performance is being measured
|
||||
pass
|
||||
@@ -0,0 +1,27 @@
|
||||
import sqlite3
|
||||
from sqlite3 import Connection
|
||||
from typing import Iterable
|
||||
|
||||
from dishka import Provider, Scope, provide
|
||||
|
||||
from argenta import App, Orchestrator
|
||||
|
||||
|
||||
class ConnectionProvider(Provider):
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def new_connection(self) -> Iterable[Connection]:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
yield conn
|
||||
conn.close()
|
||||
|
||||
|
||||
# 2. Create and configure App
|
||||
app = App()
|
||||
# ... you can add routers here ...
|
||||
|
||||
# 3. Create Orchestrator, passing our provider
|
||||
orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
|
||||
|
||||
# 4. Start the application
|
||||
if __name__ == "__main__":
|
||||
orchestrator.start_polling(app)
|
||||
@@ -0,0 +1,5 @@
|
||||
from argenta.command import Flag, PossibleValues
|
||||
|
||||
# Creating flags with any values
|
||||
message_flag = Flag(name="message", possible_values=PossibleValues.ALL)
|
||||
name_flag = Flag(name="name", possible_values=PossibleValues.ALL)
|
||||
@@ -0,0 +1,14 @@
|
||||
import re
|
||||
from argenta.command import Flag, PossibleValues
|
||||
|
||||
# Flag without value
|
||||
verbose_flag = Flag(name="verbose", possible_values=PossibleValues.NEITHER)
|
||||
|
||||
# Flag with any value
|
||||
output_flag = Flag(name="output", possible_values=PossibleValues.ALL)
|
||||
|
||||
# Flag with a list of valid values
|
||||
format_flag = Flag(name="format", possible_values=["json", "xml", "csv", "yaml"])
|
||||
|
||||
# Flag with regular expression
|
||||
email_flag = Flag(name="email", possible_values=re.compile(r"^[\w\.-]+@[\w\.-]+\.\w+$"))
|
||||
@@ -0,0 +1,6 @@
|
||||
from argenta.command import Flag, PossibleValues
|
||||
|
||||
# Creating flags without values
|
||||
help_flag = Flag(name="help", possible_values=PossibleValues.NEITHER)
|
||||
verbose_flag = Flag(name="verbose", possible_values=PossibleValues.NEITHER)
|
||||
force_flag = Flag(name="force", possible_values=PossibleValues.NEITHER)
|
||||
@@ -0,0 +1,67 @@
|
||||
import operator
|
||||
import re
|
||||
|
||||
from argenta import App, Orchestrator, Response, Router
|
||||
from argenta.app import DynamicDividingLine
|
||||
from argenta.command import Command, Flag, Flags
|
||||
from argenta.response.status import ResponseStatus
|
||||
|
||||
router = Router("Calculator")
|
||||
|
||||
operations = {"mul": operator.mul, "sub": operator.sub, "add": operator.add}
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"calc",
|
||||
description="Calculator with two numbers",
|
||||
flags=Flags(
|
||||
[
|
||||
Flag("a", possible_values=re.compile(r"^\d{,5}$")), # First number
|
||||
Flag("b", possible_values=re.compile(r"^\d{,5}$")), # Second number
|
||||
Flag(
|
||||
"operation", possible_values=["add", "sub", "mul"]
|
||||
), # Operation: add, sub, mul
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
def calc_handler(response: Response):
|
||||
# Get flag values
|
||||
a_flag = response.input_flags.get_flag_by_name("a")
|
||||
b_flag = response.input_flags.get_flag_by_name("b")
|
||||
op_flag = response.input_flags.get_flag_by_name("op")
|
||||
|
||||
# Check that all flags are provided
|
||||
if response.status != ResponseStatus.ALL_FLAGS_VALID or not all([a_flag, b_flag, op_flag]):
|
||||
print("Error: must specify --a, --b and --op")
|
||||
return
|
||||
|
||||
a = float(a_flag.input_value)
|
||||
b = float(b_flag.input_value)
|
||||
operation = op_flag.input_value
|
||||
|
||||
try:
|
||||
result = operations[operation](a, b)
|
||||
except ZeroDivisionError:
|
||||
print("Can't divide by zero")
|
||||
else:
|
||||
print(f"Result: {result}")
|
||||
|
||||
|
||||
app = App(
|
||||
initial_message="Calculator",
|
||||
repeat_command_groups_printing=False,
|
||||
prompt=">> ",
|
||||
dividing_line=DynamicDividingLine("~"),
|
||||
)
|
||||
orchestrator = Orchestrator()
|
||||
|
||||
|
||||
def main():
|
||||
app.include_router(router)
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,16 @@
|
||||
# main.py
|
||||
from .routers import router
|
||||
|
||||
from argenta import App, Orchestrator
|
||||
|
||||
app: App = App()
|
||||
orchestrator: Orchestrator = Orchestrator()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
app.include_router(router)
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,9 @@
|
||||
# routers.py
|
||||
from argenta import Command, Response, Router
|
||||
|
||||
router = Router(title="Quickstart Example")
|
||||
|
||||
|
||||
@router.command(Command("hello", description="Say hello"))
|
||||
def handler(response: Response):
|
||||
print("Hello, world!")
|
||||
@@ -0,0 +1,33 @@
|
||||
from argenta import App, Command, Orchestrator, Router, Response
|
||||
from argenta.command import Flag
|
||||
|
||||
# 1. Create app and orchestrator instances
|
||||
app = App(
|
||||
prompt=">> ",
|
||||
initial_message="Simple App",
|
||||
farewell_message="Goodbye!",
|
||||
repeat_command_groups_printing=False,
|
||||
)
|
||||
orchestrator = Orchestrator()
|
||||
|
||||
# 2. Create router for grouping commands
|
||||
main_router = Router(title="Main commands")
|
||||
|
||||
|
||||
# 3. Define command and its handler
|
||||
@main_router.command(Command("hello", description="Prints greeting message", flags=Flag("name")))
|
||||
def hello_handler(response: Response):
|
||||
"""This handler will be called for 'hello' command."""
|
||||
name = response.input_flags.get_flag_by_name("name")
|
||||
if name:
|
||||
print(f"Hello, {name.input_value}!")
|
||||
else:
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
# 4. Include router to application
|
||||
app.include_router(main_router)
|
||||
|
||||
# 5. Start application
|
||||
if __name__ == "__main__":
|
||||
orchestrator.start_polling(app)
|
||||
@@ -0,0 +1,59 @@
|
||||
from typing import cast
|
||||
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command.flag import Flag, ValidationStatus
|
||||
from argenta.command import Flags
|
||||
from argenta.di import FromDishka
|
||||
|
||||
from .repository import Priority, Task, TaskRepository
|
||||
|
||||
router = Router(title="Task Manager")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"add-task",
|
||||
description="Add a new task",
|
||||
flags=Flags(
|
||||
[
|
||||
Flag("description"),
|
||||
Flag("priority", possible_values=["low", "medium", "high"]),
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
def add_task(response: Response, repo: FromDishka[TaskRepository]):
|
||||
description_flag = response.input_flags.get_flag_by_name("description")
|
||||
|
||||
if not description_flag or not description_flag.status == ValidationStatus.VALID:
|
||||
print("Error: --description flag is required.")
|
||||
return
|
||||
|
||||
task_description = description_flag.input_value or ""
|
||||
|
||||
priority_flag = response.input_flags.get_flag_by_name("priority")
|
||||
|
||||
if priority_flag and priority_flag.status == ValidationStatus.VALID:
|
||||
priority_value = priority_flag.input_value
|
||||
else:
|
||||
priority_value = "medium"
|
||||
|
||||
priority = cast(Priority, priority_value)
|
||||
|
||||
task = Task(description=task_description, priority=priority)
|
||||
repo.add_task(task)
|
||||
|
||||
print(f"Added task: '{task.description}' with priority '{task.priority}'")
|
||||
|
||||
|
||||
@router.command(Command("list-tasks", description="List all tasks"))
|
||||
def list_tasks(response: Response, repo: FromDishka[TaskRepository]):
|
||||
tasks = repo.get_all_tasks()
|
||||
|
||||
if not tasks:
|
||||
print("No tasks found.")
|
||||
return
|
||||
|
||||
print("Tasks:")
|
||||
for i, task in enumerate(tasks, 1):
|
||||
print(f" {i}. {task.description} (Priority: {task.priority})")
|
||||
@@ -0,0 +1,18 @@
|
||||
from argenta import App, Orchestrator
|
||||
|
||||
from .handlers import router
|
||||
from .provider import TaskProvider
|
||||
|
||||
# 1. Create app and orchestrator instances
|
||||
app = App(
|
||||
initial_message="Task Manager",
|
||||
prompt="Enter a command: ",
|
||||
)
|
||||
orchestrator = Orchestrator(custom_providers=[TaskProvider()])
|
||||
|
||||
# 2. Include router with our commands
|
||||
app.include_router(router)
|
||||
|
||||
# 3. Start polling via orchestrator
|
||||
if __name__ == "__main__":
|
||||
orchestrator.start_polling(app)
|
||||
@@ -0,0 +1,9 @@
|
||||
from dishka import Provider, Scope, provide
|
||||
|
||||
from .repository import TaskRepository
|
||||
|
||||
|
||||
class TaskProvider(Provider):
|
||||
@provide(scope=Scope.APP)
|
||||
def get_repository(self) -> TaskRepository:
|
||||
return TaskRepository()
|
||||
@@ -0,0 +1,21 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
Priority = Literal["low", "medium", "high"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
description: str
|
||||
priority: Priority = "medium"
|
||||
|
||||
|
||||
class TaskRepository:
|
||||
def __init__(self):
|
||||
self._tasks: list[Task] = []
|
||||
|
||||
def add_task(self, task: Task):
|
||||
self._tasks.append(task)
|
||||
|
||||
def get_all_tasks(self) -> list[Task]:
|
||||
return self._tasks
|
||||
@@ -0,0 +1,10 @@
|
||||
from argenta import Response, Router
|
||||
|
||||
# For this router stdout redirect will be disabled
|
||||
interactive_router = Router(disable_redirect_stdout=True)
|
||||
|
||||
|
||||
@interactive_router.command("ask")
|
||||
def ask_name(response: Response):
|
||||
name = input("What is your name? ")
|
||||
print(f"Nice to meet you, {name}!")
|
||||
@@ -0,0 +1,5 @@
|
||||
from argenta import App
|
||||
from argenta.app import StaticDividingLine
|
||||
|
||||
# All routers will use static line with length 50 by default
|
||||
app = App(dividing_line=StaticDividingLine(length=50))
|
||||
@@ -0,0 +1,43 @@
|
||||
from argenta import Router, Response, Command, DataBridge
|
||||
from argenta.command import Flag
|
||||
from argenta.di import FromDishka
|
||||
|
||||
router = Router(title="Authentication")
|
||||
|
||||
|
||||
def authenticate_user(username: str) -> str:
|
||||
return f"token_for_{username}"
|
||||
|
||||
|
||||
@router.command(Command("login", flags=Flag("username")))
|
||||
def login_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
username_flag = response.input_flags.get_flag_by_name("username")
|
||||
if not username_flag or not username_flag.input_value:
|
||||
print("Error: username must be specified using the --username flag.")
|
||||
return
|
||||
|
||||
username = username_flag.input_value
|
||||
token = authenticate_user(username)
|
||||
|
||||
data_bridge.update({"auth_token": token})
|
||||
print(f"Login successful! User '{username}' authenticated.")
|
||||
|
||||
|
||||
@router.command("get-profile")
|
||||
def get_profile_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
token = data_bridge.get_by_key("auth_token")
|
||||
|
||||
if not token:
|
||||
print("Error: you are not authenticated. Please run the 'login' command first.")
|
||||
return
|
||||
|
||||
print(f"Loading profile using token: [yellow]{token}[/yellow]")
|
||||
|
||||
|
||||
@router.command("logout")
|
||||
def logout_handler(response: Response, data_bridge: FromDishka[DataBridge]):
|
||||
try:
|
||||
data_bridge.delete_by_key("auth_token")
|
||||
print("Logout successful. Session data cleared.")
|
||||
except KeyError:
|
||||
print("You were not authenticated anyway.")
|
||||
@@ -0,0 +1,12 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.response import ResponseStatus
|
||||
|
||||
router = Router(title="Example")
|
||||
|
||||
|
||||
@router.command(Command("greet", description="Greet the user"))
|
||||
def greet_handler(response: Response):
|
||||
if response.status == ResponseStatus.ALL_FLAGS_VALID:
|
||||
print("Hello! All flags are valid.")
|
||||
else:
|
||||
print("Warning: Some flags have issues.")
|
||||
@@ -0,0 +1,35 @@
|
||||
from argenta import Command, Response, Router
|
||||
from argenta.command import Flag, Flags
|
||||
from argenta.command.flag import ValidationStatus
|
||||
from argenta.response import ResponseStatus
|
||||
|
||||
router = Router(title="Flags Example")
|
||||
|
||||
|
||||
@router.command(
|
||||
Command(
|
||||
"process",
|
||||
description="Process with flags",
|
||||
flags=Flags([Flag("format", possible_values=["json", "xml"]), Flag("verbose")]),
|
||||
)
|
||||
)
|
||||
def process_handler(response: Response):
|
||||
print(f"Status: {response.status}")
|
||||
|
||||
format_flag = response.input_flags.get_flag_by_name("format")
|
||||
verbose_flag = response.input_flags.get_flag_by_name("verbose")
|
||||
|
||||
if format_flag:
|
||||
format_value = format_flag.input_value
|
||||
print(f"Format: {format_value}")
|
||||
|
||||
if verbose_flag:
|
||||
print("Verbose mode enabled")
|
||||
|
||||
if response.status == ResponseStatus.ALL_FLAGS_VALID:
|
||||
print("All flags are valid, proceeding...")
|
||||
elif response.status == ResponseStatus.INVALID_VALUE_FLAGS:
|
||||
print("Warning: Some flags have invalid values")
|
||||
for flag in response.input_flags:
|
||||
if flag.status == ValidationStatus.INVALID:
|
||||
print(f" Invalid flag: {flag.string_entity} = {flag.input_value}")
|
||||
@@ -0,0 +1,7 @@
|
||||
from argenta import Command, Response, Router
|
||||
|
||||
user_router = Router(title="User Management")
|
||||
|
||||
@user_router.command(Command("add-user", description="Adds a new user"))
|
||||
def add_user_handler(response: Response):
|
||||
print("User added successfully!")
|
||||
@@ -0,0 +1,31 @@
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
import pytest
|
||||
from pytest import CaptureFixture
|
||||
|
||||
from argenta import App, Orchestrator, Router, Command, Response
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patched_argv():
|
||||
with patch.object(sys, "argv", ["program.py"]):
|
||||
yield
|
||||
|
||||
|
||||
def test_input_incorrect_command(capsys: CaptureFixture[str]):
|
||||
router = Router()
|
||||
orchestrator = Orchestrator()
|
||||
|
||||
@router.command(Command("test"))
|
||||
def test(response: Response) -> None:
|
||||
print("test command")
|
||||
|
||||
app = App(override_system_messages=True, printer=print)
|
||||
app.include_router(router)
|
||||
app.set_unknown_command_handler(lambda command: print(f"Unknown command: {command.trigger}"))
|
||||
|
||||
with patch("builtins.input", side_effect=["help", "q"]):
|
||||
orchestrator.start_polling(app)
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert "\nUnknown command: help\n" in output
|
||||
@@ -0,0 +1,21 @@
|
||||
import io
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
from argenta import App, Router, Command, Response
|
||||
from argenta.command import InputCommand
|
||||
|
||||
|
||||
def test_simple_app() -> None:
|
||||
app = App(override_system_messages=True, repeat_command_groups_printing=False)
|
||||
router = Router(title="App")
|
||||
|
||||
@router.command(Command("HELP", description="Show help"))
|
||||
def help_cmd(response: Response):
|
||||
print("Available commands: HELP")
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("HELP"))
|
||||
|
||||
assert "Available commands:" in stdout.getvalue()
|
||||
@@ -0,0 +1,44 @@
|
||||
import io
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
from argenta.command import InputCommand
|
||||
from dishka import Provider, make_container, Scope
|
||||
|
||||
from argenta import Router, Response
|
||||
from argenta.di.integration import setup_dishka, FromDishka
|
||||
|
||||
|
||||
class Service:
|
||||
def hello(self) -> str:
|
||||
return "world"
|
||||
|
||||
|
||||
def get_service() -> Service:
|
||||
return Service()
|
||||
|
||||
|
||||
router = Router(title="DI")
|
||||
|
||||
|
||||
@router.command("HELLO")
|
||||
def hello(response: Response, service: FromDishka[Service]) -> None:
|
||||
print(f"hello {service.hello()}")
|
||||
|
||||
|
||||
class _FakeApp:
|
||||
# Minimal stub for setup_dishka; app object is not used in unit tests
|
||||
registered_routers = [router]
|
||||
|
||||
|
||||
def test_hello_uses_service():
|
||||
provider = Provider(scope=Scope.APP)
|
||||
provider.provide(get_service)
|
||||
|
||||
container = make_container(provider)
|
||||
setup_dishka(app=_FakeApp(), container=container, auto_inject=True)
|
||||
|
||||
# Call handler
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("HELLO"))
|
||||
|
||||
assert "hello world" in stdout.getvalue()
|
||||
@@ -0,0 +1,20 @@
|
||||
import io
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
from argenta import Router, Command, Response
|
||||
from argenta.command import InputCommand
|
||||
|
||||
|
||||
router = Router(title="Demo")
|
||||
|
||||
|
||||
@router.command(Command("PING", description="Ping command"))
|
||||
def ping(response: Response):
|
||||
print("PONG")
|
||||
|
||||
|
||||
def test_ping_prints_pong():
|
||||
# Call handler
|
||||
with redirect_stdout(io.StringIO()) as stdout:
|
||||
router.finds_appropriate_handler(InputCommand.parse("PING"))
|
||||
assert "PONG" in stdout.getvalue()
|
||||
@@ -0,0 +1,48 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = "Argenta"
|
||||
copyright = "2025, kolo"
|
||||
author = "kolo"
|
||||
root_doc = "index"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
locale_dirs = ['locales/']
|
||||
gettext_compact = False
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = "shibuya"
|
||||
html_static_path = ["_static"]
|
||||
|
||||
html_context = {
|
||||
"languages": [
|
||||
("English", "/en/latest/%s.html", "en"),
|
||||
("Русский", "/ru/latest/%s.html", "ru"),
|
||||
]
|
||||
}
|
||||
|
||||
html_theme_options = {
|
||||
"accent_color": "cyan",
|
||||
"nav_links": [
|
||||
{
|
||||
"title": "Sponsor me",
|
||||
"url": "https://github.com/sponsors/koloideal"
|
||||
},
|
||||
],
|
||||
"github_url": "https://github.com/koloideal/Argenta",
|
||||
"linkedin_url": "https://www.linkedin.com/in/dmitry-shevelev-31b9a6324"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
.. Argenta documentation master file, created by
|
||||
sphinx-quickstart on Sat Oct 11 19:54:43 2025.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Argenta
|
||||
=======
|
||||
|
||||
Что это и зачем?
|
||||
----------------
|
||||
|
||||
**Библиотека для построения модульных CLI-приложений с простым и приятным API.**
|
||||
|
||||
Если у вас есть функциональность, которую вы хотите предоставить в виде CLI-приложения, Argenta поможет вам в этом.
|
||||
Основная цель библиотеки — дать разработчикам возможность сосредоточиться на реализации своих идей, предоставляя для этого удобные абстракции.
|
||||
|
||||
.. image:: https://i.ibb.co/fzWcfgFq/2025-12-04-173045.png
|
||||
:alt: App example
|
||||
|
||||
Argenta предназначена для создания приложений, работающих в собственном контексте (scope). Это означает, что приложение запускается один раз и создаёт интерактивную сессию, похожую на Python REPL или MySQL консоль. При запуске пользователь входит в эту сессию, где ему доступна вся реализованная вами функциональность.
|
||||
|
||||
Один из ключевых принципов библиотеки — цикличность. После выполнения команды пользователь остаётся в интерактивной сессии, в отличие от таких библиотек, как ``argparse``, ``click`` и ``typer``, где приложение завершается после каждой команды. Выход из сессии контролируется пользователем.
|
||||
|
||||
**Ключевые особенности:**
|
||||
|
||||
* **Интерактивные сессии**: В отличие от традиционных CLI-инструментов, ``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять команды последовательно, не перезапуская приложение.
|
||||
* **Декларативный синтаксис**: Команды и их обработчики объявляются с помощью простых декораторов, что делает код интуитивно понятным и позволяет сосредоточиться на том, "что" вы хотите сделать, а не "как".
|
||||
* **Нативный DI**: Благодаря интеграции с `dishka <https://dishka.readthedocs.io/en/stable/>`_, вы можете легко внедрять зависимости прямо в обработчики команд, что упрощает их тестирование, позволяет избежать мутабельных глобалов и многое другое.
|
||||
* **Автоматическая валидация и парсинг**: Библиотека берёт на себя обработку флагов и аргументов командной строки, включая их парсинг, валидацию и преобразование типов.
|
||||
* **Гибкая настройка**: Вы можете легко кастомизировать системные сообщения, форматирование вывода, создавать кастомные обработчики нестандартного поведения и т.д.
|
||||
|
||||
-----
|
||||
|
||||
Архитектура и жизненный цикл
|
||||
-----------------------------
|
||||
|
||||
Следующая диаграмма иллюстрирует, как компоненты Argenta взаимодействуют друг с другом, обрабатывая ввод пользователя.
|
||||
|
||||
.. image:: https://i.ibb.co/hF3FdFr1/argenta-intro-drawio-2.png
|
||||
:alt: Request Lifecycle Diagram
|
||||
:align: center
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Контент:
|
||||
|
||||
root/quickstart
|
||||
root/error_handling
|
||||
root/flags
|
||||
root/overriding_formatting
|
||||
root/api/index
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Продвинутое использование:
|
||||
|
||||
root/redirect_stdout
|
||||
root/dependency_injection
|
||||
root/testing
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Для разработчиков:
|
||||
|
||||
root/contributing
|
||||
root/code_of_conduct
|
||||
root/metrics
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Ссылки проекта:
|
||||
|
||||
GitHub <https://github.com/koloideal/argenta>
|
||||
PyPI <https://pypi.org/project/argenta>
|
||||
@@ -0,0 +1,30 @@
|
||||
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
|
||||
set shell := ["bash", "-c"]
|
||||
|
||||
# Variables
|
||||
sphinxopts := ""
|
||||
sphinxbuild := "sphinx-build"
|
||||
sourcedir := "."
|
||||
builddir := "_build"
|
||||
|
||||
# Default recipe (help)
|
||||
default:
|
||||
@{{sphinxbuild}} -M help "{{sourcedir}}" "{{builddir}}" {{sphinxopts}}
|
||||
|
||||
# Build all language versions
|
||||
build:
|
||||
{{sphinxbuild}} -b html -D language=ru {{sourcedir}} {{builddir}}/html/ru
|
||||
{{sphinxbuild}} -b html -D language =en {{sourcedir}} {{builddir}}/html/en
|
||||
|
||||
# Live preview for Russian version
|
||||
live-ru:
|
||||
sphinx-autobuild -b html . _build/html/ru -D language=ru
|
||||
|
||||
# Live preview for English version
|
||||
live-en:
|
||||
sphinx-autobuild -b html . _build/html/en -D language=en
|
||||
|
||||
# Update translation files
|
||||
update-langs:
|
||||
{{sphinxbuild}} -b gettext . _build/gettext
|
||||
sphinx-intl update -p _build/gettext -l en
|
||||
@@ -0,0 +1,177 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2025, kolo
|
||||
# This file is distributed under the same license as the Argenta package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Argenta \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-12-04 20:41+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: ../../index.rst:43
|
||||
msgid "Контент:"
|
||||
msgstr "Content:"
|
||||
|
||||
#: ../../index.rst:53
|
||||
msgid "Продвинутое использование:"
|
||||
msgstr "Advanced usage:"
|
||||
|
||||
#: ../../index.rst:61
|
||||
msgid "Для разработчиков:"
|
||||
msgstr "For developers:"
|
||||
|
||||
#: ../../index.rst:68
|
||||
msgid "GitHub"
|
||||
msgstr ""
|
||||
|
||||
#: ../../index.rst:68
|
||||
msgid "PyPI"
|
||||
msgstr ""
|
||||
|
||||
#: ../../index.rst:68
|
||||
msgid "Ссылки проекта:"
|
||||
msgstr "Project links:"
|
||||
|
||||
#: ../../index.rst:7
|
||||
msgid "Argenta"
|
||||
msgstr ""
|
||||
|
||||
#: ../../index.rst:10
|
||||
msgid "Что это и зачем?"
|
||||
msgstr "What is it and why?"
|
||||
|
||||
#: ../../index.rst:12
|
||||
msgid ""
|
||||
"**Библиотека для построения модульных CLI-приложений с простым и приятным"
|
||||
" API.**"
|
||||
msgstr ""
|
||||
"**A library for building modular CLI applications with a simple and "
|
||||
"pleasant API.**"
|
||||
|
||||
#: ../../index.rst:14
|
||||
msgid ""
|
||||
"Если у вас есть функциональность, которую вы хотите предоставить в виде "
|
||||
"CLI-приложения, Argenta поможет вам в этом. Основная цель библиотеки — "
|
||||
"дать разработчикам возможность сосредоточиться на реализации своих идей, "
|
||||
"предоставляя для этого удобные абстракции."
|
||||
msgstr ""
|
||||
"If you have functionality that you want to provide as a CLI application, "
|
||||
"Argenta will help you with that. The main goal of the library is to "
|
||||
"enable developers to focus on implementing their ideas by providing "
|
||||
"convenient abstractions."
|
||||
|
||||
#: ../../index.rst:17
|
||||
msgid "App example"
|
||||
msgstr ""
|
||||
|
||||
#: ../../index.rst:20
|
||||
msgid ""
|
||||
"Argenta предназначена для создания приложений, работающих в собственном "
|
||||
"контексте (scope). Это означает, что приложение запускается один раз и "
|
||||
"создаёт интерактивную сессию, похожую на Python REPL или MySQL консоль. "
|
||||
"При запуске пользователь входит в эту сессию, где ему доступна вся "
|
||||
"реализованная вами функциональность."
|
||||
msgstr ""
|
||||
"Argenta is designed for creating applications that work in their own "
|
||||
"context (scope). This means that the application starts once and creates "
|
||||
"an interactive session, similar to Python REPL or MySQL console. When "
|
||||
"launched, the user enters this session where all the functionality you've"
|
||||
" implemented is available."
|
||||
|
||||
#: ../../index.rst:22
|
||||
msgid ""
|
||||
"Один из ключевых принципов библиотеки — цикличность. После выполнения "
|
||||
"команды пользователь остаётся в интерактивной сессии, в отличие от таких "
|
||||
"библиотек, как ``argparse``, ``click`` и ``typer``, где приложение "
|
||||
"завершается после каждой команды. Выход из сессии контролируется "
|
||||
"пользователем."
|
||||
msgstr ""
|
||||
"One of the key principles of the library is cyclicity. After executing a "
|
||||
"command, the user remains in the interactive session, "
|
||||
"unlike libraries such as ``argparse``, ``click``, "
|
||||
"and ``typer``, where the application terminates after each command. "
|
||||
"Exiting the session is controlled by the user."
|
||||
|
||||
#: ../../index.rst:24
|
||||
msgid "**Ключевые особенности:**"
|
||||
msgstr "**Key features:**"
|
||||
|
||||
#: ../../index.rst:26
|
||||
msgid ""
|
||||
"**Интерактивные сессии**: В отличие от традиционных CLI-инструментов, "
|
||||
"``Argenta`` создаёт циклические сессии, позволяя пользователю выполнять "
|
||||
"команды последовательно, не перезапуская приложение."
|
||||
msgstr ""
|
||||
"**Interactive sessions**: Unlike traditional CLI tools, ``Argenta`` "
|
||||
"creates cyclical sessions, allowing users to execute commands "
|
||||
"sequentially without restarting the application."
|
||||
|
||||
#: ../../index.rst:27
|
||||
msgid ""
|
||||
"**Декларативный синтаксис**: Команды и их обработчики объявляются с "
|
||||
"помощью простых декораторов, что делает код интуитивно понятным и "
|
||||
"позволяет сосредоточиться на том, \"что\" вы хотите сделать, а не "
|
||||
"\"как\"."
|
||||
msgstr ""
|
||||
"**Declarative syntax**: Commands and their handlers are declared using "
|
||||
"simple decorators, making the code intuitive and allowing you to focus on"
|
||||
" \"what\" you want to do, not \"how\"."
|
||||
|
||||
#: ../../index.rst:28
|
||||
msgid ""
|
||||
"**Нативный DI**: Благодаря интеграции с "
|
||||
"`dishka <https://dishka.readthedocs.io/en/stable/>`_, вы можете легко "
|
||||
"внедрять зависимости прямо в обработчики команд, что упрощает их "
|
||||
"тестирование, позволяет избежать мутабельных глобалов и многое другое."
|
||||
msgstr ""
|
||||
"**Native DI**: Thanks to integration with `dishka "
|
||||
"<https://dishka.readthedocs.io/en/stable/>`_, you can easily inject "
|
||||
"dependencies directly into command handlers, simplifying their testing, "
|
||||
"avoiding mutable globals, and much more."
|
||||
|
||||
#: ../../index.rst:29
|
||||
msgid ""
|
||||
"**Автоматическая валидация и парсинг**: Библиотека берёт на себя "
|
||||
"обработку флагов и аргументов командной строки, включая их парсинг, "
|
||||
"валидацию и преобразование типов."
|
||||
msgstr ""
|
||||
"**Automatic validation and parsing**: The library handles command-line "
|
||||
"flags and arguments, including their parsing, validation, and type "
|
||||
"conversion."
|
||||
|
||||
#: ../../index.rst:30
|
||||
msgid ""
|
||||
"**Гибкая настройка**: Вы можете легко кастомизировать системные "
|
||||
"сообщения, форматирование вывода, создавать кастомные обработчики "
|
||||
"нестандартного поведения и т.д."
|
||||
msgstr ""
|
||||
"**Flexible configuration**: You can easily customize system messages, "
|
||||
"output formatting, create custom handlers for non-standard behavior, and "
|
||||
"more."
|
||||
|
||||
#: ../../index.rst:35
|
||||
msgid "Архитектура и жизненный цикл"
|
||||
msgstr "Architecture and lifecycle"
|
||||
|
||||
#: ../../index.rst:37
|
||||
msgid ""
|
||||
"Следующая диаграмма иллюстрирует, как компоненты Argenta взаимодействуют "
|
||||
"друг с другом, обрабатывая ввод пользователя."
|
||||
msgstr ""
|
||||
"The following diagram illustrates how Argenta components interact with "
|
||||
"each other while processing user input."
|
||||
|
||||
#: ../../index.rst:39
|
||||
msgid "Request Lifecycle Diagram"
|
||||
msgstr ""
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2025, kolo
|
||||
# This file is distributed under the same license as the Argenta package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Argenta \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-01-22 04:26+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:4
|
||||
msgid "AutoCompleter"
|
||||
msgstr ""
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:6
|
||||
msgid ""
|
||||
"``AutoCompleter`` — это компонент, отвечающий за интерактивное "
|
||||
"автодополнение команд. Он улучшает пользовательский опыт, предлагая "
|
||||
"подсказки и завершая ввод на основе истории команд, что ускоряет работу и"
|
||||
" снижает вероятность опечаток."
|
||||
msgstr ""
|
||||
"``AutoCompleter`` is a component responsible for interactive command "
|
||||
"autocompletion. It improves user experience by offering suggestions and "
|
||||
"completing input based on command history, which speeds up work and "
|
||||
"reduces the likelihood of typos."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:11
|
||||
msgid "Инициализация"
|
||||
msgstr "Initialization"
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:21
|
||||
msgid "Создаёт и настраивает экземпляр ``AutoCompleter``."
|
||||
msgstr "Creates and configures an ``AutoCompleter`` instance."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:23
|
||||
msgid ""
|
||||
"``history_filename``: Имя файла для сохранения истории команд. Если "
|
||||
"указано, история будет сохраняться между сессиями. При значении ``None`` "
|
||||
"история хранится только в контексте сессии."
|
||||
msgstr ""
|
||||
"``history_filename``: Filename for saving command history. If specified, "
|
||||
"history will be saved between sessions. When set to ``None``, history is "
|
||||
"stored only within the session context."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:24
|
||||
msgid ""
|
||||
"``autocomplete_button``: Клавиша, активирующая автодополнение. По "
|
||||
"умолчанию — **Tab**."
|
||||
msgstr ""
|
||||
"``autocomplete_button``: Key that activates autocompletion. Defaults to "
|
||||
"**Tab**."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:25
|
||||
msgid ""
|
||||
"``command_highlighting``: Если True, то в реальном времени при вводе "
|
||||
"команды она будет подсвечиваться: зелёным, если такой триггер существует "
|
||||
"и красный, если нет."
|
||||
msgstr ""
|
||||
"``command_highlighting``: If True, then in real time, when entering a "
|
||||
" command, it will be highlighted: green if such a trigger exists "
|
||||
"and red if not."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:26
|
||||
msgid ""
|
||||
"``auto_suggestions``: Если True, то дополнение до раннее введённой "
|
||||
"команды будет сразу отображаться светло-серым в строке ввода."
|
||||
msgstr ""
|
||||
"``auto_suggestions``: If True, the addition to the previously entered "
|
||||
" command will immediately be displayed in light gray in the input line."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:31
|
||||
msgid "Назначение и возможности"
|
||||
msgstr "Purpose and Features"
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:33
|
||||
msgid "Основные возможности ``AutoCompleter``:"
|
||||
msgstr "Main features of ``AutoCompleter``:"
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:35
|
||||
msgid ""
|
||||
"**Автодополнение по истории**: При нажатии клавиши автодополнения (по "
|
||||
"умолчанию **Tab**) система ищет в истории команды, начинающиеся с уже "
|
||||
"введённого текста."
|
||||
msgstr ""
|
||||
"**History-based autocompletion**: When the autocompletion key is pressed "
|
||||
"(by default **Tab**), the system searches history for commands starting "
|
||||
"with the already entered text."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:37
|
||||
msgid ""
|
||||
"**Общий префикс**: Если найдено несколько команд с общим префиксом, будет"
|
||||
" подставлена только общая часть. Например, для команд ``show_users`` и "
|
||||
"``show_profile`` при вводе ``sho`` и нажатии **Tab** ввод дополнится до "
|
||||
"``show_``."
|
||||
msgstr ""
|
||||
"**Common prefix**: If multiple commands with a common prefix are found, "
|
||||
"only the common part will be inserted. For example, for commands "
|
||||
"``show_users`` and ``show_profile``, when entering ``sho`` and pressing "
|
||||
"**Tab**, the input will be completed to ``show_``."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:39
|
||||
msgid ""
|
||||
"**Постоянная история**: Если указан ``history_filename``, история команд "
|
||||
"сохраняется в файл при выходе и загружается при следующем запуске. Это "
|
||||
"делает автодополнение со временем «умнее»."
|
||||
msgstr ""
|
||||
"**Persistent history**: If ``history_filename`` is specified, command "
|
||||
"history is saved to a file on exit and loaded on the next startup. This "
|
||||
"makes autocompletion \"smarter\" over time."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:41
|
||||
msgid ""
|
||||
"**Настройка клавиши**: Клавишу автодополнения можно изменить с помощью "
|
||||
"параметра ``autocomplete_button``."
|
||||
msgstr ""
|
||||
"**Key customization**: The autocompletion key can be changed using the "
|
||||
"``autocomplete_button`` parameter."
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:46
|
||||
msgid "Пример использования"
|
||||
msgstr "Usage Example"
|
||||
|
||||
#: ../../root/api/app/autocompleter.rst:48
|
||||
msgid "``AutoCompleter`` передаётся как аргумент при инициализации `App`."
|
||||
msgstr "``AutoCompleter`` is passed as an argument when initializing `App`."
|
||||
@@ -0,0 +1,150 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2025, kolo
|
||||
# This file is distributed under the same license as the Argenta package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Argenta \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-12-02 22:27+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:4
|
||||
msgid "Dividing Lines"
|
||||
msgstr "Dividing Lines"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:6
|
||||
msgid ""
|
||||
"Разделительные линии в ``Argenta`` используются для визуального "
|
||||
"структурирования вывода и отделения блоков информации друг от друга. "
|
||||
"Библиотека предлагает два типа линий: статическую и динамическую."
|
||||
msgstr ""
|
||||
"Dividing lines in ``Argenta`` are used for visual structuring of output and separating "
|
||||
"information blocks from each other. The library offers two types of lines: static and dynamic."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:11
|
||||
msgid "``StaticDividingLine``"
|
||||
msgstr "``StaticDividingLine``"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:13
|
||||
msgid ""
|
||||
"``StaticDividingLine`` создаёт разделительную линию **фиксированной** "
|
||||
"длины. Этот тип линии полезен для создания предсказуемого и "
|
||||
"унифицированного интерфейса."
|
||||
msgstr ""
|
||||
"``StaticDividingLine`` creates a dividing line of **fixed** length. This type of line "
|
||||
"is useful for creating a predictable and unified interface."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:21
|
||||
msgid "Создаёт экземпляр статической разделительной линии."
|
||||
msgstr "Creates a static dividing line instance."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:23
|
||||
msgid ""
|
||||
"``unit_part``: Символ для построения линии (учитывается только первый "
|
||||
"символ). По умолчанию: ``-``."
|
||||
msgstr ""
|
||||
"``unit_part``: Character for building the line (only the first character is considered). "
|
||||
"Defaults to: ``-``."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:24
|
||||
msgid "``length``: Фиксированная длина линии. По умолчанию: ``25``."
|
||||
msgstr "``length``: Fixed line length. Defaults to: ``25``."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:29
|
||||
msgid "``DynamicDividingLine``"
|
||||
msgstr "``DynamicDividingLine``"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:31
|
||||
msgid ""
|
||||
"``DynamicDividingLine`` создаёт линию, длина которой **динамически** "
|
||||
"подстраивается под самую длинную строку в выводе команды. Это требует "
|
||||
"перехвата ``stdout``, в результате чего разделители идеально обрамляют "
|
||||
"выводимый контент."
|
||||
msgstr ""
|
||||
"``DynamicDividingLine`` creates a line whose length **dynamically** adjusts to the longest "
|
||||
"line in the command output. This requires capturing ``stdout``, resulting in dividers that "
|
||||
"perfectly frame the output content."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:38
|
||||
msgid "Создаёт экземпляр динамической разделительной линии."
|
||||
msgstr "Creates a dynamic dividing line instance."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:40
|
||||
msgid "``unit_part``: Символ для построения линии. По умолчанию: ``-``."
|
||||
msgstr "``unit_part``: Character for building the line. Defaults to: ``-``."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:42
|
||||
msgid "Длина вычисляется автоматически и не задаётся при инициализации."
|
||||
msgstr "Length is calculated automatically and is not set during initialization."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:45
|
||||
msgid ""
|
||||
"Обязательно почитайте про нюансы использования динамических линий и "
|
||||
"перехвата ``stdout`` в :ref:`этом разделе<root_redirect_stdout>`."
|
||||
msgstr ""
|
||||
"Be sure to read about the nuances of using dynamic lines and capturing ``stdout`` "
|
||||
"in :ref:`this section<root_redirect_stdout>`."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:50
|
||||
msgid "Назначение и использование"
|
||||
msgstr "Purpose and Usage"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:52
|
||||
msgid "Выбор между статической и динамической линией зависит от ваших задач."
|
||||
msgstr "The choice between static and dynamic lines depends on your needs."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:54
|
||||
msgid "**StaticDividingLine** идеально подходит, если:"
|
||||
msgstr "**StaticDividingLine** is ideal if:"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:56
|
||||
msgid "Вам нужен строгий и консистентный дизайн."
|
||||
msgstr "You need a strict and consistent design."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:57
|
||||
msgid ""
|
||||
"Вы используете роутеры с отключённым перехватом ``stdout`` "
|
||||
"(``disable_redirect_stdout=True``), где динамическое вычисление длины "
|
||||
"невозможно."
|
||||
msgstr ""
|
||||
"You are using routers with disabled ``stdout`` capture (``disable_redirect_stdout=True``), "
|
||||
"where dynamic length calculation is not possible."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:59
|
||||
msgid ""
|
||||
"**DynamicDividingLine** (поведение по умолчанию) — предпочтительный "
|
||||
"выбор, если:"
|
||||
msgstr "**DynamicDividingLine** (default behavior) is the preferred choice if:"
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:61
|
||||
msgid "Вы хотите, чтобы интерфейс был адаптивным."
|
||||
msgstr "You want the interface to be adaptive."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:62
|
||||
msgid "Вывод ваших команд имеет разную длину."
|
||||
msgstr "Your command output has varying lengths."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:63
|
||||
msgid ""
|
||||
"В ваших обработчиках нет интерактивных операций ввода (например, "
|
||||
"``input()``)."
|
||||
msgstr "Your handlers do not have interactive input operations (e.g., ``input()``)."
|
||||
|
||||
#: ../../root/api/app/dividing_lines.rst:65
|
||||
msgid ""
|
||||
"Тип разделителя для всего приложения задаётся при инициализации ``App`` "
|
||||
"через параметр ``dividing_line``."
|
||||
msgstr ""
|
||||
"The divider type for the entire application is set during ``App`` initialization "
|
||||
"via the ``dividing_line`` parameter."
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) 2025, kolo
|
||||
# This file is distributed under the same license as the Argenta package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Argenta \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-02-06 23:44+0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: en\n"
|
||||
"Language-Team: en <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
|
||||
#: ../../root/api/app/index.rst:4
|
||||
msgid "App"
|
||||
msgstr "App"
|
||||
|
||||
#: ../../root/api/app/index.rst:6
|
||||
msgid ""
|
||||
"Объект ``App`` — это ядро вашего консольного приложения. Он отвечает за "
|
||||
"конфигурацию, управление жизненным циклом, обработку команд и "
|
||||
"взаимодействие с пользователем, координируя работу всех компонентов: "
|
||||
"роутеров, обработчиков и системных сообщений."
|
||||
msgstr ""
|
||||
"The ``App`` object is the implementations of your console application. It"
|
||||
" handles configuration, lifecycle management, command processing, and "
|
||||
"user interaction, coordinating the work of all components: routers, "
|
||||
"handlers, and system messages."
|
||||
|
||||
#: ../../root/api/app/index.rst:11
|
||||
msgid "Инициализация"
|
||||
msgstr "Initialization"
|
||||
|
||||
#: ../../root/api/app/index.rst:31
|
||||
msgid "Создаёт и настраивает экземпляр приложения."
|
||||
msgstr "Creates and configures an application instance."
|
||||
|
||||
#: ../../root/api/app/index.rst:33
|
||||
msgid "``prompt``: Приглашение к вводу, отображаемое перед каждой командой."
|
||||
msgstr "``prompt``: Input prompt displayed before each command."
|
||||
|
||||
#: ../../root/api/app/index.rst:34
|
||||
msgid "``initial_message``: Сообщение, выводимое при запуске приложения."
|
||||
msgstr "``initial_message``: Message displayed when the application starts."
|
||||
|
||||
#: ../../root/api/app/index.rst:35
|
||||
msgid "``farewell_message``: Сообщение, выводимое при выходе из приложения."
|
||||
msgstr "``farewell_message``: Message displayed when exiting the application."
|
||||
|
||||
#: ../../root/api/app/index.rst:36
|
||||
msgid ""
|
||||
"``exit_command``: Команда, которая маркируется как триггер для выхода из "
|
||||
"приложения."
|
||||
msgstr ""
|
||||
"``exit_command``: Command that is marked as a trigger for exiting the "
|
||||
"application."
|
||||
|
||||
#: ../../root/api/app/index.rst:37
|
||||
msgid ""
|
||||
"``system_router_title``: Заголовок для системного роутера (содержит "
|
||||
"команду выхода)."
|
||||
msgstr ""
|
||||
"``system_router_title``: Title for the system router (contains the exit "
|
||||
"command)."
|
||||
|
||||
#: ../../root/api/app/index.rst:38
|
||||
msgid ""
|
||||
"``dividing_line``: Тип разделительной линии (``StaticDividingLine`` или "
|
||||
"``DynamicDividingLine``)."
|
||||
msgstr ""
|
||||
"``dividing_line``: Type of dividing line (``StaticDividingLine`` or "
|
||||
"``DynamicDividingLine``)."
|
||||
|
||||
#: ../../root/api/app/index.rst:39
|
||||
msgid ""
|
||||
"``repeat_command_groups_printing``: Если ``True``, список доступных "
|
||||
"команд выводится перед каждым вводом."
|
||||
msgstr ""
|
||||
"``repeat_command_groups_printing``: If ``True``, the list of available "
|
||||
"commands is displayed before each input."
|
||||
|
||||
#: ../../root/api/app/index.rst:40
|
||||
msgid ""
|
||||
"``override_system_messages``: Если ``True``, стандартное форматирование "
|
||||
"(цвета, ASCII-арт) отключается."
|
||||
msgstr ""
|
||||
"``override_system_messages``: If ``True``, standard formatting (colors, "
|
||||
"ASCII art) is disabled."
|
||||
|
||||
#: ../../root/api/app/index.rst:41
|
||||
msgid ""
|
||||
"``autocompleter``: Экземпляр класса :ref:`AutoCompleter "
|
||||
"<root_api_app_autocompleter>`, отвечающий за автодополнение команд."
|
||||
msgstr ""
|
||||
"``autocompleter``: Instance of the :ref:`AutoCompleter "
|
||||
"<root_api_app_autocompleter>` class responsible for command "
|
||||
"autocompletion."
|
||||
|
||||
#: ../../root/api/app/index.rst:42
|
||||
#, fuzzy
|
||||
msgid "``printer``: Функция для вывода всех системных сообщений."
|
||||
msgstr ""
|
||||
"``print_func``: Function for outputting all system messages (defaults to "
|
||||
"``rich.Console().print``)."
|
||||
|
||||
#: ../../root/api/app/index.rst:47
|
||||
msgid ""
|
||||
"В приложениях на Argenta регистр вводимых команд не важен, проверка на "
|
||||
"существование и роутинг команд производится на основании триггеров, "
|
||||
"приведённых к нижнему регистру."
|
||||
msgstr ""
|
||||
"In applications on Argenta, the case of the entered commands is not "
|
||||
"important, checking for the existence and routing of commands is "
|
||||
"performed based on triggers reduced to lowercase."
|
||||
|
||||
#: ../../root/api/app/index.rst:50
|
||||
msgid "Основные методы"
|
||||
msgstr "Main Methods"
|
||||
|
||||
#: ../../root/api/app/index.rst:54
|
||||
msgid ""
|
||||
"Регистрирует роутер в приложении. Все команды из этого роутера становятся"
|
||||
" доступными для вызова."
|
||||
msgstr ""
|
||||
"Registers a router in the application. All commands from this router "
|
||||
"become available for invocation."
|
||||
|
||||
#: ../../root/api/app/index.rst
|
||||
msgid "Parameters"
|
||||
msgstr "Parameters"
|
||||
|
||||
#: ../../root/api/app/index.rst:56
|
||||
msgid "Экземпляр ``Router`` для регистрации."
|
||||
msgstr "``Router`` instance to register."
|
||||
|
||||
#: ../../root/api/app/index.rst:60
|
||||
msgid "Регистрирует несколько роутеров одновременно."
|
||||
msgstr "Registers multiple routers simultaneously."
|
||||
|
||||
#: ../../root/api/app/index.rst:62
|
||||
msgid "Последовательность экземпляров ``Router`` для регистрации."
|
||||
msgstr "Sequence of ``Router`` instances to register."
|
||||
|
||||
#: ../../root/api/app/index.rst:66
|
||||
msgid ""
|
||||
"Добавляет текстовое сообщение, которое выводится при запуске приложения "
|
||||
"после ``initial_message``."
|
||||
msgstr ""
|
||||
"Adds a text message that is displayed when the application starts after "
|
||||
"``initial_message``."
|
||||
|
||||
#: ../../root/api/app/index.rst:68
|
||||
msgid "Строка с сообщением."
|
||||
msgstr "String with the message."
|
||||
|
||||
#: ../../root/api/app/index.rst:71
|
||||
msgid ""
|
||||
"Для вывода стандартных сообщений можно использовать готовые шаблоны из "
|
||||
":ref:`PredefinedMessages <root_api_predefined_messages>`."
|
||||
msgstr ""
|
||||
"For outputting standard messages, you can use ready-made templates from "
|
||||
":ref:`PredefinedMessages <root_api_predefined_messages>`."
|
||||
|
||||
#: ../../root/api/app/index.rst:76
|
||||
msgid "Методы установки обработчиков"
|
||||
msgstr "Handler Setup Methods"
|
||||
|
||||
#: ../../root/api/app/index.rst:78
|
||||
msgid ""
|
||||
"``App`` позволяет настраивать реакцию на различные события, такие как "
|
||||
"ошибки ввода или неизвестные команды."
|
||||
msgstr ""
|
||||
"``App`` allows you to configure responses to various events, such as "
|
||||
"input errors or unknown commands."
|
||||
|
||||
#: ../../root/api/app/index.rst:81
|
||||
msgid ""
|
||||
"Подробнее об исключениях и их обработке в соответствующем :ref:`разделе "
|
||||
"документации <root_error_handling>`."
|
||||
msgstr ""
|
||||
"For more details on exceptions and their handling, see the corresponding "
|
||||
":ref:`documentation section <root_error_handling>`."
|
||||
|
||||
#: ../../root/api/app/index.rst:87
|
||||
msgid "Устанавливает шаблон для форматирования описания команды."
|
||||
msgstr "Sets the template for formatting command descriptions."
|
||||
|
||||
#: ../../root/api/app/index.rst:89
|
||||
msgid "Обработчик принимает триггер команды (``str``) и её описание (``str``)."
|
||||
msgstr ""
|
||||
"The handler accepts the command trigger (``str``) and its description "
|
||||
"(``str``)."
|
||||
|
||||
#: ../../root/api/app/index.rst:95
|
||||
msgid "Устанавливает обработчик при некорректном введённом синтаксисе флагов."
|
||||
msgstr "Sets the handler for incorrect flag syntax input."
|
||||
|
||||
#: ../../root/api/app/index.rst:97 ../../root/api/app/index.rst:105
|
||||
msgid "Обработчик принимает строку, введённую пользователем."
|
||||
msgstr "The handler accepts the string entered by the user."
|
||||
|
||||
#: ../../root/api/app/index.rst:103
|
||||
msgid "Устанавливает обработчик при повторяющихся флагах в введённой команде."
|
||||
msgstr "Sets the handler for duplicate flags in the entered command."
|
||||
|
||||
#: ../../root/api/app/index.rst:111
|
||||
msgid "Устанавливает обработчик при вводе неизвестной команды."
|
||||
msgstr "Sets the handler for entering an unknown command."
|
||||
|
||||
#: ../../root/api/app/index.rst:113
|
||||
msgid "Обработчик принимает объект ``InputCommand`` - объект введённой команды."
|
||||
msgstr ""
|
||||
"The handler accepts an ``InputCommand`` object - the entered command "
|
||||
"object."
|
||||
|
||||
#: ../../root/api/app/index.rst:119
|
||||
msgid "Устанавливает обработчик при вводе пустой строки."
|
||||
msgstr "Sets the handler for entering an empty string."
|
||||
|
||||
#: ../../root/api/app/index.rst:121
|
||||
msgid "Обработчик не принимает аргументов."
|
||||
msgstr "The handler accepts no arguments."
|
||||
|
||||
#: ../../root/api/app/index.rst:127
|
||||
msgid "Переопределяет стандартное поведение при вызове команды выхода."
|
||||
msgstr "Overrides the default behavior when the exit command is invoked."
|
||||
|
||||
#: ../../root/api/app/index.rst:129
|
||||
msgid "Обработчик принимает объект ``Response``."
|
||||
msgstr "The handler accepts a ``Response`` object."
|
||||
|
||||
#: ../../root/api/app/index.rst:142
|
||||
msgid "PredefinedMessages"
|
||||
msgstr "PredefinedMessages"
|
||||
|
||||
#: ../../root/api/app/index.rst:144
|
||||
msgid ""
|
||||
"``PredefinedMessages`` — это контейнер, содержащий набор готовых к "
|
||||
"использованию сообщений. Они отформатированы с использованием синтаксиса "
|
||||
"``rich`` и предназначены для вывода стандартной информации, такой как "
|
||||
"подсказки по использованию."
|
||||
msgstr ""
|
||||
"``PredefinedMessages`` is a container containing a set of ready-to-use "
|
||||
"messages. They are formatted using ``rich`` syntax and are intended for "
|
||||
"displaying standard information, such as usage hints."
|
||||
|
||||
#: ../../root/api/app/index.rst:146
|
||||
msgid "Рекомендуется использовать их при старте приложения."
|
||||
msgstr "It is recommended to use them when starting the application."
|
||||
|
||||
#: ../../root/api/app/index.rst:173
|
||||
msgid "Строка: ``[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]``"
|
||||
msgstr "String: ``[b dim]Usage[/b dim]: [i]<command> <[green]flags[/green]>[/i]``"
|
||||
|
||||
#: ../../root/api/app/index.rst:175
|
||||
msgid "Отображается как: ``Usage: <command> <flags>``"
|
||||
msgstr "Displayed as: ``Usage: <command> <flags>``"
|
||||
|
||||
#: ../../root/api/app/index.rst:179
|
||||
msgid "Строка: ``[b dim]Help[/b dim]: [i]<command>[/i] [b red]--help[/b red]``"
|
||||
msgstr "String: ``[b dim]Help[/b dim]: [i]<command>[/i] [b red]--help[/b red]``"
|
||||
|
||||
#: ../../root/api/app/index.rst:181
|
||||
msgid "Отображается как: ``Help: <command> --help``"
|
||||
msgstr "Displayed as: ``Help: <command> --help``"
|
||||
|
||||
#: ../../root/api/app/index.rst:185
|
||||
msgid "Строка: ``[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>``"
|
||||
msgstr "String: ``[b dim]Autocomplete[/b dim]: [i]<part>[/i] [bold]<tab>``"
|
||||
|
||||
#: ../../root/api/app/index.rst:187
|
||||
msgid "Отображается как: ``Autocomplete: <part> <tab>``"
|
||||
msgstr "Displayed as: ``Autocomplete: <part> <tab>``"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "``ignore_command_register``: Если ``True``, регистр"
|
||||
#~ " вводимых команд игнорируется при поиске"
|
||||
#~ " обработчика."
|
||||
#~ msgstr ""
|
||||
#~ "``ignore_command_register``: If ``True``, command"
|
||||
#~ " case is ignored when searching for"
|
||||
#~ " a handler."
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user