You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
417 lines
17 KiB
417 lines
17 KiB
# -*- tcl -*- |
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt |
|
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.4.tm |
|
# |
|
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. |
|
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# (C) 2025 |
|
# |
|
# @@ Meta Begin |
|
# Application punk::nav::ns 0.1.0 |
|
# Meta platform tcl |
|
# Meta license MIT |
|
# @@ Meta End |
|
|
|
|
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
## Requirements |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
|
|
package require Tcl 8.6- |
|
|
|
|
|
|
|
tcl::namespace::eval punk::nav::ns { |
|
variable PUNKARGS |
|
variable ns_current |
|
#allow presetting |
|
if {![info exists ::punk::nav::ns::ns_current]} { |
|
set ns_current :: |
|
} |
|
namespace path {::punk::ns} |
|
|
|
namespace eval argdoc { |
|
lappend PUNKARGS [list { |
|
@id -id ::punk::nav::ns::ns/ |
|
@cmd -name punk::nav::ns::ns/\ |
|
-summary\ |
|
"Navigate and list namespaces and commands"\ |
|
-help\ |
|
{Navigate/List namespaces or namespaces and commands in the current namespace or in the |
|
targets specified with the nsglob pattern(s). |
|
|
|
|
|
This function is provided via aliases as n/ n// and n/// with v being inferred from the alias |
|
|
|
The n/ n// and n/// forms are more convenient for interactive use. |
|
examples: |
|
n/ - list namespaces below current namespace |
|
n// - list namespaces and commands below current namespace |
|
n/ p* - list namespaces below current matching p* |
|
n// p* - list namespaces below current and commands in current matching p* |
|
} |
|
@values -min 1 -max -1 -type string |
|
v -type string\ |
|
-choices {/ // ///}\ |
|
-choicelabels { |
|
/\ |
|
"list namespaces only" |
|
//\ |
|
"list namespaces and commands" |
|
///\ |
|
"list namespaces, commands and commands |
|
resolvable via 'namespace path'" |
|
}\ |
|
-help\ |
|
"The form of navigation/listing to perform." |
|
nsglob -type string -optional true -multiple true -help\ |
|
"A glob pattern supporting placeholders * and ?, to filter results. |
|
If multiple patterns are supplied, then a listing for each pattern is returned. |
|
If no patterns are supplied, then all items are listed." |
|
}] |
|
} |
|
|
|
proc ns/ {v {ns_or_glob ""} args} { |
|
variable ns_current ;#change active ns of repl by setting ns_current |
|
|
|
set ns_caller [uplevel 1 {::tcl::namespace::current}] |
|
#puts stderr "ns_cur:$ns_current ns_call:$ns_caller" |
|
|
|
|
|
set types [list all] |
|
set nspathcommands 0 |
|
if {$v eq "/"} { |
|
set types [list children] |
|
} |
|
if {$v eq "///"} { |
|
set nspathcommands 1 |
|
} |
|
|
|
set ns_or_glob [string map {:::: ::} $ns_or_glob] |
|
|
|
#todo - cooperate with repl? |
|
set out "" |
|
if {$ns_or_glob eq ""} { |
|
set is_absolute 1 |
|
set ns_queried $ns_current |
|
set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $ns_current *]] |
|
} else { |
|
set is_absolute [string match ::* $ns_or_glob] |
|
set has_globchars [regexp {[*?]} $ns_or_glob] ;#basic globs only? |
|
if {$is_absolute} { |
|
if {!$has_globchars} { |
|
if {![nsexists $ns_or_glob]} { |
|
error "cannot change to namespace $ns_or_glob" |
|
} |
|
set ns_current $ns_or_glob |
|
set ns_queried $ns_current |
|
tailcall ns/ $v "" |
|
} else { |
|
set ns_queried $ns_or_glob |
|
set out [nslist -types $types -nspathcommands $nspathcommands $ns_or_glob] |
|
} |
|
} else { |
|
if {!$has_globchars} { |
|
set nsnext [nsjoin $ns_current $ns_or_glob] |
|
if {![nsexists $nsnext]} { |
|
error "cannot change to namespace $ns_or_glob" |
|
} |
|
set ns_current $nsnext |
|
set ns_queried $nsnext |
|
set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $nsnext *]] |
|
} else { |
|
set ns_queried [nsjoin $ns_current $ns_or_glob] |
|
set out [nslist -types $types -nspathcommands $nspathcommands [nsjoin $ns_current $ns_or_glob]] |
|
} |
|
} |
|
} |
|
set ns_display "\n$ns_queried" |
|
if {$ns_current eq $ns_queried} { |
|
if {$ns_current in [info commands $ns_current] } { |
|
if {![catch [list tcl::namespace::ensemble configure $ns_current] ensemble_info]} { |
|
if {[llength $ensemble_info] > 0} { |
|
#this namespace happens to match ensemble command. |
|
#todo - keep cache of encountered ensembles from commands.. and examine namespace in the configure info. |
|
#don't rely on a+ alias here. |
|
set ns_display "\n[punk::ansi::a+ yellow bold]$ns_current (ensemble)[punk::ansi::a+]" |
|
} |
|
} |
|
} |
|
} |
|
append out $ns_display |
|
return $out |
|
} |
|
|
|
#create possibly nested namespace structure(s) - todo: allow specifying namespaces or which already or partially already exist. |
|
#todo - sync with newdir behaviour. |
|
namespace eval argdoc { |
|
lappend PUNKARGS [list { |
|
@id -id ::punk::nav::ns::newns |
|
@cmd -name punk::nav::ns::newns\ |
|
-summary\ |
|
"Create namespace or namespaces at the specified path(s)."\ |
|
-help\ |
|
"This command creates namespaces at the specified path(s). |
|
If any part of the specified path does not exist, then it will be created as well. |
|
If a specified path already exists, then it will be left as-is and no error will be raised. |
|
|
|
A summary line is returned for each created namespace, with the full path of the created |
|
namespace and a status line indicating the number of child namespaces, commands and vars in |
|
the namespace if it already existed (or showing 0 for all if it was just created). |
|
(summary incomplete - todo)" |
|
-force -type none\ |
|
-help\ |
|
"Allows creation of namespaces which may be unwise/problematic, such as empty string |
|
or namespaces with leading colons. |
|
Use with caution and only when you know what you are doing. |
|
If -force is not supplied, then an error will be raised if any supplied |
|
path is problematic and no namespaces will be created." |
|
@values -min 1 -max -1 -type string |
|
path -type string -multiple 1 -optional 0 -help\ |
|
"Path(s) (possibly with namespace separator ::) to create. |
|
Can be absolute or relative to current namespace. |
|
If any path is rejected, then no namespaces will be created. |
|
|
|
If a namespace or part of a namespace already exists, then it will be left as-is and no |
|
error will be raised. |
|
|
|
If despite passing the name tests, a namespace cannot be created for some reason then an |
|
error will be raised and processing of any remaining paths will be aborted." |
|
}] |
|
} |
|
proc newns {args} { |
|
set argd [punk::args::parse $args withid ::punk::nav::ns::newns] |
|
lassign [dict values $argd] _leaders opts values _received |
|
if {[dict exists $opts -force]} { |
|
set opt_force [dict get $opts -force] |
|
} else { |
|
set opt_force 0 |
|
} |
|
set paths [dict get $values path] |
|
|
|
variable ns_current |
|
|
|
#todo: like newdir we want to try to perform an all-or-nothing operation - so first validate all namespaces to be created before creating any of them. |
|
|
|
set ns [lindex $paths 0] ;#temporary - full implementation will loop through $paths. |
|
set is_absolute [string match ::* $ns] |
|
if {$is_absolute} { |
|
set nspath $ns |
|
} else { |
|
if {[string match :* $ns]} { |
|
#todo - disallow by default and require flag to force. |
|
#we also should disallow by default ::: (or any odd multiple? perhaps even :::: for empty ns should require -force) in between segments. |
|
puts stderr "newns WARNING namespace with leading colon '$ns' is likely to have unexpected results" |
|
} |
|
set nspath [nsjoin $ns_current $ns] |
|
} |
|
|
|
set ns_exists [nseval [nsprefix $nspath] [list ::tcl::namespace::exists [nstail $nspath] ]] |
|
|
|
if {$ns_exists} { |
|
error "Namespace $nspath already exists" |
|
} |
|
#tcl::namespace::eval [nsprefix $nspath] [list tcl::namespace::eval [nstail $nspath] {}] |
|
nseval [nsprefix $nspath] [list ::tcl::namespace::eval [nstail $nspath] {}] |
|
|
|
#display summaries of created directories (which may have already existed) by reusing d/ to get info on them. |
|
set query_paths [lmap v $paths {string cat $v "::*"}] |
|
ns/ / {*}$query_paths |
|
} |
|
proc newns_old {args} { |
|
variable ns_current |
|
if {![llength $args]} { |
|
error "usage: newns <ns> \[<ns> ...\]" |
|
} |
|
set a1 [lindex $args 0] |
|
set is_absolute [string match ::* $a1] |
|
if {$is_absolute} { |
|
set nspath [nsjoinall {*}$args] |
|
} else { |
|
if {[string match :* $a1]} { |
|
puts stderr "newns WARNING namespace with leading colon '$a1' is likely to have unexpected results" |
|
} |
|
set nspath [nsjoinall $ns_current {*}$args] |
|
} |
|
|
|
set ns_exists [nseval [nsprefix $nspath] [list ::tcl::namespace::exists [nstail $nspath] ]] |
|
|
|
if {$ns_exists} { |
|
error "Namespace $nspath already exists" |
|
} |
|
#tcl::namespace::eval [nsprefix $nspath] [list tcl::namespace::eval [nstail $nspath] {}] |
|
nseval [nsprefix $nspath] [list ::tcl::namespace::eval [nstail $nspath] {}] |
|
n/ $nspath |
|
} |
|
|
|
|
|
#nn/ ::/ nsup/ - back up one namespace level |
|
proc nsup/ {v args} { |
|
variable ns_current |
|
if {$ns_current eq "::"} { |
|
puts stderr "Already at global namespace '::'" |
|
} else { |
|
set out "" |
|
set nsq [nsprefix $ns_current] |
|
if {$v eq "/"} { |
|
set out [get_nslist -match [nsjoin $nsq *] -types [list children]] |
|
} else { |
|
set out [get_nslist -match [nsjoin $nsq *] -types [list all]] |
|
} |
|
#set out [nslist [nsjoin $nsq *]] |
|
set ns_current $nsq |
|
append out "\n$ns_current" |
|
return $out |
|
} |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#extra slash implies more verbosity (ie display commands instead of just nschildren) |
|
interp alias {} n/ {} punk::nav::ns::ns/ / |
|
interp alias {} n// {} punk::nav::ns::ns/ // |
|
interp alias {} n/// {} punk::nav::ns::ns/ /// |
|
interp alias {} newns {} punk::nav::ns::newns |
|
interp alias {} nn/ {} punk::nav::ns::nsup/ / |
|
interp alias {} nn// {} punk::nav::ns::nsup/ // |
|
if 0 { |
|
interp alias {} :/ {} punk::nav::ns::ns/ / |
|
interp alias {} :// {} punk::nav::ns::ns/ // |
|
#we can't have ::/ without just plain / which is confusing. |
|
interp alias {} ::/ {} punk::nav::ns::nsup/ / |
|
interp alias {} ::// {} punk::nav::ns::nsup/ // |
|
} |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
# Secondary API namespace |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
tcl::namespace::eval punk::nav::ns::lib { |
|
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase |
|
tcl::namespace::path [tcl::namespace::parent] |
|
} |
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
|
|
|
|
|
|
#tcl::namespace::eval punk::nav::ns::system { |
|
#} |
|
|
|
|
|
# == === === === === === === === === === === === === === === |
|
# Sample 'about' function with punk::args documentation |
|
# == === === === === === === === === === === === === === === |
|
tcl::namespace::eval punk::nav::ns { |
|
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase |
|
variable PUNKARGS |
|
variable PUNKARGS_aliases |
|
|
|
lappend PUNKARGS [list { |
|
@id -id "(package)punk::nav::ns" |
|
@package -name "punk::nav::ns" -help\ |
|
"Package |
|
Description" |
|
}] |
|
|
|
namespace eval argdoc { |
|
#namespace for custom argument documentation |
|
proc package_name {} { |
|
return punk::nav::ns |
|
} |
|
proc about_topics {} { |
|
#info commands results are returned in an arbitrary order (like array keys) |
|
set topic_funs [info commands [namespace current]::get_topic_*] |
|
set about_topics [list] |
|
foreach f $topic_funs { |
|
set tail [namespace tail $f] |
|
lappend about_topics [string range $tail [string length get_topic_] end] |
|
} |
|
#Adjust this function or 'default_topics' if a different order is required |
|
return [lsort $about_topics] |
|
} |
|
proc default_topics {} {return [list Description *]} |
|
|
|
# ------------------------------------------------------------- |
|
# get_topic_ functions add more to auto-include in about topics |
|
# ------------------------------------------------------------- |
|
proc get_topic_Description {} { |
|
punk::args::lib::tstr [string trim { |
|
package punk::nav::ns |
|
description to come.. |
|
} \n] |
|
} |
|
proc get_topic_License {} { |
|
return "MIT" |
|
} |
|
proc get_topic_Version {} { |
|
return "$::punk::nav::ns::version" |
|
} |
|
proc get_topic_Contributors {} { |
|
set authors {<unspecified>} |
|
set contributors "" |
|
foreach a $authors { |
|
append contributors $a \n |
|
} |
|
if {[string index $contributors end] eq "\n"} { |
|
set contributors [string range $contributors 0 end-1] |
|
} |
|
return $contributors |
|
} |
|
proc get_topic_custom-topic {} { |
|
punk::args::lib::tstr -return string { |
|
A custom |
|
topic |
|
etc |
|
} |
|
} |
|
# ------------------------------------------------------------- |
|
} |
|
|
|
# we re-use the argument definition from punk::args::standard_about and override some items |
|
set overrides [dict create] |
|
dict set overrides @id -id "::punk::nav::ns::about" |
|
dict set overrides @cmd -name "punk::nav::ns::about" |
|
dict set overrides @cmd -help [string trim [punk::args::lib::tstr { |
|
About punk::nav::ns |
|
}] \n] |
|
dict set overrides topic -choices [list {*}[punk::nav::ns::argdoc::about_topics] *] |
|
dict set overrides topic -choicerestricted 1 |
|
dict set overrides topic -default [punk::nav::ns::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict |
|
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] |
|
lappend PUNKARGS [list $newdef] |
|
proc about {args} { |
|
package require punk::args |
|
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on |
|
set argd [punk::args::parse $args withid ::punk::nav::ns::about] |
|
lassign [dict values $argd] _leaders opts values _received |
|
punk::args::package::standard_about -package_about_namespace ::punk::nav::ns::argdoc {*}$opts {*}[dict get $values topic] |
|
} |
|
} |
|
# end of sample 'about' function |
|
# == === === === === === === === === === === === === === === |
|
|
|
|
|
# ----------------------------------------------------------------------------- |
|
# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked |
|
# ----------------------------------------------------------------------------- |
|
# variable PUNKARGS |
|
# variable PUNKARGS_aliases |
|
namespace eval ::punk::args::register { |
|
#use fully qualified so 8.6 doesn't find existing var in global namespace |
|
lappend ::punk::args::register::NAMESPACES ::punk::nav::ns |
|
} |
|
# ----------------------------------------------------------------------------- |
|
|
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
## Ready |
|
package provide punk::nav::ns [tcl::namespace::eval punk::nav::ns { |
|
variable pkg punk::nav::ns |
|
variable version |
|
set version 0.1.0 |
|
}] |
|
return |
|
|
|
|