package require punk::mix::util 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 {![string match ::* $routine]} { set resolved [uplevel 1 [list ::namespace which $routine]] if {$resolved eq {}} { error [list {no such routine} $routine] } set routine $resolved } set routinens [namespace qualifiers $routine] if {$routinens eq {::}} { set routinens {} } set routinetail [namespace tail $routine] if {![string match ::* $base]} { set base [uplevel 1 [ list [namespace which namespace] current]]::$base } if {![namespace exists $base]} { error [list {no such namespace} $base] } set base [namespace eval $base [ list [namespace which namespace] current]] #while 1 { # set renamed ${routinens}::${routinetail}_[info cmdcount] # if {[namespace which $renamed] eq {}} break #} namespace eval $routine [ list 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::* #}] namespace eval ${routine}::lib [string map [list $base $routine] { if {[namespace exists ::lib]} { set current_paths [namespace path] if {"" ni $current_paths} { lappend current_paths } namespace path $current_paths } }] namespace eval $routine { set exportlist [list] foreach cmd [info commands [namespace current]::*] { set c [namespace tail $cmd] if {![string match _* $c]} { lappend exportlist $c } } namespace export {*}$exportlist } return $routine } #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'" } #namespace may or may not be a package # allow with or without leading :: if {[string range $cmdnamespace 0 1] eq "::"} { set cmdpackage [string range $cmdnamespace 2 end] } else { set cmdpackage $cmdnamespace set cmdnamespace ::$cmdnamespace } if {![namespace exists $cmdnamespace]} { #only do package require if the namespace not already present catch {package require $cmdpackage} pkg_load_info #recheck if {![namespace exists $cmdnamespace]} { set prov [package provide $cmdpackage] if {[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 namespace eval ${cmdnamespace}::lib [string map [list $cmdnamespace] { set nspaths [namespace path] if {"" ni $nspaths} { lappend nspaths } namespace path $nspaths }] set imported_commands [list] set nscaller [uplevel 1 [list namespace current]] if {[catch { namespace eval ${nscaller}::temp_import [list namespace import ${cmdnamespace}::*] foreach cmd [info commands ${nscaller}::temp_import::*] { set cmdtail [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 } } errM]} { puts stderr "Error loading commandset $prefix $separator $cmdnamespace" puts stderr "err: $errM" } return $imported_commands } } package provide punk::overlay [namespace eval punk::overlay { variable version set version 0.1 }]