121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
import os
|
|
import re
|
|
|
|
WIKI_DIR = r"C:\Program_coding\임도설계 및 견적자동화 프로그램 개발\docs\wiki"
|
|
PAGES_DIR = os.path.join(WIKI_DIR, "pages")
|
|
CONCEPTS_DIR = os.path.join(WIKI_DIR, "concepts")
|
|
INDEX_PATH = os.path.join(WIKI_DIR, "index.md")
|
|
|
|
errors = []
|
|
warnings = []
|
|
all_wiki_files = {} # relative_path -> absolute_path
|
|
|
|
# Collect all markdown files in wiki/ (ignoring graphify-out and memory directories)
|
|
for root, dirs, files in os.walk(WIKI_DIR):
|
|
# skip graphify-out or memory
|
|
if "graphify-out" in root or "memory" in root:
|
|
continue
|
|
for file in files:
|
|
if file.endswith(".md"):
|
|
abs_path = os.path.join(root, file)
|
|
rel_path = os.path.relpath(abs_path, WIKI_DIR).replace("\\", "/")
|
|
all_wiki_files[rel_path] = abs_path
|
|
simple_name = os.path.splitext(rel_path)[0]
|
|
all_wiki_files[simple_name] = abs_path
|
|
|
|
# Read index.md content
|
|
with open(INDEX_PATH, "r", encoding="utf-8") as f:
|
|
index_content = f.read()
|
|
|
|
# Pattern for wikilinks: [[link]] or [[link|label]]
|
|
wikilink_pat = re.compile(r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]")
|
|
|
|
for rel_path, abs_path in list(all_wiki_files.items()):
|
|
if "/" not in rel_path and rel_path != "index" and rel_path != "log":
|
|
continue
|
|
if rel_path in ["index", "log", "index.md", "log.md"]:
|
|
continue
|
|
|
|
with open(abs_path, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
line_count = len(lines)
|
|
content = "".join(lines)
|
|
|
|
# Rule 14: Max 100 lines
|
|
if line_count > 100:
|
|
warnings.append(f"[Line Count] `{rel_path}.md` exceeds 100 lines ({line_count} lines).")
|
|
|
|
# Check YAML Frontmatter via simple regex
|
|
frontmatter_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL)
|
|
if not frontmatter_match:
|
|
errors.append(f"[Frontmatter] `{rel_path}.md` has no valid YAML frontmatter.")
|
|
continue
|
|
|
|
fm_text = frontmatter_match.group(1)
|
|
|
|
# Simple regex parsing for status & page_id
|
|
status_match = re.search(r"^status:\s*(\w+)", fm_text, re.MULTILINE)
|
|
page_id_match = re.search(r"^page_id:\s*([^\n\r]+)", fm_text, re.MULTILINE)
|
|
|
|
if not status_match:
|
|
errors.append(f"[Status] `{rel_path}.md` has no status field in frontmatter.")
|
|
else:
|
|
status = status_match.group(1).strip()
|
|
if status not in ["draft", "stable", "stale"]:
|
|
errors.append(f"[Status] `{rel_path}.md` has invalid status '{status}'. Must be draft, stable, or stale.")
|
|
elif status == "stale":
|
|
warnings.append(f"[Stale Page] `{rel_path}.md` is marked as stale and needs updates from raw inputs.")
|
|
|
|
# Rule 5: page_id required for pages/
|
|
if "pages/" in rel_path:
|
|
if not page_id_match:
|
|
errors.append(f"[page_id] `{rel_path}.md` is in pages/ but lacks a 'page_id' field.")
|
|
|
|
# Check for broken wikilinks in content
|
|
links = wikilink_pat.findall(content)
|
|
for link in links:
|
|
link_clean = link.strip().replace("\\", "/").split("#")[0] # ignore anchor for file existence check
|
|
if not link_clean:
|
|
continue
|
|
found = False
|
|
|
|
# 1. Absolute link from vault root (e.g. concepts/storage_paths)
|
|
if link_clean in all_wiki_files:
|
|
found = True
|
|
# 2. Simple name match (e.g. storage_paths, B01_frontend)
|
|
elif link_clean.split("/")[-1] in all_wiki_files:
|
|
found = True
|
|
# 3. Handle subpages like [[B01_Dashboard/B01_frontend]]
|
|
elif f"pages/{link_clean}" in all_wiki_files:
|
|
found = True
|
|
elif f"concepts/{link_clean}" in all_wiki_files:
|
|
found = True
|
|
# 4. Handle nested directory patterns
|
|
elif link_clean.startswith("../"):
|
|
# Resolve relative link
|
|
curr_dir = os.path.dirname(rel_path)
|
|
resolved = os.path.normpath(os.path.join(curr_dir, link_clean)).replace("\\", "/")
|
|
if resolved in all_wiki_files or resolved.replace("pages/", "") in all_wiki_files:
|
|
found = True
|
|
|
|
if not found:
|
|
errors.append(f"[Broken Link] `{rel_path}.md` contains broken wikilink: [[{link}]]")
|
|
|
|
# Check if this file is registered in index.md (avoid orphans)
|
|
file_base = os.path.basename(abs_path)
|
|
file_name_no_ext = os.path.splitext(file_base)[0]
|
|
if file_name_no_ext not in index_content:
|
|
# check if relative path is in index
|
|
rel_path_no_ext = rel_path.replace(".md", "")
|
|
if rel_path_no_ext not in index_content and rel_path_no_ext.split("/")[-1] not in index_content:
|
|
warnings.append(f"[Orphan Page] `{rel_path}.md` is not linked or mentioned in index.md.")
|
|
|
|
print("=== LINT ERRORS ===")
|
|
for e in sorted(list(set(errors))):
|
|
print(e)
|
|
print("\n=== LINT WARNINGS ===")
|
|
for w in sorted(list(set(warnings))):
|
|
print(w)
|
|
print("\nLint completed.")
|