Julian Noble
8 months ago
4 changed files with 422 additions and 3 deletions
@ -0,0 +1,412 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt |
||||||
|
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.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) 2024 |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application punk::assertion 0.1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license <unspecified> |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# doctools header |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
#*** !doctools |
||||||
|
#[manpage_begin shellspy_module_punk::assertion 0 0.1.0] |
||||||
|
#[copyright "2024"] |
||||||
|
#[titledesc {assertion alternative to control::assert}] [comment {-- Name section and table of contents description --}] |
||||||
|
#[moddesc {per-namespace assertions with }] [comment {-- Description at end of page heading --}] |
||||||
|
#[require punk::assertion] |
||||||
|
#[keywords module assertion assert debug] |
||||||
|
#[description] |
||||||
|
#[para] The punk::assertion library has the same semantics as Tcllib's control::assert library for the assert command itself. |
||||||
|
#[para] The main difference is the way in which assert is enabled/disabled in namespaces. |
||||||
|
#[para] Due to commands such as 'namespace path' - the assert command could be available in arbitrary namespaces unrelated by tree structure to namespaces where assert has been directly imported. |
||||||
|
#[para] punk::assertion::active 0|1 allows activating and deactivating assertions in any namespace where the assert command is available - but only affecting the activations state of the namespace in which it is called. |
||||||
|
#[para] If such a non-primary assertion namespace never had active set to 0 or 1 - then it will activate/deactivate when the namespace corresponding to the found assert command (primary) is activated/deactivated. |
||||||
|
#[para] Once marked active or inactive - such a non-primary namespace will no longer follow the primary |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[section Overview] |
||||||
|
#[para] overview of punk::assertion |
||||||
|
#[subsection Concepts] |
||||||
|
#[para] - |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[subsection dependencies] |
||||||
|
#[para] packages used by punk::assertion |
||||||
|
#[list_begin itemized] |
||||||
|
|
||||||
|
package require Tcl 8.6 |
||||||
|
#*** !doctools |
||||||
|
#[item] [package {Tcl 8.6}] |
||||||
|
|
||||||
|
# #package require frobz |
||||||
|
# #*** !doctools |
||||||
|
# #[item] [package {frobz}] |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[section API] |
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# oo::class namespace |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::assertion::class { |
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace punk::assertion::class}] |
||||||
|
#[para] class definitions |
||||||
|
if {[info commands [namespace current]::interface_sample1] eq ""} { |
||||||
|
#*** !doctools |
||||||
|
#[list_begin enumerated] |
||||||
|
|
||||||
|
# oo::class create interface_sample1 { |
||||||
|
# #*** !doctools |
||||||
|
# #[enum] CLASS [class interface_sample1] |
||||||
|
# #[list_begin definitions] |
||||||
|
|
||||||
|
# method test {arg1} { |
||||||
|
# #*** !doctools |
||||||
|
# #[call class::interface_sample1 [method test] [arg arg1]] |
||||||
|
# #[para] test method |
||||||
|
# puts "test: $arg1" |
||||||
|
# } |
||||||
|
|
||||||
|
# #*** !doctools |
||||||
|
# #[list_end] [comment {-- end definitions interface_sample1}] |
||||||
|
# } |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] [comment {--- end class enumeration ---}] |
||||||
|
} |
||||||
|
} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
#keep 2 namespaces for assertActive and assertInactive so there is introspection available via namespace origin |
||||||
|
namespace eval punk::assertion::primary { |
||||||
|
|
||||||
|
namespace export * |
||||||
|
proc assertActive {expr args} { |
||||||
|
|
||||||
|
set code [catch {uplevel 1 [list expr $expr]} res] |
||||||
|
if {$code} { |
||||||
|
return -code $code $res |
||||||
|
} |
||||||
|
if {![string is boolean -strict $res]} { |
||||||
|
return -code error "invalid boolean expression: $expr" |
||||||
|
} |
||||||
|
|
||||||
|
if {$res} {return} |
||||||
|
|
||||||
|
if {[llength $args]} { |
||||||
|
#set msg "[join $args]" |
||||||
|
set msg "$args punk::assertion failed expr $expr" |
||||||
|
} else { |
||||||
|
set msg "punk::assertion failed expr $expr" ;#give a clue in the default msg about which assert lib is in use |
||||||
|
} |
||||||
|
|
||||||
|
upvar ::punk::assertion::CallbackCmd CallbackCmd |
||||||
|
# Might want to catch this |
||||||
|
namespace eval :: $CallbackCmd [list $msg] |
||||||
|
} |
||||||
|
proc assertInactive args {} |
||||||
|
|
||||||
|
} |
||||||
|
namespace eval punk::assertion::secondary { |
||||||
|
namespace export * |
||||||
|
#we need to actually define these procs here, (not import then re-export) - or namespace origin will report the original source namespace - which isn't what we want. |
||||||
|
proc assertActive {expr args} [info body ::punk::assertion::primary::assertActive] |
||||||
|
proc assertInactive args {} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# Base namespace |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::assertion { |
||||||
|
variable CallbackCmd [list return -code error] |
||||||
|
namespace import ::punk::assertion::primary::assertActive |
||||||
|
rename assertActive assert |
||||||
|
|
||||||
|
namespace export * |
||||||
|
#variable xyz |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace punk::assertion}] |
||||||
|
#[para] Core API functions for punk::assertion |
||||||
|
#[list_begin definitions] |
||||||
|
|
||||||
|
|
||||||
|
#proc sample1 {p1 n args} { |
||||||
|
# #*** !doctools |
||||||
|
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]] |
||||||
|
# #[para]Description of sample1 |
||||||
|
# #[para] Arguments: |
||||||
|
# # [list_begin arguments] |
||||||
|
# # [arg_def tring p1] A description of string argument p1. |
||||||
|
# # [arg_def integer n] A description of integer argument n. |
||||||
|
# # [list_end] |
||||||
|
# return "ok" |
||||||
|
#} |
||||||
|
|
||||||
|
#like tcllib's control::assert - we are limited to the same callback for all namespaces. |
||||||
|
#review - a per namespace - or per assert command callback may be tricky to do performantly. |
||||||
|
#Would probably involve rewriting the proc body - otherwise we have a runtime penalty in the assert of looking it up. |
||||||
|
proc callback {args} { |
||||||
|
#set nscaller [uplevel 1 [list namespace current]] |
||||||
|
#set which_assert [namespace eval $nscaller {namespace which assert}] |
||||||
|
|
||||||
|
upvar ::punk::assertion::CallbackCmd cb |
||||||
|
set n [llength $args] |
||||||
|
if {$n > 1} { |
||||||
|
return -code error "wrong # args: should be\ |
||||||
|
\"[lindex [info level 0] 0] ?command?\"" |
||||||
|
} |
||||||
|
if {$n} { |
||||||
|
set cb [lindex $args 0] |
||||||
|
return |
||||||
|
} |
||||||
|
return $cb |
||||||
|
} |
||||||
|
|
||||||
|
proc active {{on_off ""}} { |
||||||
|
set nscaller [uplevel 1 [list namespace current]] |
||||||
|
set which_assert [namespace eval $nscaller {namespace which assert}] |
||||||
|
#puts "nscaller:'$nscaller'" |
||||||
|
#puts "which_assert: $which_assert" |
||||||
|
|
||||||
|
if {$on_off eq ""} { |
||||||
|
if {$which_assert eq ""} {return 0} |
||||||
|
set assertorigin [namespace origin $which_assert] |
||||||
|
#puts "ns which assert: $which_assert" |
||||||
|
#puts "ns origin assert: $assertorigin" |
||||||
|
return [expr {"assertActive" eq [namespace tail $assertorigin]}] |
||||||
|
} |
||||||
|
if {![string is boolean -strict $on_off]} { |
||||||
|
error "invalid boolean value : $on_off" |
||||||
|
} else { |
||||||
|
set info_command [namespace eval $nscaller {info commands assert}] |
||||||
|
if {$on_off} { |
||||||
|
#Enable it in calling namespace |
||||||
|
if {"assert" eq $info_command} { |
||||||
|
#There is an assert command reachable - due to namespace path etc, it could be in another namespace entirely - (not necessarily in an ancestor namespace of the namespace's tree structure) |
||||||
|
if {$which_assert eq [punk::assertion::system::nsjoin ${nscaller} assert]} { |
||||||
|
namespace eval $nscaller { |
||||||
|
set assertorigin [namespace origin assert] |
||||||
|
set assertorigin_ns [punk::assertion::system::nsprefix $assertorigin] |
||||||
|
switch -- $assertorigin_ns { |
||||||
|
::punk::assertion { |
||||||
|
#original import - switch to primary origin |
||||||
|
rename assert {} |
||||||
|
namespace import ::punk::assertion::primary::assertActive |
||||||
|
rename assertActive assert |
||||||
|
} |
||||||
|
::punk::assertion::primary - ::punk::assertion::secondary { |
||||||
|
#keep using from same origin ns |
||||||
|
rename assert {} |
||||||
|
namespace import ${assertorigin_ns}::assertActive |
||||||
|
rename assertActive assert |
||||||
|
} |
||||||
|
default { |
||||||
|
error "The assert command in this namespace is not from punk::assertion package. Use the enable mechanism from the package associated with $assertorigin or remove the existing assert command and namespace import punk::assertion::assert" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return 1 |
||||||
|
} else { |
||||||
|
#assert is available, but isn't in the calling namespace - we should enable it in a way that is distinguishable from case where assert was explicitly imported to this namespace |
||||||
|
namespace eval $nscaller { |
||||||
|
set assertorigin [namespace origin assert] |
||||||
|
if {[string match ::punk::assertion::* $assertorigin]} { |
||||||
|
namespace import ::punk::assertion::secondary::assertActive |
||||||
|
rename assertActive assert |
||||||
|
} else { |
||||||
|
error "The reachable assert command at '$which_assert' is not from punk::assertion package. Import punk::assertion::assert - or use the enable mechanism from the package associated with $assertorigin" |
||||||
|
} |
||||||
|
} |
||||||
|
return 1 |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
#no assert command reachable |
||||||
|
puts stderr "no assert command visible from namespace '$nscaller' - use: namespace import ::punk::assertion::assert" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} else { |
||||||
|
#Disable |
||||||
|
if {"assert" eq $info_command} { |
||||||
|
if {$which_assert eq [punk::assertion::system::nsjoin ${nscaller} assert]} { |
||||||
|
#assert is present in callers NS |
||||||
|
namespace eval $nscaller { |
||||||
|
set assertorigin [namespace origin assert] |
||||||
|
set assertorigin_ns [punk::assertion::system::nsprefix $assertorigin] |
||||||
|
switch -glob -- $assertorigin_ns { |
||||||
|
::punk::assertion { |
||||||
|
#original import |
||||||
|
rename assert {} |
||||||
|
namespace import punk::assertion::primary::assertInactive |
||||||
|
rename assertInactive assert |
||||||
|
} |
||||||
|
::punk::assertion::primary - ::punk::assertion::secondary { |
||||||
|
#keep using from same origin ns |
||||||
|
rename assert {} |
||||||
|
namespace import ${assertorigin_ns}::assertInactive |
||||||
|
rename assertInactive assert |
||||||
|
} |
||||||
|
default { |
||||||
|
error "The assert command in this namespace is not from punk::assertion package. Use the disable mechanism from the package associated with $assertorigin or remove the existing assert command and namespace import punk::assertion::assert" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} else { |
||||||
|
#assert not present in callers NS - first install of secondary (if assert is from punk::assertion::*) |
||||||
|
namespace eval $nscaller { |
||||||
|
set assertorigin [namespace origin assert] |
||||||
|
set assertorigin_ns [punk::assertion::system::nsprefix $assertorigin] |
||||||
|
if {[string match ::punk::assertion::* $assertorigin]} { |
||||||
|
namespace import ::punk::assertion::secondary::assertInactive |
||||||
|
rename assertInactive assert |
||||||
|
} else { |
||||||
|
error "The reachable assert command at '$which_assert' is not from punk::assertion package. Import punk::assertion::assert - or use the enable mechanism from the package associated with $assertorigin" |
||||||
|
} |
||||||
|
} |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} else { |
||||||
|
#no assert command reachable |
||||||
|
#If caller is using assert in this namespace - they should have imported it, or ensured it was reachable via namespace path |
||||||
|
puts stderr "no assert command visible from namespace '$nscaller' - use: namespace import ::punk::assertion::assert" |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] [comment {--- end definitions namespace punk::assertion ---}] |
||||||
|
} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
# Secondary API namespace |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval punk::assertion::lib { |
||||||
|
namespace export * |
||||||
|
namespace path [namespace parent] |
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace punk::assertion::lib}] |
||||||
|
#[para] Secondary functions that are part of the API |
||||||
|
#[list_begin definitions] |
||||||
|
|
||||||
|
#proc utility1 {p1 args} { |
||||||
|
# #*** !doctools |
||||||
|
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]] |
||||||
|
# #[para]Description of utility1 |
||||||
|
# return 1 |
||||||
|
#} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[list_end] [comment {--- end definitions namespace punk::assertion::lib ---}] |
||||||
|
} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
#*** !doctools |
||||||
|
#[section Internal] |
||||||
|
namespace eval punk::assertion::system { |
||||||
|
#*** !doctools |
||||||
|
#[subsection {Namespace punk::assertion::system}] |
||||||
|
#[para] Internal functions that are not part of the API |
||||||
|
|
||||||
|
#Maintenance - snarfed from punk::ns to reduce dependencies - punk::ns::nsprefix is the master version |
||||||
|
#nsprefix/nstail are string functions - they do not concern themselves with what namespaces are present in the system |
||||||
|
proc nsprefix {{nspath {}}} { |
||||||
|
#normalize the common case of :::: |
||||||
|
set nspath [string map [list :::: ::] $nspath] |
||||||
|
set rawprefix [string range $nspath 0 end-[string length [nstail $nspath]]] |
||||||
|
if {$rawprefix eq "::"} { |
||||||
|
return $rawprefix |
||||||
|
} else { |
||||||
|
if {[string match *:: $rawprefix]} { |
||||||
|
return [string range $rawprefix 0 end-2] |
||||||
|
} else { |
||||||
|
return $rawprefix |
||||||
|
} |
||||||
|
#return [string trimright $rawprefix :] |
||||||
|
} |
||||||
|
} |
||||||
|
#see also punk::ns - keep in sync |
||||||
|
proc nstail {nspath args} { |
||||||
|
#normalize the common case of :::: |
||||||
|
set nspath [string map [list :::: ::] $nspath] |
||||||
|
set mapped [string map [list :: \u0FFF] $nspath] |
||||||
|
set parts [split $mapped \u0FFF] |
||||||
|
|
||||||
|
set defaults [list -strict 0] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
set strict [dict get $opts -strict] |
||||||
|
|
||||||
|
if {$strict} { |
||||||
|
foreach p $parts { |
||||||
|
if {[string match :* $p]} { |
||||||
|
error "nstail unpaired colon ':' in $nspath" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#e.g ::x::y:::z should return ":z" despite it being a bad idea for a command name. |
||||||
|
return [lindex $parts end] |
||||||
|
} |
||||||
|
proc nsjoin {prefix name} { |
||||||
|
if {[string match ::* $name]} { |
||||||
|
if {"$prefix" ne ""} { |
||||||
|
error "nsjoin: won't join non-empty prefix to absolute namespace path '$name'" |
||||||
|
} |
||||||
|
return $name |
||||||
|
} |
||||||
|
if {"$prefix" eq "::"} { |
||||||
|
return ::$name |
||||||
|
} |
||||||
|
#if {"$name" eq ""} { |
||||||
|
# return $prefix |
||||||
|
#} |
||||||
|
#nsjoin ::x::y "" should return ::x::y:: - this is the correct fully qualified form used to call a command that is the empty string |
||||||
|
return ${prefix}::$name |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide punk::assertion [namespace eval punk::assertion { |
||||||
|
variable pkg punk::assertion |
||||||
|
variable version |
||||||
|
set version 0.1.0 |
||||||
|
}] |
||||||
|
return |
||||||
|
|
||||||
|
#*** !doctools |
||||||
|
#[manpage_end] |
||||||
|
|
Loading…
Reference in new issue