Scripts: page migration helpers for one-per-file layout
Python + shell tooling used to split grouped index.sx files into one-directory-per-page layout (see the hyperscript gallery migration). name-mapping.json records the rename table; strip_names.py is a helper for extracting component names from .sx sources. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
389
scripts/migrate-pages.sh
Executable file
389
scripts/migrate-pages.sh
Executable file
@@ -0,0 +1,389 @@
|
||||
#!/bin/bash
|
||||
# migrate-pages.sh — Restructure SX page files so file paths match URL structure
|
||||
# All moves use git mv. Directories created with mkdir -p.
|
||||
# If source doesn't exist, skip with warning. If target exists, skip.
|
||||
set -euo pipefail
|
||||
|
||||
cd /root/rose-ash
|
||||
|
||||
# Base directory for all moves
|
||||
B="sx/sx"
|
||||
|
||||
# First, stage all current changes in sx/sx/ so that the one-per-file split
|
||||
# files become tracked by git (they are currently untracked).
|
||||
echo "=== Staging current sx/sx/ changes ==="
|
||||
git add sx/sx/
|
||||
echo " Done. $(git diff --cached --name-only | wc -l) files staged."
|
||||
|
||||
MOVED=0
|
||||
SKIPPED=0
|
||||
WARNINGS=0
|
||||
|
||||
safe_mv() {
|
||||
local src="$B/$1"
|
||||
local dst="$B/$2"
|
||||
if [ ! -f "$src" ]; then
|
||||
echo " WARN: source missing: $1"
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
return
|
||||
fi
|
||||
if [ -f "$dst" ]; then
|
||||
echo " SKIP: target exists: $2"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
return
|
||||
fi
|
||||
local dir
|
||||
dir=$(dirname "$dst")
|
||||
mkdir -p "$dir"
|
||||
git mv "$src" "$dst"
|
||||
echo " $1 -> $2"
|
||||
MOVED=$((MOVED + 1))
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== HOME ==="
|
||||
safe_mv "docs-content/home-content.sx" "home/index.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: core pages ==="
|
||||
safe_mv "geography/capabilities.sx" "geography/capabilities/index.sx"
|
||||
safe_mv "geography/modules.sx" "geography/modules/index.sx"
|
||||
safe_mv "geography/eval-rules.sx" "geography/eval-rules/index.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: CEK ==="
|
||||
safe_mv "geography/cek/cek-content.sx" "geography/cek/index.sx"
|
||||
safe_mv "geography/cek/cek-demo-content.sx" "geography/cek/demo/index.sx"
|
||||
safe_mv "geography/cek/cek-freeze-content.sx" "geography/cek/freeze/index.sx"
|
||||
safe_mv "geography/cek/cek-content-address-content.sx" "geography/cek/content/index.sx"
|
||||
|
||||
# CEK islands (demo-* files)
|
||||
for f in $B/geography/cek/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "geography/cek/$base" "geography/cek/_islands/$base"
|
||||
done
|
||||
safe_mv "geography/cek/content-address-demo.sx" "geography/cek/_islands/content-address-demo.sx"
|
||||
safe_mv "geography/cek/freeze-demo.sx" "geography/cek/_islands/freeze-demo.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Reactive Islands index ==="
|
||||
safe_mv "reactive-islands/index/reactive-islands-index-content.sx" "geography/reactive/index.sx"
|
||||
for f in $B/reactive-islands/index/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "reactive-islands/index/$base" "geography/reactive/_islands/$base"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Reactive Islands demo (examples) ==="
|
||||
safe_mv "reactive-islands/demo/reactive-islands-demo-content.sx" "geography/reactive/examples/index.sx"
|
||||
for f in $B/reactive-islands/demo/example-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
slug="${base#example-}"
|
||||
slug="${slug%.sx}"
|
||||
safe_mv "reactive-islands/demo/$base" "geography/reactive/examples/$slug/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Marshes ==="
|
||||
safe_mv "reactive-islands/marshes/reactive-islands-marshes-content.sx" "geography/marshes/index.sx"
|
||||
for f in $B/reactive-islands/marshes/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "reactive-islands/marshes/$base" "geography/marshes/_islands/$base"
|
||||
done
|
||||
for f in $B/reactive-islands/marshes/example-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
slug="${base#example-}"
|
||||
slug="${slug%.sx}"
|
||||
safe_mv "reactive-islands/marshes/$base" "geography/marshes/$slug/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Reactive lib files ==="
|
||||
safe_mv "reactive-islands/event-bridge.sx" "geography/reactive/_lib/event-bridge.sx"
|
||||
safe_mv "reactive-islands/named-stores.sx" "geography/reactive/_lib/named-stores.sx"
|
||||
safe_mv "reactive-islands/phase2.sx" "geography/reactive/_lib/phase2.sx"
|
||||
safe_mv "reactive-islands/plan.sx" "geography/reactive/_lib/plan.sx"
|
||||
safe_mv "reactive-islands/test-runner.sx" "geography/reactive/_lib/test-runner.sx"
|
||||
safe_mv "reactive-islands/test-temperature.sx" "geography/reactive/_lib/test-temperature.sx"
|
||||
safe_mv "reactive-islands/runner-placeholder/test-runner-placeholder.sx" "geography/reactive/_lib/test-runner-placeholder.sx"
|
||||
|
||||
# demo-cyst and demo-reactive-expressions → _lib
|
||||
for f in $B/reactive-islands/demo-cyst/*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "reactive-islands/demo-cyst/$base" "geography/reactive/_lib/$base"
|
||||
done
|
||||
for f in $B/reactive-islands/demo-reactive-expressions/*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "reactive-islands/demo-reactive-expressions/$base" "geography/reactive/_lib/$base"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Scopes ==="
|
||||
safe_mv "scopes/scopes-content.sx" "geography/scopes/index.sx"
|
||||
for f in $B/scopes/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "scopes/$base" "geography/scopes/_islands/$base"
|
||||
done
|
||||
safe_mv "scopes/scopes-demo-example.sx" "geography/scopes/_islands/scopes-demo-example.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Provide ==="
|
||||
safe_mv "provide/provide-content.sx" "geography/provide/index.sx"
|
||||
for f in $B/provide/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "provide/$base" "geography/provide/_islands/$base"
|
||||
done
|
||||
safe_mv "provide/provide-demo-example.sx" "geography/provide/_islands/provide-demo-example.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Spreads ==="
|
||||
safe_mv "spreads/spreads-content.sx" "geography/spreads/index.sx"
|
||||
for f in $B/spreads/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "spreads/$base" "geography/spreads/_islands/$base"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Reactive Runtime ==="
|
||||
safe_mv "reactive-runtime/overview-content.sx" "geography/reactive-runtime/index.sx"
|
||||
|
||||
# Other content files → {slug}/index.sx (strip -content suffix)
|
||||
for f in $B/reactive-runtime/*-content.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
[ "$base" = "overview-content.sx" ] && continue
|
||||
slug="${base%-content.sx}"
|
||||
safe_mv "reactive-runtime/$base" "geography/reactive-runtime/$slug/index.sx"
|
||||
done
|
||||
|
||||
# demo-* → _islands
|
||||
for f in $B/reactive-runtime/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "reactive-runtime/$base" "geography/reactive-runtime/_islands/$base"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Hypermedia Reference ==="
|
||||
safe_mv "reference/attrs-content.sx" "geography/hypermedia/reference/attributes/index.sx"
|
||||
safe_mv "reference/headers-content.sx" "geography/hypermedia/reference/headers/index.sx"
|
||||
safe_mv "reference/events-content.sx" "geography/hypermedia/reference/events/index.sx"
|
||||
safe_mv "reference/js-api-content.sx" "geography/hypermedia/reference/js-api/index.sx"
|
||||
|
||||
# Reference shared files
|
||||
safe_mv "reference/attr-detail-content.sx" "geography/hypermedia/reference/_shared/attr-detail-content.sx"
|
||||
safe_mv "reference/attr-not-found.sx" "geography/hypermedia/reference/_shared/attr-not-found.sx"
|
||||
safe_mv "reference/header-detail-content.sx" "geography/hypermedia/reference/_shared/header-detail-content.sx"
|
||||
safe_mv "reference/event-detail-content.sx" "geography/hypermedia/reference/_shared/event-detail-content.sx"
|
||||
|
||||
# Reference index
|
||||
safe_mv "examples/reference-index-content.sx" "geography/hypermedia/reference/index.sx"
|
||||
|
||||
# Example page shared
|
||||
safe_mv "examples/page-content.sx" "geography/hypermedia/example/_shared/page-content.sx"
|
||||
|
||||
# Examples-content → example/{slug}/index.sx
|
||||
for f in $B/examples-content/example-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
slug="${base#example-}"
|
||||
slug="${slug%.sx}"
|
||||
safe_mv "examples-content/$base" "geography/hypermedia/example/$slug/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== GEOGRAPHY: Isomorphism ==="
|
||||
safe_mv "analyzer/bundle-analyzer-content.sx" "geography/isomorphism/bundle-analyzer/index.sx"
|
||||
# Analyzer islands (non-content files)
|
||||
for f in $B/analyzer/*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
[ "$base" = "bundle-analyzer-content.sx" ] && continue
|
||||
safe_mv "analyzer/$base" "geography/isomorphism/bundle-analyzer/_islands/$base"
|
||||
done
|
||||
|
||||
safe_mv "routing-analyzer/content.sx" "geography/isomorphism/routing-analyzer/index.sx"
|
||||
safe_mv "routing-analyzer/routing-row.sx" "geography/isomorphism/routing-analyzer/_islands/routing-row.sx"
|
||||
|
||||
safe_mv "async-io-demo.sx" "geography/isomorphism/async-io/index.sx"
|
||||
|
||||
safe_mv "affinity-demo/content.sx" "geography/isomorphism/affinity/index.sx"
|
||||
for f in $B/affinity-demo/aff-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "affinity-demo/$base" "geography/isomorphism/affinity/_islands/$base"
|
||||
done
|
||||
|
||||
safe_mv "optimistic-demo.sx" "geography/isomorphism/optimistic/index.sx"
|
||||
safe_mv "offline-demo.sx" "geography/isomorphism/offline/index.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== LANGUAGE: Docs ==="
|
||||
safe_mv "docs-content/docs-introduction-content.sx" "language/doc/introduction/index.sx"
|
||||
safe_mv "docs-content/docs-getting-started-content.sx" "language/doc/getting-started/index.sx"
|
||||
safe_mv "docs-content/docs-components-content.sx" "language/doc/components/index.sx"
|
||||
safe_mv "docs-content/docs-evaluator-content.sx" "language/doc/evaluator/index.sx"
|
||||
safe_mv "docs-content/docs-primitives-content.sx" "language/doc/primitives/index.sx"
|
||||
safe_mv "docs-content/docs-special-forms-content.sx" "language/doc/special-forms/index.sx"
|
||||
safe_mv "docs-content/docs-server-rendering-content.sx" "language/doc/server-rendering/index.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== LANGUAGE: Spec ==="
|
||||
safe_mv "specs/architecture-content.sx" "language/spec/index.sx"
|
||||
safe_mv "specs/overview-content.sx" "language/spec/_shared/overview-content.sx"
|
||||
safe_mv "specs/detail-content.sx" "language/spec/_shared/detail-content.sx"
|
||||
safe_mv "specs/not-found.sx" "language/spec/_shared/not-found.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== LANGUAGE: Bootstrappers ==="
|
||||
safe_mv "specs/bootstrappers-index-content.sx" "language/bootstrapper/index.sx"
|
||||
safe_mv "specs/bootstrapper-js-content.sx" "language/bootstrapper/javascript/index.sx"
|
||||
safe_mv "specs/bootstrapper-py-content.sx" "language/bootstrapper/python/index.sx"
|
||||
safe_mv "specs/bootstrapper-self-hosting-content.sx" "language/bootstrapper/self-hosting/index.sx"
|
||||
safe_mv "specs/bootstrapper-self-hosting-js-content.sx" "language/bootstrapper/self-hosting-js/index.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== LANGUAGE: Page helpers ==="
|
||||
safe_mv "page-helpers-demo/content.sx" "language/bootstrapper/page-helpers/index.sx"
|
||||
for f in $B/page-helpers-demo/demo-*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
safe_mv "page-helpers-demo/$base" "language/bootstrapper/page-helpers/_islands/$base"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== LANGUAGE: Testing ==="
|
||||
safe_mv "testing/overview-content.sx" "language/test/index.sx"
|
||||
safe_mv "testing/runners-content.sx" "language/test/runners/index.sx"
|
||||
safe_mv "testing/spec-content.sx" "language/test/_shared/spec-content.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: HTMX ==="
|
||||
safe_mv "htmx/demo-content.sx" "applications/htmx/index.sx"
|
||||
safe_mv "htmx/_test/demo-content.sx" "applications/htmx/_test/demo-content.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: Hyperscript ==="
|
||||
safe_mv "hyperscript/playground-content.sx" "applications/hyperscript/index.sx"
|
||||
safe_mv "hyperscript/htmx-content.sx" "applications/hyperscript/htmx/index.sx"
|
||||
safe_mv "hyperscript/compile-result.sx" "applications/hyperscript/_islands/compile-result.sx"
|
||||
safe_mv "hyperscript/example.sx" "applications/hyperscript/_islands/example.sx"
|
||||
safe_mv "hyperscript/live-demo.sx" "applications/hyperscript/_islands/live-demo.sx"
|
||||
safe_mv "hyperscript/playground.sx" "applications/hyperscript/_islands/playground.sx"
|
||||
safe_mv "hyperscript/translation.sx" "applications/hyperscript/_islands/translation.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: CSSX ==="
|
||||
safe_mv "cssx/overview-content.sx" "applications/cssx/index.sx"
|
||||
for f in $B/cssx/*-content.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
[ "$base" = "overview-content.sx" ] && continue
|
||||
slug="${base%-content.sx}"
|
||||
safe_mv "cssx/$base" "applications/cssx/$slug/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: Protocols ==="
|
||||
safe_mv "protocols/wire-format-content.sx" "applications/protocol/index.sx"
|
||||
for f in $B/protocols/*-content.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
[ "$base" = "wire-format-content.sx" ] && continue
|
||||
slug="${base%-content.sx}"
|
||||
safe_mv "protocols/$base" "applications/protocol/$slug/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: Standalone pages ==="
|
||||
safe_mv "sx-urls.sx" "applications/sx-urls/index.sx"
|
||||
safe_mv "native-browser.sx" "applications/native-browser/index.sx"
|
||||
safe_mv "sxtp.sx" "applications/sxtp/index.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: GraphQL ==="
|
||||
safe_mv "graphql/demo-content.sx" "applications/graphql/index.sx"
|
||||
safe_mv "graphql/parse-island.sx" "applications/graphql/_islands/parse-island.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== APPLICATIONS: Pretext ==="
|
||||
safe_mv "pretext-demo/content.sx" "applications/pretext/index.sx"
|
||||
safe_mv "pretext-demo/live.sx" "applications/pretext/_islands/live.sx"
|
||||
safe_mv "pretext-demo/render-paragraph.sx" "applications/pretext/_islands/render-paragraph.sx"
|
||||
|
||||
echo ""
|
||||
echo "=== ETC: Essays ==="
|
||||
safe_mv "essays/index.sx" "etc/essay/index.sx"
|
||||
|
||||
# Philosophy essays → etc/philosophy/
|
||||
safe_mv "essays/philosophy-index.sx" "etc/philosophy/index.sx"
|
||||
safe_mv "essays/sx-manifesto.sx" "etc/philosophy/sx-manifesto/index.sx"
|
||||
safe_mv "essays/godel-escher-bach.sx" "etc/philosophy/godel-escher-bach/index.sx"
|
||||
safe_mv "essays/sx-and-wittgenstein.sx" "etc/philosophy/wittgenstein/index.sx"
|
||||
safe_mv "essays/sx-and-dennett.sx" "etc/philosophy/dennett/index.sx"
|
||||
safe_mv "essays/s-existentialism.sx" "etc/philosophy/existentialism/index.sx"
|
||||
safe_mv "essays/platonic-sx.sx" "etc/philosophy/platonic-sx/index.sx"
|
||||
|
||||
# Regular essays → etc/essay/{name}/index.sx
|
||||
PHILOSOPHY_ESSAYS="sx-manifesto godel-escher-bach sx-and-wittgenstein sx-and-dennett s-existentialism platonic-sx"
|
||||
for f in $B/essays/*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
name="${base%.sx}"
|
||||
[ "$name" = "index" ] && continue
|
||||
[ "$name" = "philosophy-index" ] && continue
|
||||
skip=false
|
||||
for p in $PHILOSOPHY_ESSAYS; do
|
||||
if [ "$name" = "$p" ]; then
|
||||
skip=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
$skip && continue
|
||||
safe_mv "essays/$base" "etc/essay/$name/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== ETC: Plans ==="
|
||||
safe_mv "plans/index.sx" "etc/plan/index.sx"
|
||||
|
||||
# Theorem prover (subdirectory)
|
||||
safe_mv "plans/theorem-prover/plan-theorem-prover-content.sx" "etc/plan/theorem-prover/index.sx"
|
||||
safe_mv "plans/theorem-prover/prove-phase1-row.sx" "etc/plan/theorem-prover/_islands/prove-phase1-row.sx"
|
||||
safe_mv "plans/theorem-prover/prove-phase2-row.sx" "etc/plan/theorem-prover/_islands/prove-phase2-row.sx"
|
||||
|
||||
# Regular plan files → etc/plan/{name}/index.sx
|
||||
for f in $B/plans/*.sx; do
|
||||
[ -f "$f" ] || continue
|
||||
base=$(basename "$f")
|
||||
name="${base%.sx}"
|
||||
[ "$name" = "index" ] && continue
|
||||
safe_mv "plans/$base" "etc/plan/$name/index.sx"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== TOOLS ==="
|
||||
safe_mv "sx-tools/overview-content.sx" "tools/sx-tools/index.sx"
|
||||
safe_mv "sx-tools-demos.sx" "tools/sx-tools/_lib/sx-tools-demos.sx"
|
||||
safe_mv "sx-tools-editor.sx" "tools/sx-tools/_lib/sx-tools-editor.sx"
|
||||
safe_mv "services-tools.sx" "tools/services/index.sx"
|
||||
safe_mv "playground/content.sx" "tools/playground/index.sx"
|
||||
safe_mv "playground/repl.sx" "tools/playground/_islands/repl.sx"
|
||||
|
||||
echo ""
|
||||
echo "======================================="
|
||||
echo "Migration complete!"
|
||||
echo " Moved: $MOVED"
|
||||
echo " Skipped: $SKIPPED"
|
||||
echo " Warnings: $WARNINGS"
|
||||
echo "======================================="
|
||||
446
scripts/migrate_one_per_file.py
Normal file
446
scripts/migrate_one_per_file.py
Normal file
@@ -0,0 +1,446 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Migrate sx_docs components to one-definition-per-file convention.
|
||||
|
||||
Reads all .sx files under sx/sx/ and sx/sxc/, splits multi-definition
|
||||
files into one file per definition.
|
||||
|
||||
Usage:
|
||||
python3 scripts/migrate_one_per_file.py --dry-run # preview
|
||||
python3 scripts/migrate_one_per_file.py # execute
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from shared.sx.parser import parse_all, serialize
|
||||
from shared.sx.types import Symbol, Keyword
|
||||
|
||||
NAMED_DEFS = {"defcomp", "defisland", "defmacro", "defpage",
|
||||
"defhandler", "defstyle", "deftype", "defeffect",
|
||||
"defrelation", "deftest"}
|
||||
|
||||
SKIP_FILES = {"boundary.sx"}
|
||||
|
||||
|
||||
def get_def_info(expr):
|
||||
"""Return (keyword, name) for a definition, or None."""
|
||||
if not isinstance(expr, list) or not expr:
|
||||
return None
|
||||
head = expr[0]
|
||||
if not isinstance(head, Symbol):
|
||||
return None
|
||||
|
||||
kw = head.name
|
||||
|
||||
if kw in NAMED_DEFS:
|
||||
if len(expr) < 2 or not isinstance(expr[1], Symbol):
|
||||
return None
|
||||
return (kw, expr[1].name.lstrip("~"))
|
||||
|
||||
if kw == "define":
|
||||
if len(expr) < 2:
|
||||
return None
|
||||
if isinstance(expr[1], Symbol):
|
||||
return ("define", expr[1].name)
|
||||
elif (isinstance(expr[1], list) and expr[1]
|
||||
and isinstance(expr[1][0], Symbol)):
|
||||
return ("define", expr[1][0].name)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def derive_local_name(def_name, file_rel_path):
|
||||
"""Derive short filename for a definition within the file's directory.
|
||||
|
||||
Strategy:
|
||||
1. If name contains '/', split on LAST '/' → namespace + local.
|
||||
Then strip redundant namespace-as-prefix from local.
|
||||
2. If no '/', try stripping the file's full path (as hyphens) prefix.
|
||||
3. Else use the full name.
|
||||
|
||||
Examples:
|
||||
name: examples/card file: examples.sx → card
|
||||
name: layouts/doc file: layouts.sx → doc
|
||||
name: reactive-islands-demo/example-counter
|
||||
file: reactive-islands/demo.sx → example-counter
|
||||
name: geography-cek/geography-cek-cek-content
|
||||
file: geography/cek.sx → cek-content
|
||||
name: docs-nav-items file: nav-data.sx → docs-nav-items
|
||||
"""
|
||||
if '/' in def_name:
|
||||
namespace, local = def_name.rsplit('/', 1)
|
||||
# Strip redundant namespace prefix from local part
|
||||
# e.g. geography-cek/geography-cek-cek-content → cek-content
|
||||
ns_prefix = namespace + '-'
|
||||
if local.startswith(ns_prefix):
|
||||
stripped = local[len(ns_prefix):]
|
||||
if stripped:
|
||||
return stripped
|
||||
return local
|
||||
|
||||
# No / in name — try stripping file path prefix
|
||||
stem = os.path.splitext(file_rel_path)[0]
|
||||
path_prefix = stem.replace('/', '-').replace('\\', '-') + '-'
|
||||
if def_name.startswith(path_prefix):
|
||||
remainder = def_name[len(path_prefix):]
|
||||
if remainder:
|
||||
return remainder
|
||||
|
||||
# Try just the file stem
|
||||
file_stem = Path(file_rel_path).stem
|
||||
stem_prefix = file_stem + '-'
|
||||
if def_name.startswith(stem_prefix):
|
||||
remainder = def_name[len(stem_prefix):]
|
||||
if remainder:
|
||||
return remainder
|
||||
|
||||
return def_name
|
||||
|
||||
|
||||
def extract_form_sources(source, exprs):
|
||||
"""Extract original source text for each top-level form.
|
||||
|
||||
Walks the source text tracking paren depth to find form boundaries.
|
||||
Returns list of (source_text, is_comment_block) for each expression
|
||||
plus any preceding comments.
|
||||
"""
|
||||
results = []
|
||||
pos = 0
|
||||
n = len(source)
|
||||
|
||||
for expr_idx in range(len(exprs)):
|
||||
# Collect leading whitespace and comments
|
||||
comment_lines = []
|
||||
form_start = pos
|
||||
|
||||
while pos < n:
|
||||
# Skip whitespace
|
||||
while pos < n and source[pos] in ' \t\r\n':
|
||||
pos += 1
|
||||
if pos >= n:
|
||||
break
|
||||
|
||||
if source[pos] == ';':
|
||||
# Comment line
|
||||
line_start = pos
|
||||
while pos < n and source[pos] != '\n':
|
||||
pos += 1
|
||||
if pos < n:
|
||||
pos += 1 # skip newline
|
||||
comment_lines.append(source[line_start:pos].rstrip())
|
||||
continue
|
||||
|
||||
# Found start of form
|
||||
break
|
||||
|
||||
if pos >= n:
|
||||
break
|
||||
|
||||
# Extract the form
|
||||
if source[pos] == '(':
|
||||
depth = 0
|
||||
in_string = False
|
||||
escape = False
|
||||
form_body_start = pos
|
||||
|
||||
while pos < n:
|
||||
c = source[pos]
|
||||
if escape:
|
||||
escape = False
|
||||
elif c == '\\' and in_string:
|
||||
escape = True
|
||||
elif c == '"':
|
||||
in_string = not in_string
|
||||
elif not in_string:
|
||||
if c == '(':
|
||||
depth += 1
|
||||
elif c == ')':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
pos += 1
|
||||
break
|
||||
pos += 1
|
||||
|
||||
form_text = source[form_body_start:pos]
|
||||
# Include preceding comments
|
||||
if comment_lines:
|
||||
full_text = '\n'.join(comment_lines) + '\n' + form_text
|
||||
else:
|
||||
full_text = form_text
|
||||
|
||||
results.append(full_text)
|
||||
else:
|
||||
# Non-paren form (symbol, etc.)
|
||||
start = pos
|
||||
while pos < n and source[pos] not in ' \t\r\n':
|
||||
pos += 1
|
||||
results.append(source[start:pos])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def process_directory(base_dir, dry_run=True):
|
||||
"""Process all .sx files, return split plan."""
|
||||
|
||||
splits = [] # (source_file, target_file, content, kw, old_name)
|
||||
single = [] # (source_file, kw, old_name)
|
||||
no_defs = [] # files with no definitions
|
||||
errors = []
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
dirs[:] = [d for d in dirs if d not in ('__pycache__', '.cache', '.pytest_cache')]
|
||||
|
||||
for filename in sorted(files):
|
||||
if not filename.endswith('.sx') or filename in SKIP_FILES:
|
||||
continue
|
||||
|
||||
filepath = os.path.join(root, filename)
|
||||
rel_path = os.path.relpath(filepath, base_dir)
|
||||
|
||||
try:
|
||||
with open(filepath, encoding='utf-8') as f:
|
||||
source = f.read()
|
||||
except Exception as e:
|
||||
errors.append((rel_path, str(e)))
|
||||
continue
|
||||
|
||||
try:
|
||||
exprs = parse_all(source)
|
||||
except Exception as e:
|
||||
errors.append((rel_path, f"Parse: {e}"))
|
||||
continue
|
||||
|
||||
# Classify
|
||||
defs = []
|
||||
non_defs = []
|
||||
for expr in exprs:
|
||||
info = get_def_info(expr)
|
||||
if info:
|
||||
defs.append((expr, info))
|
||||
else:
|
||||
non_defs.append(expr)
|
||||
|
||||
if not defs:
|
||||
no_defs.append(rel_path)
|
||||
continue
|
||||
|
||||
if len(defs) == 1 and not non_defs:
|
||||
# Single definition — stays as-is
|
||||
_, (kw, name) = defs[0]
|
||||
single.append((rel_path, kw, name))
|
||||
continue
|
||||
|
||||
# Multiple definitions — split
|
||||
file_stem = Path(filename).stem
|
||||
file_dir = os.path.dirname(rel_path)
|
||||
target_dir = os.path.join(file_dir, file_stem)
|
||||
|
||||
# Get original source for each form
|
||||
form_sources = extract_form_sources(source, exprs)
|
||||
|
||||
all_exprs = []
|
||||
for expr in exprs:
|
||||
info = get_def_info(expr)
|
||||
all_exprs.append((expr, info))
|
||||
|
||||
# Deduplicate: keep only the LAST definition for each name
|
||||
seen_names = {}
|
||||
for idx, (expr, info) in enumerate(all_exprs):
|
||||
if info:
|
||||
seen_names[info[1]] = idx
|
||||
last_idx_for_name = set(seen_names.values())
|
||||
|
||||
for idx, (expr, info) in enumerate(all_exprs):
|
||||
if info is None:
|
||||
# Non-def form
|
||||
continue
|
||||
if idx not in last_idx_for_name:
|
||||
# Earlier duplicate — skip
|
||||
continue
|
||||
|
||||
kw, name = info
|
||||
local = derive_local_name(name, rel_path)
|
||||
safe_local = local.replace('/', '-')
|
||||
target_file = os.path.join(target_dir, safe_local + '.sx')
|
||||
|
||||
# Use original source if available, else serialize
|
||||
if idx < len(form_sources):
|
||||
content = form_sources[idx]
|
||||
else:
|
||||
content = serialize(expr, pretty=True)
|
||||
|
||||
splits.append((rel_path, target_file, content, kw, name))
|
||||
|
||||
# Non-def forms: collect into _init.sx
|
||||
if non_defs:
|
||||
init_parts = []
|
||||
# Find their original source
|
||||
non_def_idx = 0
|
||||
for idx, (expr, info) in enumerate(all_exprs):
|
||||
if info is None:
|
||||
if idx < len(form_sources):
|
||||
init_parts.append(form_sources[idx])
|
||||
else:
|
||||
init_parts.append(serialize(expr, pretty=True))
|
||||
non_def_idx += 1
|
||||
|
||||
if init_parts:
|
||||
init_content = '\n\n'.join(init_parts)
|
||||
init_file = os.path.join(target_dir, '_init.sx')
|
||||
splits.append((rel_path, init_file, init_content, 'init', '_init'))
|
||||
|
||||
return splits, single, no_defs, errors
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Migrate SX to one-per-file")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--dir", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.chdir(project_root)
|
||||
|
||||
dirs = [args.dir] if args.dir else ["sx/sx", "sx/sxc"]
|
||||
|
||||
all_splits = []
|
||||
all_single = []
|
||||
all_no_defs = []
|
||||
all_errors = []
|
||||
|
||||
for d in dirs:
|
||||
if not os.path.isdir(d):
|
||||
print(f"Skip {d}")
|
||||
continue
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f" {d}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
splits, single, no_defs, errors = process_directory(d, args.dry_run)
|
||||
|
||||
# Prefix with base dir for full paths
|
||||
for s in splits:
|
||||
src, tgt, content, kw, name = s
|
||||
all_splits.append((d, os.path.join(d, src), os.path.join(d, tgt),
|
||||
content, kw, name))
|
||||
for s in single:
|
||||
path, kw, name = s
|
||||
all_single.append((d, os.path.join(d, path), kw, name))
|
||||
all_no_defs.extend(os.path.join(d, p) for p in no_defs)
|
||||
all_errors.extend((os.path.join(d, p), e) for p, e in errors)
|
||||
|
||||
# Check conflicts
|
||||
target_map = {}
|
||||
conflicts = []
|
||||
for _, src, tgt, content, kw, name in all_splits:
|
||||
if tgt in target_map:
|
||||
conflicts.append((tgt, target_map[tgt], (kw, name, src)))
|
||||
else:
|
||||
target_map[tgt] = (kw, name, src)
|
||||
|
||||
# Group splits by source file
|
||||
by_source = {}
|
||||
for _, src, tgt, content, kw, name in all_splits:
|
||||
by_source.setdefault(src, []).append((tgt, kw, name))
|
||||
|
||||
# Report
|
||||
if all_errors:
|
||||
print(f"\n--- Errors ({len(all_errors)}) ---")
|
||||
for p, e in all_errors:
|
||||
print(f" {p}: {e}")
|
||||
|
||||
if conflicts:
|
||||
print(f"\n--- {len(conflicts)} Conflicts ---")
|
||||
for tgt, existing, new in conflicts:
|
||||
print(f" {tgt}")
|
||||
print(f" existing: {existing[1]} from {existing[2]}")
|
||||
print(f" new: {new[1]} from {new[2]}")
|
||||
|
||||
total_new = len(all_splits)
|
||||
print(f"\n{'='*60}")
|
||||
print(f" Summary")
|
||||
print(f"{'='*60}")
|
||||
print(f" Files to split: {len(by_source)}")
|
||||
print(f" New files: {total_new}")
|
||||
print(f" Single-def (keep): {len(all_single)}")
|
||||
print(f" No-defs (skip): {len(all_no_defs)}")
|
||||
print(f" Conflicts: {len(conflicts)}")
|
||||
|
||||
if args.dry_run:
|
||||
print(f"\n--- Split plan ---")
|
||||
for src in sorted(by_source.keys()):
|
||||
targets = by_source[src]
|
||||
print(f"\n {src} → {len(targets)} files:")
|
||||
for tgt, kw, name in sorted(targets):
|
||||
print(f" {tgt} ({kw})")
|
||||
|
||||
print(f"\n--- Single-def files ---")
|
||||
for _, path, kw, name in sorted(all_single)[:15]:
|
||||
print(f" {path} ({kw} {name})")
|
||||
if len(all_single) > 15:
|
||||
print(f" ... and {len(all_single) - 15} more")
|
||||
|
||||
# Show a sample
|
||||
if all_splits:
|
||||
_, src, tgt, content, kw, name = all_splits[0]
|
||||
print(f"\n--- Sample: {tgt} ---")
|
||||
lines = content.split('\n')
|
||||
for line in lines[:15]:
|
||||
print(f" {line}")
|
||||
if len(lines) > 15:
|
||||
print(f" ... ({len(lines)} lines total)")
|
||||
|
||||
print(f"\nDry run. Run without --dry-run to execute.")
|
||||
return
|
||||
|
||||
# Execute
|
||||
if conflicts:
|
||||
print("Aborting due to conflicts.")
|
||||
sys.exit(1)
|
||||
|
||||
created = 0
|
||||
for _, src, tgt, content, kw, name in all_splits:
|
||||
os.makedirs(os.path.dirname(tgt), exist_ok=True)
|
||||
if os.path.exists(tgt):
|
||||
print(f" SKIP (exists): {tgt}")
|
||||
continue
|
||||
with open(tgt, 'w', encoding='utf-8') as f:
|
||||
f.write(content.rstrip() + '\n')
|
||||
created += 1
|
||||
|
||||
# Delete source files
|
||||
deleted = 0
|
||||
for src in by_source:
|
||||
if os.path.exists(src):
|
||||
os.remove(src)
|
||||
deleted += 1
|
||||
|
||||
print(f"\n Created {created}, deleted {deleted} source files.")
|
||||
|
||||
# Build name mapping: old_name -> new_path_name
|
||||
mapping = {}
|
||||
for _, src, tgt, _, kw, old_name in all_splits:
|
||||
if kw in ('init', 'preamble'):
|
||||
continue
|
||||
# Determine which base_dir this target is in
|
||||
for d in dirs:
|
||||
if tgt.startswith(d + '/'):
|
||||
new_name = os.path.splitext(os.path.relpath(tgt, d))[0]
|
||||
if old_name != new_name:
|
||||
mapping[old_name] = new_name
|
||||
break
|
||||
|
||||
mapping_file = "scripts/name-mapping.json"
|
||||
with open(mapping_file, 'w') as f:
|
||||
json.dump(mapping, f, indent=2, sort_keys=True)
|
||||
print(f" Name mapping: {mapping_file} ({len(mapping)} entries)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
256
scripts/name-mapping.json
Normal file
256
scripts/name-mapping.json
Normal file
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"__app-config": "app-config/__app-config",
|
||||
"_spec-dirs": "data/helpers/_spec-dirs",
|
||||
"_spec-search-dirs": "data/helpers/_spec-search-dirs",
|
||||
"_spec-slug-map": "data/helpers/_spec-slug-map",
|
||||
"adapter-spec-items": "nav-language/adapter-spec-items",
|
||||
"add-page-test": "pages/page-tests/add-page-test",
|
||||
"affinity-demo": "pages/docs/affinity-demo",
|
||||
"all-spec-items": "nav-language/all-spec-items",
|
||||
"api": "handlers/dispatch/api",
|
||||
"applications": "page-functions/applications",
|
||||
"applications-index": "pages/docs/applications-index",
|
||||
"async-io-demo": "pages/docs/async-io-demo",
|
||||
"attr-detail-data": "data/helpers/attr-detail-data",
|
||||
"attr-details": "data/reference-details/attr-details",
|
||||
"behavior-attrs": "data/reference/behavior-attrs",
|
||||
"bootstrapper": "page-functions/bootstrapper",
|
||||
"bootstrapper-data": "data/helpers/bootstrapper-data",
|
||||
"bootstrapper-page": "pages/docs/bootstrapper-page",
|
||||
"bootstrappers-index": "pages/docs/bootstrappers-index",
|
||||
"bootstrappers-nav-items": "nav-language/bootstrappers-nav-items",
|
||||
"browser-spec-items": "nav-language/browser-spec-items",
|
||||
"build-code-tokens": "stepper-lib/build-code-tokens",
|
||||
"bundle-analyzer": "pages/docs/bundle-analyzer",
|
||||
"bundle-analyzer-data": "data/helpers/bundle-analyzer-data",
|
||||
"call-handler": "handlers/dispatch/call-handler",
|
||||
"capabilities": "page-functions/capabilities",
|
||||
"catalog-items": "handlers/reactive-api/catalog-items",
|
||||
"cek": "page-functions/cek",
|
||||
"cek-index": "pages/docs/cek-index",
|
||||
"cek-nav-items": "nav-geography/cek-nav-items",
|
||||
"cek-page": "pages/docs/cek-page",
|
||||
"component-source": "data/helpers/component-source",
|
||||
"core-spec-items": "nav-language/core-spec-items",
|
||||
"cssx": "page-functions/cssx",
|
||||
"cssx-index": "pages/docs/cssx-index",
|
||||
"cssx-nav-items": "nav-data/cssx-nav-items",
|
||||
"cssx-page": "pages/docs/cssx-page",
|
||||
"data-test": "pages/docs/data-test",
|
||||
"data-test-data": "data/helpers/data-test-data",
|
||||
"doc": "page-functions/doc",
|
||||
"docs-index": "pages/docs/index",
|
||||
"docs-nav-items": "nav-language/docs-nav-items",
|
||||
"docs-page": "pages/docs/page",
|
||||
"essay": "page-functions/essay",
|
||||
"essay-page": "pages/docs/essay-page",
|
||||
"essays-index": "pages/docs/essays-index",
|
||||
"essays-nav-items": "nav-data/essays-nav-items",
|
||||
"etc": "page-functions/etc",
|
||||
"etc-index": "pages/docs/etc-index",
|
||||
"eval-handler-body": "handlers/dispatch/eval-handler-body",
|
||||
"eval-rules": "page-functions/eval-rules",
|
||||
"event-detail-data": "data/helpers/event-detail-data",
|
||||
"event-details": "data/reference-details/event-details",
|
||||
"events-list": "data/reference/events-list",
|
||||
"example": "page-functions/example",
|
||||
"examples": "page-functions/examples",
|
||||
"examples-index": "pages/docs/examples-index",
|
||||
"examples-nav-items": "nav-geography/examples-nav-items",
|
||||
"examples-page": "pages/docs/examples-page",
|
||||
"explore": "page-functions/explore",
|
||||
"extension-spec-items": "nav-language/extension-spec-items",
|
||||
"find-current": "nav-tree/find-current",
|
||||
"find-nav-index": "nav-tree/find-nav-index",
|
||||
"find-nav-match": "nav-tree/find-nav-match",
|
||||
"find-spec": "nav-language/find-spec",
|
||||
"flash-sale-prices": "handlers/reactive-api/flash-sale-prices",
|
||||
"geography": "page-functions/geography",
|
||||
"geography-index": "pages/docs/geography-index",
|
||||
"geography/demo-callout": "spreads/demo-callout",
|
||||
"geography/demo-cssx-tw": "spreads/demo-cssx-tw",
|
||||
"geography/demo-emit-collect": "provide/demo-emit-collect",
|
||||
"geography/demo-example": "spreads/demo-example",
|
||||
"geography/demo-nested-provide": "provide/demo-nested-provide",
|
||||
"geography/demo-provide-basic": "provide/demo-provide-basic",
|
||||
"geography/demo-reactive-spread": "spreads/demo-reactive-spread",
|
||||
"geography/demo-scope-basic": "scopes/demo-scope-basic",
|
||||
"geography/demo-scope-dedup": "scopes/demo-scope-dedup",
|
||||
"geography/demo-scope-emit": "scopes/demo-scope-emit",
|
||||
"geography/demo-semantic-vars": "spreads/demo-semantic-vars",
|
||||
"geography/demo-spread-basic": "spreads/demo-spread-basic",
|
||||
"geography/demo-spread-mechanism": "provide/demo-spread-mechanism",
|
||||
"geography/provide-content": "provide/provide-content",
|
||||
"geography/provide-demo-example": "provide/provide-demo-example",
|
||||
"geography/scopes-content": "scopes/scopes-content",
|
||||
"geography/scopes-demo-example": "scopes/scopes-demo-example",
|
||||
"geography/spreads-content": "spreads/spreads-content",
|
||||
"handler-source": "data/helpers/handler-source",
|
||||
"has-descendant-href?": "nav-tree/has-descendant-href?",
|
||||
"header-detail-data": "data/helpers/header-detail-data",
|
||||
"header-details": "data/reference-details/header-details",
|
||||
"home": "pages/docs/home",
|
||||
"host-spec-items": "nav-language/host-spec-items",
|
||||
"hypermedia": "page-functions/hypermedia",
|
||||
"hypermedia-index": "pages/docs/hypermedia-index",
|
||||
"hyperscript": "page-functions/hyperscript",
|
||||
"hyperscript-nav-items": "nav-data/hyperscript-nav-items",
|
||||
"isomorphism": "page-functions/isomorphism",
|
||||
"isomorphism-index": "pages/docs/isomorphism-index",
|
||||
"isomorphism-nav-items": "nav-geography/isomorphism-nav-items",
|
||||
"isomorphism-page": "pages/docs/isomorphism-page",
|
||||
"js-api-list": "data/reference/js-api-list",
|
||||
"language": "page-functions/language",
|
||||
"language-index": "pages/docs/language-index",
|
||||
"make-page-fn": "page-functions/make-page-fn",
|
||||
"make-spec-files": "page-functions/make-spec-files",
|
||||
"marshes": "page-functions/marshes",
|
||||
"marshes-examples-nav-items": "nav-geography/marshes-examples-nav-items",
|
||||
"marshes-index": "pages/docs/marshes-index",
|
||||
"modules": "page-functions/modules",
|
||||
"native-browser": "page-functions/native-browser",
|
||||
"native-browser-nav-items": "nav-data/native-browser-nav-items",
|
||||
"nav-data/section-nav": "nav-tree/section-nav",
|
||||
"offline-demo": "pages/docs/offline-demo",
|
||||
"optimistic-demo": "pages/docs/optimistic-demo",
|
||||
"page-helpers-demo": "pages/docs/page-helpers-demo",
|
||||
"page-test-specs": "pages/page-tests/page-test-specs",
|
||||
"philosophy": "page-functions/philosophy",
|
||||
"philosophy-index": "pages/docs/philosophy-index",
|
||||
"philosophy-nav-items": "nav-data/philosophy-nav-items",
|
||||
"philosophy-page": "pages/docs/philosophy-page",
|
||||
"plan": "page-functions/plan",
|
||||
"plan-page": "pages/docs/plan-page",
|
||||
"plans-index": "pages/docs/plans-index",
|
||||
"plans-nav-items": "nav-data/plans-nav-items",
|
||||
"playground": "page-functions/playground",
|
||||
"pretext": "page-functions/pretext",
|
||||
"pretext-demo": "pages/docs/pretext-demo",
|
||||
"pretext-nav-items": "nav-data/pretext-nav-items",
|
||||
"primitives-by-category": "data/reference/primitives-by-category",
|
||||
"primitives-data": "data/helpers/primitives-data",
|
||||
"protocol": "page-functions/protocol",
|
||||
"protocol-page": "pages/docs/protocol-page",
|
||||
"protocols-index": "pages/docs/protocols-index",
|
||||
"protocols-nav-items": "nav-data/protocols-nav-items",
|
||||
"provide": "page-functions/provide",
|
||||
"provide-index": "pages/docs/provide-index",
|
||||
"pub-actor": "handlers/pub-api/pub-actor",
|
||||
"pub-anchor": "handlers/pub-api/pub-anchor",
|
||||
"pub-browse-collection": "handlers/pub-api/pub-browse-collection",
|
||||
"pub-cid": "handlers/pub-api/pub-cid",
|
||||
"pub-collections": "handlers/pub-api/pub-collections",
|
||||
"pub-document": "handlers/pub-api/pub-document",
|
||||
"pub-follow": "handlers/pub-api/pub-follow",
|
||||
"pub-followers": "handlers/pub-api/pub-followers",
|
||||
"pub-following": "handlers/pub-api/pub-following",
|
||||
"pub-inbox": "handlers/pub-api/pub-inbox",
|
||||
"pub-outbox": "handlers/pub-api/pub-outbox",
|
||||
"pub-publish": "handlers/pub-api/pub-publish",
|
||||
"pub-status": "handlers/pub-api/pub-status",
|
||||
"pub-verify": "handlers/pub-api/pub-verify",
|
||||
"pub-webfinger": "handlers/pub-api/pub-webfinger",
|
||||
"reactive": "page-functions/reactive",
|
||||
"reactive-api/search-results": "handlers/reactive-api/search-results",
|
||||
"reactive-catalog": "handlers/reactive-api/reactive-catalog",
|
||||
"reactive-examples-nav-items": "nav-geography/reactive-examples-nav-items",
|
||||
"reactive-flash-sale": "handlers/reactive-api/reactive-flash-sale",
|
||||
"reactive-islands-index": "pages/docs/reactive-islands-index",
|
||||
"reactive-islands-nav-items": "nav-geography/reactive-islands-nav-items",
|
||||
"reactive-islands-page": "pages/docs/reactive-islands-page",
|
||||
"reactive-islands/demo/example-cyst": "reactive-islands/demo-cyst/example-cyst",
|
||||
"reactive-islands/demo/example-reactive-expressions": "reactive-islands/demo-reactive-expressions/example-reactive-expressions",
|
||||
"reactive-islands/index/demo-cyst": "reactive-islands/demo-cyst/demo-cyst",
|
||||
"reactive-islands/index/demo-reactive-expressions": "reactive-islands/demo-reactive-expressions/demo-reactive-expressions",
|
||||
"reactive-islands/test-runner": "reactive-islands/runner-placeholder/test-runner",
|
||||
"reactive-islands/test-runner-placeholder": "reactive-islands/runner-placeholder/test-runner-placeholder",
|
||||
"reactive-runtime": "page-functions/reactive-runtime",
|
||||
"reactive-runtime-nav-items": "nav-data/reactive-runtime-nav-items",
|
||||
"reactive-search-events": "handlers/reactive-api/reactive-search-events",
|
||||
"reactive-search-posts": "handlers/reactive-api/reactive-search-posts",
|
||||
"reactive-search-products": "handlers/reactive-api/reactive-search-products",
|
||||
"reactive-settle-data": "handlers/reactive-api/reactive-settle-data",
|
||||
"reactive-spec-items": "nav-language/reactive-spec-items",
|
||||
"read-spec-file": "data/helpers/read-spec-file",
|
||||
"ref-delete": "handlers/reference/ref-delete",
|
||||
"ref-delete-item": "handlers/ref-api/ref-delete-item",
|
||||
"ref-echo-headers": "handlers/reference/ref-echo-headers",
|
||||
"ref-echo-vals": "handlers/reference/ref-echo-vals",
|
||||
"ref-echo-vals-get": "handlers/ref-api/ref-echo-vals-get",
|
||||
"ref-echo-vals-post": "handlers/ref-api/ref-echo-vals-post",
|
||||
"ref-error-500": "handlers/ref-api/ref-error-500",
|
||||
"ref-flaky": "handlers/reference/ref-flaky",
|
||||
"ref-greet": "handlers/reference/ref-greet",
|
||||
"ref-oob": "handlers/reference/ref-oob",
|
||||
"ref-prompt-echo": "handlers/ref-api/ref-prompt-echo",
|
||||
"ref-retarget": "handlers/ref-api/ref-retarget",
|
||||
"ref-select-page": "handlers/reference/ref-select-page",
|
||||
"ref-slow-echo": "handlers/reference/ref-slow-echo",
|
||||
"ref-status": "handlers/reference/ref-status",
|
||||
"ref-swap-item": "handlers/reference/ref-swap-item",
|
||||
"ref-theme": "handlers/reference/ref-theme",
|
||||
"ref-time": "handlers/reference/ref-time",
|
||||
"ref-trigger-event": "handlers/ref-api/ref-trigger-event",
|
||||
"ref-trigger-search": "handlers/reference/ref-trigger-search",
|
||||
"ref-upload-name": "handlers/reference/ref-upload-name",
|
||||
"reference": "page-functions/reference",
|
||||
"reference-attr-detail": "pages/docs/reference-attr-detail",
|
||||
"reference-data": "data/helpers/reference-data",
|
||||
"reference-detail": "page-functions/reference-detail",
|
||||
"reference-event-detail": "pages/docs/reference-event-detail",
|
||||
"reference-header-detail": "pages/docs/reference-header-detail",
|
||||
"reference-index": "pages/docs/reference-index",
|
||||
"reference-nav-items": "nav-geography/reference-nav-items",
|
||||
"reference-page": "pages/docs/reference-page",
|
||||
"request-attrs": "data/reference/request-attrs",
|
||||
"request-headers": "data/reference/request-headers",
|
||||
"resolve-nav-path": "nav-tree/resolve-nav-path",
|
||||
"response-headers": "data/reference/response-headers",
|
||||
"routing-analyzer": "pages/docs/routing-analyzer",
|
||||
"routing-analyzer-data": "data/helpers/routing-analyzer-data",
|
||||
"scopes": "page-functions/scopes",
|
||||
"scopes-index": "pages/docs/scopes-index",
|
||||
"search-event-items": "handlers/reactive-api/search-event-items",
|
||||
"search-post-items": "handlers/reactive-api/search-post-items",
|
||||
"search-product-items": "handlers/reactive-api/search-product-items",
|
||||
"semantics-nav-items": "nav-geography/semantics-nav-items",
|
||||
"services": "page-functions/services",
|
||||
"settle-items": "handlers/reactive-api/settle-items",
|
||||
"slug->component": "page-functions/slug->component",
|
||||
"spec": "page-functions/spec",
|
||||
"spec-compute-stats": "spec-introspect/spec-compute-stats",
|
||||
"spec-explore": "spec-introspect/spec-explore",
|
||||
"spec-explore-define": "spec-introspect/spec-explore-define",
|
||||
"spec-explorer-data": "data/helpers/spec-explorer-data",
|
||||
"spec-explorer-data-by-slug": "data/helpers/spec-explorer-data-by-slug",
|
||||
"spec-form-effects": "spec-introspect/spec-form-effects",
|
||||
"spec-form-kind": "spec-introspect/spec-form-kind",
|
||||
"spec-form-name": "spec-introspect/spec-form-name",
|
||||
"spec-form-params": "spec-introspect/spec-form-params",
|
||||
"spec-form-signature": "spec-introspect/spec-form-signature",
|
||||
"spec-group-sections": "spec-introspect/spec-group-sections",
|
||||
"special-forms-data": "data/helpers/special-forms-data",
|
||||
"specs-explore-page": "pages/docs/specs-explore-page",
|
||||
"specs-index": "pages/docs/specs-index",
|
||||
"specs-nav-items": "nav-language/specs-nav-items",
|
||||
"specs-page": "pages/docs/specs-page",
|
||||
"split-tag": "stepper-lib/split-tag",
|
||||
"spreads": "page-functions/spreads",
|
||||
"spreads-index": "pages/docs/spreads-index",
|
||||
"steps-to-preview": "stepper-lib/steps-to-preview",
|
||||
"stream-colors": "streaming-demo/stream-colors",
|
||||
"streaming-demo": "pages/docs/streaming-demo",
|
||||
"streaming-demo-data": "streaming-demo/data",
|
||||
"sx-nav-tree": "nav-tree/sx-nav-tree",
|
||||
"sx-pub": "page-functions/sx-pub",
|
||||
"sx-tools": "page-functions/sx-tools",
|
||||
"sx-unique-attrs": "data/reference/sx-unique-attrs",
|
||||
"sx-urls": "page-functions/sx-urls",
|
||||
"sxtp": "page-functions/sxtp",
|
||||
"sxtp-nav-items": "nav-data/sxtp-nav-items",
|
||||
"test": "page-functions/test",
|
||||
"testing-index": "pages/docs/testing-index",
|
||||
"testing-nav-items": "nav-language/testing-nav-items",
|
||||
"testing-page": "pages/docs/testing-page",
|
||||
"tools": "page-functions/tools"
|
||||
}
|
||||
170
scripts/strip_names.py
Normal file
170
scripts/strip_names.py
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Phase 2: Strip names from definition forms in one-per-file .sx files.
|
||||
|
||||
Transforms:
|
||||
(defcomp ~name (params) body) -> (defcomp (params) body)
|
||||
(define name value) -> (define value)
|
||||
(define (name args) body) -> (define (args) body)
|
||||
(defpage name :path ...) -> (defpage :path ...)
|
||||
etc.
|
||||
|
||||
Self-named components (where the name is part of the content) are left as-is
|
||||
if they use a different naming convention than the file path would give.
|
||||
|
||||
Usage:
|
||||
python3 scripts/strip_names.py --dry-run # preview
|
||||
python3 scripts/strip_names.py # execute
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from shared.sx.parser import parse_all
|
||||
from shared.sx.types import Symbol, Keyword
|
||||
|
||||
NAMED_DEFS = {"defcomp", "defisland", "defmacro", "defpage",
|
||||
"defhandler", "defstyle", "deftype", "defeffect",
|
||||
"defrelation", "deftest"}
|
||||
|
||||
SKIP_FILES = {"boundary.sx", "_init.sx", "_preamble.sx"}
|
||||
|
||||
|
||||
def strip_name_from_source(source):
|
||||
"""Strip the name from a definition in raw source text.
|
||||
|
||||
Returns (new_source, old_name, def_type) or None if no change needed.
|
||||
|
||||
Works on raw text to preserve formatting, comments, etc.
|
||||
Uses the parser to understand structure, then does targeted text surgery.
|
||||
"""
|
||||
try:
|
||||
exprs = parse_all(source)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
if not exprs:
|
||||
return None
|
||||
|
||||
expr = exprs[0]
|
||||
if not isinstance(expr, list) or not expr or not isinstance(expr[0], Symbol):
|
||||
return None
|
||||
|
||||
kw = expr[0].name
|
||||
|
||||
if kw in NAMED_DEFS:
|
||||
if len(expr) < 2 or not isinstance(expr[1], Symbol):
|
||||
return None # Already unnamed or malformed
|
||||
old_name = expr[1].name
|
||||
|
||||
# Find and remove the name symbol from source text.
|
||||
# Pattern: (defXXX ~name or (defXXX name
|
||||
# We need to find the name token after the keyword and remove it.
|
||||
pattern = re.compile(
|
||||
r'(\(\s*' + re.escape(kw) + r')\s+' + re.escape(old_name) + r'(?=\s)',
|
||||
re.DOTALL
|
||||
)
|
||||
match = pattern.search(source)
|
||||
if match:
|
||||
new_source = source[:match.end(1)] + source[match.end():]
|
||||
return (new_source, old_name, kw)
|
||||
return None
|
||||
|
||||
if kw == "define":
|
||||
if len(expr) < 2:
|
||||
return None
|
||||
name_part = expr[1]
|
||||
|
||||
if isinstance(name_part, Symbol):
|
||||
old_name = name_part.name
|
||||
# (define name value) -> (define value)
|
||||
pattern = re.compile(
|
||||
r'(\(\s*define)\s+' + re.escape(old_name) + r'(?=\s)',
|
||||
re.DOTALL
|
||||
)
|
||||
match = pattern.search(source)
|
||||
if match:
|
||||
new_source = source[:match.end(1)] + source[match.end():]
|
||||
return (new_source, old_name, "define")
|
||||
|
||||
elif (isinstance(name_part, list) and name_part
|
||||
and isinstance(name_part[0], Symbol)):
|
||||
old_name = name_part[0].name
|
||||
# (define (name args...) body) -> (define (args...) body)
|
||||
# Find (define (name and remove just "name "
|
||||
pattern = re.compile(
|
||||
r'(\(\s*define\s+\()\s*' + re.escape(old_name) + r'\s*',
|
||||
re.DOTALL
|
||||
)
|
||||
match = pattern.search(source)
|
||||
if match:
|
||||
new_source = source[:match.end(1)] + source[match.end():]
|
||||
return (new_source, old_name, "define")
|
||||
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Strip names from definitions")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--dir", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.chdir(project_root)
|
||||
|
||||
dirs = [args.dir] if args.dir else ["sx/sx", "sx/sxc"]
|
||||
|
||||
stripped = 0
|
||||
skipped = 0
|
||||
errors = []
|
||||
|
||||
for d in dirs:
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
|
||||
for root, subdirs, files in os.walk(d):
|
||||
subdirs[:] = [x for x in subdirs if x not in ('__pycache__', '.cache')]
|
||||
for filename in sorted(files):
|
||||
if not filename.endswith('.sx') or filename in SKIP_FILES:
|
||||
continue
|
||||
|
||||
filepath = os.path.join(root, filename)
|
||||
|
||||
try:
|
||||
with open(filepath, encoding='utf-8') as f:
|
||||
source = f.read()
|
||||
except Exception as e:
|
||||
errors.append((filepath, str(e)))
|
||||
continue
|
||||
|
||||
result = strip_name_from_source(source)
|
||||
if result is None:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
new_source, old_name, def_type = result
|
||||
|
||||
if args.dry_run:
|
||||
print(f" {filepath}: strip {def_type} {old_name}")
|
||||
stripped += 1
|
||||
continue
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(new_source)
|
||||
stripped += 1
|
||||
|
||||
print(f"\n{'Dry run — would strip' if args.dry_run else 'Stripped'} {stripped} names")
|
||||
print(f"Skipped {skipped} (already unnamed or no definition)")
|
||||
if errors:
|
||||
print(f"Errors: {len(errors)}")
|
||||
for fp, e in errors:
|
||||
print(f" {fp}: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user