Source code for build_tools.syllable_walk_web.cli
"""
Command-line interface for the Pipe-Works Build Tools web application.
Provides ``python -m build_tools.syllable_walk_web`` entry point.
"""
from __future__ import annotations
import argparse
import sys
from configparser import ConfigParser
from dataclasses import dataclass
from pathlib import Path
[docs]
@dataclass(frozen=True)
class BuildToolsSettings:
"""Settings loaded from the ``[build_tools]`` INI section.
Attributes:
output_base: Base directory for pipeline run discovery.
sessions_dir: Optional explicit session storage directory.
corpus_dir_a: Directory containing runs to auto-load into Patch A.
corpus_dir_b: Directory containing runs to auto-load into Patch B.
port: Optional explicit port. ``None`` means auto-select.
verbose: Print startup/runtime messages when True.
"""
output_base: Path | None = None
sessions_dir: Path | None = None
corpus_dir_a: str | None = None
corpus_dir_b: str | None = None
port: int | None = None
verbose: bool = True
[docs]
def load_build_tools_settings(config_path: Path | None) -> BuildToolsSettings:
"""Load build-tools settings from the ``[build_tools]`` INI section.
Args:
config_path: Path to INI file. If missing/None, defaults are used.
Returns:
Parsed ``BuildToolsSettings`` instance.
"""
settings = BuildToolsSettings()
if config_path is None or not config_path.exists():
return settings
parser = ConfigParser()
parser.read(config_path, encoding="utf-8")
if not parser.has_section("build_tools"):
return settings
raw_output = parser.get("build_tools", "output_base", fallback=None)
output_base: Path | None = None
if raw_output is not None:
stripped = raw_output.strip()
if stripped:
output_base = Path(stripped).expanduser()
raw_sessions = parser.get("build_tools", "sessions_dir", fallback=None)
sessions_dir: Path | None = None
if raw_sessions is not None:
stripped = raw_sessions.strip()
if stripped:
sessions_dir = Path(stripped).expanduser()
raw_corpus_a = parser.get("build_tools", "corpus_dir_a", fallback=None)
corpus_dir_a: str | None = None
if raw_corpus_a is not None:
stripped = raw_corpus_a.strip()
if stripped:
corpus_dir_a = stripped
raw_corpus_b = parser.get("build_tools", "corpus_dir_b", fallback=None)
corpus_dir_b: str | None = None
if raw_corpus_b is not None:
stripped = raw_corpus_b.strip()
if stripped:
corpus_dir_b = stripped
raw_port = parser.get("build_tools", "port", fallback=None)
port: int | None = None
if raw_port is not None:
stripped = raw_port.strip()
if stripped:
port = int(stripped)
verbose = parser.getboolean("build_tools", "verbose", fallback=settings.verbose)
return BuildToolsSettings(
output_base=output_base,
sessions_dir=sessions_dir,
corpus_dir_a=corpus_dir_a,
corpus_dir_b=corpus_dir_b,
port=port,
verbose=verbose,
)
[docs]
def create_argument_parser() -> argparse.ArgumentParser:
"""Create and return the argument parser for the web server.
Returns:
Configured ArgumentParser ready to parse command-line arguments.
"""
parser = argparse.ArgumentParser(
description=(
"Launch the Pipe-Works Build Tools web application. "
"Combines Pipeline (extraction/normalization/annotation) and "
"Walker (dual-patch syllable walking, name generation) tools "
"in a browser-based interface."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples::
# Launch on auto-detected port (default)
python -m build_tools.syllable_walk_web
# Launch on a specific port
python -m build_tools.syllable_walk_web --port 9000
# Launch in quiet mode (suppress HTTP request logs)
python -m build_tools.syllable_walk_web --quiet
# Use a custom config file
python -m build_tools.syllable_walk_web --config server.ini
""",
)
parser.add_argument(
"--port",
type=int,
default=None,
help=(
"Port to serve on. If not specified, automatically finds an "
"available port (checks 8000-8099 first, then 8100-8999). "
"Default: auto-detect"
),
)
parser.add_argument(
"--quiet",
action="store_true",
default=False,
help="Suppress HTTP request logging. Default: False",
)
parser.add_argument(
"--output-base",
type=str,
default=None,
help=("Base directory for pipeline run discovery. " "Default: _working/output"),
)
parser.add_argument(
"--sessions-dir",
type=str,
default=None,
help=("Optional directory for saved walker sessions. " "Default: <output_base>/sessions"),
)
parser.add_argument(
"--config",
type=str,
default="server.ini",
help=(
"Path to INI config file. Reads the [build_tools] section for "
"output_base, sessions_dir, corpus_dir_a, corpus_dir_b, port, "
"and verbose. CLI arguments override INI values. "
"Default: server.ini"
),
)
return parser
[docs]
def parse_arguments(args: list[str] | None = None) -> argparse.Namespace:
"""Parse command-line arguments.
Args:
args: Argument list (defaults to sys.argv[1:]).
Returns:
Parsed arguments namespace.
"""
parser = create_argument_parser()
return parser.parse_args(args)
[docs]
def main(args: list[str] | None = None) -> int:
"""CLI entry point.
Returns:
Exit code: 0 for success, 1 for error, 130 for keyboard interrupt.
"""
parsed = parse_arguments(args)
try:
from build_tools.syllable_walk_web.server import run_server
# Load INI settings, then let CLI args override.
ini_settings = load_build_tools_settings(Path(parsed.config))
# Resolve output_base: CLI > INI > None
if parsed.output_base is not None:
output_base = Path(parsed.output_base)
elif ini_settings.output_base is not None:
output_base = ini_settings.output_base
else:
output_base = None
# Resolve sessions_dir: CLI > INI > None
if parsed.sessions_dir is not None:
sessions_dir = Path(parsed.sessions_dir)
elif ini_settings.sessions_dir is not None:
sessions_dir = ini_settings.sessions_dir
else:
sessions_dir = None
# Resolve port: CLI > INI > None
port = parsed.port if parsed.port is not None else ini_settings.port
# Resolve verbose: --quiet CLI flag overrides INI
verbose = not parsed.quiet if parsed.quiet else ini_settings.verbose
return run_server(
port=port,
verbose=verbose,
output_base=output_base,
sessions_dir=sessions_dir,
corpus_dir_a=ini_settings.corpus_dir_a,
corpus_dir_b=ini_settings.corpus_dir_b,
)
except KeyboardInterrupt:
return 130
except Exception as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())