⚠️ ВНИМАНИЕ⚠️ Репозиторий архивирован. Утилита доступна в составе пакета: fs-tools.
CLI-утилита для проверки структуры каталогов по правилам из файла .fs-check.
Синтаксис правил близок к .gitignore, но смысл инвертирован: строки описывают
папки и файлы, которые обязаны существовать. Утилита рекурсивно сверяет дерево
с правилами и печатает список отсутствующих путей.
Утилита не создаёт, не переименовывает и не удаляет папки и файлы — структуру
проверяемого дерева она не меняет. Единственная запись на диск — журнал нарушений
.fs-log в проверяемом каталоге (универсальный формат журнала), и только
при обнаружении отсутствующих путей. При нарушениях утилита также может отправить
веб-хук-уведомление (см. «Запуск по таймеру и уведомления»).
- Интерактивный выбор корня проверки: нативный проводник Windows (на Windows и в WSL), стандартный диалог macOS либо ввод пути в терминале (на обычном Linux), с откатом в терминал при любом сбое диалога.
- Чтение правил из
.fs-checkв выбранном каталоге (кодировкаutf-8-sig). Нет файла — понятная ошибка и ненулевой код возврата. - Положительные правила с литералами,
*(один уровень) и**(рекурсивно), разворачиваемые черезpathlib.Path.globпо уже существующим папкам. - Мандат может быть папкой или файлом: без завершающего
/проверкаexists()(…/Data/project.md), с завершающим/— строгоis_dir()(…/Addl/). - Негативы
!исключают служебные каталоги из подстановок*/**(узкий момент_Archive), не влияя на литералы. - Скрытые каталоги (имя на
.) не обходятся; симлинки при**не разыменовываются. - Отсортированный дедуплицированный список отсутствующих путей + итоговая сводка; ненулевой код возврата при наличии нарушений.
- Запуск с аргументом-каталогом (без диалога) — для cron/планировщика на всех ОС.
- При нарушениях — журнал
.fs-logв проверяемом каталоге и fire-and-forget веб-хук-уведомление (адрес и Bearer-токен — в.env).
fs-checker/
├── check_fs.py # точка входа
├── check.sh # обёртка для Linux/macOS (терминал)
├── check.command # обёртка для macOS (двойной клик в Finder)
├── check.bat # обёртка для Windows
├── checker/
│ ├── __init__.py # публичное API
│ ├── cli.py # разбор аргументов и сценарий запуска
│ ├── picker.py # выбор каталога (диалоги Windows/WSL/macOS, ввод в терминале)
│ ├── pick_folder.ps1 # нативный диалог выбора папки Windows (IFileOpenDialog)
│ ├── rule.py # разбор .fs-check (правила + PathSpec негативов)
│ ├── pathspec_compat.py # совместимость версий pathspec (выбор фабрики паттернов)
│ ├── engine.py # разворачивание правил и сбор отсутствующих путей
│ ├── report.py # формат вывода и сводки
│ ├── log.py # журнал .fs-log (универсальный формат)
│ └── notify.py # веб-хук-уведомление о нарушениях (.env, fire-and-forget)
├── tests/ # тесты (pytest)
├── examples/ # песочница-фикстура для ручного прогона
└── requirements.txt
- Python 3.10+ (используется синтаксис
X | None). - Рантайм-зависимости (
requirements.txt): pathspec — gitignore-семантика негативных правил (!...); requests — отправка веб-хука; python-dotenv — чтение.env.
Стандартной библиотеки достаточно для выбора папки: на Windows и в WSL вызывается
powershell.exe (нативный диалог IFileOpenDialog), на macOS — osascript
(AppleScript choose folder), на обычном Linux — ввод пути в терминале.
Дополнительных GUI-пакетов не требуется.
Через обёртки установка не нужна: при первом запуске они сами создают .venv и
ставят зависимости (требуется интернет один раз). Этот раздел — для прямого запуска
python3 check_fs.py или разработки:
cd fs-checker
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtПроще всего — через обёртку для своей ОС. При первом запуске она автоматически
подготовит окружение (.venv + зависимости), затем запустит утилиту:
./check.bat # Windows
./check.sh # Linux/macOS (терминал)На macOS для запуска двойным кликом в Finder используйте check.command (один раз
сделайте его исполняемым: chmod +x check.command).
Альтернатива — прямой запуск (требует заранее подготовленного окружения):
python3 check_fs.pyБез аргумента каталог выбирается интерактивно (по умолчанию предлагается рабочий
каталог). На Windows/WSL открывается нативный диалог IFileOpenDialog (в WSL путь
конвертируется через wslpath, переменные пробрасываются через WSLENV); на
macOS — osascript; на обычном Linux — ввод пути в терминале. При любом сбое
диалога — откат на ввод в терминале.
Каталог можно передать аргументом — тогда диалог не открывается (режим запуска по таймеру, см. ниже). Обёртки пробрасывают аргумент в утилиту:
./check.sh /path/to/dir # Linux/macOS
./check.bat C:\path\to\dir # Windows
python3 check_fs.py /path/to/dirЧтобы не указывать полный путь к обёртке каждый раз, добавьте алиас в конфиг своей оболочки.
bash/zsh — в ~/.bashrc или ~/.zshrc:
alias fs-check="$HOME/Projects/Work/fs-checker/check.sh"Примените изменения (source ~/.bashrc) — и запускайте командой fs-check.
PowerShell — в файле профиля ($PROFILE). Используется функция-обёртка, чтобы корректно пробрасывать аргументы:
function fs-check { & "C:\path\to\fs-checker\check.bat" @args }Перечитайте профиль (. $PROFILE) — и запускайте командой fs-check.
Для периодической проверки (cron на Linux/macOS, «Планировщик заданий» на Windows, cron внутри WSL) запускайте утилиту с аргументом-каталогом — так диалог не открывается, а проверяется именно заданная папка:
# crontab -e: ежедневно в 9:00 проверять хранилище
0 9 * * * /path/to/fs-checker/check.sh /mnt/disk/HomeПри обнаружении отсутствующих путей утилита:
- дописывает журнал
.fs-logв проверяемом каталоге (метка времени + список путей). Формат журнала универсален: если над одним каталогом запускается несколько утилит, они дополняют один и тот же файл как единая система; - шлёт веб-хук на адрес из
.env— чтобы узнать о проблеме без просмотра логов. Текст уведомления только сигнализирует о нарушениях; сами пути — в.fs-log.
Адрес и токен хранятся в .env рядом со скриптом (в корне fs-checker, не в
проверяемом каталоге) — путь фиксирован, поэтому таймер может стартовать из любого
рабочего каталога. Шаблон — в .env.example; скопируйте его в .env
(сам .env в репозиторий не попадает, .gitignore):
FSCHK_WEBHOOK_URL=https://example.com/hook
FSCHK_WEBHOOK_TOK=секретный-токенБез FSCHK_WEBHOOK_URL уведомления отключены (интерактивный режим не меняется).
Значения можно задать и через переменные окружения процесса — .env приоритетнее.
Запрос — POST с телом {"text": "<сообщение>"} и заголовком
Authorization: Bearer <FSCHK_WEBHOOK_TOK> (если токен задан). Отправка работает
по принципам UDP — «выстрелил и забыл»: минимальный таймаут, ответ не проверяется,
любые сетевые ошибки гасятся и на код возврата проверки не влияют.
Файл лежит в корне проверки (выбранном каталоге) и читается оттуда — как
.gitignore в корне репозитория. Пути в нём якорятся к этому корню.
- Кодировка
utf-8-sig(BOM проглатывается). Разделитель сегментов — POSIX/. - Комментарий — только ведущий
#; срединный/инлайновый#литерален (C#Notesвалиден). Пустые строки игнорируются. Конечные пробелы обрезаются, значимый можно экранировать\. - Якорение ведущим
/— относительно корня правил, как в.gitignore. Правила без ведущего/тоже считаются якорными. - Сегменты: литерал (
Projects),*(ровно один уровень),**(ноль и более уровней), глоб внутри сегмента (*.ru,_Archive*). - Мандат — последний сегмент. Завершающий
/→ строго каталог (is_dir()); без него → существование (exists(), файл ИЛИ папка). Мандат сверяется литерально (без glob в последнем сегменте). - Негатив
!— исключение из подстановок*/**(см. ниже).
Компактный пример:
# Фиксированные каталоги: обязаны существовать сами по себе
/Activities
/Activities/Web/Projects
# В каждом занятии — Projects и Resources
/Activities/*/Projects
/Activities/*/Resources
# Категории Web — строго каталоги (завершающий /)
/Activities/Web/Projects/Addl/
# Back/Data в проектах; обязательный файл Data/project.md (мандат-файл)
/Activities/Web/Projects/Work/*/*/Back
/Activities/Web/Projects/Work/*/*/Data/project.md
# Архивные проекты на любом уровне
/Activities/Web/Projects/**/_Archive/*/Back
# Служебный _Archive не попадает под обобщённые * и **
!_ArchiveКаждое положительное правило делится на две части:
- префикс (все сегменты, кроме последнего) — множество уже существующих
каталогов-якорей.
*/**перечисляют существующие папки; отсутствующий литерал префикса — это не нарушение, просто нет якоря; - последний сегмент — мандат: для каждого найденного якоря он обязан существовать, иначе нарушение.
Отсюда два важных следствия:
- Обязательность фиксированных каталогов. Если фиксированный (литеральный)
каталог в начале цепочки отсутствует целиком, правила, чей префикс через него
проходит, молча не дают нарушений (нет якоря). Поэтому каждый обязательный
фиксированный каталог описывается отдельным правилом-литералом, где он —
последний сегмент (
/Activities,/Activities/Web,/Activities/Web/Projects). Правило из одного сегмента берёт якорем сам корень проверки. - Узкий момент
_Archive. ОбобщённоеAddl/*/Backсвоим*перечислило бы и служебную_Archive, ошибочно требуяAddl/_Archive/Back. Негатив!_Archiveисключает каталог с этим именем из подстановок*/**(матч по имени одной компоненты, а не по полному пути), не влияя на литералы. Прунинг применяется к каждой*/**-выбранной компоненте якоря, а не только к листу — поэтому_Archiveотсекается и на промежуточных*-позициях (Work/*/_Archive/Dataне требуетproject.md). А правило с литеральным_Archive(**/_Archive/*/Back) продолжает проверять архивные проекты.
В stdout печатается заголовок с каталогом, отсортированный список отсутствующих путей (термин «пути», т.к. это и папки, и файлы) и сводка:
Каталог: /mnt/disk/Home
Отсутствуют пути (3):
Activities/3D/Resources
Activities/Web/Projects/Self/personal.example/Back
Activities/Web/Projects/Work/Fabrikam/widgets.example/Data/project.md
Проверено правил: 17. Найдено каталогов-кандидатов: 57. Отсутствует: 3.
Если нарушений нет:
Каталог: /mnt/disk/Home
Все требуемые пути на месте.
Проверено правил: 17. Найдено каталогов-кандидатов: 60. Отсутствует: 0.
Коды возврата:
| Код | Условие |
|---|---|
| 0 | нарушений нет |
| 1 | ошибка запуска: каталог не выбран/не найден/не каталог, нет .fs-check |
| 2 | проверка выполнена, найдены отсутствующие пути |
Импортируйте из пакета checker, а не из подмодулей напрямую:
from pathlib import Path
from checker import FsChecker, format_report, load_fs_rule
root = Path("/path/to/dir")
fs_rule = load_fs_rule(root) # положительные правила + PathSpec негативов
result = FsChecker(fs_rule).check(root)
print(format_report(root, result)) # result.missing — отсортированный списокЭкспортируется: main, FsChecker, CheckResult, format_report,
load_fs_rule, FsRule, Rule, Negation, FsRuleError, FS_LOG,
write_fs_log, load_webhook_config, send_webhook.
В examples/ — запускаемое дерево-фикстура с .fs-check и намеренно
отсутствующими путями (включая файл project.md). Прогон утилиты на нём показывает
ровно 7 нарушений; разбор — в examples/README.md.
pytest # тесты
ruff check checker tests # линтер
mypy --strict checker check_fs.py # проверка типовКод проходит ruff и mypy --strict без замечаний; покрытие парсинга, движка,
CLI и picker — в tests/.