Browse Source

vfs: sync punk::args 0.11.2, punk::ns 0.5.0, punk::lib 0.4.3, punk 0.2.6, punk::repl 0.5.1, tclcore moduledoc 0.3.3 into _vfscommon.vfs

make.tcl modules + vfscommonupdate + project: all four kits rebuilt
(punkbi, punksys, punk91, punk902z). Kit-mode verification: punk902z
loads args=0.11.2 ns=0.5.0 tclcore=0.3.3 lib=0.4.3 punk=0.2.6, 'i join'
emits no bad-@dynamic warning; punksys (8.6) loads args=0.11.2 and the
after cancel-id discrimination resolves 'cancel someid' to the
cancelscript form (harvested id prefix working on 8.6).

A stale tcl_oauth2_library install leftover in the project's root lib/
tree (its src/vendorlib source was removed 2026-07-12; the libs install
step does not prune targets whose source vanished) was swept into the
vfs lib by vfscommonupdate and removed again before the kit build - it
is deliberately not part of the vendored set yet (G-065..G-068
first-test candidate). The root lib/ leftover itself was removed after
this commit was first authored; its rows remain in lib/.punkcheck.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 days ago
parent
commit
7c49fc844e
  1. 57
      src/vfs/_vfscommon.vfs/modules/punk-0.2.6.tm
  2. 893
      src/vfs/_vfscommon.vfs/modules/punk/args-0.11.2.tm
  3. 56
      src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/tclcore-0.3.3.tm
  4. 75
      src/vfs/_vfscommon.vfs/modules/punk/lib-0.4.3.tm
  5. 162
      src/vfs/_vfscommon.vfs/modules/punk/ns-0.5.0.tm
  6. 15
      src/vfs/_vfscommon.vfs/modules/punk/repl-0.5.1.tm

57
src/vfs/_vfscommon.vfs/modules/punk-0.2.3.tm → src/vfs/_vfscommon.vfs/modules/punk-0.2.6.tm

