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.
		
		
		
		
		
			
		
			
				
					
					
						
							412 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
	
	
							412 lines
						
					
					
						
							17 KiB
						
					
					
				# -*- 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 999999.0a1.0 | 
						|
# Meta platform     tcl | 
						|
# Meta license      <unspecified> | 
						|
# @@ Meta End | 
						|
 | 
						|
 | 
						|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ | 
						|
# doctools header | 
						|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ | 
						|
#*** !doctools | 
						|
#[manpage_begin shellspy_module_punk::assertion 0 999999.0a1.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 999999.0a1.0  | 
						|
}] | 
						|
return | 
						|
 | 
						|
#*** !doctools | 
						|
#[manpage_end] | 
						|
 | 
						|
 |