#!/usr/bin/env python3
# $Id: ppparse_instructions 170 2026-10-08 08:36:03Z peter $
#
# ppparse_instructions --- the CLI entry point for
# parse_instructions.py's own library functions. ADDED 2026-09-11,
# per Péter's own direct request ("I thought that we can call each
# step as a atomic tool on the command line") -- closing a real gap
# found the same day: the instructions -> KiCad direction of this
# project's own complete loop (parse_instructions.py,
# resolve_components.py, kicad_layout.py, all added 2026-09-09/10)
# had, until now, only ever existed as library modules, called
# directly from Python, unlike every real module on the OTHER half
# of the loop (netlist.py/auto_layout.py/render.py/
# build_instructions.py), each of which already has its own real,
# atomic ppnetlist(1)/ppauto_layout(1)/pprender(1)/
# ppbuild_instructions(1) entry point.
#
# This one mirrors ppnetlist(1)'s own real role for the reverse
# direction: load, reconstruct, print -- no resolution against real
# component data at all (see ppresolve_components(1) for that; this
# program deliberately stays a pure, real, "read one thing, print
# it" tool, the same narrow scope ppnetlist(1) itself has).

import os
import sys
from parse_instructions import (parse_instructions, reconstruct, ParseError,
                                board_config_for)


def _version():
    """This project's own current Dewey-decimal version -- CLI-
    specific, so it lives here, not in parse_instructions.py's own
    library code. See ppnetlist's own identical helper for the
    fuller reasoning."""
    here = os.path.dirname(os.path.abspath(__file__))
    try:
        with open(os.path.join(here, "VERSION")) as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith("#"):
                    return line
    except OSError:
        pass
    return "unknown"


def main(argv=None):
    """CLI entry point, brought up to DevelopmentStandard.org's own
    "Options and Arguments" bar from the start -- see
    ppparse_instructions.1, this program's own manual page."""
    import argparse

    parser = argparse.ArgumentParser(
        prog="ppparse_instructions",
        description="Read a real build-instruction text file (as "
                     "produced by ppbuild_instructions(1)) and print the "
                     "reconstructed netlist and placement -- the exact "
                     "components, nets, and board positions "
                     "parse_instructions.py's own reconstruct() recovers "
                     "from the plain-English text alone.")
    parser.add_argument("instructions_file", metavar="INSTRUCTIONS-FILE",
                         help="a real build-instruction text file, any of "
                              "this project's own four real formats "
                              "(plain, markdown, orgmode) -- html is NOT "
                              "supported (see parse_instructions.py's own "
                              "docstring for why)")
    parser.add_argument("--version", action="version",
                         version=f"%(prog)s {_version()}")
    parser.add_argument("--debug", action="store_true",
                         help="also print each component's own real "
                              "board position (column, half, row) and, "
                              "for a passive, which net it attaches to a "
                              "rail via, where known")
    parser.add_argument("--quiet", action="store_true",
                         help="suppress the leading component/net-count "
                              "summary line; print only the component and "
                              "net listings themselves")
    args = parser.parse_args(argv)

    try:
        with open(args.instructions_file) as f:
            text = f.read()
    except OSError as e:
        print(f"ppparse_instructions: {args.instructions_file}: "
              f"{e.strerror}", file=sys.stderr)
        raise SystemExit(1)

    try:
        steps = parse_instructions(text)
        nl, p = reconstruct(steps, board_config=board_config_for(text))
    except ParseError as e:
        print(f"ppparse_instructions: {args.instructions_file}: {e}",
              file=sys.stderr)
        raise SystemExit(1)

    if not args.quiet:
        print(f"{len(nl.components)} components, {len(nl.nets)} nets")
    for ref in nl.refs():
        c = nl.components[ref]
        line = f"  {ref}: {c.value}"
        if args.debug:
            if ref in p.anchor_col:
                line += f" (anchor, col {p.anchor_col[ref]})"
                # anchor_rails, ADDED 2026-09-15, closing
                # FutureResearch.org's own "Option A" item -- unlike
                # a passive's own real rail_net (a single, real
                # Placement field, p.passive_rail_net), an anchor's
                # own stated rail(s) were never given a matching
                # Placement field of their own at all: reconstruct()
                # adds each one directly onto the reconstructed
                # netlist's own real nets instead (see that
                # function's own comment for why). Derived here the
                # same real way, directly from nl itself, rather than
                # adding a redundant, second, separate Placement
                # field just to mirror rail_net's own real shape.
                anchor_rails = sorted(
                    n for n in nl.nets_for_ref(ref) if n in ("GND", "VBUS"))
                if anchor_rails:
                    line += f", rail net(s) {', '.join(anchor_rails)}"
            elif ref in p.passive_col:
                line += (f" (passive, col {p.passive_col[ref]}, "
                         f"half {p.passive_half[ref]})")
                rail_net = p.passive_rail_net.get(ref)
                if rail_net:
                    line += f", rail net {rail_net}"
        print(line)
    for net in nl.nets:
        members = ", ".join(f"{r}.{p_}" for r, p_ in net.nodes)
        print(f"  {net.name}: {members}")


if __name__ == "__main__":
    main()
