mirror of
https://gitverse.ru/kpa39l/file-tree-catalog.git
synced 2026-09-29 09:15:06 +00:00
135 lines
5.7 KiB
Python
135 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Build a navigable Markdown catalog (КАТАЛОГ_проекта.md) of a large file tree.
|
|
|
|
Adapt ROOT (and optionally junk/MAP/docs) for each project, then run:
|
|
python3 build_catalog.py
|
|
|
|
Design rules (from real use):
|
|
- Mechanical, deterministic: no LLM for the structure/stats.
|
|
- Recurse top-level dirs + their immediate children only (maxdepth ~2) so the
|
|
doc stays readable. Summarize bulk design subfolders (note+dwg) as "N файлов".
|
|
- Tag each file's role from extension (+ naming conventions), Russian labels.
|
|
- Filter service/junk files from the body but count them in stats.
|
|
- Sort dirs with key=str.lower for natural locale ordering.
|
|
|
|
Reproduce the exact structure used for the "Винный город" СКС/ЛВС/КТСБ archive:
|
|
<root>/
|
|
<СИСТЕМА.N>/ e.g. КТСБ.1, ЛВС.2, СКС.3
|
|
<дата_Rev.XX>/ e.g. 2026.05.18_Rev.01
|
|
<шифр>.pdf итоговый комплект
|
|
*_Изменения_*.docx список изменений
|
|
*_Замечания*.xlsx замечания/ответы
|
|
note+dwg/ bulk drawings + spec files (collapse to count)
|
|
"""
|
|
import os
|
|
import sys
|
|
import datetime
|
|
import collections
|
|
|
|
# >>> EDIT THESE for a new project >>>
|
|
ROOT = "/mnt/vinogorod/ИТ/1. Винный город/Проекты/2026.05.22_Актуальные комплекты СКС, ЛВС, КТСБ"
|
|
# subfolders to collapse to a count (engine+Cad sets)
|
|
BULK_DIR_NAMES = ("note+dwg", "note", "dwg")
|
|
# service files to exclude from the body (but still counted in the stats line)
|
|
JUNK_NAMES = {"thumbs.db", "plot.log", "desktop.ini"}
|
|
JUNK_EXTS = {".db", ".dwl", ".dwl2", ".bak"}
|
|
# <<< END EDIT <<<
|
|
|
|
# numeric suffix so .1/.2 sort after the base in locale order
|
|
STRIP = str.lstrip # alias to avoid binding w/ layer in comprehension below
|
|
|
|
|
|
def collect(base):
|
|
"""Return {name: ("FILE", ext_upper) | ("DIR", file_count)} for one level."""
|
|
out = {}
|
|
for name in sorted(os.listdir(base), key=str.lower):
|
|
p = os.path.join(base, name)
|
|
if name.lower() in JUNK_NAMES or name.startswith("~$"):
|
|
continue
|
|
if os.path.isdir(p):
|
|
out[name] = ("DIR", sum(len(x) for _, _, x in os.walk(p)))
|
|
else:
|
|
ext = os.path.splitext(name)[1].lstrip(".").upper()
|
|
if ext in {e.lstrip(".").upper() for e in JUNK_EXTS}:
|
|
continue
|
|
out[name] = ("FILE", ext)
|
|
return out
|
|
|
|
|
|
def describe(ext):
|
|
return {
|
|
"PDF": "итоговый комплект PDF",
|
|
"DOCX": "список изменений (docx)",
|
|
"XLSX": "замечания/ответы (xlsx)",
|
|
"TXT": "изменения (txt)",
|
|
"DWG": "чертёж AutoCAD (dwg)",
|
|
"DOC": "документ (doc)",
|
|
"LOG": "лог",
|
|
"MD": "каталог/описание (md)",
|
|
"BAK": "резервная копия (bak)",
|
|
}.get(ext, ext)
|
|
|
|
|
|
def build(root):
|
|
# --- stats ---
|
|
ext_count = collections.Counter()
|
|
total_files = total_size = junk = 0
|
|
for dirpath, _, filenames in os.walk(root):
|
|
for f in filenames:
|
|
ext = os.path.splitext(f)[1].lower() or "(нет)"
|
|
ext_count[ext] += 1
|
|
total_files += 1
|
|
try:
|
|
total_size += os.path.getsize(os.path.join(dirpath, f))
|
|
except OSError:
|
|
pass
|
|
if (f.startswith("~$") or f.lower() in JUNK_NAMES
|
|
or ext in JUNK_EXTS):
|
|
junk += 1
|
|
|
|
L = ["# Каталог проекта", "",
|
|
"**Путь:** `%s`" % root, "",
|
|
"**Сформирован:** %s" % datetime.date.today().isoformat(), "",
|
|
"## Общая статистика", ""]
|
|
L.append("- Всего файлов: **%d** (~%.2f ГБ)" % (total_files, total_size / 1024**3))
|
|
parts = []
|
|
for e, c in ext_count.most_common():
|
|
parts.append("**%s**: %d" % (e.lstrip(".").upper() or "без расширения", c))
|
|
L.append("- По типам: %s" % ", ".join(parts))
|
|
L.append("- Из них служебных (Thumbs.db, ~$, .dwl/.dwl2/.bak): %d (не включены)" % junk)
|
|
L += ["", "## Структура и содержимое", ""]
|
|
|
|
for top in sorted(os.listdir(root), key=str.lower):
|
|
topp = os.path.join(root, top)
|
|
if not os.path.isdir(topp):
|
|
continue
|
|
L.append("### %s" % top)
|
|
L.append("")
|
|
for name, (typ, val) in collect(topp).items():
|
|
if typ == "DIR":
|
|
if name.lower() in BULK_DIR_NAMES:
|
|
L.append("- **`%s/`** — %d файлов (чертежи + спецификации)" % (name, val))
|
|
else:
|
|
L.append("**%s**" % name)
|
|
L.append("")
|
|
for sub, (styp, sval) in collect(os.path.join(topp, name)).items():
|
|
if styp == "DIR":
|
|
if sub.lower() in BULK_DIR_NAMES:
|
|
L.append("- **`%s/`** — %d файлов (чертежи + спецификации)" % (sub, sval))
|
|
else:
|
|
L.append("- **`%s/`** — %d файлов" % (sub, sval))
|
|
else:
|
|
L.append("- `%s` — %s" % (sub, describe(sval)))
|
|
L.append("")
|
|
else:
|
|
L.append("- `%s` — %s" % (name, describe(val)))
|
|
return "\n".join(L)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
root = sys.argv[1] if len(sys.argv) > 1 else ROOT
|
|
text = build(root)
|
|
out = os.path.join(root, "КАТАЛОГ_проекта.md")
|
|
with open(out, "w") as fh:
|
|
fh.write(text)
|
|
print("OK ->", out, os.path.getsize(out), "байт") |