Files

172 lines
4.9 KiB
PowerShell

param(
[string]$DocumentPath = (Join-Path $PSScriptRoot "..\http-client-app-plan.md"),
[string[]]$DisabledKeywords = @(
"<<<<<<<",
">>>>>>>",
"TODO_BLOCKER",
"FIXME_BLOCKER"
)
)
$ErrorActionPreference = "Stop"
$resolvedDocument = Resolve-Path -LiteralPath $DocumentPath
$python = Get-Command python -ErrorAction SilentlyContinue
$usePyLauncher = $false
if (-not $python) {
$python = Get-Command py -ErrorAction SilentlyContinue
$usePyLauncher = $true
}
if (-not $python) {
Write-Error "Python was not found. Install Python 3 or make the 'python'/'py' command available on PATH."
}
$checker = @'
import argparse
import json
import re
import sys
from pathlib import Path
def line_col(text, offset):
line = text.count("\n", 0, offset) + 1
last_newline = text.rfind("\n", 0, offset)
column = offset + 1 if last_newline == -1 else offset - last_newline
return line, column
def check_code_fences(text):
errors = []
fence_pattern = re.compile(r"^[ \t]*```", re.MULTILINE)
matches = list(fence_pattern.finditer(text))
if len(matches) % 2 != 0:
line, _ = line_col(text, matches[-1].start())
errors.append(f"Unbalanced fenced code block near line {line}.")
return errors
def check_json_blocks(text):
errors = []
block_pattern = re.compile(r"^[ \t]*```json[^\n]*\n(.*?)(?:\n^[ \t]*```[ \t]*$)", re.MULTILINE | re.DOTALL)
for index, match in enumerate(block_pattern.finditer(text), start=1):
raw_json = match.group(1)
try:
json.loads(raw_json)
except json.JSONDecodeError as exc:
start_line, _ = line_col(text, match.start(1))
errors.append(
f"Invalid JSON code block #{index} at line {start_line + exc.lineno - 1}, "
f"column {exc.colno}: {exc.msg}."
)
return errors
def is_table_separator(line):
stripped = line.strip()
if "|" not in stripped:
return False
cells = [cell.strip() for cell in stripped.strip("|").split("|")]
return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell or "") for cell in cells)
def pipe_count(line):
stripped = line.strip()
return stripped.count("|")
def check_tables(text):
errors = []
lines = text.splitlines()
in_fence = False
for index, line in enumerate(lines):
if re.match(r"^[ \t]*```", line):
in_fence = not in_fence
continue
if in_fence or not is_table_separator(line):
continue
table_lines = []
cursor = index - 1
while cursor >= 0 and "|" in lines[cursor]:
table_lines.insert(0, (cursor + 1, lines[cursor]))
cursor -= 1
table_lines.append((index + 1, line))
cursor = index + 1
while cursor < len(lines) and "|" in lines[cursor]:
table_lines.append((cursor + 1, lines[cursor]))
cursor += 1
counts = {pipe_count(table_line) for _, table_line in table_lines}
if len(counts) > 1:
first_line = table_lines[0][0]
errors.append(f"Markdown table pipe count mismatch near line {first_line}.")
return errors
def check_disabled_keywords(text, keywords):
errors = []
for keyword in keywords:
if not keyword:
continue
offset = text.find(keyword)
if offset != -1:
line, column = line_col(text, offset)
errors.append(f"Disabled keyword '{keyword}' found at line {line}, column {column}.")
return errors
def main():
parser = argparse.ArgumentParser(description="Verify Markdown planning document structure.")
parser.add_argument("document")
parser.add_argument("--keyword", action="append", default=[])
args = parser.parse_args()
document = Path(args.document)
text = document.read_text(encoding="utf-8-sig")
errors = []
errors.extend(check_code_fences(text))
errors.extend(check_json_blocks(text))
errors.extend(check_tables(text))
errors.extend(check_disabled_keywords(text, args.keyword))
if errors:
print(f"Documentation verification failed for {document}:")
for error in errors:
print(f" - {error}")
return 1
print(f"Documentation verification passed for {document}.")
return 0
if __name__ == "__main__":
sys.exit(main())
'@
$tempChecker = Join-Path ([System.IO.Path]::GetTempPath()) ("verify-docs-{0}.py" -f ([System.Guid]::NewGuid().ToString("N")))
try {
Set-Content -LiteralPath $tempChecker -Value $checker -Encoding UTF8
$arguments = @()
if ($usePyLauncher) {
$arguments += "-3"
}
$arguments += @($tempChecker, $resolvedDocument.Path)
foreach ($keyword in $DisabledKeywords) {
$arguments += @("--keyword", $keyword)
}
& $python.Source @arguments
exit $LASTEXITCODE
}
finally {
if (Test-Path -LiteralPath $tempChecker) {
Remove-Item -LiteralPath $tempChecker -Force
}
}