Browse Source
New scriptlib/developer/nslist.tcl backing the new cross-framework tcl-nslist agent skill (.agents/skills/ canonical, .claude/skills/ synced copy): the discovery counterpart of whatis.tcl - lists what a namespace/package actually exposes at runtime via punk::ns::get_ns_dicts (the n// machinery): child namespaces with loadable-package markers, commands classified by kind, punk::args-documented commands, -synopsis whole-namespace usage lines, -pathcommands namespace-path resolution. Auto-package-requires unloaded packages and follows ensemble commands to their implementation namespace. The tcl-whatis skill/script cross-reference it (whatis redirects with a hint when the queried name is a namespace, not a command). punk::ns 0.9.1: get_ns_dicts packagetails/packageprefixes initialization moved inside the per-namespace loop - with multiple matched namespaces (path globs, trailing **) every dict after the first carried the union of all earlier namespaces' entries, mis-marking non-package children in nslist/n// underline styling and in the returned data; a child with several deeper packages below it was appended to packageprefixes once per matching package (now once). Pinned in nslist.test (nslist_classification_multins_package_buckets_reset). Documentation-only PUNKARGS added for get_ns_dicts (returned dict keys documented) and nslist_dict; parsing stays manual. Root AGENTS.md: introspection note + scriptlib/developer inventory updated. Project version 0.15.1 + CHANGELOG entry. punk::ns suite 102/102 pass. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
12 changed files with 598 additions and 10 deletions
@ -0,0 +1,85 @@
|
||||
--- |
||||
name: tcl-nslist |
||||
description: Use when you need to discover what exists in a Tcl namespace or package in this repo - what commands/procs a namespace exposes, whether a helper already exists before writing one, or what an ensemble's implementation namespace contains. No specific command name needed (that's tcl-whatis). Runtime listing via punkshell using punk::ns::get_ns_dicts (the machinery behind the interactive n// browser): child namespaces with loadable-package markers, commands classified by kind (proc/alias/ensemble/native/oo/imported), which commands have punk::args docs, optional whole-namespace usage synopses. Auto-loads the package; sees runtime-generated commands grep cannot. |
||||
--- |
||||
|
||||
# tcl-nslist |
||||
|
||||
Namespace/package discovery for the punkshell repo - the "what is available?" |
||||
counterpart of tcl-whatis ("how do I call X?"). Asks the live interpreter via |
||||
punk::ns::get_ns_dicts - the same machinery behind the interactive n// / n/// |
||||
namespace browsers - so it reports what is actually loaded, including |
||||
runtime-generated commands, aliases and imports that grep cannot see, and |
||||
auto-loads the providing package when the namespace is not yet populated. |
||||
|
||||
## Command |
||||
|
||||
Run from the repo root (`punk91` lives in `bin/`; any punkshell kit works): |
||||
|
||||
punk91 src script scriptlib/developer/nslist.tcl ?-synopsis? ?-pathcommands? <nsglob>... |
||||
|
||||
- `<nsglob>` - namespace name or glob, qualified or not: `punk::ns` is listed |
||||
as `::punk::ns::*`. If the namespace does not exist, `package require` of |
||||
the name (then of successively shorter :: prefixes) is attempted first. |
||||
Last-segment glob chars filter members (`punk::ns::ns*`); glob chars in |
||||
earlier segments match multiple namespaces (`::tcl::*::d*`); a trailing |
||||
`::**` segment recurses through all descendant namespaces |
||||
(`punk::nav::**`). Quote glob arguments in PowerShell. |
||||
An ensemble command name (e.g `string`) is followed to its implementation |
||||
namespace, with a note. |
||||
- `-synopsis` - append punk::args usage lines for every documented command in |
||||
the namespace (a one-call API cheat-sheet; large for big namespaces). |
||||
- `-pathcommands` - also list the commands resolvable via each `namespace |
||||
path` entry. |
||||
|
||||
Exit codes: 0 every pattern listed something, 1 some pattern matched nothing, |
||||
2 usage error. |
||||
|
||||
## Output fields |
||||
|
||||
One block per matched namespace (blank-line separated); empty categories are |
||||
omitted: |
||||
|
||||
namespace: ::punk::ns fully-qualified namespace |
||||
children: argdoc lib ... child namespace tails |
||||
packagetails: lib ... children that are also loadable package names |
||||
packageprefixes: ... children with deeper loadable packages below them |
||||
commands: 147 total member commands (count) |
||||
procs: ... script procs |
||||
aliases: ... interp aliases (may lack a same-named command) |
||||
ensembles: ... |
||||
native: ... C-implemented commands |
||||
imported: ... imported from another namespace |
||||
other: ... kinds not otherwise classified |
||||
exported: ... commands matching the namespace export patterns |
||||
documented: ... have punk::args docs - tcl-whatis gives full detail |
||||
exportpatterns: {[a-z]*} raw namespace export patterns |
||||
namespacepath: ... extra namespaces this ns resolves commands from |
||||
note: ... autoload / ensemble-follow / empty-namespace notes |
||||
synopsis: with -synopsis: usage line(s) per documented command |
||||
|
||||
ooclasses/ooobjects/ooprivateclasses/ooprivateobjects/coroutines/interps/ |
||||
zlibstreams lines appear when such commands exist. `packagetails` is the |
||||
discovery lever: a child listed there can be `package require`d and listed |
||||
itself. |
||||
|
||||
## Caveats |
||||
|
||||
- Keep the `src` launch argument: it loads and queries the working-tree |
||||
modules under `src/modules`; without it the kit-stamped copies inside the |
||||
executable answer instead. |
||||
- Load noise ("src mode: registered ...") goes to stderr; read stdout only |
||||
(append `2>/dev/null` in bash, `2>$null` in PowerShell). |
||||
- Only what is loaded in that session is reported. The script auto-requires |
||||
the queried package, but a namespace can be populated further by other |
||||
packages (e.g plugins) - an unexpectedly sparse listing may mean a related |
||||
package is not loaded, not that the commands do not exist. |
||||
- `documented:` lists commands with registered punk::args definitions; |
||||
undocumented commands are still real - tcl-whatis reports their proc |
||||
signature and `-body` even without an argdoc. |
||||
- From Tcl code, the same data is available structurally: |
||||
`punk::ns::get_ns_dicts ::the::ns::* ?-allbelow <bool>? ?-nspathcommands <bool>?` |
||||
returns a list of dicts (keys documented - see |
||||
`punk91 src script scriptlib/developer/whatis.tcl -doc punk::ns::get_ns_dicts`), |
||||
and `punk::ns::nslist_dict` accepts caller-relative globs. In an interactive |
||||
punkshell the `n//` / `n///` aliases render the same information. |
||||
@ -0,0 +1,85 @@
|
||||
--- |
||||
name: tcl-nslist |
||||
description: Use when you need to discover what exists in a Tcl namespace or package in this repo - what commands/procs a namespace exposes, whether a helper already exists before writing one, or what an ensemble's implementation namespace contains. No specific command name needed (that's tcl-whatis). Runtime listing via punkshell using punk::ns::get_ns_dicts (the machinery behind the interactive n// browser): child namespaces with loadable-package markers, commands classified by kind (proc/alias/ensemble/native/oo/imported), which commands have punk::args docs, optional whole-namespace usage synopses. Auto-loads the package; sees runtime-generated commands grep cannot. |
||||
--- |
||||
|
||||
# tcl-nslist |
||||
|
||||
Namespace/package discovery for the punkshell repo - the "what is available?" |
||||
counterpart of tcl-whatis ("how do I call X?"). Asks the live interpreter via |
||||
punk::ns::get_ns_dicts - the same machinery behind the interactive n// / n/// |
||||
namespace browsers - so it reports what is actually loaded, including |
||||
runtime-generated commands, aliases and imports that grep cannot see, and |
||||
auto-loads the providing package when the namespace is not yet populated. |
||||
|
||||
## Command |
||||
|
||||
Run from the repo root (`punk91` lives in `bin/`; any punkshell kit works): |
||||
|
||||
punk91 src script scriptlib/developer/nslist.tcl ?-synopsis? ?-pathcommands? <nsglob>... |
||||
|
||||
- `<nsglob>` - namespace name or glob, qualified or not: `punk::ns` is listed |
||||
as `::punk::ns::*`. If the namespace does not exist, `package require` of |
||||
the name (then of successively shorter :: prefixes) is attempted first. |
||||
Last-segment glob chars filter members (`punk::ns::ns*`); glob chars in |
||||
earlier segments match multiple namespaces (`::tcl::*::d*`); a trailing |
||||
`::**` segment recurses through all descendant namespaces |
||||
(`punk::nav::**`). Quote glob arguments in PowerShell. |
||||
An ensemble command name (e.g `string`) is followed to its implementation |
||||
namespace, with a note. |
||||
- `-synopsis` - append punk::args usage lines for every documented command in |
||||
the namespace (a one-call API cheat-sheet; large for big namespaces). |
||||
- `-pathcommands` - also list the commands resolvable via each `namespace |
||||
path` entry. |
||||
|
||||
Exit codes: 0 every pattern listed something, 1 some pattern matched nothing, |
||||
2 usage error. |
||||
|
||||
## Output fields |
||||
|
||||
One block per matched namespace (blank-line separated); empty categories are |
||||
omitted: |
||||
|
||||
namespace: ::punk::ns fully-qualified namespace |
||||
children: argdoc lib ... child namespace tails |
||||
packagetails: lib ... children that are also loadable package names |
||||
packageprefixes: ... children with deeper loadable packages below them |
||||
commands: 147 total member commands (count) |
||||
procs: ... script procs |
||||
aliases: ... interp aliases (may lack a same-named command) |
||||
ensembles: ... |
||||
native: ... C-implemented commands |
||||
imported: ... imported from another namespace |
||||
other: ... kinds not otherwise classified |
||||
exported: ... commands matching the namespace export patterns |
||||
documented: ... have punk::args docs - tcl-whatis gives full detail |
||||
exportpatterns: {[a-z]*} raw namespace export patterns |
||||
namespacepath: ... extra namespaces this ns resolves commands from |
||||
note: ... autoload / ensemble-follow / empty-namespace notes |
||||
synopsis: with -synopsis: usage line(s) per documented command |
||||
|
||||
ooclasses/ooobjects/ooprivateclasses/ooprivateobjects/coroutines/interps/ |
||||
zlibstreams lines appear when such commands exist. `packagetails` is the |
||||
discovery lever: a child listed there can be `package require`d and listed |
||||
itself. |
||||
|
||||
## Caveats |
||||
|
||||
- Keep the `src` launch argument: it loads and queries the working-tree |
||||
modules under `src/modules`; without it the kit-stamped copies inside the |
||||
executable answer instead. |
||||
- Load noise ("src mode: registered ...") goes to stderr; read stdout only |
||||
(append `2>/dev/null` in bash, `2>$null` in PowerShell). |
||||
- Only what is loaded in that session is reported. The script auto-requires |
||||
the queried package, but a namespace can be populated further by other |
||||
packages (e.g plugins) - an unexpectedly sparse listing may mean a related |
||||
package is not loaded, not that the commands do not exist. |
||||
- `documented:` lists commands with registered punk::args definitions; |
||||
undocumented commands are still real - tcl-whatis reports their proc |
||||
signature and `-body` even without an argdoc. |
||||
- From Tcl code, the same data is available structurally: |
||||
`punk::ns::get_ns_dicts ::the::ns::* ?-allbelow <bool>? ?-nspathcommands <bool>?` |
||||
returns a list of dicts (keys documented - see |
||||
`punk91 src script scriptlib/developer/whatis.tcl -doc punk::ns::get_ns_dicts`), |
||||
and `punk::ns::nslist_dict` accepts caller-relative globs. In an interactive |
||||
punkshell the `n//` / `n///` aliases render the same information. |
||||
@ -1,4 +1,4 @@
|
||||
[project] |
||||
name = "punkshell" |
||||
version = "0.15.0" |
||||
version = "0.15.1" |
||||
license = "BSD-2-Clause" |
||||
|
||||
@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env tclsh |
||||
# nslist.tcl - runtime namespace/command discovery report, aimed at agent/LLM use |
||||
# |
||||
# Answers "what does this namespace/package actually expose?" - the discovery |
||||
# counterpart of whatis.tcl (which answers "how do I call this command?"). |
||||
# Reports for each namespace matched by <nsglob>: |
||||
# - child namespaces, marking which are also loadable package names |
||||
# - commands classified by kind (procs, aliases, ensembles, native, imported, |
||||
# oo classes/objects, coroutines, interps, zlibstreams) |
||||
# - which commands have punk::args documentation ('documented:' line) - those |
||||
# give full usage detail via whatis.tcl / punk::ns::cmdhelp |
||||
# - namespace export patterns and 'namespace path' entries |
||||
# Data comes from punk::ns::get_ns_dicts - the same runtime machinery behind the |
||||
# interactive n// and n/// namespace browsers - so runtime-generated commands are |
||||
# included and only what is actually loaded is reported. |
||||
# If the target namespace does not exist, 'package require' of the name (then of |
||||
# successively shorter :: prefixes) is attempted first to populate it. |
||||
# An ensemble command name (e.g 'string') is followed to its implementation |
||||
# namespace, with a note. |
||||
# Options: |
||||
# -synopsis append punk::args usage lines for each documented command |
||||
# -pathcommands also list the commands resolvable via each 'namespace path' entry |
||||
# |
||||
# Must run inside a punkshell interpreter (needs punk::ns, punk::args): |
||||
# punk91 src script scriptlib/developer/nslist.tcl ?-synopsis? ?-pathcommands? <nsglob>... |
||||
# Use the 'src' launch argument so the working-tree modules under src/modules are |
||||
# loaded and queried rather than the kit-stamped copies inside the executable. |
||||
# <nsglob> may be a plain namespace (punk::ns - listed as ::punk::ns::*) or a |
||||
# glob: last-segment glob chars match members, earlier-segment glob chars match |
||||
# multiple namespaces, and a trailing ::** segment recurses through all |
||||
# descendant namespaces. |
||||
# Exit 0 if every pattern produced a listing; 1 if any pattern matched nothing; |
||||
# 2 usage error. |
||||
|
||||
if {[catch {package require punk::ns} errmsg]} { |
||||
puts stderr "nslist: requires a punkshell interpreter (package require punk::ns failed: $errmsg)" |
||||
exit 1 |
||||
} |
||||
catch {package require punk::args} |
||||
|
||||
proc strip {text} { |
||||
if {[info commands ::ansistrip] ne ""} { |
||||
return [ansistrip $text] |
||||
} elseif {![catch {package require punk::ansi}]} { |
||||
return [punk::ansi::ansistrip $text] |
||||
} |
||||
return $text |
||||
} |
||||
|
||||
proc out {key val} { |
||||
puts [format "%-16s %s" $key: [strip $val]] |
||||
} |
||||
|
||||
# attempt to load a package matching $name, else successively shorter :: prefixes |
||||
proc try_autoload {name} { |
||||
set parts [lsearch -all -inline -not [split [string trimleft $name :] ::] {}] |
||||
for {set n [llength $parts]} {$n >= 1} {incr n -1} { |
||||
set prefix [join [lrange $parts 0 $n-1] ::] |
||||
if {![catch {package require $prefix}]} { |
||||
return $prefix |
||||
} |
||||
} |
||||
return {} |
||||
} |
||||
|
||||
# leading non-glob namespace of a fully-qualified (possibly glob) path e.g ::a::b* -> ::a |
||||
proc nonglob_base {fq} { |
||||
set parts [lsearch -all -inline -not [split [string trimleft $fq :] ::] {}] |
||||
set keep [list] |
||||
foreach seg $parts { |
||||
if {[regexp {[*?]} $seg]} { |
||||
break |
||||
} |
||||
lappend keep $seg |
||||
} |
||||
return ::[join $keep ::] |
||||
} |
||||
|
||||
proc emit_nsdict {nd opt_pathcommands opt_synopsis} { |
||||
set location [dict get $nd location] |
||||
out namespace $location |
||||
foreach {label key} { |
||||
children children |
||||
packagetails packagetails |
||||
packageprefixes packageprefixes |
||||
} { |
||||
set items [dict get $nd $key] |
||||
if {[llength $items]} { |
||||
out $label [lsort -dictionary $items] |
||||
} |
||||
} |
||||
set commands [dict get $nd commands] |
||||
out commands [llength $commands] |
||||
set classified [list] |
||||
foreach {label key} { |
||||
procs procs |
||||
aliases aliases |
||||
ensembles ensembles |
||||
native native |
||||
imported imported |
||||
ooclasses ooclasses |
||||
ooobjects ooobjects |
||||
ooprivateclasses ooprivateclasses |
||||
ooprivateobjects ooprivateobjects |
||||
coroutines coroutines |
||||
interps interps |
||||
zlibstreams zlibstreams |
||||
undetermined undetermined |
||||
} { |
||||
set items [dict get $nd $key] |
||||
if {[llength $items]} { |
||||
out $label [lsort -dictionary $items] |
||||
lappend classified {*}$items |
||||
} |
||||
} |
||||
set other [list] |
||||
foreach c $commands { |
||||
if {$c ni $classified} { |
||||
lappend other $c |
||||
} |
||||
} |
||||
if {[llength $other]} { |
||||
out other [lsort -dictionary $other] |
||||
} |
||||
set exported [dict get $nd exported] |
||||
if {[llength $exported]} { |
||||
out exported [lsort -dictionary $exported] |
||||
} |
||||
set documented [lsort -dictionary [dict get $nd usageinfo]] |
||||
if {[llength $documented]} { |
||||
out documented $documented |
||||
} |
||||
set exportpatterns [dict get $nd namespacexport] |
||||
if {[llength $exportpatterns]} { |
||||
out exportpatterns $exportpatterns |
||||
} |
||||
set pathdict [dict get $nd namespacepath] |
||||
if {[dict size $pathdict]} { |
||||
out namespacepath [dict keys $pathdict] |
||||
if {$opt_pathcommands} { |
||||
dict for {p pinfo} $pathdict { |
||||
set pcmds [list] |
||||
if {[dict exists $pinfo commands]} { |
||||
set pcmds [dict get $pinfo commands] |
||||
} |
||||
puts [format " pathcommands(%s): %s" $p [strip [lsort -dictionary $pcmds]]] |
||||
} |
||||
} |
||||
} |
||||
if {[dict get $nd itemcount] == 0} { |
||||
out note "(no members matched glob '[dict get $nd glob]' - namespace empty, or the package populating it is not loaded)" |
||||
} |
||||
if {$opt_synopsis && [llength $documented]} { |
||||
puts "synopsis:" |
||||
foreach c $documented { |
||||
set id [punk::ns::nsjoin $location $c] |
||||
if {![punk::args::id_exists $id]} { |
||||
# alias/import - resolve the documentation id the way cmdhelp does |
||||
set id "" |
||||
if {![catch {punk::ns::cmdinfo [punk::ns::nsjoin $location $c]} cinfo]} { |
||||
set docid [dict get $cinfo docid] |
||||
if {$docid ne "" && [punk::args::id_exists $docid]} { |
||||
set id $docid |
||||
} |
||||
} |
||||
} |
||||
set syn "" |
||||
if {$id ne "" && ![catch {punk::args::synopsis $id} s]} { |
||||
set syn [string trim [strip $s]] |
||||
} |
||||
if {$syn eq ""} { |
||||
puts [format " %s (no punk::args synopsis available)" $c] |
||||
} else { |
||||
foreach line [split $syn \n] { |
||||
puts " $line" |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
# ---------------------------------------------------------------- arg handling |
||||
set opt_synopsis 0 |
||||
set opt_pathcommands 0 |
||||
set globs [list] |
||||
foreach a $argv { |
||||
switch -- $a { |
||||
-synopsis {set opt_synopsis 1} |
||||
-pathcommands {set opt_pathcommands 1} |
||||
default {lappend globs $a} |
||||
} |
||||
} |
||||
if {![llength $globs]} { |
||||
puts stderr "usage: punk91 src script scriptlib/developer/nslist.tcl ?-synopsis? ?-pathcommands? <nsglob>..." |
||||
puts stderr " e.g: punk91 src script scriptlib/developer/nslist.tcl punk::ns" |
||||
exit 2 |
||||
} |
||||
|
||||
# ---------------------------------------------------------------- main |
||||
set failures 0 |
||||
set output_started 0 |
||||
foreach g $globs { |
||||
set fq [string map {:::: ::} $g] |
||||
if {![string match ::* $fq]} { |
||||
set fq ::[string trimleft $fq :] |
||||
} |
||||
set notes [list] |
||||
set base [nonglob_base $fq] |
||||
if {![namespace exists $base]} { |
||||
set loaded [try_autoload $base] |
||||
if {$loaded ne "" && [namespace exists $base]} { |
||||
lappend notes "loaded package '$loaded [package present $loaded]' to populate $base" |
||||
} |
||||
} |
||||
set query "" |
||||
if {![regexp {[*?]} $fq]} { |
||||
if {[namespace exists $fq]} { |
||||
set query ${fq}::* |
||||
if {[namespace ensemble exists $fq]} { |
||||
lappend notes "$fq is also an ensemble command - whatis.tcl shows its subcommand map" |
||||
} |
||||
} else { |
||||
# not a namespace - maybe a command (follow an ensemble to its namespace) |
||||
set resolved [uplevel #0 [list namespace which -command $fq]] |
||||
if {$resolved ne "" && [namespace ensemble exists $resolved]} { |
||||
set ensns [namespace ensemble configure $resolved -namespace] |
||||
lappend notes "'$g' is an ensemble command implemented in namespace $ensns - listing that (whatis.tcl shows the authoritative subcommand map)" |
||||
set query ${ensns}::* |
||||
} elseif {$resolved ne ""} { |
||||
puts stderr "nslist: '$g' is a command, not a namespace - use the tcl-whatis skill: punk91 src script scriptlib/developer/whatis.tcl [string trimleft $g :]" |
||||
incr failures |
||||
continue |
||||
} else { |
||||
puts stderr "nslist: no namespace (or command) '$g' found - and no package matching it could be loaded" |
||||
incr failures |
||||
continue |
||||
} |
||||
} |
||||
} else { |
||||
if {[namespace exists $base]} { |
||||
set query $fq |
||||
} else { |
||||
puts stderr "nslist: base namespace $base of glob '$g' not found - and no package matching it could be loaded" |
||||
incr failures |
||||
continue |
||||
} |
||||
} |
||||
if {[catch {punk::ns::get_ns_dicts $query -nspathcommands $opt_pathcommands} dicts]} { |
||||
puts stderr "nslist: punk::ns::get_ns_dicts failed for '$query': $dicts" |
||||
incr failures |
||||
continue |
||||
} |
||||
if {![llength $dicts]} { |
||||
puts stderr "nslist: no namespaces matched '$query'" |
||||
incr failures |
||||
continue |
||||
} |
||||
if {$output_started} { |
||||
puts "" |
||||
} |
||||
set output_started 1 |
||||
foreach n $notes { |
||||
out note $n |
||||
} |
||||
set i 0 |
||||
foreach nd $dicts { |
||||
if {$i > 0} { |
||||
puts "" |
||||
} |
||||
incr i |
||||
emit_nsdict $nd $opt_pathcommands $opt_synopsis |
||||
} |
||||
} |
||||
|
||||
exit [expr {$failures == 0 ? 0 : 1}] |
||||
Loading…
Reference in new issue