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.
 
 
 
 
 
 

396 lines
13 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 proctrace 0.2
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
package require Tcl 8.6-
##################
## Module Name -- proctrace.tcl
## Original Author -- Emmanuel Frecon
## Description:
##
## This module is meant to be a last resort debugging facility. It will
## arrange for being able to trace execution either at the entry of
## procedure, either of all commands within procedures. The defaults are to
## trace all procedures, except the one from a few packages known to slow
## execution down. See beginning of library for an explanation of the
## options.
##
##################
tcl::namespace::eval ::proctrace {
variable PUNKARGS
namespace eval vars {
# File to trace execution to (if no file is specified, tracing will
# occur on the standard error)
variable -file ""
# List of pattern to match against the name of current and future
# procedures. Only the procedures matching the patterns in this list
# will be considered for tracing.
variable -allowed {*}
# List of patterns to match against the name of procedure that should
# not be considered for tracing. This is a subset of the ones allowed.
variable -denied {::tcl::* ::aes::* ::logger::*}
# A boolean, turn it on to trace the execution of each command block
# within the procedures.
variable -detailed off
variable fd stderr; # File descriptor where to trace
variable version 0.2; # Current package version.
variable enabled 1; # Is tracing enabled
}
# Automatically export all procedures starting with lower case and
# create an ensemble for an easier API.
namespace export {[a-z]*}
namespace ensemble create
}
# ::proctrace::init -- Init and start tracing
#
# Arrange to trace the execution of code either at the entry of procedure,
# either of all commands within procedures. This command takes a number of
# dash led options, these are described a the beginning of the library.
#
# Arguments:
# args List of dash-led options and arguments.
#
# Results:
# None.
#
# Side Effects:
# Will start tracing, which means a LOT of output!
proc ::proctrace::init { args } {
# Detect all options available to the procedure, out of the variables that
# are dash-led.
set opts [list]
foreach o [info vars vars::-*] {
set i [string last "::-" $o]
lappend opts [string trimleft [string range $o $i end] :]
}
# "parse" the options, i.e. set the values if they should exist...
foreach {k v} $args {
if { $k in $opts } {
set vars::$k $v
} else {
return -code error "$k unknown options, should be [join $opts ,\ ]"
}
}
# Open the file for output, if relevant.
if { ${vars::-file} ne "" } {
set vars::fd [open ${vars::-file} w]
}
# Arrange to reroute procedure declaration through our command so we can
# automagically install execution traces.
rename ::proc ::proctrace::RealProc
interp alias {} ::proc {} ::proctrace::Proc
# Catch up with the current set of existing procedure to make sure we can
# also capture execution within procedure that would have been created
# before ::proctrace::init was called.
foreach p [AllProcs] {
if { [Tracable $p]} {
Follow $p 2
}
}
}
proc ::proctrace::terminate {} {set ::proctrace::vars::enabled 0}
proc ::proctrace::resume {} {set ::proctrace::vars::enabled 1}
# ::proctrace::AllProcs -- List all declared procedures
#
# Returns a list of all declared procedures, in all namespaces currently
# defined in the interpreter. The implementation recursively list all
# procedures in all sub-namespaces.
#
# Arguments:
# base Namespace at which to start.
#
# Results:
# List of all procedure in current and descendant namespaces.
#
# Side Effects:
# None.
proc ::proctrace::AllProcs { { base "::" } } {
# Get list of procedures in current namespace.
set procs [info procs [string trimright ${base} :]::*]
# Recurse in children namespaces.
foreach ns [namespace children $base] {
set procs [concat $procs [AllProcs $ns]]
}
return $procs
}
# ::proctrace::Follow -- Install traces
#
# Install traces to be able to get notified whenever procedures are
# entered or commands within procedures are executed.
#
# Arguments:
# name Name (fully-qualified) of procedure.
# lvl Call stack level at which to execute trace installation
#
# Results:
# None.
#
# Side Effects:
# Arrange for Trace procedure to be called
proc ::proctrace::Follow { name {lvl 1}} {
if { [string is true ${vars::-detailed}] } {
uplevel $lvl [list trace add execution $name enter [list ::proctrace::Trace $name]]
uplevel $lvl [list trace add execution $name enterstep [list ::proctrace::Trace $name]]
} else {
uplevel $lvl [list trace add execution $name enter [list ::proctrace::Trace $name]]
}
}
# ::proctrace::Proc -- Capturing procedure
#
# This is our re-implementation of the proc command. It calls the original
# command and also arranges to install traces if appropriate.
#
# Arguments:
# name Name of procedure
# arglist List of arguments to procedure
# body Procedure body.
#
# Results:
# None.
#
# Side Effects:
# Creates a new procedure, possibly arrange for tracing its execution.
proc ::proctrace::Proc { name arglist body } {
uplevel 1 [list ::proctrace::RealProc $name $arglist $body]
if { [Tracable $name]} {
Follow $name 2
}
}
variable tinfo
# ::proctrace::Trace -- Perform trace
#
# Trace procedure/command execution.
#
# Arguments:
# target Name of procedure
# command Command being executed
# op Operation (should be enter or enterstep, not used)
#
# Results:
# None.
#
# Side Effects:
# Trace execution on globally allocated file descriptor.
proc ::proctrace::Trace { target command op } {
if {!$::proctrace::vars::enabled} {return}
variable tinfo
if {$op eq "enter"} {
dict set tinfo($target) firstline -1
dict set tinfo($target) procoffset 0
dict set tinfo($target) level [expr {[info level]+1}]
dict set tinfo($target) subcmds 0
puts $vars::fd "ENTER $target >> $command"
return
} else {
if {[tcl::info::level] != [tcl::dict::get $tinfo($target) level]} {
return
}
}
puts $vars::fd "STEP $target >> $command"
flush $vars::fd
}
# ::proctrace::Tracable -- Should procedure be traced
#
# Decide if a procedure should be traced according to the -allowed and
# -denied options that are global to this library.
#
# Arguments:
# name Fully-qualified procedure name
#
# Results:
# 1 if the procedure should be traced, 0 otherwise.
#
# Side Effects:
# None.
proc ::proctrace::Tracable { name } {
# Traverse -allow(ance) list to allow procedure.
set allow 0
foreach ptn ${vars::-allowed} {
if { [string match $ptn $name] } {
set allow 1
break
}
}
# Possibly negate previous allowance through matching the name against the
# patterns in the -denied list.
foreach ptn ${vars::-denied} {
if { [string match $ptn $name] } {
set allow 0
break
}
}
# Return final decision.
return $allow
}
package provide proctrace $::proctrace::vars::version
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval proctrace::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval proctrace::system {
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval proctrace {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)proctrace"
@package -name "proctrace" -help\
"Package
Description"
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return proctrace
}
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 proctrace
description to come..
} \n]
}
proc get_topic_License {} {
return "<unspecified>"
}
proc get_topic_Version {} {
return "$::proctrace::version"
}
proc get_topic_Contributors {} {
set authors {{Emmanuel Frecon}}
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 "::proctrace::about"
dict set overrides @cmd -name "proctrace::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About proctrace
}] \n]
dict set overrides topic -choices [list {*}[proctrace::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [proctrace::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 ::proctrace::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::proctrace::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 ::proctrace
}
# -----------------------------------------------------------------------------
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide proctrace [tcl::namespace::eval proctrace {
variable pkg proctrace
variable version
set version 0.2
}]
return