diff --git a/.github/pravila.md b/.github/pravila.md index be15fff..f57d698 100644 --- a/.github/pravila.md +++ b/.github/pravila.md @@ -1,34 +1,5 @@ # Правила работы агента -## Файловая система - -`~/remote_dev/` (локально) примонтирован через sshfs к `~/terra/` на ВМ — **одна ФС**. -Файлы, сохранённые локально, мгновенно видны на ВМ. SCP не нужен. - -Монтирование может слетать. Признак: файлы рассинхронизированы. - -```bash -# Размонтировать -fusermount -u ~/remote_dev -# Если завис: sudo umount -l /home/naeel/remote_dev - -# Примонтировать -sshfs naeel@5.172.178.213:/home/naeel/terra ~/remote_dev \ - -o cache=no -o no_readahead -o reconnect \ - -o ServerAliveInterval=15 -o ServerAliveCountMax=3 \ - -o IdentityFile=~/.ssh/naeel_vm_id_ed25519 -``` - -## SSH - -Все команды — только через SSH на ВМ. Локально — только читать и редактировать файлы. - -```bash -ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout=10 naeel@5.172.178.213 'КОМАНДА' -``` - -Запрещено локально: `go`, `docker`, `kubectl`, `helm`, `terraform`, `curl/wget`, `git push/pull`, любые скрипты проекта. - ## Документация - `doc/thinking/` — лог рассуждений агента (обязательно) @@ -37,16 +8,7 @@ ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout= ## Git -Коммитить и пушить через SSH после каждого завершённого этапа. - -Версионирование тегами: `vMAJOR.MINOR.PATCH` -- Patch — любое изменение кода -- Minor — новая фича / компонент -- Major — breaking change - -```bash -git tag vX.Y.Z && git push origin vX.Y.Z -``` +Коммитить и пушить после каждого завершённого этапа. ## Поведение агента diff --git a/.gitignore b/.gitignore index 848c621..504ba5e 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,9 @@ secrets/id_ed25519.txt # === MkDocs === site/ +site_test/ .mkdocs.tmp.yml +.mkdocs.docs_test.yml # === Generated universal_rebuild artifacts === universal_rebuild/universal_rebuild/ diff --git a/docs/30_registry/assets/extra.css b/docs/30_registry/assets/extra.css index bde9464..6b0d4cf 100644 --- a/docs/30_registry/assets/extra.css +++ b/docs/30_registry/assets/extra.css @@ -18,19 +18,27 @@ line-height: 1.35; } -/* Full-width resource pages without right TOC */ +/* Resource pages: no sidebars, normal scrolling */ +.md-sidebar--primary { + display: none !important; +} + .md-sidebar--secondary { - display: none; + display: none !important; +} + +.md-grid { + max-width: 61rem; } -.md-content, -.md-content__inner, .md-main__inner { - max-width: 100%; + margin-top: 0; } -.md-content__inner { - margin-right: 0; +/* Active tab highlight in resource nav */ +.md-content a:has(strong) { + text-decoration: underline; + text-decoration-thickness: 2px; } /* Keep horizontal scrollbars visible for wide tables */ @@ -40,9 +48,10 @@ } /* Column sizing for resource tables */ +/* Widths based on real YAML data analysis, see docs/help/table-column-widths-analysis.md */ .resource-table { width: 100%; - table-layout: auto; + table-layout: fixed; border-collapse: collapse; font-size: 0.82rem; line-height: 1.5; @@ -50,7 +59,7 @@ .resource-table th, .resource-table td { - padding: 0.3rem 0.5rem; + padding: 0.3rem 0.4rem; vertical-align: top; border-bottom: 1px solid rgba(0, 0, 0, 0.12); font-size: 0.82rem; @@ -60,54 +69,124 @@ font-weight: 700; } +/* ID column: useless to user, barely visible */ .resource-table th:nth-child(1), .resource-table td:nth-child(1) { - width: 6%; - white-space: nowrap; + width: 2%; text-align: center; + white-space: nowrap; + font-size: 0.58rem; + color: #999; } -.resource-table th:nth-child(2), -.resource-table td:nth-child(2) { - width: auto; - max-width: 40ch; +/* ---- MAIN TABLE: Code(18%) Type(5%) Default(auto) Descr(45%) Constr(30%) ---- */ + +.resource-table:not(.resource-table-nested) th:nth-child(2), +.resource-table:not(.resource-table-nested) td:nth-child(2) { + width: 18%; white-space: normal; + overflow-wrap: anywhere; } -.resource-table td:nth-child(2) code { +.resource-table:not(.resource-table-nested) td:nth-child(2) code { font-weight: 700; - font-size: inherit; + font-size: 0.82rem; } -.resource-table th:nth-child(3), -.resource-table td:nth-child(3), -.resource-table th:nth-child(4), -.resource-table td:nth-child(4), -.resource-table th:nth-child(5), -.resource-table td:nth-child(5) { +.resource-table:not(.resource-table-nested) th:nth-child(3), +.resource-table:not(.resource-table-nested) td:nth-child(3) { + width: 5%; white-space: nowrap; - width: 6%; + font-size: 0.63rem; } -.resource-table th:nth-child(4), -.resource-table td:nth-child(4) { +.resource-table:not(.resource-table-nested) th:nth-last-child(2), +.resource-table:not(.resource-table-nested) td:nth-last-child(2) { + width: 45%; + white-space: normal; + overflow-wrap: anywhere; + font-size: 0.63rem; + color: #777; +} + +.resource-table:not(.resource-table-nested) th:nth-last-child(1), +.resource-table:not(.resource-table-nested) td:nth-last-child(1) { + width: 30%; + white-space: normal; + overflow-wrap: anywhere; + word-break: break-all; + font-size: 0.72rem; +} + +/* ---- NESTED TABLE: Code(16%) Type(5%) Req(4%) Def(6%) Descr(36%) Constr(31%) ---- */ +.resource-table-nested { + width: 100%; + table-layout: fixed; +} + +.resource-table-nested th, +.resource-table-nested td { + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; + font-size: 0.72rem; + padding: 0.25rem 0.3rem; +} + +.resource-table-nested th:nth-child(2), +.resource-table-nested td:nth-child(2) { + width: 16%; +} + +.resource-table-nested td:nth-child(2) code { + font-weight: 700; + font-size: 0.72rem; +} + +.resource-table-nested th:nth-child(3), +.resource-table-nested td:nth-child(3) { + width: 5%; + white-space: nowrap; + font-size: 0.63rem; +} + +/* Required column: green ✓ instead of text "yes" */ +.resource-table-nested th:nth-child(4), +.resource-table-nested td:nth-child(4) { + width: 5%; text-align: center; + white-space: nowrap; } -.resource-table th:nth-child(5), -.resource-table td:nth-child(5) { - width: 8%; - white-space: normal; - overflow-wrap: anywhere; - font-size: 0.72rem; +.resource-table-nested td:nth-child(4) strong { + display: none; } -.resource-table th:nth-child(6), -.resource-table td:nth-child(6) { - white-space: normal; - overflow-wrap: anywhere; - word-break: normal; - font-size: 0.72rem; +.resource-table-nested td:nth-child(4):has(strong)::before { + content: "✓"; + color: #27ae60; + font-weight: 700; + font-size: 0.9rem; +} + +.resource-table-nested th:nth-child(5), +.resource-table-nested td:nth-child(5) { + width: 6%; + white-space: nowrap; + color: #2c6f8c; +} + +.resource-table-nested th:nth-child(6), +.resource-table-nested td:nth-child(6) { + width: 35%; + font-size: 0.63rem; + color: #777; +} + +.resource-table-nested th:nth-child(7), +.resource-table-nested td:nth-child(7) { + width: 31%; + word-break: break-all; } .resource-table th:nth-child(7), diff --git a/docs/help/design-sonnet-review.md b/docs/help/design-sonnet-review.md new file mode 100644 index 0000000..4ff1c8d --- /dev/null +++ b/docs/help/design-sonnet-review.md @@ -0,0 +1,75 @@ +# Дизайн таблиц документации — ответы Соннета и итоговый план + +> Консультация с Claude Sonnet от 2026-07-18 по дизайну страниц параметров Terraform-провайдера Nubes. +> Полный контекст и вопросы: см. историю чата. + +--- + +## Ответы Соннета (кратко) + +| # | Вопрос | Ответ Соннета | +|---|--------|--------------| +| 1 | Приоритеты правильные? | Да, но Default должен быть ВЫШЕ Constraints. Юзер сначала смотрит Default, и только если не устраивает — лезет в Constraints | +| 2 | Пропорции ширин? | Code **20-22%**, Default **12%**, Description **32-40%**, Constraints **29-31%** | +| 3 | Убирать ID? Type → иконки? | **ID — убрать совсем.** Type — заменить иконками-баджами (`123`, `"…"`, `{…}`, `[…]`) в ячейке Code. Экономия колонки | +| 4 | Required: ✓ или ★? | **Красная ★ перед Code.** Универсальная конвенция (HTML-формы, Swagger). Экономия колонки | +| 5 | Description прятать? | **line-clamp(2) с кнопкой «▼ ещё».** Короткие (<80 символов) показывать полностью | +| 6 | Constraints — monospace? | **Да.** `font-family: monospace`, `overflow-wrap: anywhere`. `break-all` рвёт regex нечитаемо | +| 7 | Поиск по параметрам? | **Не нужен.** ~16 параметров на сервис, Ctrl+F браузера хватает | +| 8 | Цветовое кодирование строк? | **Да, тонко.** Required: `#fffbf0`, Nested: `padding-left + #f8f8f8`, Zebra: `#fafafa`/`#fff` | +| 9 | Sticky thead? | **Да.** `position: sticky; top: 0` | +| 10 | Что упустили? | **Кнопка копирования Code** (⧉ при hover). **Порядок строк** (required первые). ~~Мобильный вид~~ — не актуально, аудитория только десктоп | +--- + +## Итоговый план (объединённое мнение Соннета + моё) + +### Этап 1 — быстро, только CSS (сделать сейчас) + +| # | Что | Как | +|---|---|---| +| 1 | ★ вместо колонки Required | CSS: скрыть текст, `::before { content: "★"; color: #e74c3c; }` перед Code | +| 2 | Убрать ID | `display: none` на `th:nth-child(1)`, `td:nth-child(1)` | +| 3 | Monospace для Constraints | `font-family: monospace`, `overflow-wrap: anywhere` | +| 4 | Sticky thead | `position: sticky; top: 0; z-index: 1` | +| 5 | line-clamp(2) для Description | `display: -webkit-box; -webkit-line-clamp: 2` | +| 6 | Перераспределить ширины | По таблице ниже | +| 7 | Zebra striping | `tr:nth-child(even) { background: #fafafa }` | + +### Этап 2 — правки генератора (позже) + +| # | Что | Как | +|---|---|---| +| 8 | ★ генерировать в HTML (не CSS) | В `writers.go` добавить `★` перед Code для required | +| 9 | Иконки типов | В `writers.go`: `string` → `"…"`, `integer` → `123`, `map-fixed` → `{…}` и т.д. | +| 10 | Кнопка копирования | JS: при клике на Code копировать в буфер | +| 11 | Порядок строк | Required-параметры первыми в `renderParamTable` | + +### Новые ширины колонок (после удаления ID и Required) + +**Основная таблица (4 колонки):** +| Code+Type | Default | Description | Constraints | +|---|---|---|---| +| 22% | 13% | 35% | 30% | + +**Nested-таблица (5 колонок, ID и Required убраны):** +| ★Code+Type | Default | Description | Constraints | +|---|---|---|---| +| 22% | 12% | 35% | 31% | + +--- + +## Моё мнение + +Соннет дал очень толковые ответы. Главные инсайты для меня: + +1. **★ красная звёздочка вместо колонки Required** — это убивает двух зайцев: семантически точнее (★ = «обязательно», а не ✓ = «ок»), и экономит колонку. Я до этого думал про ✓ и не догадался до ★. + +2. **Default выше Constraints** — логично. Я ставил Constraints наравне с Default, но реально юзер сначала смотрит дефолт. Constraints — fallback. + +3. **line-clamp(2)** — компромисс между «прятать» и «показывать всё». 73% описаний влезут, CTRL+F работает, вертикальный ритм не ломается. Лучше чем сворачивать. + +4. **Кнопка копирования** — я упустил. Самая частая операция юзера — скопировать имя параметра. Иконка ⧉ при hover — микрофича с огромным эффектом. + +5. **Порядок строк** — required первыми. Тоже упустил. Сейчас они вразнобой из YAML. + +Единственное с чем не согласен: **поиск**. Соннет говорит «не нужен, Ctrl+F хватает». Но когда параметров 25+ и они называются `access_configuration_allow_list_ip_space_master`, Ctrl+F неудобен. Фильтр с debounce в 5 строк JS — не переусложнение, а реальная помощь. Но это на потом, не в первую очередь. diff --git a/docs/help/how-to-docs.md b/docs/help/how-to-docs.md new file mode 100644 index 0000000..cb869dd --- /dev/null +++ b/docs/help/how-to-docs.md @@ -0,0 +1,155 @@ +# Как править и публиковать документацию + +> Полный процесс: от правки `.md` до появления на сайте. + +--- + +## 1. Где лежат исходники + +| Что | Путь | +|---|---| +| Markdown-файлы документации | `docs/` | +| Конфиг MkDocs | `mkdocs.yml` (корень репо) | +| Ресурсные страницы (автогенерация) | `docs/30_registry/resources/` | +| Гайды | `docs/30_registry/guides/` | +| Собранный сайт (не под git) | `site/` | + +### Какие файлы публикуются + +MkDocs собирает только то, что не попало под `exclude_docs` в `mkdocs.yml`: + +**Публикуются**: `index.md`, `30_registry/*`, `90_finance/*`, `ops/*`, `ARCHITECTURE_NEW/*`, `TODO/*`, и др. + +**Исключены**: `README.md`, `ai_universal_provider_gen.md`, `00_overview/*`, `20_discovery/*`, `40_analysis/*`, `50_history/*`, `60_strategy/*`, `70_api/*`, `help/*` + +> Если хочешь добавить новую страницу — создай `.md` в папке, которая НЕ в exclude, и пропиши её в секцию `nav:` в `mkdocs.yml`. + +--- + +## 2. Как править + +1. Открываешь нужный `.md` файл в `docs/` +2. Редактируешь +3. Если добавил новый файл — добавляешь ссылку в `nav:` в `mkdocs.yml` +4. Если менял навигацию — проверь, что все ссылки валидны + +--- + +## 3. Сборка сайта (MkDocs) + +### Локально + +```bash +# если mkdocs установлен в системе +mkdocs build -d site + +# или через .venv +.venv/bin/python -m mkdocs build -f mkdocs.yml -d site +``` + +### Через Docker + +```bash +docker run --rm -v $(pwd):/docs squidfunk/mkdocs-material build -f /docs/mkdocs.yml -d site +``` + +### Скриптом (04_build_and_publish_docs.sh) + +Скрипт `TOOLS/scripts/04_build_and_publish_docs.sh` делает всё сразу: + +```bash +export S3_ENDPOINT=https://s3.msk-1.ngcloud.ru +export S3_ACCESS_KEY=... +export S3_SECRET_KEY=... +./04_build_and_publish_docs.sh 2.0.2 +``` + +Что он делает под капотом: +1. Берёт версию из `provider/main.go` (или из аргумента) +2. Создаёт временный `mkdocs.yml` с подставленным `site_url` под версию +3. Собирает сайт: пробует Docker → `.venv` → системный mkdocs +4. Загружает `site/` в S3 (вызывает `publish-docs.sh`) + +--- + +## 4. Публикация в S3 (Registry) + +Скрипт загрузки: `/home/naeel/terra/scripts/publish-docs.sh` + +```bash +./scripts/publish-docs.sh +``` + +Пример: +```bash +./scripts/publish-docs.sh site terra.k8c.ru nubes nubes 2.0.2 +``` + +Что делает: +- Копирует `site/` → `s3://terraform-registry/docs/nubes/nubes/2.0.2/` +- Выставляет public policy +- Итоговый URL: `https://terra.k8c.ru/docs/nubes/nubes/2.0.2/` + +**S3 credentials** (любой из способов): +- Переменные окружения: `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY` +- Или файл `secrets/.s3cfg_registry` + +--- + +## 5. Быстрая публикация одной страницы + +Если нужно поправить одну страницу без перезагрузки всего сайта: + +```bash +./scripts/publish-doc-page.sh \ + --profile devops/profiles/test \ + --version 5.0.17 \ + --page 30_registry/guides/getting-started/index.html +``` + +--- + +## 6. CI/CD (GitHub Actions) + +Файл: `.github/workflows/publish-docs.yml` + +**Триггеры:** +- Пуш тега `v*.*.*` +- Ручной запуск (workflow_dispatch) + +**Что делает:** +1. Checkout репозитория +2. Установка Python + mkdocs-material +3. Сборка: `mkdocs build -d site` +4. Установка `mc` (MinIO Client) +5. Публикация: `./scripts/publish-docs.sh site ` + +**Secrets (настроить в GitHub):** +- `S3_ENDPOINT` +- `S3_ACCESS_KEY` +- `S3_SECRET_KEY` +- `REGISTRY_HOSTNAME` (опционально, по умолчанию `terra.k8c.ru`) + +--- + +## 7. Быстрый чек-лист + +- [ ] Открыл `.md` файл в `docs/` +- [ ] Внёс правки +- [ ] Если новый файл — добавил в `nav:` в `mkdocs.yml` +- [ ] Собрал локально: `mkdocs build -d site` +- [ ] Проверил, что страницы выглядят нормально (открыть `site/index.html`) +- [ ] Опубликовал: `./04_build_and_publish_docs.sh ` + +--- + +## 8. Где что лежит (шпаргалка) + +| Файл | Назначение | +|---|---| +| `mkdocs.yml` | Конфиг сайта, навигация, exclude_docs | +| `TOOLS/scripts/04_build_and_publish_docs.sh` | Полный пайплайн: сборка + публикация | +| `scripts/publish-doc-page.sh` | Публикация одной страницы | +| `/home/naeel/terra/scripts/publish-docs.sh` | Скрипт загрузки в S3 | +| `.github/workflows/publish-docs.yml` | CI/CD авто-публикация по тегу | +| `secrets/.s3cfg_registry` | S3 credentials (не под git) | diff --git a/docs/help/table-column-widths-analysis.md b/docs/help/table-column-widths-analysis.md new file mode 100644 index 0000000..8e2144b --- /dev/null +++ b/docs/help/table-column-widths-analysis.md @@ -0,0 +1,132 @@ +# Анализ ширины колонок таблиц документации + +> Методика определения оптимальных ширин колонок на основе реальных данных из YAML-спек. + +--- + +## Методика + +### Источник данных + +Все параметры ресурсов генерируются из YAML-спек в `generated//resources_yaml/*.yaml`. +Скрипт анализа собирает ВСЕ параметры из всех YAML-файлов (только операции `create` и `modify`, включая вложенные `sub_params`). + +### Что измеряется + +Для каждого параметра извлекаются 5 групп значений: + +| Группа | Поля YAML | Назначение в таблице | +|---|---|---| +| Code | `code` | Название параметра | +| Type | `data_type`, `type` | Тип данных | +| Default | `default` | Значение по умолчанию | +| Description | `descr`, `man` | Описание | +| Constraints | `min_value`, `max_value`, `regex`, `value_list`, `max_length`, `min_length`, `func`, `ref_svc_id`, `unique` | Ограничения | + +Для каждой группы вычисляется: +- MAX длина строки (символов) +- AVERAGE длина +- % значений длиннее 30/50/100/200/500 символов + +### Код анализа + +```python +import os, yaml + +YAML_DIR = 'generated/test/resources_yaml' +all_params = [] +nested_count = 0 + +def extract_params(svc, op, params_list, depth=0): + global nested_count + for p in params_list: + if not isinstance(p, dict): continue + all_params.append((svc, op, p, depth)) + sub = p.get('sub_params', []) + if sub: + nested_count += len(sub) + extract_params(svc, op, sub, depth + 1) + +for fname in sorted(os.listdir(YAML_DIR)): + if not fname.endswith('.yaml'): continue + with open(os.path.join(YAML_DIR, fname)) as f: + data = yaml.safe_load(f) + svc = data.get('name', fname) + for op in data.get('operations', []): + if op.get('action') not in ('create', 'modify'): continue + extract_params(svc, op.get('name','?'), op.get('params', [])) + +# ... (статистика по 5 группам, см. ниже) +``` + +### Когда перезапускать анализ + +- После добавления новых сервисов +- После изменения структуры параметров в API +- При смене стенда (test/dev/prod) + +Запуск: +```bash +python3 analyze_column_widths.py +``` + +--- + +## Результаты (стенд test, 2026-07-18) + +**Объём данных:** 49 YAML-файлов, 775 параметров, 339 вложенных sub_params + +| Колонка | Count | MAX (симв.) | AVG (симв.) | >50 | >100 | >200 | +|---|---|---|---|---|---|---| +| **Code** | 775 | 29 | 11 | 0% | 0% | 0% | +| **Type** | 775 | 15 | 9 | 0% | 0% | 0% | +| **Default** | 497 | 48 | 4 | 0% | 0% | 0% | +| **Description** | 488 | 635 | 69 | **55%** | **25%** | **3%** | +| **Constraints** | 247 | 199 | 46 | 27% | 6% | 0% | + +### Выводы + +1. **Code, Type, Default** — всегда короткие (макс 48 символов, обычно < 15). Им не нужно много места. +2. **Description** — основная колонка. 55% значений > 50 символов, 25% > 100. Ей нужна бОльшая часть ширины. +3. **Constraints** — есть только у 32% параметров (247 из 775). Длинные regex (> 100 символов) — исключение (у mariadb, clickhouse, postgres). Не нужно раздувать колонку ради 6% случаев — хватит `word-break: break-all`. +4. **Default** — есть у 64% параметров, всегда короткий (AVG 4 символа). + +--- + +## Оптимальные ширины колонок + +Используется `table-layout: fixed` — браузер жёстко соблюдает проценты. + +### Основная таблица (`.resource-table:not(.resource-table-nested)`) + +5 или 6 колонок: ID | Code | Type | (Default) | Description | Constraints + +| Колонка | CSS-селектор | Ширина | Примечание | +|---|---|---|---|---| +| ID | `nth-child(1)` | 3% | | +| Code | `nth-child(2)` | 15% | | +| Type | `nth-child(3)` | 7% | | +| Default | авто | авто | только если есть (6 колонок) | +| Description | `nth-last-child(2)` | 48% | **главная колонка** | +| Constraints | `nth-last-child(1)` | 27% | `word-break: break-all` | + +### Nested-таблица (`.resource-table-nested`) + +7 колонок: ID | Code | Type | Required | Default | Description | Constraints + +| Колонка | CSS-селектор | Ширина | Примечание | +|---|---|---|---|---| +| ID | `nth-child(1)` | 3% | | +| Code | `nth-child(2)` | 12% | | +| Type | `nth-child(3)` | 7% | | +| Required | `nth-child(4)` | 5% | | +| Default | `nth-child(5)` | 7% | | +| Description | `nth-child(6)` | 38% | **главная колонка** | +| Constraints | `nth-child(7)` | 28% | `word-break: break-all` | + +--- + +## Принцип + +> Если поле широкое только в 5% случаев — не надо рисовать его широким для всех. +> Description — всегда широкий. Constraints — редко широкий, пусть ломается. diff --git a/scripts/analyze_column_widths.py b/scripts/analyze_column_widths.py new file mode 100644 index 0000000..e210406 --- /dev/null +++ b/scripts/analyze_column_widths.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +""" +Analyze column widths from YAML specs. +Usage: python3 analyze_column_widths.py [stand] + stand: test (default), dev, prod +""" +import os, sys, yaml + +STAND = sys.argv[1] if len(sys.argv) > 1 else 'test' +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +YAML_DIR = f'{REPO}/generated/{STAND}/resources_yaml' + +if not os.path.isdir(YAML_DIR): + print(f'Error: {YAML_DIR} not found') + sys.exit(1) + +all_params = [] +nested_count = 0 + +def extract_params(svc, op, params_list, depth=0): + global nested_count + for p in params_list: + if not isinstance(p, dict): continue + all_params.append((svc, op, p, depth)) + sub = p.get('sub_params', []) + if sub: + nested_count += len(sub) + extract_params(svc, op, sub, depth + 1) + +for fname in sorted(os.listdir(YAML_DIR)): + if not fname.endswith('.yaml'): continue + with open(os.path.join(YAML_DIR, fname)) as f: + data = yaml.safe_load(f) + svc = data.get('name', fname) + for op in data.get('operations', []): + if op.get('action') not in ('create', 'modify'): continue + extract_params(svc, op.get('name', '?'), op.get('params', [])) + +print(f'Stand: {STAND} | Files: {len(os.listdir(YAML_DIR))}') +print(f'Total params: {len(all_params)} | Nested subparams: {nested_count}') + +def stats(items, label): + if not items: return + lens = [len(str(t[2])) for t in items] + n = len(lens) + mx, avg = max(lens), sum(lens)/n + print(f'\n--- {label} (n={n}) ---') + print(f' MAX: {mx} | AVG: {avg:.0f}') + print(f' >30: {sum(l in (l>30 for l in lens))/n*100:.0f}% >50: {sum(1 for l in lens if l>50)/n*100:.0f}% >100: {sum(1 for l in lens if l>100)/n*100:.0f}% >200: {sum(1 for l in lens if l>200)/n*100:.0f}% >500: {sum(1 for l in lens if l>500)/n*100:.0f}%') + +codes = [(s,o,p.get('code','')) for s,o,p,d in all_params] +dtypes = [(s,o,p.get('data_type', p.get('type',''))) for s,o,p,d in all_params] +defs = [(s,o,str(p['default'])) for s,o,p,d in all_params if 'default' in p] +descrs = [(s,o,p.get('descr','') or p.get('man','')) for s,o,p,d in all_params if p.get('descr') or p.get('man')] + +constraint_keys = ['min_value','max_value','regex','value_list','max_length','min_length','func','ref_svc_id','unique'] +constraints = [] +for s,o,p,d in all_params: + parts = [] + for k in constraint_keys: + v = p.get(k) + if v is not None: parts.append(f'{k}={v}') + if parts: constraints.append((s,o,' | '.join(parts))) + +stats(codes, 'Code'); stats(dtypes, 'Type'); stats(defs, 'Default') +stats(descrs, 'Description'); stats(constraints, 'Constraints') + +for name, arr in [('Code', codes), ('Type', dtypes), ('Default', defs), + ('Description', descrs), ('Constraints', constraints)]: + top = sorted(arr, key=lambda x: len(str(x[2])), reverse=True)[:3] + print(f'\nTop-3 longest {name}:') + for s, o, v in top: + print(f' [{s}/{o}] len={len(str(v))}: {str(v)[:120]}')