mirror of
https://github.com/koloideal/Argenta.git
synced 2026-08-08 17:01:13 +03:00
Compare commits
2 Commits
311b312ab4
..
cli
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f9c650ec6 | |||
| 6b9bea155f |
@@ -1,5 +1,6 @@
|
|||||||
#### joe made this: http://goel.io/joe
|
#### joe made this: http://goel.io/joe
|
||||||
|
|
||||||
|
.devin
|
||||||
metrics/reports/diagrams
|
metrics/reports/diagrams
|
||||||
*.dist
|
*.dist
|
||||||
*build
|
*build
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Argenta
|
||||||
|
|
||||||
|
## Agent skills
|
||||||
|
|
||||||
|
### Issue tracker
|
||||||
|
|
||||||
|
Issues live as GitHub issues in `koloideal/Argenta` (uses the `gh` CLI). See `docs/agents/issue-tracker.md`.
|
||||||
|
|
||||||
|
### Triage labels
|
||||||
|
|
||||||
|
Five canonical labels used as-is: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
|
||||||
|
|
||||||
|
### Domain docs
|
||||||
|
|
||||||
|
Single-context — one `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`.
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Domain Docs
|
||||||
|
|
||||||
|
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
||||||
|
|
||||||
|
## Before exploring, read these
|
||||||
|
|
||||||
|
- **`CONTEXT.md`** at the repo root, or
|
||||||
|
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
||||||
|
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
||||||
|
|
||||||
|
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
|
||||||
|
Single-context repo (most repos):
|
||||||
|
|
||||||
|
```
|
||||||
|
/
|
||||||
|
├── CONTEXT.md
|
||||||
|
├── docs/adr/
|
||||||
|
│ ├── 0001-event-sourced-orders.md
|
||||||
|
│ └── 0002-postgres-for-write-model.md
|
||||||
|
└── src/
|
||||||
|
```
|
||||||
|
|
||||||
|
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
||||||
|
|
||||||
|
```
|
||||||
|
/
|
||||||
|
├── CONTEXT-MAP.md
|
||||||
|
├── docs/adr/ ← system-wide decisions
|
||||||
|
└── src/
|
||||||
|
├── ordering/
|
||||||
|
│ ├── CONTEXT.md
|
||||||
|
│ └── docs/adr/ ← context-specific decisions
|
||||||
|
└── billing/
|
||||||
|
├── CONTEXT.md
|
||||||
|
└── docs/adr/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use the glossary's vocabulary
|
||||||
|
|
||||||
|
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
||||||
|
|
||||||
|
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
||||||
|
|
||||||
|
## Flag ADR conflicts
|
||||||
|
|
||||||
|
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
||||||
|
|
||||||
|
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Issue tracker: GitHub
|
||||||
|
|
||||||
|
Issues and PRDs for this repo live as GitHub issues. Use the `gh` CLI for all operations.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies.
|
||||||
|
- **Read an issue**: `gh issue view <number> --comments`, filtering comments by `jq` and also fetching labels.
|
||||||
|
- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters.
|
||||||
|
- **Comment on an issue**: `gh issue comment <number> --body "..."`
|
||||||
|
- **Apply / remove labels**: `gh issue edit <number> --add-label "..."` / `--remove-label "..."`
|
||||||
|
- **Close**: `gh issue close <number> --comment "..."`
|
||||||
|
|
||||||
|
Infer the repo from `git remote -v` — `gh` does this automatically when run inside a clone.
|
||||||
|
|
||||||
|
## Pull requests as a triage surface
|
||||||
|
|
||||||
|
**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_
|
||||||
|
|
||||||
|
When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents:
|
||||||
|
|
||||||
|
- **Read a PR**: `gh pr view <number> --comments` and `gh pr diff <number>` for the diff.
|
||||||
|
- **List external PRs for triage**: `gh pr list --state open --json number,title,body,labels,author,authorAssociation,comments` then keep only `authorAssociation` of `CONTRIBUTOR`, `FIRST_TIME_CONTRIBUTOR`, or `NONE` (drop `OWNER`/`MEMBER`/`COLLABORATOR`).
|
||||||
|
- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`.
|
||||||
|
|
||||||
|
GitHub shares one number space across issues and PRs, so a bare `#42` may be either — resolve with `gh pr view 42` and fall back to `gh issue view 42`.
|
||||||
|
|
||||||
|
## When a skill says "publish to the issue tracker"
|
||||||
|
|
||||||
|
Create a GitHub issue.
|
||||||
|
|
||||||
|
## When a skill says "fetch the relevant ticket"
|
||||||
|
|
||||||
|
Run `gh issue view <number> --comments`.
|
||||||
|
|
||||||
|
## Wayfinding operations
|
||||||
|
|
||||||
|
Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets.
|
||||||
|
|
||||||
|
- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`.
|
||||||
|
- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #<map>` at the top of the child body. Labels: `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev.
|
||||||
|
- **Blocking**: GitHub's **native issue dependencies** — the canonical, UI-visible representation. Add an edge with `gh api --method POST repos/<owner>/<repo>/issues/<child>/dependencies/blocked_by -F issue_id=<blocker-db-id>`, where `<blocker-db-id>` is the blocker's numeric **database id** (`gh api repos/<owner>/<repo>/issues/<n> --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only — the live gate). Where dependencies aren't available, fall back to a `Blocked by: #<n>, #<n>` line at the top of the child body. A ticket is unblocked when every blocker is closed.
|
||||||
|
- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins.
|
||||||
|
- **Claim**: `gh issue edit <n> --add-assignee @me` — the session's first write.
|
||||||
|
- **Resolve**: `gh issue comment <n> --body "<answer>"`, then `gh issue close <n>`, then append a context pointer (gist + link) to the map's Decisions-so-far.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Triage Labels
|
||||||
|
|
||||||
|
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
|
||||||
|
|
||||||
|
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
||||||
|
| -------------------------- | -------------------- | ---------------------------------------- |
|
||||||
|
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
||||||
|
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
||||||
|
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
||||||
|
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
||||||
|
| `wontfix` | `wontfix` | Will not be actioned |
|
||||||
|
|
||||||
|
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
||||||
|
|
||||||
|
Edit the right-hand column to match whatever vocabulary you actually use.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from argenta import App, Orchestrator
|
||||||
|
from argenta.command import Router, Command, Response
|
||||||
|
|
||||||
|
router = Router(title="Example")
|
||||||
|
|
||||||
|
@router.command(Command("hello", description="Say hello"))
|
||||||
|
def hello_handler(response: Response):
|
||||||
|
print("Hello, world!")
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> App:
|
||||||
|
app = App()
|
||||||
|
app.include_router(router)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
orchestrator = Orchestrator()
|
||||||
|
orchestrator.run_repl(create_app())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from argenta import App, Orchestrator
|
||||||
|
from argenta.command import Router, Command, Response
|
||||||
|
|
||||||
|
router = Router(title="Example")
|
||||||
|
|
||||||
|
@router.command(Command("hello", description="Say hello"))
|
||||||
|
def hello_handler(response: Response):
|
||||||
|
print("Hello, world!")
|
||||||
|
|
||||||
|
app = App()
|
||||||
|
app.include_router(router)
|
||||||
|
|
||||||
|
orchestrator = Orchestrator()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
orchestrator.run_repl(app)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
my_project/
|
||||||
|
├── main.py
|
||||||
|
├── handlers.py
|
||||||
|
└── .gitignore
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
my_project/
|
||||||
|
├── src/
|
||||||
|
│ └── my_project/
|
||||||
|
│ └── application/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── __main__.py
|
||||||
|
│ ├── routers.py
|
||||||
|
│ └── handlers/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── hello_world_handler.py
|
||||||
|
└── .gitignore
|
||||||
@@ -56,6 +56,7 @@ Argenta предназначена для создания приложений,
|
|||||||
|
|
||||||
root/redirect_stdout
|
root/redirect_stdout
|
||||||
root/dependency_injection
|
root/dependency_injection
|
||||||
|
root/cli
|
||||||
root/testing
|
root/testing
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
|||||||
@@ -0,0 +1,600 @@
|
|||||||
|
# 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-08-03 18:51+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/cli.rst:4
|
||||||
|
msgid "CLI"
|
||||||
|
msgstr "CLI"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:6
|
||||||
|
msgid ""
|
||||||
|
"Помимо библиотеки, ``Argenta`` поставляется с собственным "
|
||||||
|
"CLI-инструментом. Он берёт на себя рутину, которая сопровождает "
|
||||||
|
"разработку CLI-приложений: создаёт каркас проекта, запускает приложение, "
|
||||||
|
"инспектирует зарегистрированные маршруты и собирает standalone-бинарник."
|
||||||
|
msgstr ""
|
||||||
|
"In addition to the library, ``Argenta`` ships with its own CLI tool. It "
|
||||||
|
"takes over the routine that surrounds CLI app development: scaffolds a "
|
||||||
|
"project, runs the application, inspects registered routes, and builds a "
|
||||||
|
"standalone binary."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:8
|
||||||
|
msgid ""
|
||||||
|
"CLI поставляется как опциональная зависимость — основная библиотека "
|
||||||
|
"остаётся лёгкой, а инструмент доступен только тем, кому он нужен."
|
||||||
|
msgstr ""
|
||||||
|
"The CLI is shipped as an optional dependency — the core library stays "
|
||||||
|
"light, and the tool is available only to those who need it."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:11
|
||||||
|
msgid "Установка"
|
||||||
|
msgstr "Installation"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:13
|
||||||
|
msgid "CLI доступен как опциональная зависимость ``[cli]``:"
|
||||||
|
msgstr "The CLI is available as the optional ``[cli]`` dependency:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:23
|
||||||
|
msgid "После установки команда ``argenta`` доступна в терминале:"
|
||||||
|
msgstr "After installation, the ``argenta`` command is available in the terminal:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:34
|
||||||
|
msgid ""
|
||||||
|
"Если ``argenta`` установлена без extras, команда ``argenta`` не будет "
|
||||||
|
"доступна. Установите с ``[cli]``, чтобы получить доступ к "
|
||||||
|
"CLI-инструменту."
|
||||||
|
msgstr ""
|
||||||
|
"If ``argenta`` is installed without extras, the ``argenta`` command will "
|
||||||
|
"not be available. Install with ``[cli]`` to get access to the CLI tool."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:37
|
||||||
|
msgid "Флаг ``--version``"
|
||||||
|
msgstr "The ``--version`` flag"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:39
|
||||||
|
msgid "Показать установленную версию ``Argenta``:"
|
||||||
|
msgstr "Show the installed ``Argenta`` version:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:45
|
||||||
|
msgid "Аналогично через короткий флаг:"
|
||||||
|
msgstr "Equivalently, via the short flag:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:56
|
||||||
|
msgid "Формат entrypoint"
|
||||||
|
msgstr "Entrypoint format"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:58
|
||||||
|
msgid ""
|
||||||
|
"Команды ``run``, ``routes`` и ``build`` принимают **entrypoint** — "
|
||||||
|
"указатель на объект внутри проекта, который нужно запустить, "
|
||||||
|
"инспектировать или собрать. Единый формат описан здесь, чтобы не "
|
||||||
|
"повторяться в каждой команде."
|
||||||
|
msgstr ""
|
||||||
|
"The ``run``, ``routes`` and ``build`` commands accept an **entrypoint** —"
|
||||||
|
" a pointer to an object inside the project to run, inspect, or build. The"
|
||||||
|
" shared format is documented here once instead of repeating it in each "
|
||||||
|
"command."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:60
|
||||||
|
msgid "Формат entrypoint:"
|
||||||
|
msgstr "Entrypoint format:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:67
|
||||||
|
msgid "Поддерживаются два способа адресации:"
|
||||||
|
msgstr "Two addressing styles are supported:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:69
|
||||||
|
msgid ""
|
||||||
|
"**Путь к файлу** — ``app/main.py:main``. Удобно при работе с конкретным "
|
||||||
|
"файлом."
|
||||||
|
msgstr ""
|
||||||
|
"**File path** — ``app/main.py:main``. Convenient when working with a "
|
||||||
|
"specific file."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:70
|
||||||
|
msgid ""
|
||||||
|
"**Dotted-модуль** — ``my_project.application:main``. Естественно для "
|
||||||
|
"установленных пакетов."
|
||||||
|
msgstr ""
|
||||||
|
"**Dotted module** — ``my_project.application:main``. Natural for "
|
||||||
|
"installed packages."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:72
|
||||||
|
msgid ""
|
||||||
|
"Если передан путь к директории с ``__main__.py``, он разрешается "
|
||||||
|
"автоматически — указывать файл явно не нужно."
|
||||||
|
msgstr ""
|
||||||
|
"If a directory path containing ``__main__.py`` is passed, it is resolved "
|
||||||
|
"automatically — no need to name the file explicitly."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:74
|
||||||
|
msgid "**Примеры валидных entrypoint-ов:**"
|
||||||
|
msgstr "**Examples of valid entrypoints:**"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:84
|
||||||
|
msgid ""
|
||||||
|
"Тип объекта зависит от команды: ``run`` и ``build`` ожидают callable, "
|
||||||
|
"``routes`` — инстанс ``App`` или callable, возвращающий ``App``."
|
||||||
|
msgstr ""
|
||||||
|
"The expected object type depends on the command: ``run`` and ``build`` "
|
||||||
|
"expect a callable, ``routes`` expects an ``App`` instance or a callable "
|
||||||
|
"returning ``App``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:89
|
||||||
|
msgid "Создание проектов"
|
||||||
|
msgstr "Scaffolding projects"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:92
|
||||||
|
msgid "Команда ``new``"
|
||||||
|
msgstr "The ``new`` command"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:94
|
||||||
|
msgid ""
|
||||||
|
"Создаёт новую директорию проекта с boilerplate-кодом. Это отправная "
|
||||||
|
"точка: вместо ручной настройки структуры — готовый каркас за одну "
|
||||||
|
"команду."
|
||||||
|
msgstr ""
|
||||||
|
"Creates a new project directory with boilerplate code. It is the starting"
|
||||||
|
" point: instead of setting up the structure by hand, a ready-made "
|
||||||
|
"skeleton in a single command."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:100
|
||||||
|
msgid "``project_name`` — имя директории проекта (обязательный аргумент)."
|
||||||
|
msgstr "``project_name`` — project directory name (required argument)."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:101 ../../root/cli.rst:133
|
||||||
|
msgid "``--arch`` — архитектура проекта: ``flat`` (по умолчанию) или ``src``."
|
||||||
|
msgstr "``--arch`` — project architecture: ``flat`` (default) or ``src``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:103 ../../root/cli.rst:135 ../../root/cli.rst:161
|
||||||
|
#: ../../root/cli.rst:191
|
||||||
|
msgid "**Примеры:**"
|
||||||
|
msgstr "**Examples:**"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:110
|
||||||
|
msgid "При архитектуре ``flat`` создаётся следующая структура:"
|
||||||
|
msgstr "With the ``flat`` architecture, the following structure is created:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:115
|
||||||
|
msgid "При архитектуре ``src``:"
|
||||||
|
msgstr "With the ``src`` architecture:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:125
|
||||||
|
msgid "Команда ``init``"
|
||||||
|
msgstr "The ``init`` command"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:127
|
||||||
|
msgid ""
|
||||||
|
"Делает то же, что и ``new``, но в текущей директории. Удобно, когда "
|
||||||
|
"проект уже существует и нужно добавить структуру Argenta, не создавая "
|
||||||
|
"лишний уровень вложенности."
|
||||||
|
msgstr ""
|
||||||
|
"Does the same as ``new``, but in the current directory. Convenient when "
|
||||||
|
"the project already exists and the Argenta structure needs to be added "
|
||||||
|
"without introducing an extra level of nesting."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:143
|
||||||
|
msgid ""
|
||||||
|
"Команда ``init`` не перезаписывает существующие файлы — они будут "
|
||||||
|
"пропущены."
|
||||||
|
msgstr "The ``init`` command does not overwrite existing files — they are skipped."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:148
|
||||||
|
msgid "Запуск приложения"
|
||||||
|
msgstr "Running an application"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:151
|
||||||
|
msgid "Команда ``run``"
|
||||||
|
msgstr "The ``run`` command"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:153
|
||||||
|
msgid ""
|
||||||
|
"Запускает оркестратор ``Argenta`` из callable-entrypoint. Это "
|
||||||
|
"альтернатива прямому вызову ``python main.py``, но с автоматической "
|
||||||
|
"настройкой окружения."
|
||||||
|
msgstr ""
|
||||||
|
"Starts the ``Argenta`` orchestrator from a callable entrypoint. It is an "
|
||||||
|
"alternative to calling ``python main.py`` directly, but with automatic "
|
||||||
|
"environment setup."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:159 ../../root/cli.rst:189 ../../root/cli.rst:249
|
||||||
|
msgid "Формат entrypoint — см. :ref:`Формат entrypoint <cli_entrypoint>`."
|
||||||
|
msgstr "Entrypoint format — see :ref:`Entrypoint format <cli_entrypoint>`."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:173
|
||||||
|
msgid ""
|
||||||
|
"Команда ``run`` устанавливает переменную окружения "
|
||||||
|
"``RUN_FROM_ARGENTA_RUNNER=1``. ``ArgParser`` видит этот флаг и пропускает"
|
||||||
|
" парсинг ``sys.argv``, поэтому аргументы самого ``argenta`` (типа "
|
||||||
|
"``--help``, ``--version``) не конфликтуют с аргументами запускаемого "
|
||||||
|
"приложения. REPL стартует чисто, без ошибок про неизвестные флаги."
|
||||||
|
msgstr ""
|
||||||
|
"The ``run`` command sets the ``RUN_FROM_ARGENTA_RUNNER=1`` environment "
|
||||||
|
"variable. ``ArgParser`` sees this flag and skips parsing ``sys.argv``, so"
|
||||||
|
" the ``argenta`` arguments themselves (such as ``--help``, ``--version``)"
|
||||||
|
" do not conflict with the arguments of the application being launched. "
|
||||||
|
"The REPL starts cleanly, with no errors about unknown flags."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:178
|
||||||
|
msgid "Инспекция маршрутов"
|
||||||
|
msgstr "Inspecting routes"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:181
|
||||||
|
msgid "Команда ``routes``"
|
||||||
|
msgstr "The ``routes`` command"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:183
|
||||||
|
msgid ""
|
||||||
|
"Отображает все зарегистрированные роутеры, команды, алиасы и флаги в виде"
|
||||||
|
" дерева. Принимает как инстанс ``App``, так и callable, возвращающий "
|
||||||
|
"``App``."
|
||||||
|
msgstr ""
|
||||||
|
"Displays all registered routers, commands, aliases, and flags as a tree. "
|
||||||
|
"Accepts either an ``App`` instance or a callable returning ``App``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:198
|
||||||
|
msgid ""
|
||||||
|
"Инстанс ``App`` передаётся напрямую, если роутеры подключены на уровне "
|
||||||
|
"модуля:"
|
||||||
|
msgstr ""
|
||||||
|
"An ``App`` instance is passed directly when routers are registered at the"
|
||||||
|
" module level:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:204
|
||||||
|
msgid ""
|
||||||
|
"Фабрика ``create_app`` передаётся, если роутеры регистрируются внутри "
|
||||||
|
"функции — например, зависят от конфига или DI:"
|
||||||
|
msgstr ""
|
||||||
|
"A ``create_app`` factory is passed when routers are registered inside a "
|
||||||
|
"function — for example, when they depend on config or DI:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:211
|
||||||
|
msgid ""
|
||||||
|
"При использовании callable-entrypoint REPL не запускается — фабрика "
|
||||||
|
"вызывается, и маршруты считываются из возвращённого ``App``."
|
||||||
|
msgstr ""
|
||||||
|
"When a callable entrypoint is used, the REPL is not started — the factory"
|
||||||
|
" is called, and routes are read from the returned ``App``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:213 ../../root/cli.rst:325
|
||||||
|
msgid "Пример вывода:"
|
||||||
|
msgstr "Example output:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:238
|
||||||
|
msgid "Сборка бинарника"
|
||||||
|
msgstr "Building a binary"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:241
|
||||||
|
msgid "Команда ``build``"
|
||||||
|
msgstr "The ``build`` command"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:243
|
||||||
|
msgid ""
|
||||||
|
"Компилирует проект в standalone-бинарник с помощью `Nuitka "
|
||||||
|
"<https://nuitka.net/>`_, которая входит в ``[cli]`` extra."
|
||||||
|
msgstr ""
|
||||||
|
"Compiles a project into a standalone binary using `Nuitka "
|
||||||
|
"<https://nuitka.net/>`_, which is included in the ``[cli]`` extra."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:251
|
||||||
|
msgid ""
|
||||||
|
"``--output`` / ``-o`` — имя выходного бинарника (по умолчанию — имя файла"
|
||||||
|
" или пакета)."
|
||||||
|
msgstr ""
|
||||||
|
"``--output`` / ``-o`` — output binary name (defaults to the file or "
|
||||||
|
"package name)."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:252
|
||||||
|
msgid ""
|
||||||
|
"``--`` — разделитель, после которого передаются **произвольные флаги "
|
||||||
|
"Nuitka**. Они добавляются к вызову Nuitka после аргументов Argenta, "
|
||||||
|
"поэтому могут переопределять дефолты и добавлять любые опции, которые "
|
||||||
|
"Nuitka поддерживает."
|
||||||
|
msgstr ""
|
||||||
|
"``--`` — a separator after which **arbitrary Nuitka flags** are passed."
|
||||||
|
" They are appended to the Nuitka invocation after Argenta's arguments,"
|
||||||
|
" so they can override defaults and add any options Nuitka supports."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:254
|
||||||
|
#, fuzzy
|
||||||
|
msgid "**Базовые примеры:**"
|
||||||
|
msgstr "**Basic examples:**"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:262
|
||||||
|
msgid "**Примеры с флагами Nuitka:**"
|
||||||
|
msgstr "**Examples with Nuitka flags:**"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:271
|
||||||
|
msgid "Что делает Argenta по умолчанию"
|
||||||
|
msgstr "What Argenta does by default"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:273
|
||||||
|
msgid "Команда ``build`` формирует вызов Nuitka со следующими аргументами:"
|
||||||
|
msgstr "The ``build`` command assembles a Nuitka invocation with the following arguments:"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:275
|
||||||
|
msgid ""
|
||||||
|
"``--standalone --onefile`` — собирает единый бинарник со всеми "
|
||||||
|
"зависимостями внутри."
|
||||||
|
msgstr ""
|
||||||
|
"``--standalone --onefile`` — builds a single binary with all dependencies"
|
||||||
|
" bundled inside."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:276
|
||||||
|
msgid ""
|
||||||
|
"``--output-filename=<name>`` — имя выходного файла (из ``--output`` или "
|
||||||
|
"имени entrypoint)."
|
||||||
|
msgstr ""
|
||||||
|
"``--output-filename=<name>`` — output file name (from ``--output`` or the"
|
||||||
|
" entrypoint name)."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:277
|
||||||
|
msgid "``--jobs=<cpu_count>`` — параллельная компиляция на всех ядрах."
|
||||||
|
msgstr "``--jobs=<cpu_count>`` — parallel compilation across all cores."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:278
|
||||||
|
msgid ""
|
||||||
|
"``--lto=no`` — LTO отключён по умолчанию (быстрее сборка, медленнее "
|
||||||
|
"запуск)."
|
||||||
|
msgstr ""
|
||||||
|
"``--lto=no`` — LTO is disabled by default (faster build, slower startup)."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:279
|
||||||
|
msgid ""
|
||||||
|
"``--include-windows-runtime-dlls=no`` — на Windows не включает runtime "
|
||||||
|
"DLL в бинарник."
|
||||||
|
msgstr ""
|
||||||
|
"``--include-windows-runtime-dlls=no`` — on Windows, runtime DLLs are not"
|
||||||
|
" bundled into the binary."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:280
|
||||||
|
msgid ""
|
||||||
|
"``--python-flag=-m`` — добавляется автоматически, если entrypoint "
|
||||||
|
"указывает на ``__main__.py``."
|
||||||
|
msgstr ""
|
||||||
|
"``--python-flag=-m`` — added automatically when the entrypoint points at"
|
||||||
|
" a ``__main__.py``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:282
|
||||||
|
msgid ""
|
||||||
|
"Все эти дефолты можно переопределить, передав соответствующий флаг после "
|
||||||
|
"``--``. Например, ``-- --lto=yes`` включит LTO, а ``-- --jobs=1`` "
|
||||||
|
"отключит параллельную сборку."
|
||||||
|
msgstr ""
|
||||||
|
"Any of these defaults can be overridden by passing the corresponding flag"
|
||||||
|
" after ``--``. For example, ``-- --lto=yes`` enables LTO, and ``--"
|
||||||
|
" --jobs=1`` disables parallel compilation."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:285
|
||||||
|
msgid "Основные флаги Nuitka и их нюансы"
|
||||||
|
msgstr "Key Nuitka flags and their trade-offs"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:287
|
||||||
|
msgid ""
|
||||||
|
"Полный список флагов — в `документации Nuitka <https://nuitka.net/user-"
|
||||||
|
"documentation/user-manual.html>`_. Ниже — те, с которыми чаще всего "
|
||||||
|
"сталкиваются при сборке CLI-приложений."
|
||||||
|
msgstr ""
|
||||||
|
"The full flag list is in the `Nuitka documentation <https://nuitka.net"
|
||||||
|
"/user-documentation/user-manual.html>`_. Below are the ones most often"
|
||||||
|
" encountered when building CLI applications."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:289
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "``--lto={yes,no,auto}``"
|
||||||
|
msgstr "``--lto={yes,no,auto}``"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:290
|
||||||
|
msgid ""
|
||||||
|
"Link-Time Optimization. ``yes`` — бинарник меньше и быстрее запускается, "
|
||||||
|
"но сборка длится заметно дольше. ``no`` (дефолт Argenta) — сборка "
|
||||||
|
"быстрее, бинарник больше. ``auto`` — Nuitka выбирает сам. Для "
|
||||||
|
"production-сборки имеет смысл ``yes``, для итеративной разработки — "
|
||||||
|
"``no``."
|
||||||
|
msgstr ""
|
||||||
|
"Link-Time Optimization. ``yes`` — the binary is smaller and starts"
|
||||||
|
" faster, but the build takes noticeably longer. ``no`` (Argenta's"
|
||||||
|
" default) — faster build, larger binary. ``auto`` — Nuitka decides. For"
|
||||||
|
" production builds ``yes`` makes sense; for iterative development, ``no``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:292
|
||||||
|
msgid "``--include-package=<package>``"
|
||||||
|
msgstr "``--include-package=<package>``"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:293
|
||||||
|
msgid ""
|
||||||
|
"Явно включает пакет в бинарник. Nuitka отслеживает импорты статически, "
|
||||||
|
"поэтому пакеты, которые импортируются динамически (через ``importlib``, "
|
||||||
|
"плагины, ``__import__``), в бинарник не попадают — их нужно добавлять "
|
||||||
|
"вручную. Типичные кандидаты: ``numpy``, ``pandas``, ``rich``, "
|
||||||
|
"``prompt_toolkit``."
|
||||||
|
msgstr ""
|
||||||
|
"Explicitly includes a package in the binary. Nuitka tracks imports"
|
||||||
|
" statically, so packages imported dynamically (via ``importlib``, plugins,"
|
||||||
|
" ``__import__``) do not end up in the binary — they must be added by hand."
|
||||||
|
" Common candidates: ``numpy``, ``pandas``, ``rich``, ``prompt_toolkit``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:295
|
||||||
|
msgid "``--include-data-files=<source>=<dest>``"
|
||||||
|
msgstr "``--include-data-files=<source>=<dest>``"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:296
|
||||||
|
msgid ""
|
||||||
|
"Включает файлы данных (шаблоны, конфиги, ассеты) в бинарник. Формат: "
|
||||||
|
"``--include-data-files=assets/logo.png=assets/logo.png``. Для директорий "
|
||||||
|
"целиком — ``--include-data-dir=assets=assets``. Без этого файлы, которые "
|
||||||
|
"приложение читает во время выполнения, не будут найдены в собранном "
|
||||||
|
"бинарнике."
|
||||||
|
msgstr ""
|
||||||
|
"Includes data files (templates, configs, assets) into the binary. Format:"
|
||||||
|
" ``--include-data-files=assets/logo.png=assets/logo.png``. For whole"
|
||||||
|
" directories — ``--include-data-dir=assets=assets``. Without this, files"
|
||||||
|
" the application reads at runtime will not be found inside the built binary."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:298
|
||||||
|
msgid "``--enable-plugin=<plugin>``"
|
||||||
|
msgstr "``--enable-plugin=<plugin>``"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:299
|
||||||
|
msgid ""
|
||||||
|
"Включает `плагин Nuitka <https://nuitka.net/user-documentation/user-"
|
||||||
|
"manual.html#plugins>`_ для поддержки фреймворков, требующих специальной "
|
||||||
|
"обработки. Распространённые: ``anti-bloat`` (вырезает ненужные части "
|
||||||
|
"тяжёлых пакетов), ``numpy`` (корректная сборка с numpy), ``tk-inter`` "
|
||||||
|
"(Tkinter GUI), ``triton`` (PyTorch triton kernels)."
|
||||||
|
msgstr ""
|
||||||
|
"Enables a `Nuitka plugin <https://nuitka.net/user-documentation/user-"
|
||||||
|
"manual.html#plugins>`_ for frameworks that need special handling. Common"
|
||||||
|
" ones: ``anti-bloat`` (strips unneeded parts of heavy packages), ``numpy``"
|
||||||
|
" (correct numpy bundling), ``tk-inter`` (Tkinter GUI), ``triton`` (PyTorch"
|
||||||
|
" triton kernels)."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:301
|
||||||
|
msgid "``--onefile`` / ``--standalone``"
|
||||||
|
msgstr "``--onefile`` / ``--standalone``"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:302
|
||||||
|
msgid ""
|
||||||
|
"``--onefile`` (дефолт Argenta) — единый бинарник, удобный для "
|
||||||
|
"дистрибуции. При запуске распаковывается во временную директорию, поэтому"
|
||||||
|
" стартует медленнее. ``--standalone`` — папка с бинарником и "
|
||||||
|
"зависимостями, стартует быстрее, но дистрибуция — это вся папка целиком. "
|
||||||
|
"Чтобы переключиться: ``-- --standalone`` (переопределит дефолтный "
|
||||||
|
"``--onefile``)."
|
||||||
|
msgstr ""
|
||||||
|
"``--onefile`` (Argenta's default) — a single binary, convenient for"
|
||||||
|
" distribution. It unpacks into a temporary directory on startup, so it"
|
||||||
|
" starts slower. ``--standalone`` — a folder with the binary and its"
|
||||||
|
" dependencies; starts faster, but distribution means shipping the whole"
|
||||||
|
" folder. To switch: ``-- --standalone`` (overrides the default ``--onefile``)."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:304
|
||||||
|
msgid "``--jobs=<n>``"
|
||||||
|
msgstr "``--jobs=<n>``"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:305
|
||||||
|
msgid ""
|
||||||
|
"Количество параллельных процессов компиляции. Дефолт Argenta — все ядра "
|
||||||
|
"(``os.cpu_count()``). На машинах с малым объёмом памяти имеет смысл "
|
||||||
|
"ограничить: ``-- --jobs=2``."
|
||||||
|
msgstr ""
|
||||||
|
"Number of parallel compilation processes. Argenta's default is all cores"
|
||||||
|
" (``os.cpu_count()``). On memory-constrained machines it makes sense to"
|
||||||
|
" limit it: ``-- --jobs=2``."
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:314
|
||||||
|
msgid "Информация об окружении"
|
||||||
|
msgstr "Environment information"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:317
|
||||||
|
msgid "Команда ``info``"
|
||||||
|
msgstr "The ``info`` command"
|
||||||
|
|
||||||
|
#: ../../root/cli.rst:319
|
||||||
|
msgid ""
|
||||||
|
"Отображает версию ``Argenta``, версию Python, платформу и ссылку на "
|
||||||
|
"документацию."
|
||||||
|
msgstr ""
|
||||||
|
"Displays the ``Argenta`` version, Python version, platform, and a link to"
|
||||||
|
" the documentation."
|
||||||
|
|
||||||
|
#~ msgid "Командная строка"
|
||||||
|
#~ msgstr "Command Line Interface"
|
||||||
|
|
||||||
|
#~ msgid "Создаёт новую директорию проекта с boilerplate-кодом."
|
||||||
|
#~ msgstr "Creates a new project directory with boilerplate code."
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "``entrypoint`` — путь к callable в "
|
||||||
|
#~ "формате ``<path/to/file.py>:<callable>`` или "
|
||||||
|
#~ "``<path.to.module>:<callable>``."
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "``entrypoint`` — path to a callable "
|
||||||
|
#~ "in the format ``<path/to/file.py>:<callable>`` "
|
||||||
|
#~ "or ``<path.to.module>:<callable>``."
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "Команда ``run`` устанавливает переменную "
|
||||||
|
#~ "окружения ``RUN_FROM_ARGENTA_RUNNER=1``, что "
|
||||||
|
#~ "отключает парсинг аргументов командной строки"
|
||||||
|
#~ " в ``ArgParser``. Это позволяет запустить"
|
||||||
|
#~ " REPL без конфликтов с CLI-аргументами "
|
||||||
|
#~ "``argenta``."
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "The ``run`` command sets the "
|
||||||
|
#~ "``RUN_FROM_ARGENTA_RUNNER=1`` environment variable, "
|
||||||
|
#~ "which disables command-line argument "
|
||||||
|
#~ "parsing in ``ArgParser``. This allows "
|
||||||
|
#~ "starting the REPL without conflicts with"
|
||||||
|
#~ " ``argenta`` CLI arguments."
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "``entrypoint`` — путь к ``App`` или "
|
||||||
|
#~ "callable в формате "
|
||||||
|
#~ "``<path/to/file.py>:<app_or_callable>``."
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "``entrypoint`` — path to an ``App`` "
|
||||||
|
#~ "or callable in the format "
|
||||||
|
#~ "``<path/to/file.py>:<app_or_callable>``."
|
||||||
|
|
||||||
|
#~ msgid "Если передан инстанс ``App``:"
|
||||||
|
#~ msgstr "If an ``App`` instance is passed:"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "Если передан callable (фабрика), он "
|
||||||
|
#~ "будет вызван, и результат будет "
|
||||||
|
#~ "использован для отображения маршрутов:"
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "If a callable (factory) is passed, "
|
||||||
|
#~ "it will be called, and the result"
|
||||||
|
#~ " will be used to display routes:"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "``entrypoint`` — путь к callable в "
|
||||||
|
#~ "формате ``<path/to/file.py>:<callable>``."
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "``entrypoint`` — path to a callable "
|
||||||
|
#~ "in the format ``<path/to/file.py>:<callable>``."
|
||||||
|
|
||||||
|
#~ msgid "Для использования команды ``build`` необходимо установить ``Nuitka``:"
|
||||||
|
#~ msgstr "To use the ``build`` command, you must install ``Nuitka``:"
|
||||||
|
|
||||||
|
#~ msgid "Формат ентрипойнтов"
|
||||||
|
#~ msgstr "Entrypoint Format"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "Все команды, принимающие ентрипойнт (``run``,"
|
||||||
|
#~ " ``routes``, ``build``), используют единый "
|
||||||
|
#~ "формат:"
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "All commands that accept an entrypoint"
|
||||||
|
#~ " (``run``, ``routes``, ``build``) use a "
|
||||||
|
#~ "unified format:"
|
||||||
|
|
||||||
|
#~ msgid ""
|
||||||
|
#~ "Поддерживаются как пути к файлам, так"
|
||||||
|
#~ " и dotted-модули. Если передан путь к"
|
||||||
|
#~ " директории с ``__main__.py``, он будет "
|
||||||
|
#~ "разрешён автоматически."
|
||||||
|
#~ msgstr ""
|
||||||
|
#~ "Both file paths and dotted modules "
|
||||||
|
#~ "are supported. If a directory path "
|
||||||
|
#~ "containing ``__main__.py`` is passed, it "
|
||||||
|
#~ "will be resolved automatically."
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Argenta \n"
|
"Project-Id-Version: Argenta \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-12-04 20:39+0300\n"
|
"POT-Creation-Date: 2026-08-03 17:30+0300\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: en\n"
|
"Language: en\n"
|
||||||
@@ -82,8 +82,9 @@ msgid "E2E-тестирование цикла"
|
|||||||
msgstr "E2E Testing of the Loop"
|
msgstr "E2E Testing of the Loop"
|
||||||
|
|
||||||
#: ../../root/testing.rst:48
|
#: ../../root/testing.rst:48
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
"Полный запуск цикла ``start_polling`` можно покрывать через подпроцесс с "
|
"Полный запуск цикла ``run_repl`` можно покрывать через подпроцесс с "
|
||||||
"передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё "
|
"передачей строк в ``stdin``. Это тяжелее и обычно не требуется. Если всё "
|
||||||
"же необходимо — пример ниже."
|
"же необходимо — пример ниже."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -0,0 +1,330 @@
|
|||||||
|
.. _root_cli:
|
||||||
|
|
||||||
|
CLI
|
||||||
|
===
|
||||||
|
|
||||||
|
Помимо библиотеки, ``Argenta`` поставляется с собственным CLI-инструментом. Он берёт на себя рутину, которая сопровождает разработку CLI-приложений: создаёт каркас проекта, запускает приложение, инспектирует зарегистрированные маршруты и собирает standalone-бинарник.
|
||||||
|
|
||||||
|
CLI поставляется как опциональная зависимость — основная библиотека остаётся лёгкой, а инструмент доступен только тем, кому он нужен.
|
||||||
|
|
||||||
|
Установка
|
||||||
|
---------
|
||||||
|
|
||||||
|
CLI доступен как опциональная зависимость ``[cli]``:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
pip install argenta[cli]
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
uv add argenta[cli]
|
||||||
|
|
||||||
|
После установки команда ``argenta`` доступна в терминале:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta --help
|
||||||
|
|
||||||
|
.. image:: https://i.ibb.co/p60T7fvh/image.png
|
||||||
|
:alt: Argenta CLI help
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Если ``argenta`` установлена без extras, команда ``argenta`` не будет доступна. Установите с ``[cli]``, чтобы получить доступ к CLI-инструменту.
|
||||||
|
|
||||||
|
Флаг ``--version``
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Показать установленную версию ``Argenta``:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta --version
|
||||||
|
|
||||||
|
Аналогично через короткий флаг:
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta -v
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. _cli_entrypoint:
|
||||||
|
|
||||||
|
Формат entrypoint
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Команды ``run``, ``routes`` и ``build`` принимают **entrypoint** — указатель на объект внутри проекта, который нужно запустить, инспектировать или собрать. Единый формат описан здесь, чтобы не повторяться в каждой команде.
|
||||||
|
|
||||||
|
Формат entrypoint:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
<path/to/file.py>:<object_name>
|
||||||
|
<path.to.module>:<object_name>
|
||||||
|
|
||||||
|
Поддерживаются два способа адресации:
|
||||||
|
|
||||||
|
* **Путь к файлу** — ``app/main.py:main``. Удобно при работе с конкретным файлом.
|
||||||
|
* **Dotted-модуль** — ``my_project.application:main``. Естественно для установленных пакетов.
|
||||||
|
|
||||||
|
Если передан путь к директории с ``__main__.py``, он разрешается автоматически — указывать файл явно не нужно.
|
||||||
|
|
||||||
|
**Примеры валидных entrypoint-ов:**
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
app/main.py:main
|
||||||
|
app/main.py:app
|
||||||
|
app/main.py:create_app
|
||||||
|
my_project.application:main
|
||||||
|
my_project/application/__main__.py:main
|
||||||
|
|
||||||
|
Тип объекта зависит от команды: ``run`` и ``build`` ожидают callable, ``routes`` — инстанс ``App`` или callable, возвращающий ``App``.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Создание проектов
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Команда ``new``
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Создаёт новую директорию проекта с boilerplate-кодом. Это отправная точка: вместо ручной настройки структуры — готовый каркас за одну команду.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta new <project_name> [--arch flat|src]
|
||||||
|
|
||||||
|
* ``project_name`` — имя директории проекта (обязательный аргумент).
|
||||||
|
* ``--arch`` — архитектура проекта: ``flat`` (по умолчанию) или ``src``.
|
||||||
|
|
||||||
|
**Примеры:**
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta new my-app
|
||||||
|
argenta new my-app --arch src
|
||||||
|
|
||||||
|
При архитектуре ``flat`` создаётся следующая структура:
|
||||||
|
|
||||||
|
.. literalinclude:: ../code_snippets/cli/flat_structure.txt
|
||||||
|
:language: text
|
||||||
|
|
||||||
|
При архитектуре ``src``:
|
||||||
|
|
||||||
|
.. literalinclude:: ../code_snippets/cli/src_structure.txt
|
||||||
|
:language: text
|
||||||
|
|
||||||
|
.. image:: https://i.ibb.co/gY6zTQd/image.png
|
||||||
|
:alt: argenta new command output
|
||||||
|
|
||||||
|
Команда ``init``
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Делает то же, что и ``new``, но в текущей директории. Удобно, когда проект уже существует и нужно добавить структуру Argenta, не создавая лишний уровень вложенности.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta init [--arch flat|src]
|
||||||
|
|
||||||
|
* ``--arch`` — архитектура проекта: ``flat`` (по умолчанию) или ``src``.
|
||||||
|
|
||||||
|
**Примеры:**
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta init
|
||||||
|
argenta init --arch src
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Команда ``init`` не перезаписывает существующие файлы — они будут пропущены.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Запуск приложения
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Команда ``run``
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Запускает оркестратор ``Argenta`` из callable-entrypoint. Это альтернатива прямому вызову ``python main.py``, но с автоматической настройкой окружения.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta run <entrypoint>
|
||||||
|
|
||||||
|
Формат entrypoint — см. :ref:`Формат entrypoint <cli_entrypoint>`.
|
||||||
|
|
||||||
|
**Примеры:**
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta run app/main.py:main
|
||||||
|
argenta run my_project.application:main
|
||||||
|
|
||||||
|
.. image:: https://i.ibb.co/fVPzxWxp/image.png
|
||||||
|
:alt: argenta run command output
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Команда ``run`` устанавливает переменную окружения ``RUN_FROM_ARGENTA_RUNNER=1``. ``ArgParser`` видит этот флаг и пропускает парсинг ``sys.argv``, поэтому аргументы самого ``argenta`` (типа ``--help``, ``--version``) не конфликтуют с аргументами запускаемого приложения. REPL стартует чисто, без ошибок про неизвестные флаги.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Инспекция маршрутов
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Команда ``routes``
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Отображает все зарегистрированные роутеры, команды, алиасы и флаги в виде дерева. Принимает как инстанс ``App``, так и callable, возвращающий ``App``.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta routes <entrypoint>
|
||||||
|
|
||||||
|
Формат entrypoint — см. :ref:`Формат entrypoint <cli_entrypoint>`.
|
||||||
|
|
||||||
|
**Примеры:**
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta routes app/main.py:app
|
||||||
|
argenta routes app/main.py:create_app
|
||||||
|
|
||||||
|
Инстанс ``App`` передаётся напрямую, если роутеры подключены на уровне модуля:
|
||||||
|
|
||||||
|
.. literalinclude:: ../code_snippets/cli/app_instance.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Фабрика ``create_app`` передаётся, если роутеры регистрируются внутри функции — например, зависят от конфига или DI:
|
||||||
|
|
||||||
|
.. literalinclude:: ../code_snippets/cli/app_factory.py
|
||||||
|
:language: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
При использовании callable-entrypoint REPL не запускается — фабрика вызывается, и маршруты считываются из возвращённого ``App``.
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
──────────────────────────────────────────
|
||||||
|
App Stats
|
||||||
|
──────────────────────────────────────────
|
||||||
|
Total Routers: 1
|
||||||
|
Total Commands: 1
|
||||||
|
Total Aliases: 0
|
||||||
|
Total Flags: 0
|
||||||
|
──────────────────────────────────────────
|
||||||
|
|
||||||
|
📦 App object: <App>
|
||||||
|
└── 📁 Router: Example
|
||||||
|
└── ⚡ hello
|
||||||
|
📝 description: Say hello
|
||||||
|
|
||||||
|
.. image:: https://i.ibb.co/wNFvKcqM/image.png
|
||||||
|
:alt: argenta routes command output
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Сборка бинарника
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Команда ``build``
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Компилирует проект в standalone-бинарник с помощью `Nuitka <https://nuitka.net/>`_, которая входит в ``[cli]`` extra.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta build <entrypoint> [--output <name>] [-- <nuitka-flags>...]
|
||||||
|
|
||||||
|
Формат entrypoint — см. :ref:`Формат entrypoint <cli_entrypoint>`.
|
||||||
|
|
||||||
|
* ``--output`` / ``-o`` — имя выходного бинарника (по умолчанию — имя файла или пакета).
|
||||||
|
* ``--`` — разделитель, после которого передаются **произвольные флаги Nuitka**. Они добавляются к вызову Nuitka после аргументов Argenta, поэтому могут переопределять дефолты и добавлять любые опции, которые Nuitka поддерживает.
|
||||||
|
|
||||||
|
**Базовые примеры:**
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta build app/main.py:main
|
||||||
|
argenta build app/main.py:main --output myapp
|
||||||
|
argenta build app/__main__.py:main -o myapp
|
||||||
|
|
||||||
|
**Примеры с флагами Nuitka:**
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta build app/main.py:main -- --lto=yes
|
||||||
|
argenta build app/main.py:main -- --include-package=numpy
|
||||||
|
argenta build app/main.py:main -o myapp -- --lto=auto --include-data-files=assets/*=assets/
|
||||||
|
|
||||||
|
Что делает Argenta по умолчанию
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Команда ``build`` формирует вызов Nuitka со следующими аргументами:
|
||||||
|
|
||||||
|
* ``--standalone --onefile`` — собирает единый бинарник со всеми зависимостями внутри.
|
||||||
|
* ``--output-filename=<name>`` — имя выходного файла (из ``--output`` или имени entrypoint).
|
||||||
|
* ``--jobs=<cpu_count>`` — параллельная компиляция на всех ядрах.
|
||||||
|
* ``--lto=no`` — LTO отключён по умолчанию (быстрее сборка, медленнее запуск).
|
||||||
|
* ``--include-windows-runtime-dlls=no`` — на Windows не включает runtime DLL в бинарник.
|
||||||
|
* ``--python-flag=-m`` — добавляется автоматически, если entrypoint указывает на ``__main__.py``.
|
||||||
|
|
||||||
|
Все эти дефолты можно переопределить, передав соответствующий флаг после ``--``. Например, ``-- --lto=yes`` включит LTO, а ``-- --jobs=1`` отключит параллельную сборку.
|
||||||
|
|
||||||
|
Основные флаги Nuitka и их нюансы
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Полный список флагов — в `документации Nuitka <https://nuitka.net/user-documentation/user-manual.html>`_. Ниже — те, с которыми чаще всего сталкиваются при сборке CLI-приложений.
|
||||||
|
|
||||||
|
``--lto={yes,no,auto}``
|
||||||
|
Link-Time Optimization. ``yes`` — бинарник меньше и быстрее запускается, но сборка длится заметно дольше. ``no`` (дефолт Argenta) — сборка быстрее, бинарник больше. ``auto`` — Nuitka выбирает сам. Для production-сборки имеет смысл ``yes``, для итеративной разработки — ``no``.
|
||||||
|
|
||||||
|
``--include-package=<package>``
|
||||||
|
Явно включает пакет в бинарник. Nuitka отслеживает импорты статически, поэтому пакеты, которые импортируются динамически (через ``importlib``, плагины, ``__import__``), в бинарник не попадают — их нужно добавлять вручную. Типичные кандидаты: ``numpy``, ``pandas``, ``rich``, ``prompt_toolkit``.
|
||||||
|
|
||||||
|
``--include-data-files=<source>=<dest>``
|
||||||
|
Включает файлы данных (шаблоны, конфиги, ассеты) в бинарник. Формат: ``--include-data-files=assets/logo.png=assets/logo.png``. Для директорий целиком — ``--include-data-dir=assets=assets``. Без этого файлы, которые приложение читает во время выполнения, не будут найдены в собранном бинарнике.
|
||||||
|
|
||||||
|
``--enable-plugin=<plugin>``
|
||||||
|
Включает `плагин Nuitka <https://nuitka.net/user-documentation/user-manual.html#plugins>`_ для поддержки фреймворков, требующих специальной обработки. Распространённые: ``anti-bloat`` (вырезает ненужные части тяжёлых пакетов), ``numpy`` (корректная сборка с numpy), ``tk-inter`` (Tkinter GUI), ``triton`` (PyTorch triton kernels).
|
||||||
|
|
||||||
|
``--onefile`` / ``--standalone``
|
||||||
|
``--onefile`` (дефолт Argenta) — единый бинарник, удобный для дистрибуции. При запуске распаковывается во временную директорию, поэтому стартует медленнее. ``--standalone`` — папка с бинарником и зависимостями, стартует быстрее, но дистрибуция — это вся папка целиком. Чтобы переключиться: ``-- --standalone`` (переопределит дефолтный ``--onefile``).
|
||||||
|
|
||||||
|
``--jobs=<n>``
|
||||||
|
Количество параллельных процессов компиляции. Дефолт Argenta — все ядра (``os.cpu_count()``). На машинах с малым объёмом памяти имеет смысл ограничить: ``-- --jobs=2``.
|
||||||
|
|
||||||
|
.. image:: https://i.ibb.co/VsVXxf7/image.png
|
||||||
|
:alt: argenta build command output
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Информация об окружении
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Команда ``info``
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Отображает версию ``Argenta``, версию Python, платформу и ссылку на документацию.
|
||||||
|
|
||||||
|
.. code-block:: shell
|
||||||
|
|
||||||
|
argenta info
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
Argenta 1.2.0
|
||||||
|
Python 3.13.0
|
||||||
|
Platform Linux-7.1.5-zen1-2-zen-x86_64-with-glibc2.40
|
||||||
|
Docs https://argenta.readthedocs.io
|
||||||
|
|
||||||
|
.. image:: https://i.ibb.co/B5k8Ftyg/image.png
|
||||||
|
:alt: argenta info command output
|
||||||
@@ -4,12 +4,12 @@ from argenta import App, Orchestrator
|
|||||||
from argenta.app import PredefinedMessages, StaticDividingLine, AutoCompleter
|
from argenta.app import PredefinedMessages, StaticDividingLine, AutoCompleter
|
||||||
from argenta.app.dividing_line.models import DynamicDividingLine
|
from argenta.app.dividing_line.models import DynamicDividingLine
|
||||||
from argenta.orchestrator import ArgParser
|
from argenta.orchestrator import ArgParser
|
||||||
from mock.mock_app.routers import work_router
|
from examples.example_app.routers import work_router
|
||||||
|
|
||||||
app: App = App(
|
app: App = App(
|
||||||
dividing_line=StaticDividingLine('~')
|
dividing_line=StaticDividingLine('~')
|
||||||
)
|
)
|
||||||
orchestrator: Orchestrator = Orchestrator(arg_parser=ArgParser(processed_args=[]))
|
orchestrator: Orchestrator = Orchestrator()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
+103
-43
@@ -1,4 +1,7 @@
|
|||||||
from typer import Typer
|
from importlib.metadata import version
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from typer import Context, Typer
|
||||||
|
|
||||||
from .commands import (
|
from .commands import (
|
||||||
build_handler,
|
build_handler,
|
||||||
@@ -9,50 +12,107 @@ from .commands import (
|
|||||||
run_handler,
|
run_handler,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app = Typer(
|
||||||
|
name="argenta",
|
||||||
|
help="Argenta CLI — scaffold, run, inspect, and build CLI apps.",
|
||||||
|
no_args_is_help=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _version_callback(value: bool) -> None:
|
||||||
|
if value:
|
||||||
|
typer.echo(f"argenta {version('argenta')}")
|
||||||
|
raise typer.Exit()
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback()
|
||||||
|
def _root(
|
||||||
|
version_flag: bool = typer.Option(
|
||||||
|
None,
|
||||||
|
"--version",
|
||||||
|
"-v",
|
||||||
|
callback=_version_callback,
|
||||||
|
is_eager=True,
|
||||||
|
help="Show Argenta version and exit.",
|
||||||
|
),
|
||||||
|
) -> None:
|
||||||
|
"""Argenta CLI — scaffold, run, inspect, and build CLI apps."""
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
"run",
|
||||||
|
help="Start the orchestrator REPL from a callable entrypoint.",
|
||||||
|
short_help="Start the orchestrator REPL",
|
||||||
|
epilog="Example: argenta run app/main.py:main",
|
||||||
|
)
|
||||||
|
def _run(entrypoint_path: str = typer.Argument(help="Entrypoint as <path/to/file.py>:<callable>")) -> None:
|
||||||
|
run_handler(entrypoint_path)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
"init",
|
||||||
|
help="Scaffold a flat or src boilerplate in the current project directory.",
|
||||||
|
short_help="Initialize architecture in existing project",
|
||||||
|
epilog="Run from the project root. Example: argenta init --arch src",
|
||||||
|
)
|
||||||
|
def _init(arch: str = typer.Option("flat", "--arch", help="Architecture: flat or src")) -> None:
|
||||||
|
init_handler(arch=arch) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
"new",
|
||||||
|
help="Create a new project directory with a flat or src boilerplate.",
|
||||||
|
short_help="Create a new project with boilerplate",
|
||||||
|
epilog="Example: argenta new my-app --arch src",
|
||||||
|
)
|
||||||
|
def _new(
|
||||||
|
project_name: str = typer.Argument(help="Name of the new project directory"),
|
||||||
|
arch: str = typer.Option("flat", "--arch", help="Architecture: flat or src"),
|
||||||
|
) -> None:
|
||||||
|
new_handler(project_name=project_name, arch=arch) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
"routes",
|
||||||
|
help="Display all registered routes, commands, aliases, and flags. Accepts an App instance or a callable returning App.",
|
||||||
|
short_help="Show registered routes and commands",
|
||||||
|
epilog="Examples:\n argenta routes app/main.py:app\n argenta routes app/main.py:create_app",
|
||||||
|
)
|
||||||
|
def _routes(entrypoint_path: str = typer.Argument(help="Entrypoint as <path/to/file.py>:<app_or_callable>")) -> None:
|
||||||
|
routes_handler(entrypoint_path)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
name="info",
|
||||||
|
help="Display Argenta version, Python version, and platform info.",
|
||||||
|
short_help="Show Argenta version and environment info",
|
||||||
|
)
|
||||||
|
def _info() -> None:
|
||||||
|
info_handler()
|
||||||
|
|
||||||
|
|
||||||
|
@app.command(
|
||||||
|
name="build",
|
||||||
|
help="Compile a project entrypoint into a standalone binary using Nuitka. "
|
||||||
|
"Any Nuitka flags can be passed after a `--` separator, e.g. "
|
||||||
|
"`argenta build app/main.py:main -- --lto=yes --include-package=numpy`.",
|
||||||
|
short_help="Build a standalone binary",
|
||||||
|
epilog=(
|
||||||
|
"Examples:\n"
|
||||||
|
" argenta build app/main.py:main --output myapp\n"
|
||||||
|
" argenta build app/main.py:main -- --lto=yes --include-data-files=assets/*=assets/"
|
||||||
|
),
|
||||||
|
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
||||||
|
)
|
||||||
|
def _build(
|
||||||
|
ctx: Context,
|
||||||
|
entry_point: str = typer.Argument(help="Entrypoint as <path/to/file.py>:<callable>"),
|
||||||
|
output_name: str | None = typer.Option(None, "--output", "-o", help="Output binary name"),
|
||||||
|
) -> None:
|
||||||
|
build_handler(entry_point=entry_point, output_name=output_name, extra_nuitka_args=ctx.args)
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
app = Typer()
|
|
||||||
app.command(
|
|
||||||
"run",
|
|
||||||
help="Command to start the orchestrator repl; the path to the callable object is required",
|
|
||||||
short_help="Start the orchestrator REPL",
|
|
||||||
epilog="Example: run app/main.py:main",
|
|
||||||
)(run_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
"init",
|
|
||||||
help="Creates a flat/src boilerplate architecture in an existing project",
|
|
||||||
short_help="Initialize architecture in existing project",
|
|
||||||
epilog="Make sure you are in the project root before running this command.",
|
|
||||||
)(init_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
"new",
|
|
||||||
help="Creates a project and in it flat/src boilerplate architecture",
|
|
||||||
short_help="Create a new project with boilerplate",
|
|
||||||
epilog="This will create a new directory with the project structure.",
|
|
||||||
)(new_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
"routes",
|
|
||||||
help="Creates a project and in it flat/src boilerplate architecture",
|
|
||||||
short_help="Create a new project with boilerplate",
|
|
||||||
epilog="This will create a new directory with the project structure.",
|
|
||||||
)(routes_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
name="info",
|
|
||||||
help="Displays information about the installed Argenta package and environment",
|
|
||||||
short_help="Show Argenta version and environment info",
|
|
||||||
epilog="Uses metadata to retrieve the installed package version.",
|
|
||||||
)(info_handler)
|
|
||||||
|
|
||||||
app.command(
|
|
||||||
name="build",
|
|
||||||
help="Compiles the project into a standalone binary using Nuitka",
|
|
||||||
short_help="Build a standalone binary",
|
|
||||||
)(build_handler)
|
|
||||||
|
|
||||||
app()
|
app()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
__all__ = [
|
||||||
|
"GITIGNORE_CONTENT",
|
||||||
|
"FLAT_MAIN_TEMPLATE",
|
||||||
|
"FLAT_HANDLERS_TEMPLATE",
|
||||||
|
"SRC_MAIN_TEMPLATE",
|
||||||
|
"SRC_ROUTERS_TEMPLATE",
|
||||||
|
"SRC_HANDLER_TEMPLATE",
|
||||||
|
"create_file",
|
||||||
|
]
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
GITIGNORE_CONTENT = """
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
"""
|
||||||
|
|
||||||
|
FLAT_MAIN_TEMPLATE = """
|
||||||
|
from argenta import Orchestrator, App
|
||||||
|
|
||||||
|
from handlers import router
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = App()
|
||||||
|
app.include_router(router)
|
||||||
|
|
||||||
|
orchestrator = Orchestrator()
|
||||||
|
orchestrator.run_repl(app)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
"""
|
||||||
|
|
||||||
|
FLAT_HANDLERS_TEMPLATE = """
|
||||||
|
from argenta import Router, Response
|
||||||
|
|
||||||
|
router = Router("Hello command")
|
||||||
|
|
||||||
|
@router.command("hello")
|
||||||
|
def hello_handler(response: Response):
|
||||||
|
print("Hello world!")
|
||||||
|
"""
|
||||||
|
|
||||||
|
SRC_MAIN_TEMPLATE = """
|
||||||
|
from argenta import Orchestrator, App
|
||||||
|
|
||||||
|
from .routers import router
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = App()
|
||||||
|
app.include_router(router)
|
||||||
|
|
||||||
|
orchestrator = Orchestrator()
|
||||||
|
orchestrator.run_repl(app)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
"""
|
||||||
|
|
||||||
|
SRC_ROUTERS_TEMPLATE = """
|
||||||
|
from argenta import Router
|
||||||
|
from .handlers.hello_world_handler import hello_handler
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
router.command("hello")(hello_handler)
|
||||||
|
"""
|
||||||
|
|
||||||
|
SRC_HANDLER_TEMPLATE = """
|
||||||
|
from argenta import Response
|
||||||
|
|
||||||
|
|
||||||
|
def hello_handler(response: Response) -> None:
|
||||||
|
print("Hello world!")
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def create_file(path: Path, content: str) -> None:
|
||||||
|
if not path.exists():
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(content.strip(), encoding="utf-8")
|
||||||
|
else:
|
||||||
|
print(f"Skipped: {path} (already exists)")
|
||||||
@@ -8,7 +8,11 @@ from pathlib import Path
|
|||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
def build_handler(entry_point: str, output_name: str | None = None) -> None:
|
def build_handler(
|
||||||
|
entry_point: str,
|
||||||
|
output_name: str | None = None,
|
||||||
|
extra_nuitka_args: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
console = Console()
|
console = Console()
|
||||||
file_path, _, callable_name = entry_point.partition(":")
|
file_path, _, callable_name = entry_point.partition(":")
|
||||||
|
|
||||||
@@ -39,7 +43,7 @@ def build_handler(entry_point: str, output_name: str | None = None) -> None:
|
|||||||
"--standalone",
|
"--standalone",
|
||||||
"--onefile",
|
"--onefile",
|
||||||
f"--output-filename={name}",
|
f"--output-filename={name}",
|
||||||
f"--jobs={os.cpu_count()}",
|
f"--jobs={os.cpu_count() or 1}",
|
||||||
"--lto=no",
|
"--lto=no",
|
||||||
"--include-windows-runtime-dlls=no",
|
"--include-windows-runtime-dlls=no",
|
||||||
]
|
]
|
||||||
@@ -47,6 +51,12 @@ def build_handler(entry_point: str, output_name: str | None = None) -> None:
|
|||||||
if is_main_module:
|
if is_main_module:
|
||||||
args.append("--python-flag=-m")
|
args.append("--python-flag=-m")
|
||||||
|
|
||||||
|
# User-provided Nuitka flags are appended last, so they can override
|
||||||
|
# Argenta's defaults (e.g. --lto=yes, --jobs=1) and add anything else
|
||||||
|
# Nuitka supports (--include-package, --include-data-files, --enable-plugin, ...).
|
||||||
|
if extra_nuitka_args:
|
||||||
|
args.extend(extra_nuitka_args)
|
||||||
|
|
||||||
args.append(target)
|
args.append(target)
|
||||||
|
|
||||||
result = subprocess.run(args, check=False)
|
result = subprocess.run(args, check=False)
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ def info_handler() -> None:
|
|||||||
table.add_row("Platform", f"{platform.system()} {platform.release()} ({platform.machine()})")
|
table.add_row("Platform", f"{platform.system()} {platform.release()} ({platform.machine()})")
|
||||||
table.add_row("Docs", "https://argenta.readthedocs.io")
|
table.add_row("Docs", "https://argenta.readthedocs.io")
|
||||||
|
|
||||||
console.print(f"[bold red]{text2art("Argenta", font='tarty1')}[/bold red]")
|
console.print(f"[bold red]{text2art('Argenta', font='tarty1')}[/bold red]")
|
||||||
console.print(Padding(table, pad=(2, 5)))
|
console.print(Padding(table, pad=(2, 5)))
|
||||||
console.print(Padding("[i]made with ❤ by [b]kolo[/b][/i]", pad=(0, 17)))
|
console.print(Padding("[i]made with ❤ by [b]kolo[/b][/i]", pad=(0, 17)))
|
||||||
|
|||||||
@@ -3,96 +3,28 @@ __all__ = ["init_handler"]
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
GITIGNORE_CONTENT = """
|
from ._templates import (
|
||||||
__pycache__/
|
FLAT_HANDLERS_TEMPLATE,
|
||||||
*.py[cod]
|
FLAT_MAIN_TEMPLATE,
|
||||||
.env
|
GITIGNORE_CONTENT,
|
||||||
.venv/
|
SRC_HANDLER_TEMPLATE,
|
||||||
env/
|
SRC_MAIN_TEMPLATE,
|
||||||
"""
|
SRC_ROUTERS_TEMPLATE,
|
||||||
|
create_file,
|
||||||
FLAT_MAIN_TEMPLATE = """
|
)
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from handlers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def init_handler(arch: Literal["flat", "src"] = "flat") -> None:
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLAT_HANDLERS_TEMPLATE = """
|
|
||||||
from argenta import Router, Response
|
|
||||||
|
|
||||||
router = Router("Hello command")
|
|
||||||
|
|
||||||
@router.command("hello")
|
|
||||||
def start_handler(response: Response):
|
|
||||||
print("Hello world!")
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from .routers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_ROUTERS_TEMPLATE = """
|
|
||||||
from argenta import Router
|
|
||||||
from .handlers.hello_world_handler import hello_handler
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
router.command('hello')(hello_handler)
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_HANDLER_TEMPLATE = """
|
|
||||||
from argenta import Response
|
|
||||||
|
|
||||||
|
|
||||||
def hello_handler(response: Response) -> None:
|
|
||||||
print("Hello world!")
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def create_file(path: Path, content: str) -> None:
|
|
||||||
if not path.exists():
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_text(content.strip(), encoding="utf-8")
|
|
||||||
else:
|
|
||||||
print(f"Skipped: {path} (already exists)")
|
|
||||||
|
|
||||||
|
|
||||||
def init_handler(with_arch: Literal["flat", "src"] = "flat") -> None:
|
|
||||||
cwd = Path.cwd()
|
cwd = Path.cwd()
|
||||||
project_name = cwd.name.lower().replace(" ", "_")
|
project_name = cwd.name.lower().replace(" ", "_")
|
||||||
|
|
||||||
create_file(cwd / ".gitignore", GITIGNORE_CONTENT)
|
create_file(cwd / ".gitignore", GITIGNORE_CONTENT)
|
||||||
|
|
||||||
if with_arch == "flat":
|
if arch == "flat":
|
||||||
create_file(cwd / "main.py", FLAT_MAIN_TEMPLATE)
|
create_file(cwd / "main.py", FLAT_MAIN_TEMPLATE)
|
||||||
create_file(cwd / "handlers.py", FLAT_HANDLERS_TEMPLATE)
|
create_file(cwd / "handlers.py", FLAT_HANDLERS_TEMPLATE)
|
||||||
|
|
||||||
elif with_arch == "src":
|
elif arch == "src":
|
||||||
base_pkg = cwd / "src" / project_name / "application"
|
base_pkg = cwd / "src" / project_name / "application"
|
||||||
|
|
||||||
create_file(base_pkg / "__main__.py", SRC_MAIN_TEMPLATE)
|
create_file(base_pkg / "__main__.py", SRC_MAIN_TEMPLATE)
|
||||||
|
|||||||
@@ -4,102 +4,34 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
|
from ._templates import (
|
||||||
GITIGNORE_CONTENT = """
|
FLAT_HANDLERS_TEMPLATE,
|
||||||
__pycache__/
|
FLAT_MAIN_TEMPLATE,
|
||||||
*.py[cod]
|
GITIGNORE_CONTENT,
|
||||||
.env
|
SRC_HANDLER_TEMPLATE,
|
||||||
.venv/
|
SRC_MAIN_TEMPLATE,
|
||||||
env/
|
SRC_ROUTERS_TEMPLATE,
|
||||||
"""
|
create_file,
|
||||||
|
)
|
||||||
FLAT_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from handlers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def new_handler(project_name: str, arch: Literal["flat", "src"] = "flat") -> None:
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
FLAT_HANDLERS_TEMPLATE = """
|
|
||||||
from argenta import Router, Response
|
|
||||||
|
|
||||||
router = Router("Hello command")
|
|
||||||
|
|
||||||
@router.command("hello")
|
|
||||||
def start_handler(response: Response):
|
|
||||||
print("Hello world!")
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_MAIN_TEMPLATE = """
|
|
||||||
from argenta import Orchestrator, App
|
|
||||||
|
|
||||||
from .routers import router
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = App()
|
|
||||||
app.include_router(router)
|
|
||||||
|
|
||||||
orchestrator = Orchestrator()
|
|
||||||
orchestrator.run_repl(app)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_ROUTERS_TEMPLATE = """
|
|
||||||
from argenta import Router
|
|
||||||
from .handlers.hello_world_handler import hello_handler
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
router.command('hello')(hello_handler)
|
|
||||||
"""
|
|
||||||
|
|
||||||
SRC_HANDLER_TEMPLATE = """
|
|
||||||
from argenta import Response
|
|
||||||
|
|
||||||
|
|
||||||
def hello_handler(response: Response) -> None:
|
|
||||||
print("Hello world!")
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def create_file(path: Path, content: str) -> None:
|
|
||||||
if not path.exists():
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
path.write_text(content.strip(), encoding="utf-8")
|
|
||||||
else:
|
|
||||||
print(f"Skipped: {path} (already exists)")
|
|
||||||
|
|
||||||
|
|
||||||
def new_handler(project_name: str, with_arch: Literal["flat", "src"] = "flat") -> None:
|
|
||||||
base_dir = Path.cwd() / project_name
|
base_dir = Path.cwd() / project_name
|
||||||
|
|
||||||
if base_dir.exists():
|
if base_dir.exists():
|
||||||
print(f"Error: Directory '{project_name}' already exists.")
|
print(f"Error: Directory '{project_name}' already exists.")
|
||||||
sys.exit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
base_dir.mkdir(parents=True)
|
base_dir.mkdir(parents=True)
|
||||||
print(f"Initialized project directory: {base_dir}")
|
print(f"Initialized project directory: {base_dir}")
|
||||||
|
|
||||||
create_file(base_dir / ".gitignore", GITIGNORE_CONTENT)
|
create_file(base_dir / ".gitignore", GITIGNORE_CONTENT)
|
||||||
|
|
||||||
if with_arch == "flat":
|
if arch == "flat":
|
||||||
create_file(base_dir / "main.py", FLAT_MAIN_TEMPLATE)
|
create_file(base_dir / "main.py", FLAT_MAIN_TEMPLATE)
|
||||||
create_file(base_dir / "handlers.py", FLAT_HANDLERS_TEMPLATE)
|
create_file(base_dir / "handlers.py", FLAT_HANDLERS_TEMPLATE)
|
||||||
|
|
||||||
elif with_arch == "src":
|
elif arch == "src":
|
||||||
pkg_name = project_name.lower().replace(" ", "_").replace("-", "_")
|
pkg_name = project_name.lower().replace(" ", "_").replace("-", "_")
|
||||||
app_pkg = base_dir / "src" / pkg_name / "application"
|
app_pkg = base_dir / "src" / pkg_name / "application"
|
||||||
|
|
||||||
|
|||||||
@@ -6,25 +6,53 @@ from rich.console import Console
|
|||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
|
|
||||||
|
from argenta.app.models import App
|
||||||
|
|
||||||
from ..infrastructure.entrypoint_resolver.entity import (
|
from ..infrastructure.entrypoint_resolver.entity import (
|
||||||
|
CallableEntryPoint,
|
||||||
EntryPointAsApp,
|
EntryPointAsApp,
|
||||||
EntrypointResolver,
|
EntrypointResolver,
|
||||||
|
)
|
||||||
|
from ..infrastructure.entrypoint_resolver.exceptions import (
|
||||||
|
EntrypointError,
|
||||||
|
EntrypointNotAppInstanceError,
|
||||||
ResolveFromStringError,
|
ResolveFromStringError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def routes_handler(entrypoint_path: str) -> None:
|
def routes_handler(entrypoint_path: str) -> None:
|
||||||
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
|
file_path, _, callable_name = entrypoint_path.partition(":")
|
||||||
if not entrypoint_callable_name:
|
if not callable_name:
|
||||||
raise ResolveFromStringError(
|
Console().print(
|
||||||
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name>"
|
f'[bold red]Error:[/bold red] "{entrypoint_path}" must be in format '
|
||||||
|
f'"<path/to/file.py>:<app_object>" or "<path.to.module>:<app_object>"'
|
||||||
)
|
)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
app_instance = EntrypointResolver[EntryPointAsApp](entrypoint_path).parse_entrypoint_with_type(
|
try:
|
||||||
entrypoint_callable_name
|
app_instance = EntrypointResolver[EntryPointAsApp](file_path).parse_entrypoint_with_type(
|
||||||
)
|
callable_name
|
||||||
|
)
|
||||||
app = app_instance.instance_object
|
app = app_instance.instance_object
|
||||||
|
except EntrypointNotAppInstanceError:
|
||||||
|
try:
|
||||||
|
callable_entrypoint = EntrypointResolver[CallableEntryPoint](
|
||||||
|
file_path
|
||||||
|
).parse_entrypoint_with_type(
|
||||||
|
callable_name
|
||||||
|
)
|
||||||
|
except (ResolveFromStringError, EntrypointError) as e:
|
||||||
|
Console().print(f"[bold red]Error:[/bold red] {e}")
|
||||||
|
raise SystemExit(1)
|
||||||
|
app = callable_entrypoint.instance_object()
|
||||||
|
if not isinstance(app, App):
|
||||||
|
Console().print(
|
||||||
|
f"[bold red]Error:[/bold red] callable must return an App instance, got {type(app).__name__}"
|
||||||
|
)
|
||||||
|
raise SystemExit(1)
|
||||||
|
except (ResolveFromStringError, EntrypointError) as e:
|
||||||
|
Console().print(f"[bold red]Error:[/bold red] {e}")
|
||||||
|
raise SystemExit(1)
|
||||||
routers = app.registered_routers
|
routers = app.registered_routers
|
||||||
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|||||||
@@ -2,23 +2,33 @@ __all__ = ["run_handler"]
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
from ..infrastructure.entrypoint_resolver.entity import (
|
from ..infrastructure.entrypoint_resolver.entity import (
|
||||||
CallableEntryPoint,
|
CallableEntryPoint,
|
||||||
EntrypointResolver,
|
EntrypointResolver,
|
||||||
|
)
|
||||||
|
from ..infrastructure.entrypoint_resolver.exceptions import (
|
||||||
|
EntrypointError,
|
||||||
ResolveFromStringError,
|
ResolveFromStringError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_handler(entrypoint_path: str) -> None:
|
def run_handler(entrypoint_path: str) -> None:
|
||||||
os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1"
|
os.environ["RUN_FROM_ARGENTA_RUNNER"] = "1"
|
||||||
entrypoint_path, _, entrypoint_callable_name = entrypoint_path.partition(":")
|
file_path, _, callable_name = entrypoint_path.partition(":")
|
||||||
if not entrypoint_callable_name:
|
if not callable_name:
|
||||||
raise ResolveFromStringError(
|
Console().print(
|
||||||
"Path to callable object that run orchestrator repl must be in the format <path/to/file.py>:<object_name> or <path.to.module>:<object_name>"
|
f'[bold red]Error:[/bold red] "{entrypoint_path}" must be in format '
|
||||||
|
f'"<path/to/file.py>:<callable>" or "<path.to.module>:<callable>"'
|
||||||
)
|
)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
runner = EntrypointResolver[CallableEntryPoint](entrypoint_path).parse_entrypoint_with_type(
|
try:
|
||||||
entrypoint_callable_name
|
runner = EntrypointResolver[CallableEntryPoint](file_path).parse_entrypoint_with_type(
|
||||||
)
|
callable_name
|
||||||
|
)
|
||||||
runner.instance_object()
|
runner.instance_object()
|
||||||
|
except (ResolveFromStringError, EntrypointError) as e:
|
||||||
|
Console().print(f"[bold red]Error:[/bold red] {e}")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user