From 155157e3db836ba9fe912801536da57067c5c509 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Mon, 3 Aug 2026 03:46:06 +1000 Subject: [PATCH] G-032 outputs: core 0.3.0 delivered (vfscommon), moduledoc 0.4.0/0.2.0 promoted, templates repack make.tcl vfscommonupdate + modules ripple from the G-032 change-set (driven with native Tcl903 per the recorded mkimg trap - kits assembled archive-relative, no G-134 warnings): _vfscommon punkboot/core.tcl regenerated from the 0.3.0 master; moduledoc::punkexe 0.3.0 -> 0.4.0 and moduledoc::punkboot 0.1.0 -> 0.2.0 version renames; punk::mix templates modpod repacked carrying the layout _config sweep (synced copies under the #modpod-templates tree - punkcheck-managed, never hand-edited). Claude-Session: https://claude.ai/code/session_01Y1diJnhjUxKgEG6EwYAzxj Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- .../src/vfs/_config/project_main.tcl | 24 +- .../project-0.1/src/vfs/_config/punk_main.tcl | 10 +- .../src/vfs/_config/punkboot_core.tcl | 423 ++++++++++++++++- .../src/vfs/_vfscommon.vfs/punkboot/core.tcl | 423 ++++++++++++++++- .../{punkboot-0.1.0.tm => punkboot-0.2.0.tm} | 54 ++- .../{punkexe-0.3.0.tm => punkexe-0.4.0.tm} | 141 +++++- .../modules/punk/mix/templates-0.2.0.tm | Bin 364797 -> 375060 bytes src/vfs/_vfscommon.vfs/punkboot/core.tcl | 431 +++++++++++++++++- 8 files changed, 1439 insertions(+), 67 deletions(-) rename src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/{punkboot-0.1.0.tm => punkboot-0.2.0.tm} (78%) rename src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/{punkexe-0.3.0.tm => punkexe-0.4.0.tm} (75%) diff --git a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/project_main.tcl b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/project_main.tcl index a8b8ffe6..eb437b45 100644 --- a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/project_main.tcl +++ b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/project_main.tcl @@ -15,7 +15,9 @@ # detection, package modes (internal/minted/src/os + proj: - see the # punk::args::moduledoc::punkexe package for the documented launch surface), # module and library path setup, punk::libunknown, and the default dispatch -# (tclsh/shellspy/punk/shell/script). +# (tclsh/shellspy/punk/shell/script/buildinfo/help) with its G-032 help +# surface (' help', '-help', ' -help' - tabled usage +# listing your registered subcommands, degrading to a plain list). # #WHAT TO EDIT # Only the ==CUSTOMIZE== blocks below. Everything else is contract plumbing. @@ -44,8 +46,9 @@ apply { args { #A dict mapping subcommand name -> Tcl handler script. The handler runs #after boot with ::argv/::argc already holding the subcommand's arguments; #the typical handler is a one-liner requiring your app package. Built-in - #subcommands (tclsh shellspy punk shell script) come from the boot core - #and cannot be shadowed - a colliding name is reported and ignored. + #subcommands (tclsh shellspy punk shell script buildinfo help) come from + #the boot core and cannot be shadowed - a colliding name is reported and + #ignored. # #Example - a kit for an app 'mytool' with a maintenance subcommand: # namespace eval ::punkboot {variable project_subcommands [dict create \ @@ -54,6 +57,21 @@ apply { args { # ]} namespace eval ::punkboot {variable project_subcommands [dict create]} + #Optional per-name help/parse metadata (G-032): with this declared, your + #subcommands appear in ' help' with summaries, 'help ' / + #' -help' render your punk::args definition, and parse 1 validates + #arguments through it before your handler runs (usage errors tabled, + #exit 1). All keys optional; a name without an entry keeps full + #passthrough semantics. Contract detail: punk::args::moduledoc::punkboot + #registration definition. + # namespace eval ::punkboot {variable project_subcommand_info [dict create \ + # mytool {summary "Frob the widgets in a directory." \ + # argsid (script)::mykit::mytool \ + # package punk::args::moduledoc::mykit \ + # parse 0} \ + # ]} + namespace eval ::punkboot {variable project_subcommand_info [dict create]} + # ==CUSTOMIZE== 2. launch defaults -------------------------------------- #A dict with optional keys deciding what a launch without an explicit #subcommand does: diff --git a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punk_main.tcl b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punk_main.tcl index eb4543b4..53b054c2 100644 --- a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punk_main.tcl +++ b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punk_main.tcl @@ -18,11 +18,17 @@ apply { args { #dict: subcommand name -> Tcl handler script. The handler runs with #::argv/::argc already holding the subcommand's arguments; the typical #handler is just 'package require app-yourapp'. Built-in subcommands - #(tclsh shellspy punk shell script) come from the boot core and cannot be - #shadowed. punkshell itself declares none. + #(tclsh shellspy punk shell script buildinfo help) come from the boot + #core and cannot be shadowed. punkshell itself declares none. #Example: # namespace eval ::punkboot {variable project_subcommands [dict create \ # mytool {package require app-mytool}]} + #Optional help/parse metadata per declared name (G-032 - drives the + #launcher 'help' tables and argument gates; all keys optional, see + #punk::args::moduledoc::punkboot's registration definition): + # namespace eval ::punkboot {variable project_subcommand_info [dict create \ + # mytool {summary "One-line summary." argsid (script)::mykit::mytool \ + # package punk::args::moduledoc::mykit parse 0}]} namespace eval ::punkboot {variable project_subcommands [dict create]} # ==CUSTOMIZE== launch defaults ------------------------------------------ diff --git a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punkboot_core.tcl b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punkboot_core.tcl index 41e20475..738bc1fb 100644 --- a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punkboot_core.tcl +++ b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punkboot_core.tcl @@ -11,9 +11,13 @@ #package_mode parsing (minted|os|internal|src with the proj: scope prefix, #G-033), module-path and auto_path assembly, punk::libunknown, src-mode ##modpod registration, then the default subcommand dispatch -#(tclsh/shellspy/punk/shell/script) extended by the thin main's declared -#project subcommands (G-031 registration model - see the dispatch section -#near the end of this file). +#(tclsh/shellspy/punk/shell/script/buildinfo/help) extended by the thin +#main's declared project subcommands (G-031 registration model - see the +#dispatch section near the end of this file). The launch surface documents +#itself via punk::args (G-032): ' help ?subcommand?', '-help' and +#' -help' render tabled usage from the moduledoc::punkexe +#definitions plus the live registrations, degrading to a plain subcommand +#list when that stack is unavailable. # #The package_mode surface (documented in full by punk::args::moduledoc::punkexe): #an optional FIRST launch argument of the form minted|os|internal|src or any @@ -33,6 +37,11 @@ # to this file's own path) # project_subcommands dict: subcommand name -> handler script (see the # dispatch section; built-ins cannot be shadowed) +# project_subcommand_info dict: subcommand name -> info dict with +# optional keys summary/argsid/package/parse feeding +# the G-032 launcher help + declared-parse wiring +# (see the launcher help section; a name without an +# info entry keeps full passthrough semantics) # launch_defaults dict: noargs / unknownfirst (see dispatch section) # ------------------------------------------------------------------------------ @@ -42,7 +51,7 @@ namespace eval ::punkboot { #boot-core identity (G-031; stamped into kits + reported by the G-025 #buildinfo surfaces) - variable core_version 0.2.1 + variable core_version 0.3.0 } if {![info exists ::punkboot::launch_args]} { namespace eval ::punkboot [list variable launch_args $::argv] @@ -53,6 +62,9 @@ if {![info exists ::punkboot::main_script]} { if {![info exists ::punkboot::project_subcommands]} { namespace eval ::punkboot {variable project_subcommands [dict create]} } +if {![info exists ::punkboot::project_subcommand_info]} { + namespace eval ::punkboot {variable project_subcommand_info [dict create]} +} if {![info exists ::punkboot::launch_defaults]} { namespace eval ::punkboot {variable launch_defaults [dict create]} } @@ -1509,20 +1521,291 @@ if {![info exists ::punkboot::launch_defaults]} { } namespace eval ::punkboot [list variable boot_state $boot_state_dict] + #--------------------------------------------------------------- + #Launcher help + declared-parse machinery (G-032) + #The launch surface documents itself through punk::args (definition ids + #from the punk::args::moduledoc::punkexe family): + # help ?subcommand? tabled usage; the top-level table lists + # built-ins AND project-registered + # subcommands with summaries + # -help same as bare 'help' - only on kits whose + # unknownfirst default is 'script' (a + # tool-style kit routes flags to its own + # processor; its surface stays reachable + # via the 'help' word) + # -help the subcommand's tabled usage when -help + # is its FIRST argument (tclsh: only when + # -help is the SOLE argument - stock + # parity keeps every other dash form in + # ::argv; project subcommands: only when + # the registration declares an argsid) + #Everything is guarded and lazy (the G-030 degradation doctrine): nothing + #here loads punk::args on a normal boot/dispatch path. When punk::args or + #the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same + #env hook as make.tcl), help degrades to a plain subcommand list, parse + #gates degrade to the historic switch semantics, and boot never fails. + #The tabled rendering additionally depends on the punk::ansi/textblock + #stack - rendering degrades (minimal errorstyle) independently of parsing. + #The per-kit top-level usage id (script)::punkexe.launcher is cloned from + #the static (script)::punkexe definition at first render, with the + #subcommand choices/choicelabels replaced by this kit's LIVE surface; + #(script)::punkexe.launcherhelp is the same clone of + #(script)::punkexe::help for subject validation. + #--------------------------------------------------------------- + namespace eval ::punkboot { + variable help_flags [list -help --help -h /?] + variable launch_builtin_subcommands [list] + variable launch_subcommands [list] + variable launcher_punkargs_state "" ;#"" unprobed | ok | plain + variable launcher_builtin_summaries [dict create \ + tclsh "Run as a (near) stock tclsh - no punk modules loaded." \ + script "Run a script non-interactively in the punk script environment; honest exit codes." \ + shell "Interactive punk shell (repl)." \ + punk "punk shell launch; piped-stdin friendly." \ + shellspy "Experimental shellspy command-line processor." \ + buildinfo "Print the kit's build/identity stamp report and exit (G-025)." \ + help "Show usage for the launch surface or one of its subcommands (G-032)." \ + ] + proc launcher_punkargs_ready {} { + #lazy guarded capability probe, one verdict per process. ok requires + #punk::args AND the moduledoc launch definitions to load and resolve + #with the snapshot actually available in this kit/context. + variable launcher_punkargs_state + if {$launcher_punkargs_state ne ""} { + return [expr {$launcher_punkargs_state eq "ok"}] + } + set launcher_punkargs_state plain + if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBOOT_PLAIN)]} { + return 0 + } + if {[catch { + package require punk::args + package require punk::args::moduledoc::punkexe + if {[punk::args::get_spec (script)::punkexe] eq ""} { + error "definition (script)::punkexe did not resolve" + } + punk::args::get_spec (script)::punkexe::buildinfo + punk::args::get_spec (script)::punkexe::help + }]} { + return 0 + } + set launcher_punkargs_state ok + return 1 + } + proc launcher_errstyle {} { + #tabled (standard) parse errors when the rendering stack is + #loadable; minimal otherwise - rendering degrades independently of + #parsing (the G-030 split) + catch {package require punk::ansi} + catch {package require textblock} + if {[package provide punk::ansi] ne "" && [package provide textblock] ne ""} { + return standard + } + return minimal + } + proc launcher_subcommand_summary {name} { + #plain-text one-line summary for a known subcommand (built-in table + #or the project registration's declared summary) + variable launcher_builtin_summaries + variable project_subcommand_info + if {[dict exists $launcher_builtin_summaries $name]} { + return [dict get $launcher_builtin_summaries $name] + } + if {[dict exists $project_subcommand_info $name summary]} { + return [dict get $project_subcommand_info $name summary] + } + return "Project-declared subcommand (no summary registered)." + } + proc launcher_live_choices {} { + #this kit's subcommand set in display order: the static definition's + #choice order first (the documented built-in family), then 'help', + #then project registrations in declaration order + variable launch_subcommands + set ordered [list] + catch { + set spec [punk::args::get_spec (script)::punkexe] + foreach c [dict get $spec FORMS _default ARG_INFO subcommand -choices] { + if {$c in $launch_subcommands && $c ni $ordered} { + lappend ordered $c + } + } + } + foreach c $launch_subcommands { + if {$c ni $ordered} { + lappend ordered $c + } + } + return $ordered + } + proc launcher_overview_id {} { + #define (script)::punkexe.launcher: the per-kit top-level usage - + #the moduledoc (script)::punkexe definition with the subcommand + #choices/choicelabels replaced by this kit's live surface. + #Returns the id, or "" when the clone cannot be built. + variable launch_builtin_subcommands + set id (script)::punkexe.launcher + set existing "" + catch {set existing [punk::args::raw_def $id]} + if {$existing ne ""} { + return $id + } + if {[catch { + set spec [punk::args::get_spec (script)::punkexe] + set labels [dict get $spec FORMS _default ARG_INFO subcommand -choicelabels] + set choices [launcher_live_choices] + foreach c $choices { + if {$c eq "help" || $c ni $launch_builtin_subcommands || ![dict exists $labels $c]} { + dict set labels $c " [launcher_subcommand_summary $c]" + } + } + set ov [dict create \ + @id [list -id $id] \ + subcommand [list -choices $choices -choicelabels $labels] \ + ] + punk::args::define [punk::args::resolved_def -override $ov (script)::punkexe] + }]} { + return "" + } + return $id + } + proc launcher_helpdef_id {} { + #define (script)::punkexe.launcherhelp: the moduledoc + #(script)::punkexe::help definition with the subject choices + #replaced by the live subcommand set, so 'help ' validates + #(and prefix-resolves) against what this kit actually offers. + set id (script)::punkexe.launcherhelp + set existing "" + catch {set existing [punk::args::raw_def $id]} + if {$existing ne ""} { + return $id + } + if {[catch { + set ov [dict create \ + @id [list -id $id] \ + subject [list -choices [launcher_live_choices]] \ + ] + punk::args::define [punk::args::resolved_def -override $ov (script)::punkexe::help] + }]} { + return "" + } + return $id + } + proc launcher_plain_help {} { + #degraded help: plain subcommand list built without punk::args + set exebase [file rootname [file tail [info nameofexecutable]]] + set lines [list] + lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?" + lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal)," + lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)" + lappend lines " subcommands:" + foreach sub [launcher_plain_choices] { + lappend lines [format " %-12s %s" $sub [launcher_subcommand_summary $sub]] + } + lappend lines " ('$exebase help ?subcommand?' shows detail; plain list because the punk::args" + lappend lines " tabled help stack is unavailable in this context, or PUNKBOOT_PLAIN is set)" + return [join $lines \n] + } + proc launcher_plain_choices {} { + #display order without punk::args: documented built-in order, then + #project registrations in declaration order + variable launch_subcommands + set ordered [list] + foreach c [list tclsh script shell punk shellspy buildinfo help] { + if {$c in $launch_subcommands} { + lappend ordered $c + } + } + foreach c $launch_subcommands { + if {$c ni $ordered} { + lappend ordered $c + } + } + return $ordered + } + proc launcher_show_help {{subject ""}} { + #render help to stdout: the top-level launch surface (empty + #subject) or one subcommand's usage. Tabled via punk::args when + #available; degrades to the plain list/summary. Never errors. + variable launch_builtin_subcommands + variable project_subcommand_info + if {[launcher_punkargs_ready]} { + set id "" + if {$subject eq ""} { + set id [launcher_overview_id] + } elseif {$subject eq "help"} { + set id [launcher_helpdef_id] + } elseif {$subject in $launch_builtin_subcommands} { + set id (script)::punkexe::$subject + } else { + #project-registered: guarded require of the declared + #package (if any), then the declared definition id (if any) + if {[dict exists $project_subcommand_info $subject package]} { + catch {package require [dict get $project_subcommand_info $subject package]} + } + if {[dict exists $project_subcommand_info $subject argsid]} { + set id [dict get $project_subcommand_info $subject argsid] + } else { + #help by registration alone: the summary line + puts stdout "$subject - [launcher_subcommand_summary $subject]" + puts stdout "(no punk::args definition registered for this subcommand - if it is an application entry point it may provide its own -help handling)" + return + } + } + if {$id ne "" && ![catch {punk::args::usage $id} out]} { + puts stdout $out + return + } + #fall through to the plain forms on any render failure + } + if {$subject eq ""} { + puts stdout [launcher_plain_help] + } else { + puts stdout "$subject - [launcher_subcommand_summary $subject]" + puts stdout "(plain help: the punk::args tabled help stack is unavailable in this context, or PUNKBOOT_PLAIN is set)" + } + return + } + proc launcher_unknown_first_error {word} { + #G-032 reclassification rule: an unknown first argument that names + #no existing file (and is no lib:* scriptlib reference) is an + #obvious mistake - refuse it with usage on stderr instead of + #silently attempting it as a script. Returns 1 when the refusal + #was emitted (caller exits 1); 0 when punk::args is unavailable + #(caller falls back to the historic script reclassification). + variable launch_subcommands + if {![launcher_punkargs_ready]} { + return 0 + } + set exebase [file rootname [file tail [info nameofexecutable]]] + puts stderr "$exebase: unknown subcommand '$word' (no such subcommand, and no such script file exists)" + set id [launcher_overview_id] + if {$id ne "" && ![catch {punk::args::usage $id} out]} { + puts stderr $out + } else { + puts stderr "known subcommands: [join [launcher_plain_choices] {, }]" + } + puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation" + return 1 + } + } + #--------------------------------------------------------------- #Subcommand selection (G-031 registration model) - #Built-ins (tclsh/shellspy/punk/shell/script) ship with this core; a thin - #main may extend the set via ::punkboot::project_subcommands (dict: - #name -> handler script, evaluated in the dispatch below with ::argv/::argc - #holding the subcommand's arguments). Built-in names cannot be shadowed - - #a colliding declaration is reported and ignored. + #Built-ins (tclsh/shellspy/punk/shell/script/buildinfo/help) ship with + #this core; a thin main may extend the set via + #::punkboot::project_subcommands (dict: name -> handler script, evaluated + #in the dispatch below with ::argv/::argc holding the subcommand's + #arguments). Built-in names cannot be shadowed - a colliding declaration + #is reported and ignored. ::punkboot::project_subcommand_info optionally + #carries per-name help/parse metadata (summary/argsid/package/parse - see + #the launcher help section above, G-032). #::punkboot::launch_defaults keys (both optional): # noargs subcommand assumed for a bare launch (default: shell) # unknownfirst 'script' treats a non-subcommand first argument as a # script invocation (default), or the name of a known # subcommand to receive the whole arglist as its arguments #--------------------------------------------------------------- - set builtin_subcommands [list tclsh shellspy punk shell script buildinfo] + set builtin_subcommands [list tclsh shellspy punk shell script buildinfo help] set known_subcommands $builtin_subcommands foreach k [dict keys $::punkboot::project_subcommands] { if {$k in $builtin_subcommands} { @@ -1531,6 +1814,9 @@ if {![info exists ::punkboot::launch_defaults]} { lappend known_subcommands $k } } + #publish for the launcher help machinery (and post-boot probes) + set ::punkboot::launch_builtin_subcommands $builtin_subcommands + set ::punkboot::launch_subcommands $known_subcommands if {[dict exists $::punkboot::launch_defaults noargs]} { set default_noargs [dict get $::punkboot::launch_defaults noargs] } else { @@ -1544,10 +1830,56 @@ if {![info exists ::punkboot::launch_defaults]} { set subcommand [lindex $arglist 0] if {$subcommand in $known_subcommands} { set subcommand_arglist [lrange $arglist 1 end] + #G-032: a help flag as the subcommand's FIRST argument renders that + #subcommand's usage. tclsh: only when it is the SOLE argument (stock + #parity keeps every other leading-dash form - including '-help' with + #further arguments - in ::argv); project subcommands: only when the + #registration declares an argsid (a handler-only registration keeps + #full passthrough - the application may do its own -help handling). + if {[lindex $subcommand_arglist 0] in $::punkboot::help_flags} { + set do_subhelp 0 + if {$subcommand eq "tclsh"} { + if {[llength $subcommand_arglist] == 1} { + set do_subhelp 1 + } + } elseif {$subcommand in $builtin_subcommands} { + set do_subhelp 1 + } elseif {[dict exists $::punkboot::project_subcommand_info $subcommand argsid]} { + set do_subhelp 1 + } + if {$do_subhelp} { + ::punkboot::launcher_show_help $subcommand + exit 0 + } + } } else { set subcommand_arglist $arglist if {[llength $subcommand_arglist]} { + #G-032: a help flag in first position is a launch-surface help + #request on kits whose unknown-first default is 'script'. + #Tool-style kits (unknownfirst names a project subcommand) keep + #routing flags to their processor - their surface stays + #discoverable via the 'help' subcommand word. + if {$subcommand in $::punkboot::help_flags && $default_unknownfirst eq "script"} { + ::punkboot::launcher_show_help + exit 0 + } set subcommand $default_unknownfirst + if {$subcommand eq "script"} { + #G-032 unknown-first-arg reclassification rule (recorded in + #goals/G-032-launcher-punkargs.md): reclassify to 'script' + #only when the argument plausibly names a script - an + #existing file path, or a lib:* scriptlib reference. Anything + #else is refused with usage on stderr (exit 1); when + #punk::args is unavailable the refusal degrades to the + #historic always-reclassify behaviour. + set unknown_word [lindex $subcommand_arglist 0] + if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} { + if {[::punkboot::launcher_unknown_first_error $unknown_word]} { + exit 1 + } + } + } } else { set subcommand $default_noargs } @@ -1726,8 +2058,20 @@ if {![info exists ::punkboot::launch_defaults]} { #implementation is punk::buildinfo - this arm is the thin #exe-subcommand wrapper. Stdout carries only the report; honest #exit code; no repl fallthrough (G-015-compatible). + #G-032: the argument contract ((script)::punkexe::buildinfo, + #@values -min 0 -max 0) is enforced through punk::args when + #available: any argument earns the historic one-line refusal PLUS + #the tabled usage error on stderr, exit 2 (buildinfo.test pins the + #one-liner and the exit code). The empty-argument fast path never + #touches punk::args; a '-help' first argument was already + #intercepted at selection. if {[llength $subcommand_arglist]} { puts stderr "buildinfo: takes no arguments" + if {[::punkboot::launcher_punkargs_ready]} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid (script)::punkexe::buildinfo} parse_msg]} { + puts stderr $parse_msg + } + } exit 2 } if {[catch {package require punk::buildinfo} errM]} { @@ -1736,6 +2080,41 @@ if {![info exists ::punkboot::launch_defaults]} { } exit [punk::buildinfo::main] } + help { + #G-032: launcher help surface. Bare 'help' renders the top-level + #tabled usage (built-ins + project-registered subcommands with + #summaries); 'help ' renders that subcommand's usage. + #Subject validation parses through the launcherhelp clone when + #punk::args is available (tabled choice errors on stderr exit 1, + #unambiguous prefixes resolve); degraded mode accepts exact names + #only and renders the plain forms. Help goes to stdout, exit 0 + #(G-015 output-cleanliness/exit-code doctrine). + if {![llength $subcommand_arglist]} { + ::punkboot::launcher_show_help + exit 0 + } + if {[::punkboot::launcher_punkargs_ready]} { + set helpdef_id [::punkboot::launcher_helpdef_id] + if {$helpdef_id ne ""} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} { + puts stderr $argd + exit 1 + } + set help_subject "" + catch {set help_subject [dict get $argd values subject]} + ::punkboot::launcher_show_help $help_subject + exit 0 + } + } + #degraded: exact-name subject only + set help_subject [lindex $subcommand_arglist 0] + if {[llength $subcommand_arglist] > 1 || $help_subject ni $::punkboot::launch_subcommands} { + puts stderr "help: expected 'help ?subcommand?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]" + exit 1 + } + ::punkboot::launcher_show_help $help_subject + exit 0 + } default { #a project-declared subcommand (thin-main customization point): the #handler script runs in this frame, ::argv/::argc already hold the @@ -1743,6 +2122,30 @@ if {![info exists ::punkboot::launch_defaults]} { #misdeclared launch_defaults value - the selection above maps every #launch form to a known subcommand. if {[dict exists $::punkboot::project_subcommands $subcommand]} { + #G-032 declared-parse gate: a registration may opt its + #arguments into punk::args validation via + #project_subcommand_info keys parse=1 + argsid (optional + #package requirement). A parse failure is a tabled usage + #error on stderr, exit 1, and the handler is not invoked. + #Degrades to the historic no-validation passthrough when + #punk::args (or the declared id) is unavailable. + if {[dict exists $::punkboot::project_subcommand_info $subcommand parse] + && [string is true -strict [dict get $::punkboot::project_subcommand_info $subcommand parse]] + && [dict exists $::punkboot::project_subcommand_info $subcommand argsid] + && [::punkboot::launcher_punkargs_ready]} { + if {[dict exists $::punkboot::project_subcommand_info $subcommand package]} { + catch {package require [dict get $::punkboot::project_subcommand_info $subcommand package]} + } + set project_argsid [dict get $::punkboot::project_subcommand_info $subcommand argsid] + set project_argsid_ok 0 + catch {set project_argsid_ok [expr {[punk::args::raw_def $project_argsid] ne ""}]} + if {$project_argsid_ok} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $project_argsid} parse_msg]} { + puts stderr $parse_msg + exit 1 + } + } + } eval [dict get $::punkboot::project_subcommands $subcommand] } else { puts stderr "main.tcl: no handler for subcommand '$subcommand' (check launch_defaults in the kit's main.tcl) - known subcommands: $known_subcommands" diff --git a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_vfscommon.vfs/punkboot/core.tcl b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_vfscommon.vfs/punkboot/core.tcl index 41e20475..738bc1fb 100644 --- a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_vfscommon.vfs/punkboot/core.tcl +++ b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/vfs/_vfscommon.vfs/punkboot/core.tcl @@ -11,9 +11,13 @@ #package_mode parsing (minted|os|internal|src with the proj: scope prefix, #G-033), module-path and auto_path assembly, punk::libunknown, src-mode ##modpod registration, then the default subcommand dispatch -#(tclsh/shellspy/punk/shell/script) extended by the thin main's declared -#project subcommands (G-031 registration model - see the dispatch section -#near the end of this file). +#(tclsh/shellspy/punk/shell/script/buildinfo/help) extended by the thin +#main's declared project subcommands (G-031 registration model - see the +#dispatch section near the end of this file). The launch surface documents +#itself via punk::args (G-032): ' help ?subcommand?', '-help' and +#' -help' render tabled usage from the moduledoc::punkexe +#definitions plus the live registrations, degrading to a plain subcommand +#list when that stack is unavailable. # #The package_mode surface (documented in full by punk::args::moduledoc::punkexe): #an optional FIRST launch argument of the form minted|os|internal|src or any @@ -33,6 +37,11 @@ # to this file's own path) # project_subcommands dict: subcommand name -> handler script (see the # dispatch section; built-ins cannot be shadowed) +# project_subcommand_info dict: subcommand name -> info dict with +# optional keys summary/argsid/package/parse feeding +# the G-032 launcher help + declared-parse wiring +# (see the launcher help section; a name without an +# info entry keeps full passthrough semantics) # launch_defaults dict: noargs / unknownfirst (see dispatch section) # ------------------------------------------------------------------------------ @@ -42,7 +51,7 @@ namespace eval ::punkboot { #boot-core identity (G-031; stamped into kits + reported by the G-025 #buildinfo surfaces) - variable core_version 0.2.1 + variable core_version 0.3.0 } if {![info exists ::punkboot::launch_args]} { namespace eval ::punkboot [list variable launch_args $::argv] @@ -53,6 +62,9 @@ if {![info exists ::punkboot::main_script]} { if {![info exists ::punkboot::project_subcommands]} { namespace eval ::punkboot {variable project_subcommands [dict create]} } +if {![info exists ::punkboot::project_subcommand_info]} { + namespace eval ::punkboot {variable project_subcommand_info [dict create]} +} if {![info exists ::punkboot::launch_defaults]} { namespace eval ::punkboot {variable launch_defaults [dict create]} } @@ -1509,20 +1521,291 @@ if {![info exists ::punkboot::launch_defaults]} { } namespace eval ::punkboot [list variable boot_state $boot_state_dict] + #--------------------------------------------------------------- + #Launcher help + declared-parse machinery (G-032) + #The launch surface documents itself through punk::args (definition ids + #from the punk::args::moduledoc::punkexe family): + # help ?subcommand? tabled usage; the top-level table lists + # built-ins AND project-registered + # subcommands with summaries + # -help same as bare 'help' - only on kits whose + # unknownfirst default is 'script' (a + # tool-style kit routes flags to its own + # processor; its surface stays reachable + # via the 'help' word) + # -help the subcommand's tabled usage when -help + # is its FIRST argument (tclsh: only when + # -help is the SOLE argument - stock + # parity keeps every other dash form in + # ::argv; project subcommands: only when + # the registration declares an argsid) + #Everything is guarded and lazy (the G-030 degradation doctrine): nothing + #here loads punk::args on a normal boot/dispatch path. When punk::args or + #the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same + #env hook as make.tcl), help degrades to a plain subcommand list, parse + #gates degrade to the historic switch semantics, and boot never fails. + #The tabled rendering additionally depends on the punk::ansi/textblock + #stack - rendering degrades (minimal errorstyle) independently of parsing. + #The per-kit top-level usage id (script)::punkexe.launcher is cloned from + #the static (script)::punkexe definition at first render, with the + #subcommand choices/choicelabels replaced by this kit's LIVE surface; + #(script)::punkexe.launcherhelp is the same clone of + #(script)::punkexe::help for subject validation. + #--------------------------------------------------------------- + namespace eval ::punkboot { + variable help_flags [list -help --help -h /?] + variable launch_builtin_subcommands [list] + variable launch_subcommands [list] + variable launcher_punkargs_state "" ;#"" unprobed | ok | plain + variable launcher_builtin_summaries [dict create \ + tclsh "Run as a (near) stock tclsh - no punk modules loaded." \ + script "Run a script non-interactively in the punk script environment; honest exit codes." \ + shell "Interactive punk shell (repl)." \ + punk "punk shell launch; piped-stdin friendly." \ + shellspy "Experimental shellspy command-line processor." \ + buildinfo "Print the kit's build/identity stamp report and exit (G-025)." \ + help "Show usage for the launch surface or one of its subcommands (G-032)." \ + ] + proc launcher_punkargs_ready {} { + #lazy guarded capability probe, one verdict per process. ok requires + #punk::args AND the moduledoc launch definitions to load and resolve + #with the snapshot actually available in this kit/context. + variable launcher_punkargs_state + if {$launcher_punkargs_state ne ""} { + return [expr {$launcher_punkargs_state eq "ok"}] + } + set launcher_punkargs_state plain + if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBOOT_PLAIN)]} { + return 0 + } + if {[catch { + package require punk::args + package require punk::args::moduledoc::punkexe + if {[punk::args::get_spec (script)::punkexe] eq ""} { + error "definition (script)::punkexe did not resolve" + } + punk::args::get_spec (script)::punkexe::buildinfo + punk::args::get_spec (script)::punkexe::help + }]} { + return 0 + } + set launcher_punkargs_state ok + return 1 + } + proc launcher_errstyle {} { + #tabled (standard) parse errors when the rendering stack is + #loadable; minimal otherwise - rendering degrades independently of + #parsing (the G-030 split) + catch {package require punk::ansi} + catch {package require textblock} + if {[package provide punk::ansi] ne "" && [package provide textblock] ne ""} { + return standard + } + return minimal + } + proc launcher_subcommand_summary {name} { + #plain-text one-line summary for a known subcommand (built-in table + #or the project registration's declared summary) + variable launcher_builtin_summaries + variable project_subcommand_info + if {[dict exists $launcher_builtin_summaries $name]} { + return [dict get $launcher_builtin_summaries $name] + } + if {[dict exists $project_subcommand_info $name summary]} { + return [dict get $project_subcommand_info $name summary] + } + return "Project-declared subcommand (no summary registered)." + } + proc launcher_live_choices {} { + #this kit's subcommand set in display order: the static definition's + #choice order first (the documented built-in family), then 'help', + #then project registrations in declaration order + variable launch_subcommands + set ordered [list] + catch { + set spec [punk::args::get_spec (script)::punkexe] + foreach c [dict get $spec FORMS _default ARG_INFO subcommand -choices] { + if {$c in $launch_subcommands && $c ni $ordered} { + lappend ordered $c + } + } + } + foreach c $launch_subcommands { + if {$c ni $ordered} { + lappend ordered $c + } + } + return $ordered + } + proc launcher_overview_id {} { + #define (script)::punkexe.launcher: the per-kit top-level usage - + #the moduledoc (script)::punkexe definition with the subcommand + #choices/choicelabels replaced by this kit's live surface. + #Returns the id, or "" when the clone cannot be built. + variable launch_builtin_subcommands + set id (script)::punkexe.launcher + set existing "" + catch {set existing [punk::args::raw_def $id]} + if {$existing ne ""} { + return $id + } + if {[catch { + set spec [punk::args::get_spec (script)::punkexe] + set labels [dict get $spec FORMS _default ARG_INFO subcommand -choicelabels] + set choices [launcher_live_choices] + foreach c $choices { + if {$c eq "help" || $c ni $launch_builtin_subcommands || ![dict exists $labels $c]} { + dict set labels $c " [launcher_subcommand_summary $c]" + } + } + set ov [dict create \ + @id [list -id $id] \ + subcommand [list -choices $choices -choicelabels $labels] \ + ] + punk::args::define [punk::args::resolved_def -override $ov (script)::punkexe] + }]} { + return "" + } + return $id + } + proc launcher_helpdef_id {} { + #define (script)::punkexe.launcherhelp: the moduledoc + #(script)::punkexe::help definition with the subject choices + #replaced by the live subcommand set, so 'help ' validates + #(and prefix-resolves) against what this kit actually offers. + set id (script)::punkexe.launcherhelp + set existing "" + catch {set existing [punk::args::raw_def $id]} + if {$existing ne ""} { + return $id + } + if {[catch { + set ov [dict create \ + @id [list -id $id] \ + subject [list -choices [launcher_live_choices]] \ + ] + punk::args::define [punk::args::resolved_def -override $ov (script)::punkexe::help] + }]} { + return "" + } + return $id + } + proc launcher_plain_help {} { + #degraded help: plain subcommand list built without punk::args + set exebase [file rootname [file tail [info nameofexecutable]]] + set lines [list] + lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?" + lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal)," + lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)" + lappend lines " subcommands:" + foreach sub [launcher_plain_choices] { + lappend lines [format " %-12s %s" $sub [launcher_subcommand_summary $sub]] + } + lappend lines " ('$exebase help ?subcommand?' shows detail; plain list because the punk::args" + lappend lines " tabled help stack is unavailable in this context, or PUNKBOOT_PLAIN is set)" + return [join $lines \n] + } + proc launcher_plain_choices {} { + #display order without punk::args: documented built-in order, then + #project registrations in declaration order + variable launch_subcommands + set ordered [list] + foreach c [list tclsh script shell punk shellspy buildinfo help] { + if {$c in $launch_subcommands} { + lappend ordered $c + } + } + foreach c $launch_subcommands { + if {$c ni $ordered} { + lappend ordered $c + } + } + return $ordered + } + proc launcher_show_help {{subject ""}} { + #render help to stdout: the top-level launch surface (empty + #subject) or one subcommand's usage. Tabled via punk::args when + #available; degrades to the plain list/summary. Never errors. + variable launch_builtin_subcommands + variable project_subcommand_info + if {[launcher_punkargs_ready]} { + set id "" + if {$subject eq ""} { + set id [launcher_overview_id] + } elseif {$subject eq "help"} { + set id [launcher_helpdef_id] + } elseif {$subject in $launch_builtin_subcommands} { + set id (script)::punkexe::$subject + } else { + #project-registered: guarded require of the declared + #package (if any), then the declared definition id (if any) + if {[dict exists $project_subcommand_info $subject package]} { + catch {package require [dict get $project_subcommand_info $subject package]} + } + if {[dict exists $project_subcommand_info $subject argsid]} { + set id [dict get $project_subcommand_info $subject argsid] + } else { + #help by registration alone: the summary line + puts stdout "$subject - [launcher_subcommand_summary $subject]" + puts stdout "(no punk::args definition registered for this subcommand - if it is an application entry point it may provide its own -help handling)" + return + } + } + if {$id ne "" && ![catch {punk::args::usage $id} out]} { + puts stdout $out + return + } + #fall through to the plain forms on any render failure + } + if {$subject eq ""} { + puts stdout [launcher_plain_help] + } else { + puts stdout "$subject - [launcher_subcommand_summary $subject]" + puts stdout "(plain help: the punk::args tabled help stack is unavailable in this context, or PUNKBOOT_PLAIN is set)" + } + return + } + proc launcher_unknown_first_error {word} { + #G-032 reclassification rule: an unknown first argument that names + #no existing file (and is no lib:* scriptlib reference) is an + #obvious mistake - refuse it with usage on stderr instead of + #silently attempting it as a script. Returns 1 when the refusal + #was emitted (caller exits 1); 0 when punk::args is unavailable + #(caller falls back to the historic script reclassification). + variable launch_subcommands + if {![launcher_punkargs_ready]} { + return 0 + } + set exebase [file rootname [file tail [info nameofexecutable]]] + puts stderr "$exebase: unknown subcommand '$word' (no such subcommand, and no such script file exists)" + set id [launcher_overview_id] + if {$id ne "" && ![catch {punk::args::usage $id} out]} { + puts stderr $out + } else { + puts stderr "known subcommands: [join [launcher_plain_choices] {, }]" + } + puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation" + return 1 + } + } + #--------------------------------------------------------------- #Subcommand selection (G-031 registration model) - #Built-ins (tclsh/shellspy/punk/shell/script) ship with this core; a thin - #main may extend the set via ::punkboot::project_subcommands (dict: - #name -> handler script, evaluated in the dispatch below with ::argv/::argc - #holding the subcommand's arguments). Built-in names cannot be shadowed - - #a colliding declaration is reported and ignored. + #Built-ins (tclsh/shellspy/punk/shell/script/buildinfo/help) ship with + #this core; a thin main may extend the set via + #::punkboot::project_subcommands (dict: name -> handler script, evaluated + #in the dispatch below with ::argv/::argc holding the subcommand's + #arguments). Built-in names cannot be shadowed - a colliding declaration + #is reported and ignored. ::punkboot::project_subcommand_info optionally + #carries per-name help/parse metadata (summary/argsid/package/parse - see + #the launcher help section above, G-032). #::punkboot::launch_defaults keys (both optional): # noargs subcommand assumed for a bare launch (default: shell) # unknownfirst 'script' treats a non-subcommand first argument as a # script invocation (default), or the name of a known # subcommand to receive the whole arglist as its arguments #--------------------------------------------------------------- - set builtin_subcommands [list tclsh shellspy punk shell script buildinfo] + set builtin_subcommands [list tclsh shellspy punk shell script buildinfo help] set known_subcommands $builtin_subcommands foreach k [dict keys $::punkboot::project_subcommands] { if {$k in $builtin_subcommands} { @@ -1531,6 +1814,9 @@ if {![info exists ::punkboot::launch_defaults]} { lappend known_subcommands $k } } + #publish for the launcher help machinery (and post-boot probes) + set ::punkboot::launch_builtin_subcommands $builtin_subcommands + set ::punkboot::launch_subcommands $known_subcommands if {[dict exists $::punkboot::launch_defaults noargs]} { set default_noargs [dict get $::punkboot::launch_defaults noargs] } else { @@ -1544,10 +1830,56 @@ if {![info exists ::punkboot::launch_defaults]} { set subcommand [lindex $arglist 0] if {$subcommand in $known_subcommands} { set subcommand_arglist [lrange $arglist 1 end] + #G-032: a help flag as the subcommand's FIRST argument renders that + #subcommand's usage. tclsh: only when it is the SOLE argument (stock + #parity keeps every other leading-dash form - including '-help' with + #further arguments - in ::argv); project subcommands: only when the + #registration declares an argsid (a handler-only registration keeps + #full passthrough - the application may do its own -help handling). + if {[lindex $subcommand_arglist 0] in $::punkboot::help_flags} { + set do_subhelp 0 + if {$subcommand eq "tclsh"} { + if {[llength $subcommand_arglist] == 1} { + set do_subhelp 1 + } + } elseif {$subcommand in $builtin_subcommands} { + set do_subhelp 1 + } elseif {[dict exists $::punkboot::project_subcommand_info $subcommand argsid]} { + set do_subhelp 1 + } + if {$do_subhelp} { + ::punkboot::launcher_show_help $subcommand + exit 0 + } + } } else { set subcommand_arglist $arglist if {[llength $subcommand_arglist]} { + #G-032: a help flag in first position is a launch-surface help + #request on kits whose unknown-first default is 'script'. + #Tool-style kits (unknownfirst names a project subcommand) keep + #routing flags to their processor - their surface stays + #discoverable via the 'help' subcommand word. + if {$subcommand in $::punkboot::help_flags && $default_unknownfirst eq "script"} { + ::punkboot::launcher_show_help + exit 0 + } set subcommand $default_unknownfirst + if {$subcommand eq "script"} { + #G-032 unknown-first-arg reclassification rule (recorded in + #goals/G-032-launcher-punkargs.md): reclassify to 'script' + #only when the argument plausibly names a script - an + #existing file path, or a lib:* scriptlib reference. Anything + #else is refused with usage on stderr (exit 1); when + #punk::args is unavailable the refusal degrades to the + #historic always-reclassify behaviour. + set unknown_word [lindex $subcommand_arglist 0] + if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} { + if {[::punkboot::launcher_unknown_first_error $unknown_word]} { + exit 1 + } + } + } } else { set subcommand $default_noargs } @@ -1726,8 +2058,20 @@ if {![info exists ::punkboot::launch_defaults]} { #implementation is punk::buildinfo - this arm is the thin #exe-subcommand wrapper. Stdout carries only the report; honest #exit code; no repl fallthrough (G-015-compatible). + #G-032: the argument contract ((script)::punkexe::buildinfo, + #@values -min 0 -max 0) is enforced through punk::args when + #available: any argument earns the historic one-line refusal PLUS + #the tabled usage error on stderr, exit 2 (buildinfo.test pins the + #one-liner and the exit code). The empty-argument fast path never + #touches punk::args; a '-help' first argument was already + #intercepted at selection. if {[llength $subcommand_arglist]} { puts stderr "buildinfo: takes no arguments" + if {[::punkboot::launcher_punkargs_ready]} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid (script)::punkexe::buildinfo} parse_msg]} { + puts stderr $parse_msg + } + } exit 2 } if {[catch {package require punk::buildinfo} errM]} { @@ -1736,6 +2080,41 @@ if {![info exists ::punkboot::launch_defaults]} { } exit [punk::buildinfo::main] } + help { + #G-032: launcher help surface. Bare 'help' renders the top-level + #tabled usage (built-ins + project-registered subcommands with + #summaries); 'help ' renders that subcommand's usage. + #Subject validation parses through the launcherhelp clone when + #punk::args is available (tabled choice errors on stderr exit 1, + #unambiguous prefixes resolve); degraded mode accepts exact names + #only and renders the plain forms. Help goes to stdout, exit 0 + #(G-015 output-cleanliness/exit-code doctrine). + if {![llength $subcommand_arglist]} { + ::punkboot::launcher_show_help + exit 0 + } + if {[::punkboot::launcher_punkargs_ready]} { + set helpdef_id [::punkboot::launcher_helpdef_id] + if {$helpdef_id ne ""} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} { + puts stderr $argd + exit 1 + } + set help_subject "" + catch {set help_subject [dict get $argd values subject]} + ::punkboot::launcher_show_help $help_subject + exit 0 + } + } + #degraded: exact-name subject only + set help_subject [lindex $subcommand_arglist 0] + if {[llength $subcommand_arglist] > 1 || $help_subject ni $::punkboot::launch_subcommands} { + puts stderr "help: expected 'help ?subcommand?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]" + exit 1 + } + ::punkboot::launcher_show_help $help_subject + exit 0 + } default { #a project-declared subcommand (thin-main customization point): the #handler script runs in this frame, ::argv/::argc already hold the @@ -1743,6 +2122,30 @@ if {![info exists ::punkboot::launch_defaults]} { #misdeclared launch_defaults value - the selection above maps every #launch form to a known subcommand. if {[dict exists $::punkboot::project_subcommands $subcommand]} { + #G-032 declared-parse gate: a registration may opt its + #arguments into punk::args validation via + #project_subcommand_info keys parse=1 + argsid (optional + #package requirement). A parse failure is a tabled usage + #error on stderr, exit 1, and the handler is not invoked. + #Degrades to the historic no-validation passthrough when + #punk::args (or the declared id) is unavailable. + if {[dict exists $::punkboot::project_subcommand_info $subcommand parse] + && [string is true -strict [dict get $::punkboot::project_subcommand_info $subcommand parse]] + && [dict exists $::punkboot::project_subcommand_info $subcommand argsid] + && [::punkboot::launcher_punkargs_ready]} { + if {[dict exists $::punkboot::project_subcommand_info $subcommand package]} { + catch {package require [dict get $::punkboot::project_subcommand_info $subcommand package]} + } + set project_argsid [dict get $::punkboot::project_subcommand_info $subcommand argsid] + set project_argsid_ok 0 + catch {set project_argsid_ok [expr {[punk::args::raw_def $project_argsid] ne ""}]} + if {$project_argsid_ok} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $project_argsid} parse_msg]} { + puts stderr $parse_msg + exit 1 + } + } + } eval [dict get $::punkboot::project_subcommands $subcommand] } else { puts stderr "main.tcl: no handler for subcommand '$subcommand' (check launch_defaults in the kit's main.tcl) - known subcommands: $known_subcommands" diff --git a/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkboot-0.1.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkboot-0.2.0.tm similarity index 78% rename from src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkboot-0.1.0.tm rename to src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkboot-0.2.0.tm index 84c2bb53..c6af3ed4 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkboot-0.1.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkboot-0.2.0.tm @@ -6,7 +6,7 @@ # (C) 2026 # # @@ Meta Begin -# Application punk::args::moduledoc::punkboot 0.1.0 +# Application punk::args::moduledoc::punkboot 0.2.0 # Meta platform tcl # Meta license BSD # @@ Meta End @@ -64,6 +64,10 @@ tcl::namespace::eval punk::args::moduledoc::punkboot { project_subcommands optional dict: subcommand name -> handler script (see the registration definition id (script)::punkboot::registration) + project_subcommand_info optional dict: subcommand name -> + help/parse metadata (summary/argsid/ + package/parse - G-032; same + registration definition) launch_defaults optional dict: noargs / unknownfirst then source the core, first hit of: /punkboot/core.tcl (baked kit) @@ -99,22 +103,52 @@ tcl::namespace::eval punk::args::moduledoc::punkboot { "::punkboot::project_subcommands - dict mapping subcommand name -> Tcl handler script. Consulted by the boot core's default dispatch AFTER the built-ins (tclsh shellspy punk - shell script buildinfo): a declared name extends the launch - surface; a name colliding with a built-in is reported on - stderr and ignored (built-ins cannot be shadowed). The + shell script buildinfo help): a declared name extends the + launch surface; a name colliding with a built-in is reported + on stderr and ignored (built-ins cannot be shadowed). The handler runs in the boot frame with ::argv/::argc already holding the subcommand's arguments - typically a one-line 'package require app-yourapp'. + ::punkboot::project_subcommand_info - optional dict mapping a + declared subcommand name -> an info dict consumed by the + G-032 launcher help/parse wiring. All keys optional: + summary one-line summary shown beside the name in the + top-level 'help' table + argsid punk::args definition id documenting the + subcommand: 'help ' and ' -help' + render it. Without an argsid, ' -help' + stays FULL passthrough to the handler (the app + may do its own -help) and 'help ' shows + the summary line. + package package to require (guarded) before resolving + argsid - typically a moduledoc-style package + carrying the definition + parse boolean (default 0): validate the subcommand's + arguments through argsid BEFORE the handler runs; + a punk::args usage error (stderr, exit 1) stops + dispatch. Degrades to no-validation when + punk::args or the id is unavailable. + A name without an info entry keeps full passthrough + semantics. Help for a registered subcommand appears in the + launcher's tables by registration alone - no dispatch edits. + ::punkboot::launch_defaults - dict with optional keys: noargs subcommand assumed for a bare launch (default: shell) unknownfirst 'script' treats a non-subcommand first - argument as a script invocation (default), or - the name of a known subcommand to receive the - whole arglist (tool-style kits) - - Both dicts default to empty: an undeclared thin main yields + argument that names an existing file (or a + lib:* scriptlib reference) as a script + invocation and refuses anything else with + usage (default; the refusal is G-032 and + degrades to always-treat-as-script without + punk::args), or the name of a known + subcommand to receive the whole arglist + (tool-style kits - these also keep help + FLAGS routed to that subcommand; the 'help' + word remains the launch-surface entry) + + All dicts default to empty: an undeclared thin main yields exactly the punkshell launch behaviour." }] # -- --- --- --- --- --- --- --- --- --- --- --- --- --- @@ -216,6 +250,6 @@ namespace eval ::punk::args::register { ## Ready package provide punk::args::moduledoc::punkboot [tcl::namespace::eval punk::args::moduledoc::punkboot { variable version - set version 0.1.0 + set version 0.2.0 }] return diff --git a/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkexe-0.3.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkexe-0.4.0.tm similarity index 75% rename from src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkexe-0.3.0.tm rename to src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkexe-0.4.0.tm index d7807171..7755f1de 100644 --- a/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkexe-0.3.0.tm +++ b/src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/punkexe-0.4.0.tm @@ -8,7 +8,7 @@ # (C) 2026 # # @@ Meta Begin -# Application punk::args::moduledoc::punkexe 0.3.0 +# Application punk::args::moduledoc::punkexe 0.4.0 # Meta platform tcl # Meta license BSD # @@ Meta End @@ -18,7 +18,7 @@ # doctools header # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ #*** !doctools -#[manpage_begin punkshell_module_punk::args::moduledoc::punkexe 0 0.3.0] +#[manpage_begin punkshell_module_punk::args::moduledoc::punkexe 0 0.4.0] #[copyright "2026"] #[titledesc {punk executable launch documentation}] [comment {-- Name section and table of contents description --}] #[moddesc {-}] [comment {-- Description at end of page heading --}] @@ -26,17 +26,20 @@ #[keywords module launch subcommand tclsh] #[description] #[para] punk::args definitions documenting the punk executable launch dispatch -#[para] (the subcommand family implemented in the kit boot script src/vfs/_config/punk_main.tcl) +#[para] (the subcommand family implemented in the shared boot core sourced by the +#[para] kit boot script - masters src/vfs/_config/punkboot_core.tcl + punk_main.tcl) # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ #*** !doctools #[section Overview] #[para] These definitions are the documentation home for the launch subcommand -#[para] family of the punk executables: tclsh, script, shell, punk, shellspy -#[para] (G-118 item 10). They are authored to be consumable by the future -#[para] launcher help/parse wiring (goal G-032) - rendering, option parsing and -#[para] degradation wiring stay with that goal. Id convention follows the +#[para] family of the punk executables: tclsh, script, shell, punk, shellspy, +#[para] buildinfo, help (G-118 item 10). The launcher help/parse wiring (G-032) +#[para] renders and parses from them at dispatch time: the boot core's help +#[para] surface clones (script)::punkexe / (script)::punkexe::help per kit with +#[para] the live subcommand set, and per-subcommand ids back ' +#[para] -help' and the declared argument gates. Id convention follows the #[para] make.tcl precedent for script-level surfaces: (script)::punkexe and #[para] (script)::punkexe::, with explicit @form -synopsis overrides #[para] since a constructed id is not an invocable command name. @@ -75,9 +78,9 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { variable PUNKARGS namespace eval argdoc { - #Top-level launch dispatch (kit boot script punk_main.tcl, mirrored in - #project_main.tcl for generated projects). Display-first definitions: - #the dispatch itself does not parse via punk::args yet (goal G-032). + #Top-level launch dispatch (thin mains punk_main.tcl/project_main.tcl + #sourcing the shared boot core - G-031). The core's G-032 help/parse + #wiring consumes these definitions at dispatch time (see the Overview). # -- --- --- --- --- --- --- --- --- --- --- --- --- --- lappend PUNKARGS [list { @id -id (script)::punkexe @@ -102,9 +105,28 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { The next argument selects a subcommand from the choices below. When the first non-mode argument is not a known subcommand: - with no arguments at all the interactive punk shell is launched - ('shell'); any other first argument is treated as a script - invocation (handled as for the 'script' subcommand)." + with no arguments at all the interactive punk shell is + launched ('shell'); a first argument naming an existing file + (or a lib:* scriptlib reference) is treated as a script + invocation (handled as for the 'script' subcommand); anything + else is refused with this usage on stderr, exit 1 (G-032 - + the refusal degrades to the historic treat-as-script + behaviour when punk::args is unavailable in the kit/context). + + Help (G-032): 'help' or '-help' as the first non-mode + argument renders this usage; 'help ' or + ' -help' renders a subcommand's own usage + (see the 'help' choice below for the carve-outs). A kit + whose thin main routes unknown first arguments to a project + subcommand (a tool-style kit) keeps routing help FLAGS to + that subcommand - its launch surface stays reachable via the + 'help' subcommand word. Project-registered subcommands + appear in the rendered table by registration alone, with + the summaries and definition ids their registrations + declare (see punk::args::moduledoc::punkboot's registration + definition). These built-in launch behaviours are subject + to the thin main's declared launch defaults - the rendered + per-kit table is the authority for a given executable." @form -synopsis " ?packagemode? ?subcommand? ?arg ...?" @leaders -min 0 -max 2 packagemode -type string -optional 1 -default internal -help\ @@ -133,7 +155,7 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { ties (faithful to the project's snapshot vintage) The launch reports the detected project root and effective precedence." - subcommand -type string -optional 1 -choicerestricted 0 -choices {tclsh script shell punk shellspy buildinfo} -choicelabels { + subcommand -type string -optional 1 -choicerestricted 0 -choices {tclsh script shell punk shellspy buildinfo help} -choicelabels { tclsh " Run as a (near) stock tclsh - no punk modules loaded." script @@ -146,6 +168,8 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { " Experimental shellspy command-line processor." buildinfo " Print the kit's build/identity stamp report and exit (G-025)." + help + " Show usage for the launch surface or one of its subcommands (G-032)." } -help\ "Subcommand to run. Each subcommand's own contract is documented under its definition id (script)::punkexe::." @@ -183,6 +207,10 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { script text into ::argv and reads stdin, exactly like stock tclsh. That parity is deliberate; one-liner affordances belong to the punk-level launch surfaces, not this subcommand. + G-032 help carve-out (the single deliberate exception): a help + flag (-help/--help/-h//?) as the SOLE argument renders this + usage and exits 0; the same flag accompanied by ANY further + argument keeps the stock all-args-to-::argv semantics above. A scriptfile of the form 'lib:name' is refused with a pointer to the 'script' subcommand: scriptlib resolution is a punk @@ -264,9 +292,12 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { }] # -- --- --- --- --- --- --- --- --- --- --- --- --- --- - #Sibling subcommands: summary-level contracts. Deep punk::args parse - #definitions for these surfaces are G-032's lane ('script' is its - #natural first parsed subcommand). + #Sibling subcommands: summary-level contracts. These are declared + #PASSTHROUGH surfaces (G-032 decision): their arguments belong to the + #script/app they launch, so dispatch never parses them - only the + #first-argument help flag is intercepted (see each -help body). + #buildinfo below is the dispatch-parsed subcommand (exact @values + #contract enforced through punk::args). # -- --- --- --- --- --- --- --- --- --- --- --- --- --- lappend PUNKARGS [list { @id -id (script)::punkexe::script @@ -285,7 +316,11 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { is a usage error - no interactive fallback), or via scriptlib resolution when the argument has the form 'lib:name' (resolved from the scriptlib locations associated with the executable, - including extensionless and shebang'd scripts)." + including extensionless and shebang'd scripts). + + A help flag (-help/--help/-h//?) as the FIRST argument renders + this usage and exits 0 (G-032); in any later position it is an + ordinary script argument." @form -synopsis " script ?scriptfile|lib:name? ?arg ...?" @values -min 0 -max -1 scriptfile -type string -optional 1 -help\ @@ -304,7 +339,9 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { "Launch the interactive punk shell (app-punkshell). With arguments, a script can be run first with the interactive shell maintained afterwards. This is also the default when the - executable is launched with no arguments at all." + executable is launched with no arguments at all. + A help flag (-help etc) as the FIRST argument renders this + usage and exits 0 (G-032)." @form -synopsis " shell ?arg ...?" @values -min 0 -max -1 arg -type any -optional 1 -multiple 1 -help\ @@ -322,7 +359,9 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { interactive shell is launched directly. Commands piped into stdin are supported (e.g: echo 'puts hello' | punk) and stderr is kept clean of launch chatter so exec-style - callers do not misreport failure." + callers do not misreport failure. + A help flag (-help etc) as the FIRST argument renders this + usage and exits 0 (G-032)." @form -synopsis " punk ?arg ...?" @values -min 0 -max -1 arg -type any -optional 1 -multiple 1 -help\ @@ -338,7 +377,9 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { "Pass all arguments to the experimental shellspy command-line processor (app-shellspy). Note: 'shellspy' is only this launch subcommand - it is not the project name (the project is - punkshell)." + punkshell). + A help flag (-help etc) as the FIRST argument renders this + usage and exits 0 (G-032)." @form -synopsis " shellspy ?arg ...?" @values -min 0 -max -1 arg -type any -optional 1 -multiple 1 -help\ @@ -376,12 +417,68 @@ tcl::namespace::eval punk::args::moduledoc::punkexe { stamp_present=0 marks an unstamped kit; fields of absent notions are omitted rather than invented. + Arguments are refused: exit 2 with a one-line refusal plus + this usage as a punk::args error table on stderr (G-032; the + table degrades away when punk::args is unavailable). A help + flag (-help etc) as the first argument renders this usage and + exits 0. + In-process equivalent (no exec): the 'buildinfo' shell command / punk::buildinfo::report." @form -synopsis " buildinfo" @values -min 0 -max 0 }] # -- --- --- --- --- --- --- --- --- --- --- --- --- --- + + #help subcommand: the G-032 launcher help surface. The boot core + #renders per-kit clones of these definitions: (script)::punkexe.launcher + #(the top-level table with the kit's LIVE subcommand set - built-ins + + #project registrations) and (script)::punkexe.launcherhelp (this + #definition with the subject choices replaced by that live set). + # -- --- --- --- --- --- --- --- --- --- --- --- --- --- + lappend PUNKARGS [list { + @id -id (script)::punkexe::help + @cmd -name "punkexe help"\ + -summary\ + "Show usage for the launch surface or one of its subcommands (G-032)."\ + -help\ + "Render launch usage on stdout and exit 0 (G-032). + + With no argument: the top-level table - the package modes and + every subcommand this kit's launch surface carries (the + built-in family plus project-registered subcommands, each + with its one-line summary). + + With a subcommand argument: that subcommand's own usage. For + a project-registered subcommand this is the punk::args + definition id its registration declares; a registration + without one gets its registered summary line (such a + subcommand may implement its own -help handling - the + launcher does not intercept its arguments). + + ' -help' renders the same top-level table on kits + whose unknown-first-argument default is 'script'; + ' -help' renders the subcommand usage + (see the top-level definition for the tool-style-kit and + tclsh carve-outs). + + Degradation (G-030 doctrine): when punk::args, these + definitions, or the rendering stack are unavailable in the + kit/context - or PUNKBOOT_PLAIN=1 is set - help degrades to + a plain subcommand list and boot/dispatch never fail on the + help path. + + The subject choices below are the built-in family; in a live + kit the valid (and prefix-resolvable) choices are that kit's + actual subcommand set." + @form -synopsis " help ?subcommand?" + @values -min 0 -max 1 + subject -type string -optional 1 -choices {tclsh script shell punk shellspy buildinfo help} -help\ + "Subcommand to show usage for. In a kit the valid choices are + the live launch surface: the built-ins plus any + project-registered subcommands." + }] + # -- --- --- --- --- --- --- --- --- --- --- --- --- --- } #*** !doctools @@ -404,7 +501,7 @@ namespace eval ::punk::args::register { package provide punk::args::moduledoc::punkexe [tcl::namespace::eval punk::args::moduledoc::punkexe { variable pkg punk::args::moduledoc::punkexe variable version - set version 0.3.0 + set version 0.4.0 }] return diff --git a/src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm b/src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm index 1db630caf3dc107fc95ce6638172e770a157c43e..18b81fff234d0c5aee5ce5ef82b4fec20ce05ec5 100644 GIT binary patch delta 59833 zcmZU3V{oQHw{2|OnAo;$Yhv4W-q^Nn+qP{^>||mmXTEdmo;r1JS3OA@AGy)~#n^%hhUoukBu1vrmL{e|E*7Rl^e(nUE{4{o&O{D?e}twcrgp}rME3s>h-~d$ z?OZJF%!%m!d*(lCR~u6z<9~vTO#eaKxfYOEdR-{vbVG&BK@c7pP&B6E&mzo|6|ktkNhM?G-3ivGa~Q*v%u8D z(%HqCi1dGBB^7{SCn;iM!T)cgZjuls5}|J@+4rc2Nzs3c-&?51@%FyN#> zEbJs+bhxA>aO@-`Ft8><1ajb{W)$oueZ(u!Bq9j(CPUO<;D7u34=Sk+5h5u987>hE zxrq?H4=ia9n;oT8hJ^#_zmB|!0jkTmuCWyZ4FrUb1q5W3M64*6 zrWg(mmDUdo2Ay^h4vq-;`2ht20tTu1ZEL^9iSm7;&lK()?)x-V`fwt13?(o&2zhWXgH$08!-mR-!y&JwtuI36~XVV;X!xddN(A3=^PnS$oS z+^ZXWN9*`DNIMqd4mc~$Hk~{wVs6N>E9244$X+ca?UfSA?Ck>Nhcsle(_FKC`91Ax zRGunTqE>Yym`n<)t9dpSD!zqCKb9|DfAN0ae5gq4sJH8!mb}HugL~fSa1|tCRwjhn zB^?&fz6B1#qnmOsWG=X=KQI5-2sD-dH>qK95C-09=0bRNvki_GmsE*n(s~N^(22ue*vy za%R?|(eXaqYF9I*lPo>1?zuZ{Y8yP$m1Mv3UuF=ZQ!D7uXOGB&2JIK9 z`06&?gitI}%I*p`v?>g=q)LqAo9I3ZBM-WFV8R$Ewo3uUFeOt+aA58=uEdNX%)3W2 zP7m6LmGgv5Fy%-_>BFJyn{ct5a}6DEZeUEM2!Kk%1iB%ALuai!=TW1)0Zz>;#$He? zbC238-2+4(XT_7sZj*e~G^hl}Hz08e>z3d!oyZ8jSJ@Ap4ad>GMW&`5 z@V^m5+yel+APh)mm@!x8fT=-~!1I)q3K??L-kMvNsBQ;6xWOUnrD!j~hi^e6a;OD( zvzrtND76To&gw{^9<1CyJdjI}wvSf!N>(7`q%;th#9D@I2sE~~l&!|O_;sViccqkL z50mT5N3a~DJB13kK`or%T>E+`duV#%s1@!`5chzbsjnp99Jnk(^DeAhm2Oyu6e|RK zMf5*Fp$+5?SVqe|dI!qRug_47{o<*5CA@G<>Z-rV;pR-pT|4`Fyje=l(RDvvxJ;+3 zw(UlM>-2wK_r1KH;-&z)-F|Y;mx-=|o4?zz7r-f-OyB9Gwuo>-dFgrr@6lQ(Pdpk$ z006*~K?qg9cA!#R&Ul(}Z?X@gCu4=3@&h{M?8o3=Bbr&B|h~vn>=-~NGXahWZl$mnyNor;-DEgVy4%M;*PZBjmV=~={!g~ z*5Dx0oH)d}_EMoTKDf?tyJ(8R7d;dqeg@URgfnn1On&QEWm= zGz^wUd|@okQf`HjHI1Kaz22XUT7FsUH?kw<?K6r>N{M zitvNwOG4=VP2NT|MIQ)hy>?1730VM|JB1o^TeE!$|J9P|ihDy8&GE()Q`s|Eq%=|} zOjhx|Uzth*rh^i*zMX2jj6s)TW=2CyKYeelZ�+ssis=U*_Ft!yYL-c~nzubb3Dl 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 delta 49561 zcmV)9K*hh5@)rG}7O=Jg0w4;LQ4=DQUjqlT-~t8#v-}0u2mv6IAqN$c&kG5Y@ecn1 zAd`U<8Iw#G46_vwLjeIGlYtZ&vvCpZ0|6kDAqN$+autLD0w4;L!3i0&5EtGA0U(p^ z8#V(hs{(^39ET?y0f#3X0=Fj|1Ay2O`I`b=!|2Y}A_xEg3KReUYM1ek0a2HXu>}p6 z7WD%bf0bBmZ`(!^{;pp!hjD0(?VSdlAftK}}sU0T)| z{_pq9EJ?|-ofpE8OmgShd3|P9y?*d@m1#18RHRU4lj~)e%roznW;H1*|A)@vyimrC zV`h8(UVpYWjy5I|Kco@O-k;IZSWVltt~C9AfAR5GYUC)_nN^hzWMUwzYL~4k)YVdD zdfe-uz8n4R`2rCE1D5@_Y}bkmv^6;z89J%ha3 z!7r*T-*l#M_#ELeWp#+YKrj&8jn<{Wf0W8p#;wRNu8pt5V9Y2;g^$ z<1NZffIzlpuv0`UtS4cutqtXF!qVw(60*vak%QCg|MvdvjAj>f_Vf9S#V(xP3Fhn9 zKYhHMUHt3(KWDFB)57{}6Of74e{aWhhA8YfqASui2rVG>W2I2crLBtv-t5soT-@7X z2JkLsbjo`1?SyY7#f%Fk*KM}{fUF6Ti_w=msgq;&X#nOez>q4v(hg<8jz$nY%^|>P z=AG2ZLV%tZs5RNKds~+|gq{z^bRO9%tB|BEt+2Pt+S-w`q;nz?bnKQ~OWG{nZw}Oi(P$(@V z^oO>=Gv>S>hD zi!@%C zh3c$)aX+0JJK>#7Ip&Xr^xE8Sk$WSkmU(N9m?!QtOnjxUV#ASUg`jJYlHjDzvl~^E zR*N_nI(J0n#Fpp9E_!dDCwD5zDAbX1*`e#EOHMhy z^FGXZXlCQX7A1Ih;@-%V`1$Yad&{jjA?Fpr0oF|I;9>P?D)TRbUwRRY6{K}cY z)X*|te`@Y4-w>eRGr(yY;88kj9(wrn(llDk^0rI?NrWpCOHsz%;G3ACX?I<9Jk1JC zrZ(hoFGbR`Zz$)_?b^Wfzp_xjT*(QJd&#}i(i79dn0~@a!(%TFTgkq)VGeNns(ppY zjz_UKlZVqfahfO@=F+kJbU$pTv1`!5l*kYFe*_9S9i)Sl<)oYV(02JOjBfUB^zysE zcSyltOadD{nEg|R^O!8t2IQrxa4O-WXH!Sq@*XXX2iMVA1EmWk`A~}ZWB;TNj%U)e zF!&seKqvcb!1-`LDP47Po$~bTnLI>eltVDMyQi^$1dGk7hF9qaHLry ze}47V@((tg9}8SHLo8orNw-PZ1o=8)gRbrlmOONUthx7o0cUKwC#h1>CvR}F7uA2l zfrsd2jXDbvXOW7YwfQvNTe~j@rt+n);qKF*RVCf#Rb%WUsXF{z8gtN|&do|&C2a{M z*}8MkjYOr|S+8;?w!(yvPKJSwt7%9)O*&EfL2iUr()_e@B-Uz?4$o5Mt}%Q3tn(61 zk&#{=xV8D6Z|~XO9lZM2yOYGaz5fGHO9KQH000OG0Qs8&U7)Bkm4^iY009mF0B4s0 z-2_#akg)|7fB*jh005O&U2oeq6n*!vxQGM9-YRF^p5``vSQiu>uyxon3|M9uv_wa2 zBvK_Qr*4q{zH>=RmR+w+gdmP7-mi1d{m5wSOlxyV%8~HoY<|93yYppk^jh7VH?6*1 z)I`BD;JbET{*YOHxLe{+B4$lQgCjwWxi?|yhbosGu7 z|MBjZ%hy-gyT57{4o}l_WX^RLHy%Rytx8m1uQdl56c+ofQzizu8Mn9N4wLh?O}W2K!cB*_hH zEBe@g&jas5?5g9Xo7kP)Gc612qo+|;$vtn02+u#142wyLtWDTEg*gvjpIt@ET| zcqJLSTUpGABVlzwtdydVmI{-%wbb5?#_aYQdMG4G=^I?t-iqA6=y9LVg}rg}d2Nce zk`SKH`B;9$YmwiI8;Jlrl?jL;nlwq*2pWL*f46wC;&|Y(n*x4L$p)yn*=Qu1rrOdy zu}Qig5uS0=t}5lqq3X^{{=>mC@ZN^^J?ueLi&`R4IRh>#ijIdX*U;!`BdiMGMYYRd z^^|`vo+i&^UAo+=2Co9+3T0V3ebdI)qEer_@9zybbFK8+P?TEGa#^%>y0oLJG{2RaDhk0G;16ch5SiSoxt< zT(3=&p{guC&t~*ft16!Xx&Z=F(FDZ;e~N@2;hJqAZv2)ant~OGc5O|)Pn1wNxzMp} zqBd8G!rXE7>>3Z+gsj%PwxNwiqX^e8KZ?4kvp)Ae^7w& z8SqrNSmQhm1g!vna%Bin|4eo{OPy7YQ|6C zY>8rk9*jvveOD_j^%>sngWs_;?O7smXHxTc$OTlr)H5KoG36;Kcr+pH2iQ#p)P$GD16;iP>-MC_NaNil!G)6!Qi1s#5 zn$2cs8bl~);-s*7iNboBf<02*&*){W(PIlf zCoNUe=QyFkS0W55be|5+DOex-P9wIJ5d}AlS~4ituqNOhBix(WYW9f);a z#G0Pb>$DTWq1j1~GLrlV!_Nn>rgGdNJ9+@B-#nbgHF5MGP)h>@6aWAS2mtu10$nqh zHiODn001gq0RU^4q2UB2mte64JeS)A1Qmb(cH73aDEvR4Vk+`0NmrmOC(TW1*I7Pk z()ykx&WhdU^f+8xAPGvSK!626$*QZn?nB%syiaoXuNlk?1|(&xr_HyHS5rwOFtcaR zo;~~b?)A63{Ay7YwOSTsq9TQVSC>hasjIYB%{QapM~^=_ANTsb{x>iF{OXTyqE~-E ze)rYyPT_?ti+0;pT*r$nQPpK!CM&gxmzQatl(#VCs!%x$vo6XjwM|O3EsMV-%X-r5 ze;ZeIQchJ>F3+yl)!BSmd}OanE?sVDU^d#YARmR=_~beh+NO0IEi1Tn69{b%rU z3C}+_Z=UHl)#6qSH}Mt!0naUq&8C0I3E*U?MsxGg1b*xF%j6;fxZ^r4^0A8Zm8yze zxrBLPp!zbwIfoVemgagPQE`*glG7X~nIMjSyo_rF(5;H3nyPY_*Xah3Q^UB+Xd5rD z;)_Hr<88eY-2VFW@hO6*HU&Hk%dKH#fT7nP{VmXE0X@KTl!%imP=sKJE2o-ub3jC2AX&RhnO@(FPEjto~k9 zf5%_SJkI_O`=M^q`jY0uRl*i6i!J`1tkZWe6jH+{05J@;%Mw@-JdDVScXcu6zp5(P zEVA2EBB|*#OBe7n2V9-ven)=@M6ch6AKPN30PSg2Gulsa+MMQICF^*X)vDSpaFcL` zRa(LL%S%8EAiKIeBWkU-x3u^C*IBhJ(``Lg$-6qqSIy?pw&6y>7Os+IhO`QJ;eE7b zsf6AgxNxya1_WIt2}38Kzz4Edc@mfO70jxNHO^P5b($q(qKY?&PJ4e7s$IE`mp}ol zV!7KSxDl|?Y7J)!L1xUw@5(F}!NChCo~mp6a7xdlu?i>=g9!!kUD?^2kD0H}B*i=Y6du;bHdy#W;9C%^{; zn-x;&b&5N^E{hF8k}?SaqLw@2O+z@|c)~_WZQefQ>v7hk?nqZ>TaaYI9Dv#aog<31o&5T9taoFPf`3c!DY*NQxeGGJR7d>#WY z7{S&JANez31yk-o5 z*rZ9LT>|Q|q^#7gO4M-2BdPPDB1Rf#J79&1k4&XtjX1zXRxE&~=+OzS&)VpfW%arC;LXdM>qxmPC)TdV193hhmfg2FX(f8D_fWP;jf8&(?VI0cPfyiimu7VYBnekf zp3|1$g}o|nfb8AvGTR_SAy|1qn|P*#k+0LT5>WiP#yzB&2mROUe_75J>QOi7HxS>_;PRY>iRCr>8ly}MqzR_}lDmR?KV!L?Xf zFv11job%b8fZV;Lx3Cj+GoQ^Q$2`_F%9Gn2Q(uVJg{wDDhtugKqjDS(m~ z6q!=Z=d0ajb332U;NL-L1Lyq+sNWlaL;T_`9!#}_<4`BFyJNRVpP742@AHM$)P1_w zBIJ>o#n(anMdDi&t6N|5O5 z<)F;eWTqa^dVI!!J$Qou8S!6K9Ks5~kqm{0|AB`_j&v*I)7=apUNvITD4AR!gi*Cl zrXYqPdQR(`c$=P9e}y$9pPbgK#S&y58uWiPjpInYz!gg(K4216Bx1p6m=*C#y}wh> zSj2liR5zgCp_+nRSdmP(J*#QLv1U588tSE&$zONCT8Ao6k`;1Z`6AL69!;=;?M8kM zqki?`%Rk7l6ZL}ccODm>VId!dJ79Vl@-w0;LBCwSXJ;huP0!#3Rr1?{iVWeZifl(( z0t21j-o&>P^`(#izRlx`&^#^gD{jEYt81G{R^x7Ioa&bjy;k zju$0RXPuDTf$GzW$28QCu8|A?#m{hM&($*+@40&K6WQGm6sx*SlS=57WDlcXPNxjP zyt>VobFFCMo}kcD^ zzAP5V`UK%XBJrAZE?8`~j*tp~pTS$CkkY0-l`-dAd>8m?eoGfr@@|>!Ujt4G3#fkRG!3&O?;wF}$ zIjqD>1=;dy7T+c%4~sS{(&$}oBur|B3g{U3CdUIr2LmA_Lke&uO=`GNpyHuaw!IbB zgqXa>rB|YNO%C1UJ)`C5E8P&F(3t!qL21_gxW>AE+!h4X(?0N0&BNbo3G&(Xb75<6 zmY?740mV5BBC{cRlWBj9s7(>IF-5d# z7r7eXgKOgA46gGO3ReUEHuZ3S0T5obzPaml|3w=!;6e8h zL4)XHx7=(GXdA{X&X}T)&4WwA#Cy4zR|zqU0X<_85P!>uRlYS}_E+h8jiwG^oWHZg zNK~JPj`T4^u}6PEBfg7QJ8a4Y@=0Q_AWt=8SM)W?5G-6Gx7{F0~9^5@RiD06c4ZeJ@&NP5`gr01bb|H4pXm^@y6qDj~uxP^>Rt zA4+}kMK}_HPk!Z#}t7;G-zFM~Ga@j7NLK;Fuc)KaVlOb+Gj0|C>#gwPaX zBSaV|d%l0ySMl}TVemT`?JxM)h%sScV`L!1&koXRh|6o%qDOJgr_zGM;950UBd&fl zt+%+I!xJse1YE~fYCt~I$91(pX;D2*`g7KNp|@cpcJu6^|0w)mVT5f+$2gwj$0J%YGM@J1)u&6ru3q3CnfdQK>1wwEx3 z;gOjJkJFK+n)?`ZkCw>$Z;1Rrtt&POxtVqs8M@BVWO9?>Q6IuDab6?It=MMKHl-BX z#MpmqY9?t|&4oqlsHo?xm%>ciR*tS}c;y;6Iq_!HSC-ivH0m<8$po~H>omDBf1^=I zfhJRF0XwyB@w# zS~{$Ythj-5pzU@8``u9ir{}RpB{;-#yHkJOqjF}(-h3dbp!A7=AsG$uO;7yT<)1$4 zkQnf3pp0_4Pp|}*l!;<(K8rtj^2q<}(dVD-n zw`8n-`&;#C=tTF`>m*TMy^B$6RnN|!YoB6WY#pcgyRtv>g6||Joz5}(hKb-TRwkA_u3p_5Jk0b)o{DVWm+Af1)t!lIUz0-6v+;d z-RJxC<)c^EY$Z#N9)xfp9_<0KA7F?d1G;r;j)Q#9PJs{wl(BEC;Fc(=cY#HHS(M^M zX2VBuetQ#4?zviJRs`dFkT-4>x zo5MR)5)`&)2+!%t*VQ~CPwUZ_o)|q&2Ji9pyJ+wpmVb8=P!?UG)ia*VY$N_7Bk+GZ?{nm!85^HvH{oRO1?@|-gQ5y)>%@Kh58unenKiZd zc2nIVT=-+2*U=;QCHOS(3czyn$rF4Fe}jure&Ycm{ebi5XP@$)`-k2w%bLdHUygZY znZ@}9K|{aM+w>bqX3ZZBH1d@5Cp~)9WF)#Yh;p#SUmjz5E2@KZN3?&@)}%n?FLHGB zTi#XuPP9r((l)1UovaCxn|v7gsu9L$vvs24NLw@Sqe;Da>JB>u$hHQIYN&L4EBobX z89G#vz05T$*%Xx)QvUj)<&ytSe2Zyi2MD0kSBIeDWlXS6-9bnijGpsdb*f^JH7@Z3 zqJUFACDC&}Eb`|&l<|M{6Ygk8Bu{h*_DJabOvwL?Y50sN?9mwAVfg&1`tr}e|MBH_ z^Y7l^M@%%6wi+9kXgHNP6MsKvDhjKuM$zT>fz7v{LniT-qnWZ2U`zDVkMa5FJUBSK z^ft^CksAfsS5P0O`v*4x4~{E3w2zx?PxBycLg2Y_!zYn$M1X$@1s&cz6m{IUk2s)k zO8}=obVsDsoD7EXvQDp)_F+X}PDGJ9L68R1X@}zU%U>#EI=e=1FPiY!UrAAw6oore zznMIq;msbM2W4mUjbd^@QZGBi4>3r4h9mZ2-|h96U1Dkms3HIsJw2WNKB-^Vt3Oa! zDnRrYUB9p2{BD2#^1JW<_-6ix7vKHvo3Dby*{1PktS@=^Z!s!{uf_|&rMF!t2ZeO^rc%d-720Q>@H;11-{LMS&dx$BIDHlHcsafz z>wVY)Kp&>Ogw_69?B6I4P-qX*|4V%u;BO3GvJQWQg;*i@$%OJB$WD%lX7J66Y!hQR z8ucDKVibMkI*9Qcl%2B{9VW z?ov!1AxKrQ^O*#wmmst(6PbWx+R5!H?~Qv$(QliV1(}>nGJQB+7==XHSBlcJB;|v5 z1XO>Q4FyP{`WTmpV8Sh1Fk+=on{5ANau4e0B#a~AZgGEKq>k&$x#R=Ar`R3L@@Yxd z=Lq6JVtaORhQ3Y=DS;gzrpFF75XMIMOjIQolwC#Mwx^m1Jz~=g+|5nr-AC2y+s1eXlao2CqVOM1_NcewM8<7l|_NZwDwB6TRUQpYu&1?HWX{FP+ z+g4P!v*449_LGa*Ay<6!PMK~>av2R4>TDM9{}cF&1~unFit&swgT)!zVl0q7LZaZ| zImHdnw{col0sRb*4kwjy8Ykz(l38z!W()FLf}Ta+Zb1gGPf^ANnxGs58rIX$hLnHv z#*xcYdT)r~I7B*Q$?>RI<-ylQIN*R>ddnTI$!=BEZYv2Qu}*yvC+JiXP5;N)Z#esx z*JOvvTEQZ#vbagB%d;K+mag!3rDai*s5+wbRFD_qYDfNTApfzqRE+4jWgHPxk;GS+ zcmZdpK1CjRDFL>G2|O(YySW+Do}GWZ&)Q^K0!*O7O4^dLa1besny1iD3_M^wVx9qC zK8SYO<%iYwOf1Wu2=B_iUBrwLE+7JY@BStYn zgz}2||I`!pi{Anp+54_GVenh~j|vbJ9~WX*?ZZM8F~zNBaq`U{UcPqHH8FpJ2yQ$WmUwr@l{M%Q5{O*l5 z-i}XOiMCE4495UaZf4$&vPC%vcfy(FwOrvP?Knyj&(*CY*o5V_;tcFG299 z=%Hz7L&E>@eK&zjBwoE`w3EoV-BHOv2H$Ai4C(4I#lA_c?w;K_1>q^}) zCBJQ5Z0$SM3OeSuEEWSyt2H!=Gl0SSCf2ox-AS=)8>RBO-tD)^yKQ;21QQS%nz6d% z8vtug)=UXMYRNFN=#GELPFLJ`|2B^P_9FVv+4StuUGr*}nM23?Supq^F7mfn^z;Ti zAsgVqpt*g%cYoam*aH$9EfkK47s+WybH29@i6sZ!X)5c0Xl==ETY>7QpFkGG#62@g zM}Y1Ajc!k2ws$={Huhjmv8M)ahc;CsYS3Wg-ofYA`Q*{aPF|Av6VA1ZWWPzT0eRE0Y5YAJ*0#W2)S@ z)5rK=KX@BI;^2Ms`j1%qF}C?`pKV$EWscPw=4(KSdXEKgY9P^o-1+#EPo^qMP!yyB z5ouY11YrDMs8oMVr#Q-7mxdW(wCxUMCSU&2kI>X(wWFdsWHCWgpM;TCQdx<|X4@$T zu(E6pJqr!aR!!ns%pz8+dV-liH?>Zk$r$0j_4iQ_S{WOD%}cJ0A__#vOZUk$B|Hgsx9t(n4IT zJXL=Kn>y|eVj!)!SW5w7fR6?^`}nfA-i{u! zgKm7rxF37={SI$;K6m%Qlp{eAXmfXk&q3>CHilq+9#z4B9MCOT?1zF*6%V~X!9?nS zh?xt^NU=c^q}Pgy3KY3&+3?8ehu_zY8Ml8i)HVj)zGKA18`B<&uX!@yM)1^Av7k2f zR)SVr9@w_doQiEq1_ou4iuy^>qzs9fmq>pRd9eH)Xs%{e70UuVZ8&EHhv^6MR#<+ksHX4}IB$8eB;;eDjj#VN$#8g3-K2RAX-$66Luo!yR`^0H^95-5qzsGSqD zTE;q#qu7;{#h$`p@>3<16vrJ(8KIkmj~5p-%m8jKC#avJvQSjn3UGh5r4Xa079@E^ zOA6N#D}G!{3EQ>!1FTA=8iYx&XH)B)$!+R#8jf)Ka`KP|H@0bUj2Ka&5d2 zR{2?C9j-Ml(#1(RQ&b8blE0U9dCFXEprkK|UN{!Qlv+Mz@dK|6r`#rf5~`>v)xfAe z78lo?P$o2|DH|Ih3!{IA8mik3mNsK244m1DDiB~k>bMuMP!2i@1q#~Hsn@7@9NY%S z95ZZVWPjVVhOa8A`-^p67r1p&#c71wZiQY~E(mCNhkp<2{1CvM3+wfp7eD;_%QsWi zO1u`r1-{>uu`9NXdTXTT24xLHB3*qhL(Fk2ADi#CXfp!nFgt%3YiG~bMYcl!`CCp` z$P21ggk%{t)3IJ55D<5X>f0@Vfdul%682iRcB>eMmZvSpnMglY&a%!pC91>P+;lzC zfTNpO`@D!=XDDJdW}`lrd`i$FSOp!HC~f;9uatn}*h=k)CW(y&x{zdJgKJ}{g#znA zSH^L;_n{E1k^D&F!RvWrrSX3-7mu z(=+RynvqGR^GSPkvrw@d+HC6$_W`bP-8o{A2>Sl|C{mNHye#C13U0nnu>cKL0%2o1 zV(b^a#*s>~skC6LBqu1OcsTWeeDBB8-&rB{dLGRsXY3^nFdI;Uh$u{T3SAyf!j4e> zs)S>z5uJa31i3X6tw90^mYfaR>@73(lk@X)5^(UwpHdZEE$uYYJJpbQ+p3u%3t?L& z;>E3~(`M-B{^U=dJlQ+>ox%Sv2Cd;7l(FC?YRnLXo#hoe5m1%l$`b-B77nuGX8TD> z^@SwTog&<~I}?0X-ButUo?6w~BOUvtN3@C^av^^WlrT3awyYAYHP~44DdNK#+#ZxP z8Vnq+YdAwU*rH;+qmbZjQc`h>UUN?xf>z%d>lp3ow};1w&pd+RK|$p z6abr9y0jyWNlumfu=*)?k77}#7Z@hpl+l0j-srWf!cavRyuHZyRNk_&T;$@Yshw-o z8`Od*pPcav+iaa@w~+#pOYf*GA))Vfo=~4j2UUqkmn1FL`gy7wRMxv~&yQPYIf=ig z2g%iGNJWC335V%<}-RNz@}6)j+WN=j9&4f3R1p6_uj)#wI(DEV$Vy;Su`m zie15aEO1$eQGf8c}zo)wQd8I^yrjuY`M*1)yh8R4U?)V8RdD7&D^o-7>Y~M_LS^*eF?7PUIM%)@UVJT34X=Y`+5E+Q@}Yl=Lz7Zd z%q!ymjKQ9)<{aVh+<1X(pKhnUsJJV z;Lwe*x2D(+l5)wmLbdleXt{67Fc-@uNlFUDXw)z#pL64e7_U7_)WZn;o-0bM=mw@r zU?#>7=I^o~AZ!B!0iVND^@V@>9y2O8lvLLB)<|n;+UPP?#Z_Y|lQLu|N(i6U)&7lY zW&BKbS`w$L^UXwJ8}2Y|Ga(6O8=JY)L2uj%dKIkNfJBmY!9P>#DJhU>0Q&qh6Ygt` zLh3t@guk*WM82CDqP0h{+5{M~s73>8yz$CE$(Sl z6yPPpq8jSxHS~;RX~MQIl+p7>?PKYEw5!+&$`#>>DR@g_?F8Ep0VeSV<%xCNJ^8;) z&W~41U&^YZ+lq}-wu$uQr+4RzpWdGT^z@TYHjPbMUuMmI_{x87&&}B}T~tV7hDm5| zsq(k`S=)Gkx z9sl}#$@t_Sk~`UhJ`9fvj`&|q5qp3zni)ly%FA}N@1Z!aK=P~~=Qax$%$I}9l79Q0 zr{vR?@RE|-9HRnfXHGN22qMQ@%RpudX}JORH8n0oS=FiOAQet|t+4b28rbY2{TfOg zBNO0+P1Ao#-50cd4=^XRtBY$PPMWvAYK5OE2|gFY6k)CjOt6qTIS`Z8xpU@`xgI>+ zMWndW<>9>@Ii_vMNxQP13^#jp(iZN0=x2K?Cbd`au+2tV)FXqvo~GRa~x3W?F%~m=PsuVvW~f%E)Rk~;P{y3 zgvBh}l8w}J9lZ?v3$?rD$VUF)8psV)P=0v)Pa~o4idurtc6pXwS=}p)BT*EfTPeY9Ojc(&%q znCkA8NKx!CrBW3(0?~bSuAZn*kYjwRHpymTVvm>smY4WhifLS{zhLO1 zi%YXHmTeuwd7Sx8S}|3qdOQmRA2xq+p8nfYH5{})9G>XV%i=1@qs47h<1a`7m?Q{? zV~X*pHMR}~cG<*OWLy%6{Mi`2m6V%Z&5@*XG`UJD?zoAfz(^}3Id=N-6bVO|^R)3r zjI&vB-Qwb_&E_DkmN9bOnmexKJHl+i9^aDfug=#76T(+Wi; z1Jj-kb&V2!mswK!3mHP1yFTebQxj$(HB4tc>Rg0{^6dm0o zNH02a?%bw4i52X7v$A(1q*t&_MS!o;Mn~Z)etf{CyCxof+(eyEL~xl9Toh@@k+O5?1dR!a1y)mwjYsYGvxySd=5O@Y0z7C`uY6nK8-8*{CR&z&z?lY7-T z+UM)nO>N%0W?>G`m2??tV9lwqRt8OL&Q^Yl)YEFt#0YrF0y&XT-k9nr0__rywiq1j z_)t^a&_#g*qJGA0`zFAe>cYfA&aDN95(em0nu^Em)kSU?4IOD~7fgSkDMlI7NCA&_ zO@^G-?bcQBh39A|@3u9zu3nu39>P%~7`&N!9w^Vz#;`ozF4fntbX?QH^_a-t{reX# z{BdwcJ8Q^L0k+t^+9D~nhFy$J0JZzpC=M{k7+p+}%^t-OWr$epw^2L29=HywA?1ax zO@Pg?cNdqZBzlm5B7uLM=qw$#H^pZ;_c#+i7Mtestw33Xt*oc!QM}MEV{QiA9Ane4 z%f;)6-G;;p=-@M!#>TZav3=OO&ApM4%;!ssSmlA43!b-NYE(`UlU&@;gyqR7=Lnh~ zP{R$Jb({f9=+gYz@J$`^K_5Xt->Yaj96n^wa#QD40S_P9)IeJp;9(gX;iPfeS6a^=dXd?hb2 zoD#Xd3AUEVB`g8SvK*YZGFf0`3#GbnH)?dsrg$BUr6aY@E~1RUWK#YlvG%F1(DC#J=p07V488t+XV zWfGIB&_HCGec=g)g|n1DZF(@`fu-{@jA%C01W4}-0Z=Mc#1S+sLXEUeM*08a zll|=2MVFIM>s8NryS-V>LbX-R+uaQv_u-{k3){b0_r`xCg=^A9ZD@9&7&%%OX?-TO zYMLT85(a4cU_@1YixEq3oYDF*=FTIYAdJTVuZ#t&{4CZR+Y?1ECCEXa8a{MZRAN}# z7X^xGvYE! z@ys)tu+M*$FN#4?pS#ZkaA})33W~{7jMVjjnL1Zbw2iS1O#u*;r%rEMV{oM$>dfDv^9E+@!~3xB9<^ykGU&We8>j(Hhl4no<}fx2A4A!|WS5%jUM z{w8`KqZxSxD~^oE=C!2J!$_3qOnNe`UdoDNlpdO;5~&Dk?p$HNukaqmyPNGYepY}^ zf8OP|XpSjwZ2}x3<1_F7nDAis)(_n^4wVG4HS>^6BTW!$UZ-sO2*4(>4%0Ovj^V+F z^wodK?GG*~uZE+Z**Qgdz$9g${SWh(x4VpWbSp0ap<~I!fI_Ako?9Y}SoET336V$+r2=0Sw&<*=1OVZdr#SU3^W8QavgN-vTLN6#W` zWt^HoPQ5I#kLZjIkvA3%&7Ey27IcZ)ymQzstADC%QAbVZTdMwl!zHd|m(seqASnQs zi6kZKB?eDwdeu>9#^P>=s0q@(#!&O^Hlnfs!lEm&l3?DU>;c~cJdRjvG(TsSfq{R< za_(x3-L#Mju#H-7*Z z%&>96@Upmk8;Xf!Ld6p*N9;8UkQACs!Fpw~0l}}0ml0vQv5^1LO)?ono%5e>`_Kmish-aPL;Z7)ht z-6S|wd|MQzB!HZXYHS4#8uiJ!Xj|nhJItxyzxeXsU;O?n$`inScle8yyO6JpS4iMs zgWA-a_vFxoN5n)nL;M3=4m9q!jCDD+|M>ZY@#ua#O<66&RkVw!YN{+ZWkY``YopTQ z2c{)amR(cA!sVwMs|7b_sfaqiT2ZoGLClNo(B@p~-DT2^xLl9ADLAVbonu&ziM)hW zXeDIU;6Zdc5$86dTmqzAbIr@9mQ9sRCOtd7S?B@~LAr`=DsHM;SL8pZD>SZ24b+#! z3M3LdOuv$BK0sQM^OY+6mlJbDB3Bbt+PQ0gm3M`L;X+_;mAD&-yr#tXH$ z^~@!vw~le#B1l;LgTx$WHz>N{8dcx9Rp%yLAp zyXUH`-D6`-cj}BKAXb~0h}YiT|D~RtKUP*Us*}7}Z{}oY8R*-erY(8U$dg&M>|I*;l0o}w(!0an3drlCmr~RH=>^k_E^Gt?Dthpb} z=0^*80U~Wn^+vT&;#*I>v>y_KTBj0oW`LGK#G^h@6h{sc$``0VV?7tQmWiBNDh5Y> zaUhMWXQozc;GsyZApVYu*D~>0*r`R}J6JQlwe#?1Z%-9W zvyO+2*Rn?5XP|Eppbu>@lkV69@BDmgbivBQpf0+9z85niBv$`qt^*J;*I%*jJ@NUc1xojC; zZl!erxQNMTZ;UQleI$(H4FPg{V^%${=;pY<-zCaq3l~GTD32`~7>SYHAMO6Z)w8?)7w4cU4!dT5`37cc;4i%GBZZGV-aMHeu~@ zY*kaZ6M71)ok^$HKFv%vnMQzSxzy*?EZtUr|CXQf6`suZZ`6kQ#flzVoyF8DT|>?p zT{E9Ya&f4=RALo_$(T!MTa%)uXNU-6wQlr(D2j2ZlAx4(#}y7l>rExZzPI*KBr|Pf zz|L2;SrB$YLrT{HOu z;CM-X%0pjI+W02AL^>nF#>&ylpqn#oiP$ZYPL?aYkF99xfpGN|pKtBrb5NL3wijz0q~8RL%~`h$O7N zpa|7OR+f|81?ss{CX*81=_hQPU|^C1>D^h$XAyZR%i^m7XgsCoi!vM)a=W>5?e%hn zDD`1nP6V4`PNR*R%v9tt>fdrTX7PzO$fIj^3k>`O!+6|FPL}ETzTrLkh=T;pY4g1Z z?^4f%TT*4d=09SoT>n@lqkk_mGt(qxFW!fG;2ak~xxzg;V>4N=Q-STc4+Z=RjlI|> zn47Fqe_M(Lp`hZhCEw@R)6z!W%Fk=I%Umck2{fpRYgx20V#k(3L^tuXSy6S?o-3zs;Y41E`B^bdVO!$U|0M>YK#|C zRJMh=z_-3P3K}J$v!Rv~6OjbiSZ=-!!h_bqD31s|s#+Y~EQzfdnLS+*-sYBj zj=jk?d)14CyU`HZ1JG+553b(lEa8gfZORZknbhxx-8$R7;^9&<)0_Y-0hourk?&S_ zc25I5mQmZ$Gle{>-B)rv-#1`&WI$FkUjkXKG3G2Nc)Q=3 zkSeQq2{$+@^E}6X3W@@V6393jUQh4d_QyGQ$h)?bkQAK{ zc4iFL!l){**F_!hU0k- zMeP$8&cr_@E$mr~;SoQHA@bD~wyQrbVp)$TB8WEiHyAMidwnXux$}p zK^RfEm4LZNfPKx`g7$~r^$weOO?9wkzTo7Pgy5Qgs=Nr4(XIvs=ouoM1kkQc|1Rj-GF{+ z@~MXyW6%2Xm7&jG(?L=JlwMZBU4~rmbTt=Mzy5lZ7ih6h0M`6bKU@wJ`^ZR%r8HCS zysy_`pU)?t=cem_u)m{Pd8IaD;D=$=H!*V;UeFm`<@SH|@yA;ixqV>Lj1taele}1npQZ=UOl7 zC<~q%Vi%Bd@&De>=}EKK(YQ@o$6**)&VAFSnE8=C0tqVz{%5p9Ph?O*Cg5~}Fvs8A zrCy7(2KVU$$#$<&&{brvyUcI3FawPCv5Sz%1mdm}>6tbXK`)tl$KC z15OJWA6L5Is0bZ<2|f$&-$D9@JRW=&(dE_#=ZL@+ZP(BdlOnOSUWLZPA-M>VgT_#l z`s8vLb=M7tp3~b?-KyM-A{o^Ina{P?hsyaLU&Vec@(&2L?6fsT+Mg|F4{9%Dhb3%0 zwkJo8CTAaot6;XSI<~If;xjaspj9=vz&|i(db#CHx_llbW{|QGLEpD8Ak5?$`eoo1 zI-(DBWD+yjoN@Fw%AVuwCOJCf&hrvkhem&lY8+R(gOC#ZRo=sGNZeYI6c$)4;TTsN z++!d&sX8R?#*&9o=|}WgG=^m?!K!)E(>XmPVaqVSR=OQtq0hu4s3oixUBn8{&$Nsv zf|`50lg9tbTu;IUZlE*P5vsUSh9D#012_98_{)GevVKh9c>vSkc)%+=(3duU@No*b zukTn^S3v(%i!0Cq10$ICcLTh^52LKSk}_9M`B(%Uqq~0ebs@OL6$fu~Yp#xt+szEk z#BjTdBI@C6ZGti>f-h2)QkfBH+@w5`wv^_wV<{~^N~(P-fjd-Q`wBi?p;VOr;#yOK#tQxHn1PJYZ z>c=sklTY~*vP=;ij59UN-qXt@Z#JB8#j9K7u5W{(oUMU*wP3(v%~fN{uYb8*5{ALrl;OIFQ%sdb6-Ysep5gJ{ zw~+KPf6O0FeLQP=Dw$5YmOPSs_p8xK3iep*(qPB`kht+iguQl-p`}U}9X5i5I`CCN zPOIL0Vo^7h841IylT*RPLEBd?V;UL@WA@oRP*3__=7{7(NnmppjZd;3xUPkMOqzVV zaX~)KFH0GaI2|}P;MW?vZUvHwl+Nx^g48I2MxsBn?JvD^t9ZrRhpGiFFy7y70_Pcq zklAxVOe##ENLlR!L&>~qBWWTl2V090jWkg_pw{{!iS%bL)U?Bd25EueE}kx>{{Aml zW`^0MtHP7lbCkbV&+z!C&c~fNM3cf3rK9+N{BF9j?^fUaZk=o}ON|0tnk$_d9(z!d zn!_!41wo-x-}h~L^yGdMSr1!xI2qJ`^KcE2;!U#{ni`tZ(0o*FX%r+61$06CP?3e_I4-z z2shsFgDUkoX{nLcpiQ8RHs{a0P!8?d^?rcH`m{dk^2WnQDSyGcYyCXgY}CE8!h80- zs{^4xPaiLlr`@#^YN)f*7#yN;}a*4 zc7U{#A?jU{)~mKQ-O8vL>SE=f^9JVc5ZtnD2D1-+PPBqdL1@uT;dqEu#kf@S)^}nD z-{DFvS1CuvSh!e-`DZXM(X9aDSv6RQStY+5Vy! zhZ0vYbRtGHN?WbSBhko;*Lm!*oM6j{ld#1h5LEs#aE>nMM>HPO-CAM zlrm3oI$>2xbxzX9EW${Y!V-QI4QP{8!QG+v59tmUqM1PEMEMjO@t5$&;sWT!+NJ?j zrWO9~1t+alNm-%8igPI~Sa|$N?rKr8M~{zl_OxpS8aie!g*Gkk}?Jh#Klh(4NQjRs}ssr(McU+CsU_bSfK7*2>{hd!OYa zP0Mwv05X8zcKD~rZLkItk0fx!Ij!C&6m_!F?lhJ|$2c(IGrxozgOe>Dzu?Rvgu^ak zPyokhUrGDHS!#&nXdB-ayVLG`lm;`2isS022iBfHe?i(FTSGCi$c6DIL|FW9+XwNd zJC2=_T*TF#Lih#i@TD49zMioWwj*mRG_l?p<9+~=UQ*4|B^F)z-`){?j;1yPQVKJY6cTVmBlh+8u6O4E-?6thIcrI%(Cb{FQL>{KhhIMsIC zs-L!5513Wov<1;K@3DR3+5S0Sm@MkjcsD%$%b`AUv}bL5-8o5*H~jQwEgyh*lYYx3 zh5jsrLie~?Q~Ufn=-|~i`z}sqO-N|02Th_JKAKf4na^zYn2R=!v^>Lh=E(X18EzeLjE zl8uH(}1jacP-Zc9-R2+YB6YJ&B{c)J_@F~}l z;@vu}X>ocYK4lnc-Z{+?`O9d|NtTLQ=+?n{BiF;+T(~7H4z3;?k>LM5VR26(ndUg?-F`7?v3MAW5=1^&W6_LXG`_zO6#&mj5XQjpZzCjUc$ zT4j_|1^EcqQexq~Gp(Cq<&In+q5te|izS)Bx#jq5s*ogd=$j@Hwl(~58WNB{Y?MJg zS*;k2P(SR_Ago>ZT$4xhO=mxVwz(pS;7x*HA$dq~_@|;8s1}{~f4D8B53gY(S-fWJ zW^cK-o;7>DZ+dk6Z3GOmOYYVm8-8~+h6wyjMXUJJ5!(@8`$K0*-10zfv7{im^S)@z z^^L&?nUU%2#6V!J(bGxSUQ>^ zyzcLY+JeMOqzJY5wp`jkdZa_Es&TqmYC}Z~soNgI%=6>wv(%fCIhO0q{O=pR82%|6 z8p*9huq2u=(chY#m@T|7-+e2r_d!eD@-Qf+@b^P%H+}_Oz;vj{ws*$@RB1b0Qk5HY z>FRzQI~hfKo29ykyy{4b=25?GeZYFUg(iK>t3nx&o1z;_Ai!Fv6~`}S*R(iN<$N_I zE8rPa;CB)wmd+M{kM!*97+9iVB>X_(Zzo}7(64Rp32jLmnyar{s6%;%yd+glK*wkn z?KYsAM1^~Ht})`^rm2Jtq-n3KRk2(`IIZ{h-#U?=9qiuZzy?21#F%$O$E+QWP*i`A zjTrOi#$}XREZOFYksh+tGa6BUH9qk^NL)+K)JD%@k>-9RE6yam+YyB~W~IXH6tlyf zL|}^5$>0QnO=mtcmGxNZvX#d2vKeYHui{7X(t7-Fi?X-SZNFi%0O9Y+ep;VLrP5?C z&9WGqMEAYRZt^29kg;U{{&vQ3ksq{tgYb9Cn_Af45#Ygb>&-i}nd^B;W>#Mx)euo# z6EuyZ_r}cVC@aaDO(Se={i?7kv3mQni=-ijugA2QfEk#lPDh9oy<3n^EOaFn9kpqInH0x z>0J;oHRY7dO2E&U)XgT?|5jkAKFO?J3;ZJY`>gRgz4{aiE(V>KLCyGWU8GLf=0wA( zBhoNLl+lNbTI?o<*mvhGQEabqAvjLp;jEhs;9&^Dm9C5bWVitc@6+F>pLZFD2JW8m z@;YdV*X6^f9=1R1pyVMJW z1+-~gJRIiWH%flM*9rXdn598ymxEO-GqvLA+O6p}Uapz-SNuzV(CX-~-hml7m*;lahW% zG(_5422ASVq!~Z&RQdU=JQAf*njPaxc&elwg7GtuOc2r5=I==skHCuG+RfuXW_69uGs!go;8?jflGJ9_S5<2PA5jWp##a#*ZF7YPPR~>Dtf*DX^M9Qn|M_&yxjb zxPHa2?)k)$4IslyGdrT;a%La&JUJ9Evk*NJ%Hm+GVX80?l~DX9U^Jqr`0mWay-6mW0{ z>CFuwU>FoK7vM=^+?bCn8*oMVi5HAyJz8DC0}~jM0K9I@+TFln4eYO#C%+HJ+Ig>g z*d)Is{)onh{E@1021}R@)22{>N_FQ8jo?vz_o2gu!WsP?@ywau65ZB&0+{pjY11F~6CdSLAGj0N0_7zBuuq)v=kB(!uv_ z1>UB}a)75lI<2XJ;>WbVe^z5i7A1X1EAGH1PdXn2eTsxB(}d}$;U`&4 zO?v>+TIRngS8nkGQHn8l3(rQ$NXO=$7$cldFbHk@s<(TnR4Rc;Y#su>?))% znu9Wh#U%G*r}W-_j<5f56V7EkT$zPAnJWSqQO9l1^~Iy-?vQ5hx9Kb>Th^L1-#j{` zmzL^2u)bM@ZS#TtlY;jSclJx=*RNh{gyD>HXcp)G+iQkCf?6wu9qe$C&dhB2H5oG` zR&XDIHbd{<}>rWr%tL>CFP&v#NTxBE^{ zv^~#vHcgwOSN3*nHB0-iMhV$$?6Lh>fXmiO_(5kZs}6Lj8*b?AJZRR0}v!I z5m-*itdSKo4isniZcb_`<)0}3vx2qlspsKO$|`Wxqqz#OWw``z6<`tT=#Dn-af&qU zoL;vvxqa4jH?d<=JSv6Yfr)lV?rjb~&8NydHeTm}Dmpd4;d&xzKg|DG=p_kFe1dC3 zRoz0=MS5j}_v#Tt7nTsK?C<58p1T~>~zlMH*zAHeW*U4agPPGx+m90KMI$$ zTV->7p}|@5_nl3&>ED0;dM6$K#^YBpC*|1 zrdHNxEWgVx0wkSoOPB?+Y-FeMwtSt3l)pg*TDj>cUeE5A)nIU5z*GEUjh5U#j46&6 z3QRNQ{cQi~p`0PNeA<|}2f@pF9P=;~nye#GKSY>jo(DpzRfMMEDL|uCi?cGtgg}F@C@^0vNlpou3oaZ za3qyc!eH5C(j(#oMV=;um2%aCii8w}fS>vJTUJFTN8aM5;)UMRrcL}n)iE3QQz(rw zpj!=J=}rKXN` zPfWm*>SzIvEPbzA70}{zYK!C^N5O%x?;%bseM4@-nr7e3#39RuqSoy-i6xS2I@yTr zYK=73h2N^gL+{tFV^KkE#>#%qdstLQh9xUk;0l=}EEHTcs&Ay;#1XffoYFy2T-Vca zxReuQzwnvP*tn$Sn)rn9Ooel=;HwR(gGpLrGx5j>u`*W3TR^FX7NbgYF*$Sd9+re# z_yBhuQbfA^mF!*kpb>I<^h(13rn^QfXa57@~UMTJlm1ll2+IXTM~KEu*gOieO^PNO8CG(U#VG{46n6=^Z+WeM(8TW3YB>j7#=@{&dCzE#j0%J*>+LRT=8 znJsakM4C)#eOWg}_!;h38gq!Bg?lZN=IO|O4TP3=N7+M=8}ah}jv%3?K+D!ZfapPny0*5^Toq{=mdYq-0aPh zULspR(V5@$Vmdq{nJ*MEVan*NUw?uu{u%0sJy+u?$AZCrQE_3BO%)g6;&(DelII1T z~;&tMgoe$eMIFUR@wxHhdPiC~}SLR*bM#=xPX*Y0l;MdP768 z8vaU6g1J0Tq4X zObi#`GDC&Q%6a#SL)g>Y!>7u6J7cpVka|M1pc=7Ha@bvNE|@bVYQO}qT9e$fhtk`T zD3%Y)qIf^;x{+TKuoL$RI3Mt@PPF8?T2UWMeUDx|_KFVHi*n*Pp!jAzs^6*Pk9{!( zJ-lU1t^Nspk*HvlmzWSuhKP*rMj!1G!tE0PyAPj3SKY_L`Ds=;huO6M)5qkBXTvKs z;$tpPeh*e2Qa9JS>mI1MJ@fHs8@sRB*|jyTgk>cTjaH|$a$Le4kP8Tg8PS=c7Lj&^ zV~i@1j&P;te#a#_#)wg^AM|gwJfV!z+(X6(gJqLbU`brG_?QZ~{VxL9>a(#%l*-0{ zjlHlYK1M@>ef1$>Vf-z#F=jFCn3J^Dj-KsbiSAz2@JON zIw^tD3USUey}3CqB>H+zxP}k0*{~b!Z(MNH^1#noRQ3Z1ww05ly&| zciqqnSI!T=Lj0w4cjS7)vkI1%?krer)GBXqb7kXKdZ8l}3~Db(7^8z`XN zvWE%>uZH`rxTH*5+bbRj>lI~d}sc)Vs0I_5|geI#1YU73a?j$<#Jv5HqqEv?;> zNx==1@*ZUkUn3^%CR@KRzYdqwsupNY1;C9_2-2%ThMNp=oz-TJ;MF`1lis+UxG^ z*0I=G>5@5H!RL(zOCBm${!>8V^E%#d@pjX72n(8-J|#+ah#XIPlrd7drd3sb2qIf3 zdAV{f3ZpIbBw9dpMO*?imWQ3aXYq-mV_P}E0v;de6qsI z-1=%0?dsbgIB&KPdbYlkOyYhpGO|aOTjq8zd6ia)Mj6d5Oj6}M+!_Yd;i%MhVx*eZ z$UOfDl6_kD^Z0r~4W+D=_~E7{azc40=#g-dU-GFj6nO{Y)Wb-?2$Jj1dA!(ty;Ld! z@p5+!-vag5pY)FdAL>%6cm+mGJ}D=n5?wG7d`>u@O)${H%W1Lr*pyO>NSFg{md_^1 zjWupVoI|GWXynYzemm~2)ZhGpH*f5syTCi=$EUcTIJksW=ns7lrB3MqLjQH zXq6Nudl)K0Xo&wOQ=B5Q1+Dh4Rv0OE-(b=IGV@S>?fIj2Fk!_2NGk>tRt$i&VlZLF z07xqa6IKj>v|=z}#Q;bv1`}2cfV5&TVZ{JQD+Uu*41ly^Fk!_2NGk>tRt$i&VlZLF z07xqa6IKj>v|=z}#Q;bv1`}2cfV5&TVZ{JQD+Uu*41ly^Fk!_2NGk>tRt$i&VlZLF z07xqa6IKj>v|=z}#Q;bv1`}2cfV5&TVZ{JQD+Uu*41ly^Fk!_2NGk>tRt$i&VlZLF z07xqa6IKj>v|=z}#Q;bv1`}2cfV5&TVZ{JQD+Uu*41ly^Fk!_2NGk>tRt$i&VlZLF z07xqa6IKj>v|=z}#Q;bv1`}2cfV5&TVZ{JQD+Uu*41ly^Fk!_2NGk>tRt$i&VlZLF z07xqa6IKj>v|=z}#Q;bv1`}2cfV5&TVZ{JQD+Uu*41ly^Fk!_2NGk>tRt)^36@v#W z20&Udn6P31q!oh+D+WMXF_^Go0HhUz2`dIbS}~ZgVgRHSg9$4JKw2@FuwnqD6@v*Y z20&Udn6P31q!oh+D+WMXF_^Go;2*6RJXkRR(u%=^6$2ox7))3(0Md%VgcSoItr$#L zF#ytv!Gsk9AgvfoSTO+7iot{x10by!Ojt1h(u%=^6$2ox7))3(0Md%VgcSoItr$#L zF#ytv!Gsk9AgvfoSTO+7iot{x10by!Ojt1h(u%=^6$2ox7))3(0Md%VgcSoItr$#L zF#ytv!Gsk9AgvfoSTO+7iot{x10by!Ojt1h(u%=^6$2ox7))64KUy)EuwnqD6@v*Y z20&Udn6P31q!oh+D+WMXF_^Go0HhUz2`dIbS}~ZgVgRHSg9$4JKw2@FuwnqD6@v*Y z20&Udn6P4EkXHPH6~AD`FIe#lR{Vk$zhK2LSn&&1{DKv~V8t(3@e5Y`f)&4D#V=U# z3s(Gs6~AD`FIe#lR{Vk$zhK2LSn&&1{DKv~V8t(3@e5Y`f)&4D#V=U#3s(Gs6~AD` zFIe#lR{Vk$zhK4xAFQ~2;V)Kv9s{YI^6(9o82y>Gl>5_*w<4gYG4ggzSO{Cliij<>9VDA1`cULK+x+$LRkHGO9+Xbe>F{k z&Ju!2$(@5%{x`0ivQ`U=^5*|8bo@UGS%v?n;NQ+NA9Q@`|BfEN!Ae5krTx#ZbC) z;=u-3S*UsF6p=JY%KtR{n-HbwHp0Gxf@Axy*xm*9Rmxx^EIm{Y&p$C*3QZF%JCr@| fKM6)kLK7?#ln&oN32w?$6YOia82-QC7vg^bi2?y2 diff --git a/src/vfs/_vfscommon.vfs/punkboot/core.tcl b/src/vfs/_vfscommon.vfs/punkboot/core.tcl index 41e20475..eb9d4d87 100644 --- a/src/vfs/_vfscommon.vfs/punkboot/core.tcl +++ b/src/vfs/_vfscommon.vfs/punkboot/core.tcl @@ -11,9 +11,13 @@ #package_mode parsing (minted|os|internal|src with the proj: scope prefix, #G-033), module-path and auto_path assembly, punk::libunknown, src-mode ##modpod registration, then the default subcommand dispatch -#(tclsh/shellspy/punk/shell/script) extended by the thin main's declared -#project subcommands (G-031 registration model - see the dispatch section -#near the end of this file). +#(tclsh/shellspy/punk/shell/script/buildinfo/help) extended by the thin +#main's declared project subcommands (G-031 registration model - see the +#dispatch section near the end of this file). The launch surface documents +#itself via punk::args (G-032): ' help ?subcommand?', '-help' and +#' -help' render tabled usage from the moduledoc::punkexe +#definitions plus the live registrations, degrading to a plain subcommand +#list when that stack is unavailable. # #The package_mode surface (documented in full by punk::args::moduledoc::punkexe): #an optional FIRST launch argument of the form minted|os|internal|src or any @@ -33,6 +37,11 @@ # to this file's own path) # project_subcommands dict: subcommand name -> handler script (see the # dispatch section; built-ins cannot be shadowed) +# project_subcommand_info dict: subcommand name -> info dict with +# optional keys summary/argsid/package/parse feeding +# the G-032 launcher help + declared-parse wiring +# (see the launcher help section; a name without an +# info entry keeps full passthrough semantics) # launch_defaults dict: noargs / unknownfirst (see dispatch section) # ------------------------------------------------------------------------------ @@ -42,7 +51,7 @@ namespace eval ::punkboot { #boot-core identity (G-031; stamped into kits + reported by the G-025 #buildinfo surfaces) - variable core_version 0.2.1 + variable core_version 0.3.0 } if {![info exists ::punkboot::launch_args]} { namespace eval ::punkboot [list variable launch_args $::argv] @@ -53,6 +62,9 @@ if {![info exists ::punkboot::main_script]} { if {![info exists ::punkboot::project_subcommands]} { namespace eval ::punkboot {variable project_subcommands [dict create]} } +if {![info exists ::punkboot::project_subcommand_info]} { + namespace eval ::punkboot {variable project_subcommand_info [dict create]} +} if {![info exists ::punkboot::launch_defaults]} { namespace eval ::punkboot {variable launch_defaults [dict create]} } @@ -1509,20 +1521,299 @@ if {![info exists ::punkboot::launch_defaults]} { } namespace eval ::punkboot [list variable boot_state $boot_state_dict] + #--------------------------------------------------------------- + #Launcher help + declared-parse machinery (G-032) + #The launch surface documents itself through punk::args (definition ids + #from the punk::args::moduledoc::punkexe family): + # help ?subcommand? tabled usage; the top-level table lists + # built-ins AND project-registered + # subcommands with summaries + # -help same as bare 'help' - only on kits whose + # unknownfirst default is 'script' (a + # tool-style kit routes flags to its own + # processor; its surface stays reachable + # via the 'help' word) + # -help the subcommand's tabled usage when -help + # is its FIRST argument (tclsh: only when + # -help is the SOLE argument - stock + # parity keeps every other dash form in + # ::argv; project subcommands: only when + # the registration declares an argsid) + #Everything is guarded and lazy (the G-030 degradation doctrine): nothing + #here loads punk::args on a normal boot/dispatch path. When punk::args or + #the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same + #env hook as make.tcl), help degrades to a plain subcommand list, parse + #gates degrade to the historic switch semantics, and boot never fails. + #The tabled rendering additionally depends on the punk::ansi/textblock + #stack - rendering degrades (minimal errorstyle) independently of parsing. + #The per-kit top-level usage id (script)::punkexe.launcher is cloned from + #the static (script)::punkexe definition at first render, with the + #subcommand choices/choicelabels replaced by this kit's LIVE surface; + #(script)::punkexe.launcherhelp is the same clone of + #(script)::punkexe::help for subject validation. + #--------------------------------------------------------------- + namespace eval ::punkboot { + variable help_flags [list -help --help -h /?] + variable launch_builtin_subcommands [list] + variable launch_subcommands [list] + variable launcher_punkargs_state "" ;#"" unprobed | ok | plain + variable launcher_builtin_summaries [dict create \ + tclsh "Run as a (near) stock tclsh - no punk modules loaded." \ + script "Run a script non-interactively in the punk script environment; honest exit codes." \ + shell "Interactive punk shell (repl)." \ + punk "punk shell launch; piped-stdin friendly." \ + shellspy "Experimental shellspy command-line processor." \ + buildinfo "Print the kit's build/identity stamp report and exit (G-025)." \ + help "Show usage for the launch surface or one of its subcommands (G-032)." \ + ] + proc launcher_punkargs_ready {} { + #lazy guarded capability probe, one verdict per process. ok requires + #punk::args AND the moduledoc launch definitions to load and resolve + #with the snapshot actually available in this kit/context. + variable launcher_punkargs_state + if {$launcher_punkargs_state ne ""} { + return [expr {$launcher_punkargs_state eq "ok"}] + } + set launcher_punkargs_state plain + if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBOOT_PLAIN)]} { + return 0 + } + if {[catch { + package require punk::args + #punk::args (and the punk::ansi/textblock render stack) call + #punk::lib without requiring it (known module-web gap): on + #Tcl 8.6 the choices-grid render path needs punk::lib's + #forward-compat builtins (lpop etc). Guarded - require order + #matters (punk::lib itself hard-requires punk::args, so this + #call-time direction breaks no cycle); a kit without punk::lib + #still probes ok and rendering degrades where it must. + catch {package require punk::lib} + package require punk::args::moduledoc::punkexe + if {[punk::args::get_spec (script)::punkexe] eq ""} { + error "definition (script)::punkexe did not resolve" + } + punk::args::get_spec (script)::punkexe::buildinfo + punk::args::get_spec (script)::punkexe::help + }]} { + return 0 + } + set launcher_punkargs_state ok + return 1 + } + proc launcher_errstyle {} { + #tabled (standard) parse errors when the rendering stack is + #loadable; minimal otherwise - rendering degrades independently of + #parsing (the G-030 split) + catch {package require punk::ansi} + catch {package require textblock} + if {[package provide punk::ansi] ne "" && [package provide textblock] ne ""} { + return standard + } + return minimal + } + proc launcher_subcommand_summary {name} { + #plain-text one-line summary for a known subcommand (built-in table + #or the project registration's declared summary) + variable launcher_builtin_summaries + variable project_subcommand_info + if {[dict exists $launcher_builtin_summaries $name]} { + return [dict get $launcher_builtin_summaries $name] + } + if {[dict exists $project_subcommand_info $name summary]} { + return [dict get $project_subcommand_info $name summary] + } + return "Project-declared subcommand (no summary registered)." + } + proc launcher_live_choices {} { + #this kit's subcommand set in display order: the static definition's + #choice order first (the documented built-in family), then 'help', + #then project registrations in declaration order + variable launch_subcommands + set ordered [list] + catch { + set spec [punk::args::get_spec (script)::punkexe] + foreach c [dict get $spec FORMS _default ARG_INFO subcommand -choices] { + if {$c in $launch_subcommands && $c ni $ordered} { + lappend ordered $c + } + } + } + foreach c $launch_subcommands { + if {$c ni $ordered} { + lappend ordered $c + } + } + return $ordered + } + proc launcher_overview_id {} { + #define (script)::punkexe.launcher: the per-kit top-level usage - + #the moduledoc (script)::punkexe definition with the subcommand + #choices/choicelabels replaced by this kit's live surface. + #Returns the id, or "" when the clone cannot be built. + variable launch_builtin_subcommands + set id (script)::punkexe.launcher + set existing "" + catch {set existing [punk::args::raw_def $id]} + if {$existing ne ""} { + return $id + } + if {[catch { + set spec [punk::args::get_spec (script)::punkexe] + set labels [dict get $spec FORMS _default ARG_INFO subcommand -choicelabels] + set choices [launcher_live_choices] + foreach c $choices { + if {$c eq "help" || $c ni $launch_builtin_subcommands || ![dict exists $labels $c]} { + dict set labels $c " [launcher_subcommand_summary $c]" + } + } + set ov [dict create \ + @id [list -id $id] \ + subcommand [list -choices $choices -choicelabels $labels] \ + ] + punk::args::define [punk::args::resolved_def -override $ov (script)::punkexe] + }]} { + return "" + } + return $id + } + proc launcher_helpdef_id {} { + #define (script)::punkexe.launcherhelp: the moduledoc + #(script)::punkexe::help definition with the subject choices + #replaced by the live subcommand set, so 'help ' validates + #(and prefix-resolves) against what this kit actually offers. + set id (script)::punkexe.launcherhelp + set existing "" + catch {set existing [punk::args::raw_def $id]} + if {$existing ne ""} { + return $id + } + if {[catch { + set ov [dict create \ + @id [list -id $id] \ + subject [list -choices [launcher_live_choices]] \ + ] + punk::args::define [punk::args::resolved_def -override $ov (script)::punkexe::help] + }]} { + return "" + } + return $id + } + proc launcher_plain_help {} { + #degraded help: plain subcommand list built without punk::args + set exebase [file rootname [file tail [info nameofexecutable]]] + set lines [list] + lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?" + lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal)," + lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)" + lappend lines " subcommands:" + foreach sub [launcher_plain_choices] { + lappend lines [format " %-12s %s" $sub [launcher_subcommand_summary $sub]] + } + lappend lines " ('$exebase help ?subcommand?' shows detail; plain list because the punk::args" + lappend lines " tabled help stack is unavailable in this context, or PUNKBOOT_PLAIN is set)" + return [join $lines \n] + } + proc launcher_plain_choices {} { + #display order without punk::args: documented built-in order, then + #project registrations in declaration order + variable launch_subcommands + set ordered [list] + foreach c [list tclsh script shell punk shellspy buildinfo help] { + if {$c in $launch_subcommands} { + lappend ordered $c + } + } + foreach c $launch_subcommands { + if {$c ni $ordered} { + lappend ordered $c + } + } + return $ordered + } + proc launcher_show_help {{subject ""}} { + #render help to stdout: the top-level launch surface (empty + #subject) or one subcommand's usage. Tabled via punk::args when + #available; degrades to the plain list/summary. Never errors. + variable launch_builtin_subcommands + variable project_subcommand_info + if {[launcher_punkargs_ready]} { + set id "" + if {$subject eq ""} { + set id [launcher_overview_id] + } elseif {$subject eq "help"} { + set id [launcher_helpdef_id] + } elseif {$subject in $launch_builtin_subcommands} { + set id (script)::punkexe::$subject + } else { + #project-registered: guarded require of the declared + #package (if any), then the declared definition id (if any) + if {[dict exists $project_subcommand_info $subject package]} { + catch {package require [dict get $project_subcommand_info $subject package]} + } + if {[dict exists $project_subcommand_info $subject argsid]} { + set id [dict get $project_subcommand_info $subject argsid] + } else { + #help by registration alone: the summary line + puts stdout "$subject - [launcher_subcommand_summary $subject]" + puts stdout "(no punk::args definition registered for this subcommand - if it is an application entry point it may provide its own -help handling)" + return + } + } + if {$id ne "" && ![catch {punk::args::usage $id} out]} { + puts stdout $out + return + } + #fall through to the plain forms on any render failure + } + if {$subject eq ""} { + puts stdout [launcher_plain_help] + } else { + puts stdout "$subject - [launcher_subcommand_summary $subject]" + puts stdout "(plain help: the punk::args tabled help stack is unavailable in this context, or PUNKBOOT_PLAIN is set)" + } + return + } + proc launcher_unknown_first_error {word} { + #G-032 reclassification rule: an unknown first argument that names + #no existing file (and is no lib:* scriptlib reference) is an + #obvious mistake - refuse it with usage on stderr instead of + #silently attempting it as a script. Returns 1 when the refusal + #was emitted (caller exits 1); 0 when punk::args is unavailable + #(caller falls back to the historic script reclassification). + variable launch_subcommands + if {![launcher_punkargs_ready]} { + return 0 + } + set exebase [file rootname [file tail [info nameofexecutable]]] + puts stderr "$exebase: unknown subcommand '$word' (no such subcommand, and no such script file exists)" + set id [launcher_overview_id] + if {$id ne "" && ![catch {punk::args::usage $id} out]} { + puts stderr $out + } else { + puts stderr "known subcommands: [join [launcher_plain_choices] {, }]" + } + puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation" + return 1 + } + } + #--------------------------------------------------------------- #Subcommand selection (G-031 registration model) - #Built-ins (tclsh/shellspy/punk/shell/script) ship with this core; a thin - #main may extend the set via ::punkboot::project_subcommands (dict: - #name -> handler script, evaluated in the dispatch below with ::argv/::argc - #holding the subcommand's arguments). Built-in names cannot be shadowed - - #a colliding declaration is reported and ignored. + #Built-ins (tclsh/shellspy/punk/shell/script/buildinfo/help) ship with + #this core; a thin main may extend the set via + #::punkboot::project_subcommands (dict: name -> handler script, evaluated + #in the dispatch below with ::argv/::argc holding the subcommand's + #arguments). Built-in names cannot be shadowed - a colliding declaration + #is reported and ignored. ::punkboot::project_subcommand_info optionally + #carries per-name help/parse metadata (summary/argsid/package/parse - see + #the launcher help section above, G-032). #::punkboot::launch_defaults keys (both optional): # noargs subcommand assumed for a bare launch (default: shell) # unknownfirst 'script' treats a non-subcommand first argument as a # script invocation (default), or the name of a known # subcommand to receive the whole arglist as its arguments #--------------------------------------------------------------- - set builtin_subcommands [list tclsh shellspy punk shell script buildinfo] + set builtin_subcommands [list tclsh shellspy punk shell script buildinfo help] set known_subcommands $builtin_subcommands foreach k [dict keys $::punkboot::project_subcommands] { if {$k in $builtin_subcommands} { @@ -1531,6 +1822,9 @@ if {![info exists ::punkboot::launch_defaults]} { lappend known_subcommands $k } } + #publish for the launcher help machinery (and post-boot probes) + set ::punkboot::launch_builtin_subcommands $builtin_subcommands + set ::punkboot::launch_subcommands $known_subcommands if {[dict exists $::punkboot::launch_defaults noargs]} { set default_noargs [dict get $::punkboot::launch_defaults noargs] } else { @@ -1544,10 +1838,56 @@ if {![info exists ::punkboot::launch_defaults]} { set subcommand [lindex $arglist 0] if {$subcommand in $known_subcommands} { set subcommand_arglist [lrange $arglist 1 end] + #G-032: a help flag as the subcommand's FIRST argument renders that + #subcommand's usage. tclsh: only when it is the SOLE argument (stock + #parity keeps every other leading-dash form - including '-help' with + #further arguments - in ::argv); project subcommands: only when the + #registration declares an argsid (a handler-only registration keeps + #full passthrough - the application may do its own -help handling). + if {[lindex $subcommand_arglist 0] in $::punkboot::help_flags} { + set do_subhelp 0 + if {$subcommand eq "tclsh"} { + if {[llength $subcommand_arglist] == 1} { + set do_subhelp 1 + } + } elseif {$subcommand in $builtin_subcommands} { + set do_subhelp 1 + } elseif {[dict exists $::punkboot::project_subcommand_info $subcommand argsid]} { + set do_subhelp 1 + } + if {$do_subhelp} { + ::punkboot::launcher_show_help $subcommand + exit 0 + } + } } else { set subcommand_arglist $arglist if {[llength $subcommand_arglist]} { + #G-032: a help flag in first position is a launch-surface help + #request on kits whose unknown-first default is 'script'. + #Tool-style kits (unknownfirst names a project subcommand) keep + #routing flags to their processor - their surface stays + #discoverable via the 'help' subcommand word. + if {$subcommand in $::punkboot::help_flags && $default_unknownfirst eq "script"} { + ::punkboot::launcher_show_help + exit 0 + } set subcommand $default_unknownfirst + if {$subcommand eq "script"} { + #G-032 unknown-first-arg reclassification rule (recorded in + #goals/G-032-launcher-punkargs.md): reclassify to 'script' + #only when the argument plausibly names a script - an + #existing file path, or a lib:* scriptlib reference. Anything + #else is refused with usage on stderr (exit 1); when + #punk::args is unavailable the refusal degrades to the + #historic always-reclassify behaviour. + set unknown_word [lindex $subcommand_arglist 0] + if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} { + if {[::punkboot::launcher_unknown_first_error $unknown_word]} { + exit 1 + } + } + } } else { set subcommand $default_noargs } @@ -1726,8 +2066,20 @@ if {![info exists ::punkboot::launch_defaults]} { #implementation is punk::buildinfo - this arm is the thin #exe-subcommand wrapper. Stdout carries only the report; honest #exit code; no repl fallthrough (G-015-compatible). + #G-032: the argument contract ((script)::punkexe::buildinfo, + #@values -min 0 -max 0) is enforced through punk::args when + #available: any argument earns the historic one-line refusal PLUS + #the tabled usage error on stderr, exit 2 (buildinfo.test pins the + #one-liner and the exit code). The empty-argument fast path never + #touches punk::args; a '-help' first argument was already + #intercepted at selection. if {[llength $subcommand_arglist]} { puts stderr "buildinfo: takes no arguments" + if {[::punkboot::launcher_punkargs_ready]} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid (script)::punkexe::buildinfo} parse_msg]} { + puts stderr $parse_msg + } + } exit 2 } if {[catch {package require punk::buildinfo} errM]} { @@ -1736,6 +2088,41 @@ if {![info exists ::punkboot::launch_defaults]} { } exit [punk::buildinfo::main] } + help { + #G-032: launcher help surface. Bare 'help' renders the top-level + #tabled usage (built-ins + project-registered subcommands with + #summaries); 'help ' renders that subcommand's usage. + #Subject validation parses through the launcherhelp clone when + #punk::args is available (tabled choice errors on stderr exit 1, + #unambiguous prefixes resolve); degraded mode accepts exact names + #only and renders the plain forms. Help goes to stdout, exit 0 + #(G-015 output-cleanliness/exit-code doctrine). + if {![llength $subcommand_arglist]} { + ::punkboot::launcher_show_help + exit 0 + } + if {[::punkboot::launcher_punkargs_ready]} { + set helpdef_id [::punkboot::launcher_helpdef_id] + if {$helpdef_id ne ""} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} { + puts stderr $argd + exit 1 + } + set help_subject "" + catch {set help_subject [dict get $argd values subject]} + ::punkboot::launcher_show_help $help_subject + exit 0 + } + } + #degraded: exact-name subject only + set help_subject [lindex $subcommand_arglist 0] + if {[llength $subcommand_arglist] > 1 || $help_subject ni $::punkboot::launch_subcommands} { + puts stderr "help: expected 'help ?subcommand?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]" + exit 1 + } + ::punkboot::launcher_show_help $help_subject + exit 0 + } default { #a project-declared subcommand (thin-main customization point): the #handler script runs in this frame, ::argv/::argc already hold the @@ -1743,6 +2130,30 @@ if {![info exists ::punkboot::launch_defaults]} { #misdeclared launch_defaults value - the selection above maps every #launch form to a known subcommand. if {[dict exists $::punkboot::project_subcommands $subcommand]} { + #G-032 declared-parse gate: a registration may opt its + #arguments into punk::args validation via + #project_subcommand_info keys parse=1 + argsid (optional + #package requirement). A parse failure is a tabled usage + #error on stderr, exit 1, and the handler is not invoked. + #Degrades to the historic no-validation passthrough when + #punk::args (or the declared id) is unavailable. + if {[dict exists $::punkboot::project_subcommand_info $subcommand parse] + && [string is true -strict [dict get $::punkboot::project_subcommand_info $subcommand parse]] + && [dict exists $::punkboot::project_subcommand_info $subcommand argsid] + && [::punkboot::launcher_punkargs_ready]} { + if {[dict exists $::punkboot::project_subcommand_info $subcommand package]} { + catch {package require [dict get $::punkboot::project_subcommand_info $subcommand package]} + } + set project_argsid [dict get $::punkboot::project_subcommand_info $subcommand argsid] + set project_argsid_ok 0 + catch {set project_argsid_ok [expr {[punk::args::raw_def $project_argsid] ne ""}]} + if {$project_argsid_ok} { + if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $project_argsid} parse_msg]} { + puts stderr $parse_msg + exit 1 + } + } + } eval [dict get $::punkboot::project_subcommands $subcommand] } else { puts stderr "main.tcl: no handler for subcommand '$subcommand' (check launch_defaults in the kit's main.tcl) - known subcommands: $known_subcommands"