@ -8494,7 +8494,12 @@ namespace eval punk {
} }
#keep help-text lines manually folded (~70 cols) - the usage tables don't yet #keep help-text lines manually folded (~70 cols) - the usage tables don't yet
#wrap to terminal width, so line lengths here directly set the table width #wrap to terminal width, so line lengths here directly set the table width
set basehelp {Help system for the punk shell. #help text is authored as indented blocks (structural leading newline) and
#the generated definition declares @normalize (G-045): block-form
#multi-line values are re-based to the file-style convention at resolve
#time - no manual undent/trim and no left-margin authoring in this builder
set basehelp {
Help system for the punk shell.
With no arguments - an overview of some key shell With no arguments - an overview of some key shell
commands is displayed. commands is displayed.
When the first argument is a recognised topic - help When the first argument is a recognised topic - help
@ -8504,12 +8509,19 @@ namespace eval punk {
command info (type and synopsis) is shown for a command info (type and synopsis) is shown for a
resolvable command, or the resolved path for an resolvable command, or the resolved path for an
external executable.} external executable.}
set topichelp "Help topic, or command words for basic command info.\nTopics accept their aliases and unique prefixes\n(some short words deliberately fall through to command lookup)." set topichelp {
Help topic, or command words for basic command info.
Topics accept their aliases and unique prefixes
(some short words deliberately fall through to command lookup).}
set specs [list] set specs [list]
lappend specs ::punk::help help "Punk shell help system." "" lappend specs ::punk::help help "Punk shell help system." ""
lappend specs ::punk::help_chunks punk::help_chunks "Punk shell help system - content as {channel text} chunks." "\n\nhelp_chunks returns the help content as a list of\n{channel text} chunks rather than emitting it." lappend specs ::punk::help_chunks punk::help_chunks "Punk shell help system - content as {channel text} chunks." {
help_chunks returns the help content as a list of
{channel text} chunks rather than emitting it.}
foreach {id name summary extra} $specs { foreach {id name summary extra} $specs {
set def "" set def ""
append def "@normalize" \n
append def "@id -id $id" \n append def "@id -id $id" \n
append def "@cmd -name $name -summary \"$summary\" -help \"$basehelp$extra\"" \n append def "@cmd -name $name -summary \"$summary\" -help \"$basehelp$extra\"" \n
append def "@leaders -min 0 -max -1" \n append def "@leaders -min 0 -max -1" \n
@ -8604,7 +8616,11 @@ namespace eval punk {
warnings for known Tcl bugs affecting this interpreter warnings for known Tcl bugs affecting this interpreter
and known bugs in bundled library packages and known bugs in bundled library packages
(as detected by the punk::lib::check::has_tclbug_* (as detected by the punk::lib::check::has_tclbug_*
and has_libbug_* checks)." and has_libbug_* checks).
A warning whose buginfo reports a shipped punkshell
mitigation keeps its severity level but is annotated
'(mitigated)' and rendered subdued (grey), with the
mitigation described."
@values -min 0 -max 0 @values -min 0 -max 0
} }
proc tcl {context args} { proc tcl {context args} {
@ -8627,17 +8643,36 @@ namespace eval punk {
if {[dict exists $buginfo level]} { if {[dict exists $buginfo level]} {
set level [dict get $buginfo level] set level [dict get $buginfo level]
} }
switch -- $level { #mitigated is an axis orthogonal to level: the defect keeps its severity
minor {set highlight [punk::ansi::a+ cyan]} #classification but a shipped punkshell mitigation covers it, so the
medium {set highlight [punk::ansi::a+ yellow]} #warning renders subdued (grey) with a '(mitigated)' annotation and any
major {set highlight [punk::ansi::a+ red bold]} #mitigation text from the buginfo dict.
default {set highlight ""} set mitigated 0
if {[dict exists $buginfo mitigated]} {
set mitigated [dict get $buginfo mitigated]
}
if {$mitigated} {
set highlight [punk::ansi::a+ term-grey]
} else {
switch -- $level {
minor {set highlight [punk::ansi::a+ cyan]}
medium {set highlight [punk::ansi::a+ yellow]}
major {set highlight [punk::ansi::a+ red bold]}
default {set highlight ""}
}
}
set levelshown $level
if {$mitigated} {
append levelshown " (mitigated)"
} }
set indent " " set indent " "
append warningblock \n $highlight "warning level: $level $bp triggered." append warningblock \n $highlight "warning level: $levelshown $bp triggered."
if {[dict exists $buginfo description]} { if {[dict exists $buginfo description]} {
append warningblock \n "[punk::lib::indent [dict get $buginfo description] $indent]" append warningblock \n "[punk::lib::indent [dict get $buginfo description] $indent]"
} }
if {[dict exists $buginfo mitigation] && [dict get $buginfo mitigation] ne ""} {
append warningblock \n "[punk::lib::indent "mitigated: [dict get $buginfo mitigation]" $indent]"
}
if {[dict exists $buginfo url] && [dict get $buginfo url] ne ""} { if {[dict exists $buginfo url] && [dict get $buginfo url] ne ""} {
#full reference url (e.g. non tcl-core trackers such as tcludp) #full reference url (e.g. non tcl-core trackers such as tcludp)
append warningblock \n "${indent}see [punk::ansi::hyperlink [dict get $buginfo url]]" append warningblock \n "${indent}see [punk::ansi::hyperlink [dict get $buginfo url]]"
@ -9403,7 +9438,7 @@ punkcheck::cli set_alias punkcheck
package provide punk [namespace eval punk { package provide punk [namespace eval punk {
#FUNCTL #FUNCTL
variable version variable version
set version 0.2.3 set version 0.2.6
}] }]

893
src/vfs/_vfscommon.vfs/modules/punk/args-0.6.0.tm → src/vfs/_vfscommon.vfs/modules/punk/args-0.11.2.tm

File diff suppressed because it is too large Load Diff

56
src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/tclcore-0.2.0.tm → src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/tclcore-0.3.3.tm

@ -8,7 +8,7 @@
# (C) 2025 # (C) 2025
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::args::moduledoc::tclcore 0.2.0 # Application punk::args::moduledoc::tclcore 0.3.3
# Meta platform tcl # Meta platform tcl
# Meta license MIT # Meta license MIT
# @@ Meta End # @@ Meta End
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin punkshell_module_punk::args::moduledoc::tclcore 0 0.2.0] #[manpage_begin punkshell_module_punk::args::moduledoc::tclcore 0 0.3.3]
#[copyright "2025"] #[copyright "2025"]
#[titledesc {punk::args definitions for tcl core commands}] [comment {-- Name section and table of contents description --}] #[titledesc {punk::args definitions for tcl core commands}] [comment {-- Name section and table of contents description --}]
#[moddesc {tcl core argument definitions}] [comment {-- Description at end of page heading --}] #[moddesc {tcl core argument definitions}] [comment {-- Description at end of page heading --}]
@ -257,7 +257,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
-summary\ -summary\
"first start-of-word index after supplied index ${$I}start${$NI}"\ "first start-of-word index after supplied index ${$I}start${$NI}"\
-help\ -help\
"Returns the index of the first start-of-word location that occurs after a starting index start {Returns the index of the first start-of-word location that occurs after a starting index start
in the string str. A start-of-word location is defined to be the first word character following a in the string str. A start-of-word location is defined to be the first word character following a
non-word character. Returns -1 if there are no more start-of-word locations after the starting point. non-word character. Returns -1 if there are no more start-of-word locations after the starting point.
@ -268,7 +268,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
set idx [tcl_startOfNextWord $theString $idx]} { set idx [tcl_startOfNextWord $theString $idx]} {
puts "Word start index: $idx" puts "Word start index: $idx"
} }
" }
@values -min 2 -max 2 @values -min 2 -max 2
str -type string str -type string
start -type indexexpression start -type indexexpression
@ -4527,6 +4527,31 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
############################################################################################################################################################ ############################################################################################################################################################
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#the after-id shape is harvested from the RUNNING interpreter (G-054 technique;
#user-directed 2026-07-13, probe record in goals/G-055) rather than hard-coded:
#a scheduled command's id is "after#N" in all known releases (tclTimer.c
#"after#%d"), and the cancelid/info forms use it as their id discriminator so
#accept/reject parity follows the interpreter these docs load into.
#The probe is safe: create + immediately cancel - no event loop entry, no output,
#no lasting state.
#The harvested prefix is consumed via tstr ${$after_id_prefix} placeholders in the
#::after definition below. The variable MUST live in the argdoc namespace: that is
#the DEFSPACE registered PUNKARGS definitions resolve their placeholders in
#whenever an argdoc child exists - even when the PUNKARGS list itself is in the
#parent, as here - and an unresolvable param is left silently literal (see the
#'Interpolation and the defspace' section of the punk::args::define help).
namespace eval argdoc {
set after_id_prefix "after#"
if {![catch {after 999999 {}} _aip_id]} {
catch {after cancel $_aip_id}
if {[regexp {^(.+#)\d+$} $_aip_id -> _aip_prefix]} {
set after_id_prefix $_aip_prefix
}
}
unset -nocomplain _aip_id _aip_prefix
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list { lappend PUNKARGS [list {
#test of @form #test of @form
@id -id ::after @id -id ::after
@ -4570,7 +4595,15 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
@leaders -min 1 -max 1 @leaders -min 1 -max 1
cancel -choices {cancel} cancel -choices {cancel}
@values -min 1 -max 1 @values -min 1 -max 1
id #id typed by the harvested id shape: a word of any other shape belongs to the
#cancelscript form (real 'after cancel <non-id>' is a script-match no-op).
#An id-SHAPED word remains genuinely ambiguous with a script of the same text -
#real Tcl resolves that by id liveness at runtime (tries the id first, falls
#back to script match), which no static type expresses (goals/G-055 record).
id -type stringstartswith(${$after_id_prefix}) -typesynopsis id -help\
"Identifier of the delayed command to cancel.
It must have been the return value from a previous
after command (${$after_id_prefix}N shaped)."
#@form -form {cancelscript} -synopsis "after cancel script ?script...?" #@form -form {cancelscript} -synopsis "after cancel script ?script...?"
@ -4593,7 +4626,13 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
@leaders -min 1 -max 1 @leaders -min 1 -max 1
info -choices {info} -choiceprefixreservelist {idle} info -choices {info} -choiceprefixreservelist {idle}
@values -min 0 -max 1 @values -min 0 -max 1
id -optional 1 #real 'after info <non-id-shaped-word>' errors at runtime ("event ... doesn't
#exist") - the model's shape rejection keeps error-vs-ok parity
id -optional 1 -type stringstartswith(${$after_id_prefix}) -typesynopsis id -help\
"Identifier of an existing event handler - the
return value from some previous call to after
(${$after_id_prefix}N shaped). It must not have triggered
yet or been canceled."
} "@doc -name Manpage: -url [manpage_tcl after]"\ } "@doc -name Manpage: -url [manpage_tcl after]"\
{ {
@ -4848,7 +4887,6 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
return [punk::args::ensemble_subcommands_definition -groupdict $groups -columns 2 array] return [punk::args::ensemble_subcommands_definition -groupdict $groups -columns 2 array]
} }
lappend PUNKARGS [list { lappend PUNKARGS [list {
@dynamic
@id -id ::array @id -id ::array
@cmd -name "Built-in: array"\ @cmd -name "Built-in: array"\
-summary\ -summary\
@ -6463,7 +6501,6 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list { lappend PUNKARGS [list {
@dynamic
@id -id ::join @id -id ::join
@cmd -name "Built-in: join"\ @cmd -name "Built-in: join"\
-summary\ -summary\
@ -9299,7 +9336,6 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
}]} }]}
} }
punk::args::define { punk::args::define {
@dynamic
@id -id ::split @id -id ::split
@cmd -name "Built-in: split"\ @cmd -name "Built-in: split"\
-summary\ -summary\
@ -12543,7 +12579,7 @@ namespace eval ::punk::args::register {
package provide punk::args::moduledoc::tclcore [tcl::namespace::eval punk::args::moduledoc::tclcore { package provide punk::args::moduledoc::tclcore [tcl::namespace::eval punk::args::moduledoc::tclcore {
variable pkg punk::args::moduledoc::tclcore variable pkg punk::args::moduledoc::tclcore
variable version variable version
set version 0.2.0 set version 0.3.3
}] }]
return return

75
src/vfs/_vfscommon.vfs/modules/punk/lib-0.4.0.tm → src/vfs/_vfscommon.vfs/modules/punk/lib-0.4.3.tm

@ -8,7 +8,7 @@
# (C) 2024 # (C) 2024
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::lib 0.4.0 # Application punk::lib 0.4.3
# Meta platform tcl # Meta platform tcl
# Meta license BSD # Meta license BSD
# @@ Meta End # @@ Meta End
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin punkshell_module_punk::lib 0 0.4.0] #[manpage_begin punkshell_module_punk::lib 0 0.4.3]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {punk general utility functions}] [comment {-- Name section and table of contents description --}] #[titledesc {punk general utility functions}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk library}] [comment {-- Description at end of page heading --}] #[moddesc {punk library}] [comment {-- Description at end of page heading --}]
@ -250,6 +250,63 @@ tcl::namespace::eval punk::lib::check {
return [dict create bug $bug bugref e38dc74e2 description $description level medium] return [dict create bug $bug bugref e38dc74e2 description $description level medium]
} }
#G-076: version gate for the tcl9 dead-console defect (upstream ticket f10d91c2d3, root-caused
#in G-039). Shared by the 'help tcl' warning and the repl dead-console watchdog arming (both
#consult has_tclbug_console_deadspin). Empty = no released Tcl is known to contain the upstream
#fix, so every Tcl 9 windows runtime classifies as affected. Set this only after the G-039 kill
#procedure, re-run on the fixed released runtime with the watchdog disabled, shows a clean
#script-visible eof exit (see goals/G-076-tcl9-deadconsole-fix-adoption.md).
variable tclbug_console_deadspin_fixed_in ""
#pure classifier, separated for testability - facts in, verdict out
proc tclbug_console_deadspin_applies {platform tclversion fixed_in} {
if {$platform ne "windows"} {
return 0
}
if {![package vsatisfies $tclversion 9]} {
#tcl 8.6 has a different console driver - out of scope per G-039 (user decision 2026-07-12)
return 0
}
if {$fixed_in eq ""} {
#no released fix known - all Tcl 9 windows runtimes affected
return 1
}
return [expr {[package vcompare $tclversion $fixed_in] < 0}]
}
proc has_tclbug_console_deadspin {} {
#Tcl 9 windows console driver defect pair (win/tclWinConsole.c): when the hosting
#console dies (killed conhost/terminal), (a) ConsoleEventProc drops the error/EOF
#notification so a stdin readable fileevent never fires - the script is blind to the
#dead console; (b) ConsoleReaderThread busy-loops on the persistent error, spinning
#~2 cores until the channel is closed. Root-caused 2026-07-12 - see archived goal
#G-039; upstream ticket f10d91c2d3 filed 2026-07-12. punk::repl >= 0.5.0 mitigates
#with a console liveness watchdog (repl::console_watchdog) gated on this same check.
#Version-based detection only - a behavioural probe would require killing a console.
#The buginfo dict carries the mitigated/mitigation axis: level stays major (the core
#defect's severity), mitigated reports whether punk::repl >= 0.5.0 (console liveness
#watchdog) is available to this runtime - 'help tcl' renders mitigated warnings subdued.
variable tclbug_console_deadspin_fixed_in
set bug [tclbug_console_deadspin_applies $::tcl_platform(platform) [info patchlevel] $tclbug_console_deadspin_fixed_in]
set description "Tcl 9 windows console driver: a dead console (killed conhost/terminal) is never\ndelivered to the script as a fileevent, and the core's console reader thread busy-loops\non the persistent error - an orphaned tclsh spins CPU indefinitely. Plain tclsh scripts\nreading a console stdin have no script-level escape (see goal G-076)."
set replversion [package provide punk::repl]
if {$replversion eq ""} {
#not loaded - determine what version would be provided, without loading it:
#an unsatisfiable require triggers the package unknown scan (registering ifneeded
#scripts) then fails before any load (0.4.3 dev modules are alpha - below 999999).
catch {package require punk::repl 999999}
set available [package versions punk::repl]
if {[llength $available]} {
set replversion [lindex [lsort -command {package vcompare} $available] end]
}
}
set mitigated [expr {$bug && $replversion ne "" && [package vsatisfies $replversion 0.5-]}]
set mitigation ""
if {$mitigated} {
set mitigation "punk::repl $replversion is available to this runtime: its console liveness watchdog\n(armed when the repl serves the process-default console) closes the dead channel and\nexits cleanly instead of spinning. Non-repl console reads remain exposed."
}
return [dict create bug $bug bugref f10d91c2d3 description $description level major mitigated $mitigated mitigation $mitigation]
}
#has_libbug_* procs report bugs in bundled/vendored library packages rather than the Tcl core. #has_libbug_* procs report bugs in bundled/vendored library packages rather than the Tcl core.
#They are surfaced through the same 'help tcl' warning report as the has_tclbug_* checks. #They are surfaced through the same 'help tcl' warning report as the has_tclbug_* checks.
@ -2455,8 +2512,11 @@ namespace eval punk::lib {
#no change in origin - so we are into the arguments of the command, or have an invalid subcommand - stop looking for subcommands #no change in origin - so we are into the arguments of the command, or have an invalid subcommand - stop looking for subcommands
#todo - detect invalid subcommand and count as unknown command? #todo - detect invalid subcommand and count as unknown command?
break break
} elseif {[dict get $test_cinfo cmdtype] in {"proc" "native" "notfound"}} { } elseif {[dict get $test_cinfo cmdtype] in {"proc" "native" "notfound" "doconly"}} {
#we have a subcommand that won't be introspectable at a deeper level. #we have a subcommand that won't be introspectable at a deeper level.
#(doconly: G-051 - cmdinfo's truthful cmdtype for documentation-only
#levels such as 'string is xdigit', previously reported 'notfound';
#treated identically here to preserve prior analysis behaviour)
set cinfo $test_cinfo set cinfo $test_cinfo
set ctype [dict get $cinfo cmdtype] set ctype [dict get $cinfo cmdtype]
break break
@ -2714,7 +2774,12 @@ namespace eval punk::lib {
dict lappend resultd commands_native $dispatchwords dict lappend resultd commands_native $dispatchwords
} }
} }
"notfound" { "notfound" -
"doconly" {
#doconly: G-051 - documentation-only level (e.g 'string is xdigit'),
#previously reported as 'notfound'; bucketed identically to preserve
#prior analysis behaviour (a future refinement may count doconly as a
#valid command word rather than notfound)
if {$dispatchwords ni [dict get $resultd commands_notfound]} { if {$dispatchwords ni [dict get $resultd commands_notfound]} {
dict lappend resultd commands_notfound $dispatchwords dict lappend resultd commands_notfound $dispatchwords
} }
@ -9173,7 +9238,7 @@ namespace eval ::punk::args::register {
package provide punk::lib [tcl::namespace::eval punk::lib { package provide punk::lib [tcl::namespace::eval punk::lib {
variable pkg punk::lib variable pkg punk::lib
variable version variable version
set version 0.4.0 set version 0.4.3
}] }]
return return

162
src/vfs/_vfscommon.vfs/modules/punk/ns-0.2.0.tm → src/vfs/_vfscommon.vfs/modules/punk/ns-0.5.0.tm

@ -7,7 +7,7 @@
# (C) 2023 # (C) 2023
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::ns 0.2.0 # Application punk::ns 0.5.0
# Meta platform tcl # Meta platform tcl
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
@ -4845,6 +4845,15 @@ y" {return quirkykeyscript}
return [cmdinfo {*}$next] return [cmdinfo {*}$next]
} }
} }
if {$cmdtype eq "notfound" && $docid ne ""} {
#G-051: resolution landed on a punk::args id with no corresponding real
#command - a documentation-only level below (or beside) a real command,
#e.g the per-class id "::tcl::string::is true" documenting a level below
#the real ::tcl::string::is (the class words are arguments, not
#subcommands). Report it distinctly so consumers can tell it from a
#genuinely unknown command. The docid remains authoritative for display.
set cmdtype doconly
}
return [list origin $origin cmdtype $cmdtype args_resolved [list [lindex $commands 0] {*}$consumed_args] args_remaining $remainingargs docid $docid stack $stack] return [list origin $origin cmdtype $cmdtype args_resolved [list [lindex $commands 0] {*}$consumed_args] args_remaining $remainingargs docid $docid stack $stack]
} }
proc cmd_traverse {ns formid args} { proc cmd_traverse {ns formid args} {
@ -5023,8 +5032,49 @@ y" {return quirkykeyscript}
#(would not support shor-form prefix of subcommand - even if the proc implementation did) #(would not support shor-form prefix of subcommand - even if the proc implementation did)
set docid_exists 0 set docid_exists 0
set eparams [list] set eparams [list]
set a_spaceform ""
if {[punk::args::id_exists "$origin [lindex $args $i]"]} { if {[punk::args::id_exists "$origin [lindex $args $i]"]} {
set a [lindex $args $i] set a_spaceform [lindex $args $i]
} elseif {$docid ne "" && [punk::args::id_exists $docid]} {
#G-051 space-form docid prefix parity: no space-form id exists for the
#exact word - if the current level's definition has a choices-bearing
#first leader, resolve the word with the same shared resolver argument
#parsing uses (punk::args::choiceword_match - honouring -choiceprefix,
#-nocase, -choicealiases, -choiceprefixdenylist,
#-choiceprefixreservelist) and retry the space-form lookup with the
#canonical word - so 'i string is tr' lands on the documentation for
#what 'string is tr' actually executes. No second matching rule: a word
#parse would reject resolves nothing here either, and a canonical with
#no space-form id falls through to the normal per-level handling.
set pf_spec [punk::args::get_spec $docid]
set pf_fid [lindex [dict get $pf_spec form_names] 0]
set pf_leaders [dict get $pf_spec FORMS $pf_fid LEADER_NAMES]
if {[llength $pf_leaders]} {
set pf_arginfo [dict get $pf_spec FORMS $pf_fid ARG_INFO [lindex $pf_leaders 0]]
set pf_allchoices [punk::args::system::Dict_getdef $pf_arginfo -choices {}]
foreach {_pf_g pf_members} [punk::args::system::Dict_getdef $pf_arginfo -choicegroups {}] {
lappend pf_allchoices {*}$pf_members
}
if {[llength $pf_allchoices]} {
set pf_matchinfo [punk::args::choiceword_match [lindex $args $i]\
[punk::args::system::Dict_getdef $pf_arginfo -nocase 0]\
$pf_allchoices\
[punk::args::system::Dict_getdef $pf_arginfo -choicealiases {}]\
[punk::args::system::Dict_getdef $pf_arginfo -choiceprefix 1]\
[punk::args::system::Dict_getdef $pf_arginfo -choiceprefixdenylist {}]\
[punk::args::system::Dict_getdef $pf_arginfo -choiceprefixreservelist {}]\
]
if {[dict get $pf_matchinfo matched]} {
set pf_canonical [dict get $pf_matchinfo canonical]
if {$pf_canonical ne [lindex $args $i] && [punk::args::id_exists "$origin $pf_canonical"]} {
set a_spaceform $pf_canonical
}
}
}
}
}
if {$a_spaceform ne ""} {
set a $a_spaceform
#review - tests? #review - tests?
#puts stderr "cmd_traverse - skipping to documented subcommand '$origin $a'" #puts stderr "cmd_traverse - skipping to documented subcommand '$origin $a'"
#we can only seek beyond an undocumented subcommand level via a space delimited path, as we can make no assumption about the actual location of a subcommand relative to its parent #we can only seek beyond an undocumented subcommand level via a space delimited path, as we can make no assumption about the actual location of a subcommand relative to its parent
@ -5034,7 +5084,7 @@ y" {return quirkykeyscript}
set origin [list $origin $a] set origin [list $origin $a]
incr i incr i
set queryargs [lrange $args $i end] set queryargs [lrange $args $i end]
set resolvedargs [list $a] ;# set resolvedargs [list $a] ;#the canonical word (a resolved prefix/alias records its canonical, as parse normalization does)
set queryargs_untested $queryargs set queryargs_untested $queryargs
} elseif {[punk::args::id_exists $docid]} { } elseif {[punk::args::id_exists $docid]} {
set docid_exists 1 set docid_exists 1
@ -5374,6 +5424,10 @@ y" {return quirkykeyscript}
on separate lines. on separate lines.
If -form formname|<int> is given, supply only If -form formname|<int> is given, supply only
the synopsis for that form. the synopsis for that form.
For a multiform command, trailing argument words
after the command path underline the form(s) they
match - the best candidate when no form fully
matches (G-041 multi-form candidacy).
" "
@opts @opts
-form -type number|name -default * -help\ -form -type number|name -default * -help\
@ -5422,10 +5476,38 @@ y" {return quirkykeyscript}
return return
} }
#G-041: for a multiform definition with trailing argument words and no explicit
#-form restriction, determine the form(s) the words match via the advisory parse
#(punk::args::parse_status multi-form candidacy) - the matching/best-candidate
#form's synopsis line is underlined below (e.g 's after cancel someid' marks the
#cancel form). Skipped when alias currying makes the remaining words unreliable.
set markforms [list]
set docid_forms [list]
if {$form eq "*" && !$excess && [llength $unresolved_args] && $doc_id ne ""} {
catch {set docid_forms [punk::args::forms $doc_id]}
if {[llength $docid_forms] > 1} {
if {![catch {punk::args::parse_status $unresolved_args withid $doc_id} pstat]} {
dict for {fname finfo} [dict get $pstat formstatus] {
if {[dict get $finfo status] eq "valid"} {
lappend markforms $fname
}
}
if {![llength $markforms]} {
#no form fully matches - mark the best candidate
set markforms [list [dict get $pstat form]]
}
}
}
}
#when we use list operations on $syn - it can get extra braces due to ANSI - use join to bring back to a string without extraneous bracing #when we use list operations on $syn - it can get extra braces due to ANSI - use join to bring back to a string without extraneous bracing
switch -- $opt_return { switch -- $opt_return {
full - summary { full - summary {
set resultstr "" set resultstr ""
#ordinal position of each non-comment line maps to the definition's form
#(declaration order - both the full and summary renders emit one synopsis
#line per form in that order)
set formidx 0
foreach synline [split $syn \n] { foreach synline [split $syn \n] {
if {[string range $synline 0 1] in {"# " "##"}} { if {[string range $synline 0 1] in {"# " "##"}} {
append resultstr $synline \n append resultstr $synline \n
@ -5454,8 +5536,14 @@ y" {return quirkykeyscript}
append lineout " " [list $part] append lineout " " [list $part]
} }
} }
set lineout [string trim $lineout]
#G-041: underline the form(s) the supplied trailing words match
if {[llength $markforms] && [lindex $docid_forms $formidx] in $markforms} {
set lineout "[punk::ansi::a+ underline]$lineout[punk::ansi::a+ nounderline]"
}
incr formidx
#must be no leading space for tests in test::punk::args synopsis.test #must be no leading space for tests in test::punk::args synopsis.test
append resultstr [string trim $lineout] \n append resultstr $lineout \n
} }
} }
@ -5520,8 +5608,13 @@ y" {return quirkykeyscript}
} {${[punk::args::resolved_def -types opts ::punk::args::arg_error -scheme]}} { } {${[punk::args::resolved_def -types opts ::punk::args::arg_error -scheme]}} {
-form -default 0 -help\ -form -default * -help\
"Ordinal index or name of command form" "Restrict to the listed command forms - each element an
ordinal index or form name (see punk::args::parse -form).
With the default * the usage display presents the form
best matching any supplied argument words (G-041
multi-form candidacy) - e.g 'i after cancel <id>'
presents the cancel form."
-grepstr -default "" -type list -typesynopsis regex -help\ -grepstr -default "" -type list -typesynopsis regex -help\
"Case insensitive grep for pattern in the output. "Case insensitive grep for pattern in the output.
list consisting of regex, optionally followed by ANSI names for highlighting" list consisting of regex, optionally followed by ANSI names for highlighting"
@ -5605,15 +5698,24 @@ y" {return quirkykeyscript}
#-caller attributes any failure message to the queried command rather than #-caller attributes any failure message to the queried command rather than
#an internal parse call site. #an internal parse call site.
set pstatus [punk::args::parse_status $queryargs -form $opt_form -caller $querycommand withid $rootdoc] set pstatus [punk::args::parse_status $queryargs -form $opt_form -caller $querycommand withid $rootdoc]
#With NO supplied args a failure only reflects missing required #A failing advisory parse renders with the error scheme and its
#leaders/values - nothing to mark and nothing wrong with the user's (absent) #message even when NO args were supplied - see the matching site in
#input, so we show plain usage in the info scheme instead of the #the main cmdhelp body for the history (G-046 item 5 suppression
#internal-looking parse error (G-046 item 5). #reversed 2026-07-12 after the G-049 -caller attribution made the
#messages accurate).
#G-041: render the usage for the form the advisory parse selected
#(the matched or best-candidate form within the caller's -form
#selection); for a no-form-match pass every ranked candidate and for
#an ambiguous match every matching form - the first form's argument
#table renders and all passed forms are marked in the synopsis.
if {![dict get $pstatus ok] && [dict get $pstatus failureclass] in {noformmatch multipleformmatches}} {
dict set nextopts -form [dict keys [dict get $pstatus formstatus]]
} else {
dict set nextopts -form [dict get $pstatus form]
}
if {$opt_return eq "dict"} { if {$opt_return eq "dict"} {
if {$scheme_received} { if {$scheme_received} {
dict set pstatus scheme [dict get $opts -scheme] dict set pstatus scheme [dict get $opts -scheme]
} elseif {![dict get $pstatus ok] && ![llength $queryargs]} {
dict set pstatus scheme info
} }
return [dict create origin $rootorigin docid $rootdoc cmdtype $rootorigintype args_remaining $queryargs parsestatus $pstatus] return [dict create origin $rootorigin docid $rootdoc cmdtype $rootorigintype args_remaining $queryargs parsestatus $pstatus]
} }
@ -5623,11 +5725,6 @@ y" {return quirkykeyscript}
dict set nextopts -scheme info dict set nextopts -scheme info
} }
set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus] set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} elseif {![llength $queryargs]} {
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0]
} else { } else {
set result [punk::args::arg_error [dict get $pstatus message] [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus] set result [punk::args::arg_error [dict get $pstatus message] [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} }
@ -5712,15 +5809,27 @@ y" {return quirkykeyscript}
#-errorstyle minimal so no usage table is built inside a discarded error and #-errorstyle minimal so no usage table is built inside a discarded error and
#dynamically updated ensembles are reflected). #dynamically updated ensembles are reflected).
set pstatus [punk::args::parse_status $args_remaining -form $opt_form -caller $caller_display withid $origindoc] set pstatus [punk::args::parse_status $args_remaining -form $opt_form -caller $caller_display withid $origindoc]
#With NO supplied trailing args a failure only reflects missing required #A failing advisory parse renders with the error scheme and its message even
#leaders/values (e.g 'i string is') - nothing to mark and nothing wrong with the #when NO trailing args were supplied: 'i if' indicating "Bad number of
#user's (absent) input, so we show plain usage in the info scheme instead of the #trailing values for if. Got 0 values. Expected at least 2" is useful signal
#internal-looking parse error (G-046 item 5). #that the command cannot be called bare. (History: G-046 item 5 suppressed
#this path because the pre-G-049 message was internal-looking - "for
#punk::args::parse $args_remaining" - but the G-049 -caller attribution made
#the messages accurate, so the suppression was reversed 2026-07-12 by user
#direction, restoring the pre-G-046 indication with the improved messages.)
#G-041: render the usage for the form the advisory parse selected (the
#matched or best-candidate form within the caller's -form selection); for a
#no-form-match pass every ranked candidate and for an ambiguous match every
#matching form - the first form's argument table renders and all passed
#forms are marked in the synopsis.
if {![dict get $pstatus ok] && [dict get $pstatus failureclass] in {noformmatch multipleformmatches}} {
dict set nextopts -form [dict keys [dict get $pstatus formstatus]]
} else {
dict set nextopts -form [dict get $pstatus form]
}
if {$opt_return eq "dict"} { if {$opt_return eq "dict"} {
if {$scheme_received} { if {$scheme_received} {
dict set pstatus scheme [dict get $opts -scheme] dict set pstatus scheme [dict get $opts -scheme]
} elseif {![dict get $pstatus ok] && ![llength $args_remaining]} {
dict set pstatus scheme info
} }
return [dict create origin $origin docid $origindoc cmdtype $origintype args_remaining $args_remaining parsestatus $pstatus] return [dict create origin $origin docid $origindoc cmdtype $origintype args_remaining $args_remaining parsestatus $pstatus]
} }
@ -5730,11 +5839,6 @@ y" {return quirkykeyscript}
dict set nextopts -scheme info dict set nextopts -scheme info
} }
set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus] set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} elseif {![llength $args_remaining]} {
if {!$scheme_received} {
dict set nextopts -scheme info
}
set result [punk::args::arg_error "" [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0]
} else { } else {
set result [punk::args::arg_error [dict get $pstatus message] [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus] set result [punk::args::arg_error [dict get $pstatus message] [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} }
@ -7657,6 +7761,6 @@ namespace eval ::punk::args::register {
## Ready ## Ready
package provide punk::ns [tcl::namespace::eval punk::ns { package provide punk::ns [tcl::namespace::eval punk::ns {
variable version variable version
set version 0.2.0 set version 0.5.0
}] }]
return return

15
src/vfs/_vfscommon.vfs/modules/punk/repl-0.5.0.tm → src/vfs/_vfscommon.vfs/modules/punk/repl-0.5.1.tm

@ -634,12 +634,16 @@ proc repl::start {args} {
#thread busy-loops on the persistent error - an orphaned shell would spin CPU forever. #thread busy-loops on the persistent error - an orphaned shell would spin CPU forever.
#Poll liveness on the process console so the repl can finish via the normal eof path. #Poll liveness on the process console so the repl can finish via the normal eof path.
#Armed only for a tcl9 console channel (-inputmode present) on the process-default #Armed only for a tcl9 console channel (-inputmode present) on the process-default
#console; piped/foreign/8.6 inputs are unaffected. #console; piped/foreign/8.6 inputs are unaffected. The version gate (G-076) is
#punk::lib::check::has_tclbug_console_deadspin - shared with the 'help tcl' warning -
#so runtimes at or past a verified fixed Tcl release (check::tclbug_console_deadspin_fixed_in)
#don't arm the probe.
variable console_watchdog_afterids variable console_watchdog_afterids
variable console_watchdog_ms variable console_watchdog_ms
set watchdog_chan "" set watchdog_chan ""
if {"windows" eq $::tcl_platform(platform) && [console_is_default] if {"windows" eq $::tcl_platform(platform) && [console_is_default]
&& ![info exists console_watchdog_afterids($inchan)]} { && ![info exists console_watchdog_afterids($inchan)]
&& [dict get [punk::lib::check::has_tclbug_console_deadspin] bug]} {
if {![catch {chan configure $inchan} wdconf] && [dict exists $wdconf -inputmode]} { if {![catch {chan configure $inchan} wdconf] && [dict exists $wdconf -inputmode]} {
set watchdog_chan $inchan set watchdog_chan $inchan
set console_watchdog_afterids($inchan) [after $console_watchdog_ms [list [namespace current]::console_watchdog $inchan]] set console_watchdog_afterids($inchan) [after $console_watchdog_ms [list [namespace current]::console_watchdog $inchan]]
@ -983,7 +987,10 @@ namespace eval repl::argdoc {
Armed by repl::start only for a tcl9 console channel (-inputmode present Armed by repl::start only for a tcl9 console channel (-inputmode present
in the chan configure dict) serving the process-default console on in the chan configure dict) serving the process-default console on
windows. Scheduling state is kept per channel name in windows, and only while punk::lib::check::has_tclbug_console_deadspin
reports the runtime affected (G-076 shared version gate - a Tcl release
containing the verified upstream fix for ticket f10d91c2d3 arms nothing).
Scheduling state is kept per channel name in
repl::console_watchdog_afterids; a watchdog whose channel has repl::console_watchdog_afterids; a watchdog whose channel has
disappeared disarms itself silently." disappeared disarms itself silently."
@values -min 1 -max 1 @values -min 1 -max 1
@ -4518,7 +4525,7 @@ namespace eval ::punk::args::register {
package provide punk::repl [namespace eval punk::repl { package provide punk::repl [namespace eval punk::repl {
variable version variable version
set version 0.5.0 set version 0.5.1
}] }]
#repl::start $program_read_stdin_pipe #repl::start $program_read_stdin_pipe
Loading…
Cancel
Save