Files
my-NexusTradingForexBot/scripts/div_balance_check.py
T
gavindiazandClaude Code 3a005ddcc6
Docs / Validate documentation (push) Canceled after 0s
Docs / Deploy GitHub Pages (push) Canceled after 0s
CI Final Summary (AI) / AI Final Summary (push) Canceled after 0s
CI Final Summary (AI) / Push/PR event feed (push) Canceled after 0s
CI / CI Integrity and Change Classification (push) Canceled after 0s
CI / Code Quality & Tests (push) Canceled after 0s
Dependency Lock & Migration Safety / Dependency drift (lock vs pyproject) (push) Canceled after 0s
Dependency Lock & Migration Safety / Migration safety (fail-loud + version postconditions) (push) Canceled after 0s
JS Tests / Frontend JS Unit Tests (push) Canceled after 0s
Security / CodeQL Analysis (push) Canceled after 0s
Security / Trivy Vulnerability Scan (push) Canceled after 0s
Tests (OS Matrix) / Py Tests (macos-latest) (push) Canceled after 0s
Tests (OS Matrix) / Py Tests (windows-latest) (push) Canceled after 0s
CI / Extended Validation (Tier 2) (push) Canceled after 0s
CI / Heavy CI (database-provider) (push) Canceled after 0s
CI / Heavy CI (e2e) (push) Canceled after 0s
CI / Heavy CI (integration) (push) Canceled after 0s
CI / Heavy CI (model-validation) (push) Canceled after 0s
CI / Heavy CI (research-backtest) (push) Canceled after 0s
CI / Aggregate CI Results (push) Canceled after 0s
chore(repo): initial import of Nexus Trading Forex Bot into self-hosted hosting
Full project import for the self-hosted code hosting
(https://code.aifunny.ltd/gavindiaz/my-NexusTradingForexBot.git).

Includes the live-freshness + scaler-hash fixes for the permanent
STALE dashboard (tick_pipeline now_utc stamps; infer_probabilities
computes model_input_hash on the actual scaled 70D tensor), the
data-fetch default-path fix, the g29 web-auth test fixes, and a
Chinese user manual (使用手册.md) for the web dashboard.

Scratch diagnostic scripts (check*.py, contain a live web-auth token)
and the .kilo/ agent-worktree directory are gitignored, not committed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-23 23:24:54 +08:00

141 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""
div_balance_check.py — strict div-balance stack parser for Nexus HTML files using HTMLParser.
Contract (per repo convention):
* index.html is pure LF
* After ANY HTML edit we must guarantee there are NO mismatched/extra closing
divs and ZERO unclosed divs at EOF.
This script uses html.parser to strip script/style/template raw text and comments
robustly before running a real tag-stack parse.
"""
from __future__ import annotations
import re
import sys
from html.parser import HTMLParser
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
PAIRED_TAGS = {
"div",
"section",
"article",
"header",
"footer",
"main",
"aside",
"nav",
"ul",
"ol",
"li",
"table",
"thead",
"tbody",
"tr",
"td",
"th",
"form",
"button",
"select",
"script",
"style",
"template",
}
# Match only one HTML tag at a time. Attribute text cannot contain angle
# brackets, preventing pathological backtracking and handling whitespace in
# closing tags such as ``</script\t>`` correctly.
TAG_RE = re.compile(r"<\s*(/?)\s*([a-zA-Z][a-zA-Z0-9]*)(?:\s+[^<>]*?)?\s*(/?)>")
class _HTMLStripper(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.fed: list[str] = []
self.ignore_depth = 0
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
t = tag.lower()
if t in ("script", "style", "template"):
self.ignore_depth += 1
if self.ignore_depth == 0:
attr_str = "".join(f' {k}="{v}"' if v is not None else f" {k}" for k, v in attrs)
self.fed.append(f"<{t}{attr_str}>")
def handle_endtag(self, tag: str) -> None:
t = tag.lower()
if t in ("script", "style", "template"):
if self.ignore_depth > 0:
self.ignore_depth -= 1
return
if self.ignore_depth == 0:
self.fed.append(f"</{t}>")
def handle_comment(self, data: str) -> None:
pass
def _strip_ignored(text: str) -> str:
parser = _HTMLStripper()
parser.feed(text)
parser.close()
return "".join(parser.fed)
def check_file(path: Path) -> tuple[bool, list[str]]:
raw = path.read_text(encoding="utf-8", errors="replace")
issues: list[str] = []
if path.name == "index.html" and "\r\n" in raw:
issues.append("index.html is NOT pure LF (contains CRLF)")
stack: list[tuple[str, int]] = []
for m in TAG_RE.finditer(_strip_ignored(raw)):
slash, tag, _attrs, selfclose = m.group(1), m.group(2).lower(), m.group(3), m.group(4)
line = raw.count("\n", 0, m.start()) + 1
if tag not in PAIRED_TAGS:
continue
if slash == "/":
if not stack:
issues.append(f"line {line}: EXTRA closing </{tag}> (nothing open)")
continue
top_tag, top_line = stack[-1]
if top_tag != tag:
issues.append(
f"line {line}: MISMATCHED close </{tag}> (top of stack is <{top_tag}> opened at line {top_line})"
)
for i in range(len(stack) - 1, -1, -1):
if stack[i][0] == tag:
del stack[i]
break
else:
stack.pop()
elif selfclose != "/":
stack.append((tag, line))
if stack:
issues.extend(f"line {ln}: UNCLOSED <{tag}> at EOF" for tag, ln in stack)
return (not issues), issues
def main(argv: list[str]) -> int:
files = [Path(a) for a in argv] if argv else [REPO_ROOT / "Web" / "index.html"]
ok = True
for f in files:
if not f.exists():
print(f"[SKIP] {f} (not found)")
continue
balanced, issues = check_file(f)
if balanced:
print(f"[OK] {f} — div/block balance clean")
else:
ok = False
print(f"[FAIL] {f}")
for issue in issues:
print(f" - {issue}")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))