package require punk::mix::util package require punk::args tcl::namespace::eval ::punk::overlay { #based *loosely* on: wiki.tcl-lang.org/page/ensemble+extend # extend an ensemble-like routine with the routines in some namespace # # e.g custom_from_base ::punk::mix::cli ::punk::mix::base # proc custom_from_base {routine base} { if {![tcl::string::match ::* $routine]} { set resolved [uplevel 1 [list ::tcl::namespace::which $routine]] if {$resolved eq {}} { error [list {no such routine} $routine] } set routine $resolved } set routinens [tcl::namespace::qualifiers $routine] if {$routinens eq {::}} { set routinens {} } set routinetail [tcl::namespace::tail $routine] if {![tcl::string::match ::* $base]} { set base [uplevel 1 [ list [tcl::namespace::which namespace] current]]::$base } if {![tcl::namespace::exists $base]} { error [list {no such namespace} $base] } set base [tcl::namespace::eval $base [ list [tcl::namespace::which namespace] current]] #while 1 { # set renamed ${routinens}::${routinetail}_[info cmdcount] # if {[namespace which $renamed] eq {}} break #} tcl::namespace::eval $routine [ ::list tcl::namespace::ensemble configure $routine -unknown [ ::list ::apply {{base ensemble subcommand args} { ::list ${base}::_redirected $ensemble $subcommand }} $base ] ] punk::mix::util::namespace_import_pattern_to_namespace_noclobber ::punk::mix::util::* ${routine}::util #namespace eval ${routine}::util { #::namespace import ::punk::mix::util::* #} punk::mix::util::namespace_import_pattern_to_namespace_noclobber ${base}::lib::* ${routine}::lib #namespace eval ${routine}::lib [string map [list $base] { # ::namespace import ::lib::* #}] tcl::namespace::eval ${routine}::lib [tcl::string::map [list $base $routine] { if {[tcl::namespace::exists ::lib]} { ::set current_paths [tcl::namespace::path] if {"" ni $current_paths} { ::lappend current_paths } tcl::namespace::path $current_paths } }] tcl::namespace::eval $routine { ::set exportlist [::list] ::foreach cmd [tcl::info::commands [tcl::namespace::current]::*] { ::set c [tcl::namespace::tail $cmd] if {![tcl::string::match _* $c]} { ::lappend exportlist $c } } tcl::namespace::export {*}$exportlist } return $routine } punk::args::define { @id -id ::punk::overlay::import_commandset @cmd -name punk::overlay::import_commandset\ -summary\ "Import commands into caller's namespace with optional prefix and separator."\ -help\ "Import commands that have been exported by another namespace into the caller's namespace. Usually a prefix and optionally a separator should be used. This is part of the punk::mix CLI commandset infrastructure - design in flux. Todo - .toml configuration files for defining CLI configurations." @values prefix -type string separator -type string -help\ "A string, usually punctuation, to separate the prefix and the command name of the final imported command. The value \"::\" is disallowed in this context." cmdnamespace -type string -help\ "Namespace from which to import commands. Commands are those that have been exported." } #load *exported* commands from cmdnamespace into caller's namespace - prefixing each command with $prefix #Note: commandset may be imported by different CLIs with different bases *at the same time* #so we don't make commands from the cli or its base available automatically (will generally require fully-qualified commands to use code from cli/base) #we do load punk::mix::util::* into the util subnamespace even though the commandset might not be loaded in a cli using punk::mix::base i.e punk::mix::util is a common dependency for CLIs. #commandsets designed to be used with a specific cli/base may choose to do their own import e.g with util::namespace_import_pattern_to_namespace_noclobber and/or set namespace path if they #want the convenience of using lib:xxx with commands coming from those packages. #This won't stop the commandset being used with other cli/bases unless the import is done by looking up the callers namespace. #The basic principle is that the commandset is loaded into the caller(s) with a prefix #- but commandsets should explicitly package require if they have any backwards dependencies on cli/base (which they may or may not be loaded into) proc import_commandset {prefix separator cmdnamespace} { set bad_seps [list "::"] if {$separator in $bad_seps} { error "import_commandset invalid separator '$separator'" } if {$prefix in $bad_seps} { error "import_commandset invalid prefix '$prefix'" } if {"$prefix$separator" in $bad_seps} { error "import_commandset invalid prefix/separator combination '$prefix$separator'" } if {"[string index $prefix end][string index $separator 0]" in $bad_seps} { error "import_commandset invalid prefix/separator combination '$prefix$separator'" } #review - do we allow prefixes/separators such as a::b? #namespace may or may not be a package # allow with or without leading :: if {[tcl::string::range $cmdnamespace 0 1] eq "::"} { set cmdpackage [tcl::string::range $cmdnamespace 2 end] } else { set cmdpackage $cmdnamespace set cmdnamespace ::$cmdnamespace } if {![tcl::namespace::exists $cmdnamespace]} { #only do package require if the namespace not already present catch {package require $cmdpackage} pkg_load_info #recheck if {![tcl::namespace::exists $cmdnamespace]} { set prov [package provide $cmdpackage] if {[tcl::string::length $prov]} { set provinfo "(package $cmdpackage is present with version $prov)" } else { set provinfo "(package $cmdpackage not present)" } error "punk::overlay::import_commandset supplied namespace '$cmdnamespace' doesn't exist. $provinfo Pkg_load_result: $pkg_load_info Usage: import_commandset prefix separator namespace" } } punk::mix::util::namespace_import_pattern_to_namespace_noclobber ::punk::mix::util::* ${cmdnamespace}::util #let child namespace 'lib' resolve parent namespace and thus util::xxx tcl::namespace::eval ${cmdnamespace}::lib [tcl::string::map [list $cmdnamespace] { ::set nspaths [tcl::namespace::path] if {"" ni $nspaths} { ::lappend nspaths } tcl::namespace::path $nspaths }] set imported_commands [list] set imported_tails [list] set nscaller [uplevel 1 [list tcl::namespace::current]] if {[catch { #review - noclobber? tcl::namespace::eval ${nscaller}::temp_import [list tcl::namespace::import ${cmdnamespace}::*] foreach cmd [tcl::info::commands ${nscaller}::temp_import::*] { set cmdtail [tcl::namespace::tail $cmd] if {$cmdtail eq "_default"} { set import_as ${nscaller}::${prefix} } else { set import_as ${nscaller}::${prefix}${separator}${cmdtail} } rename $cmd $import_as lappend imported_commands $import_as lappend imported_tails [namespace tail $import_as] } #make imported commands exported so they are available to the ensemble tcl::namespace::eval ${nscaller} [list namespace export {*}$imported_tails] } errM]} { puts stderr "Error loading commandset $prefix $separator $cmdnamespace" puts stderr "err: $errM" } return $imported_commands } } package provide punk::overlay [tcl::namespace::eval punk::overlay { variable version set version 0.1 }]