From 7af968865ae9a5c85c63e50e1790dc514586c466 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Tue, 3 Feb 2026 22:50:36 +1100 Subject: [PATCH] agent hints, test package changes, pattern module changes, new module punk::net::vxlan, etc (late checkin) --- AGENTS.md | 166 ++++ bin/getzig.cmd | 34 +- .../modules/include_modules.config | 1 + src/bootsupport/modules/packagetest-0.1.7.tm | Bin 0 -> 12090 bytes .../modules/patterncipher-0.1.1.tm | 1 - src/bootsupport/modules/punk/lib-0.1.5.tm | 52 + .../punk/mix/commandset/module-0.1.0.tm | 20 +- src/bootsupport/modules/punk/repl-0.1.2.tm | 312 +++--- src/bootsupport/modules/punk/repo-0.1.1.tm | 2 +- src/bootsupport/modules/shellfilter-0.2.1.tm | 8 + src/bootsupport/modules/shellrun-0.1.1.tm | 2 +- src/bootsupport/modules/test/tomlish-1.1.5.tm | Bin 56588 -> 56623 bytes src/lib/app-punkshell/punkshell.tcl | 8 +- src/lib/app-shellspy/shellspy.tcl | 23 +- src/modules/punk/lib-999999.0a1.0.tm | 52 + .../modules/template_module-0.0.4.tm | 2 +- .../templates/modules/template_test-0.0.1.tm | 139 +++ .../utility/scriptappwrappers/multishell.cmd | 7 +- .../mix/commandset/module-999999.0a1.0.tm | 20 +- src/modules/punk/net/vxlan-999999.0a1.0.tm | 365 +++++++ src/modules/punk/net/vxlan-buildversion.txt | 3 + src/modules/punk/repl-999999.0a1.0.tm | 312 +++--- src/modules/punk/repo-999999.0a1.0.tm | 2 +- src/modules/shellfilter-999999.0a1.0.tm | 8 + src/modules/shellrun-0.1.1.tm | 2 +- src/modules/test/AGENTS.md | 14 + .../ansi-999999.0a1.0.tm | 110 +-- .../define.test#..+args+define.test.fauxlink | 0 .../opts.test#..+args+opts.test.fauxlink | 0 .../args-999999.0a1.0.tm | 87 +- .../lib-999999.0a1.0.tm | 82 +- .../ns-999999.0a1.0.tm | 4 +- src/modules/test/runtestmodules.tcl | 166 ++++ .../modules/include_modules.config | 1 + .../bootsupport/modules/packagetest-0.1.7.tm | Bin 0 -> 12090 bytes .../modules/patterncipher-0.1.1.tm | 1 - .../src/bootsupport/modules/punk/lib-0.1.5.tm | 52 + .../punk/mix/commandset/module-0.1.0.tm | 20 +- .../bootsupport/modules/punk/repl-0.1.2.tm | 312 +++--- .../bootsupport/modules/punk/repo-0.1.1.tm | 2 +- .../bootsupport/modules/shellfilter-0.2.1.tm | 8 + .../src/bootsupport/modules/shellrun-0.1.1.tm | 2 +- .../bootsupport/modules/test/tomlish-1.1.5.tm | Bin 56588 -> 56623 bytes .../modules/include_modules.config | 1 + .../bootsupport/modules/packagetest-0.1.7.tm | Bin 0 -> 12090 bytes .../modules/patterncipher-0.1.1.tm | 1 - .../src/bootsupport/modules/punk/lib-0.1.5.tm | 52 + .../punk/mix/commandset/module-0.1.0.tm | 20 +- .../bootsupport/modules/punk/repl-0.1.2.tm | 312 +++--- .../bootsupport/modules/punk/repo-0.1.1.tm | 2 +- .../bootsupport/modules/shellfilter-0.2.1.tm | 8 + .../src/bootsupport/modules/shellrun-0.1.1.tm | 2 +- .../bootsupport/modules/test/tomlish-1.1.5.tm | Bin 56588 -> 56623 bytes src/scriptapps/bin/getzig.bash | 13 +- src/scriptapps/bin/getzig.ps1 | 11 +- src/vendormodules/include_modules.config | 9 +- src/vendormodules/packageTest-0.1.4.tm | Bin 11955 -> 0 bytes src/vendormodules/packageTest-0.1.5.tm | Bin 11963 -> 0 bytes src/vendormodules/packagetest-0.1.7.tm | Bin 0 -> 12090 bytes .../pattern/IPatternBuilder-2.0.tm | 326 +++++++ .../pattern/IPatternInterface-2.0.tm | 43 + .../pattern/IPatternSystem-2.0.tm | 122 +++ src/vendormodules/pattern/ms-1.0.12.tm | 330 +++++++ src/vendormodules/pattern2-2.0.tm | 911 ++++++++++++++++++ src/vendormodules/patterncipher-0.1.1.tm | 1 - src/vendormodules/patterndispatcher-1.2.4.tm | 1 + src/vendormodules/test/pattern-1.2.8.tm | Bin 0 -> 54941 bytes src/vendormodules/test/tomlish-1.1.5.tm | Bin 56588 -> 56623 bytes src/vendormodules/treeobj-1.3.1.tm | Bin 11181 -> 11656 bytes src/vfs/_config/project_main.tcl | 2 + src/vfs/_config/punk_main.tcl | 2 + 71 files changed, 3720 insertions(+), 851 deletions(-) create mode 100644 AGENTS.md create mode 100644 src/bootsupport/modules/packagetest-0.1.7.tm create mode 100644 src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/modules/template_test-0.0.1.tm create mode 100644 src/modules/punk/net/vxlan-999999.0a1.0.tm create mode 100644 src/modules/punk/net/vxlan-buildversion.txt create mode 100644 src/modules/test/AGENTS.md create mode 100644 src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/tests/define.test#..+args+define.test.fauxlink create mode 100644 src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/tests/opts.test#..+args+opts.test.fauxlink create mode 100644 src/modules/test/runtestmodules.tcl create mode 100644 src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/packagetest-0.1.7.tm create mode 100644 src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/packagetest-0.1.7.tm delete mode 100644 src/vendormodules/packageTest-0.1.4.tm delete mode 100644 src/vendormodules/packageTest-0.1.5.tm create mode 100644 src/vendormodules/packagetest-0.1.7.tm create mode 100644 src/vendormodules/pattern/IPatternBuilder-2.0.tm create mode 100644 src/vendormodules/pattern/IPatternInterface-2.0.tm create mode 100644 src/vendormodules/pattern/IPatternSystem-2.0.tm create mode 100644 src/vendormodules/pattern/ms-1.0.12.tm create mode 100644 src/vendormodules/pattern2-2.0.tm create mode 100644 src/vendormodules/test/pattern-1.2.8.tm diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..bdda469d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,166 @@ +# AGENTS.md + +Agent handbook for the ShellSpy (Punk Shell) repository. These guidelines cover builds, linting, testing, code style, and day-to-day conventions for all contributors and agentic assistants. Always check for nested `AGENTS.md` files before editing subdirectories—this root spec applies repo-wide unless overridden deeper in the tree. + +## Quickstart Checklist +- Confirm Windows-friendly Tcl toolchain (8.6+ required, 9.0 supported). +- Run `tclsh make.tcl` once after cloning to populate generated assets. +- Keep edits within the scoped instructions of any nested `AGENTS.md`. +- Use `tclint` before submitting code to align formatting and structure. +- Execute at least one relevant test script (`tclsh scriptlib/tests/.tcl`). +- Document changes impacting build, tooling, or developer workflow. + +## Build & Bootstrap Commands +- **Primary build**: `tclsh make.tcl` (Windows default) or `punk make.tcl` inside Punk shell. +- **Alt entry point**: `punk build.tcl` or `tclsh build.tcl` for kettle-style builds. +- **Bootstrap shell**: `pmix KettleShell` from inside Punk shell for advanced packaging tasks. +- **Clean/resync**: Remove `build/` artifacts then rerun `tclsh make.tcl`; avoid partial cleans that break boot modules. +- **Binary images**: Use `punk make.tcl --target ` when producing platform-specific bundles (see script comments for targets). + +## Testing Strategy +- **Test location**: `scriptlib/tests/` holds all Tcl test scripts; keep new tests there. +- **Run entire suite**: Iterate with `for /r scriptlib\tests %f in (*.tcl) do tclsh %f` (Windows) or a similar shell loop on POSIX. +- **Run single test**: `tclsh scriptlib/tests/.tcl` (e.g., `tclsh scriptlib/tests/json.tcl`). +- **Focused verification**: Mirror production pipelines inside tests using Punk pipeline syntax for parity. +- **Test dependencies**: Every test must `package require punk`; declare extra packages explicitly to avoid hidden dependencies. +- **Failure triage**: Capture stderr logs; prefer `try/on error` blocks inside tests for clearer diagnostics. + +## Linting & Formatting +- **Command**: `tclint` (configured via `tclint.toml` in repo root). +- **Files covered**: `.tcl`, `.tm`, `.sdc`; extend config if new extensions appear. +- **Line length**: Hard cap at 400 characters; wrap pipelines thoughtfully instead of exceeding. +- **Blank lines**: No more than 10 consecutive blanks. +- **Indentation**: 4 spaces; tabs are disallowed in Tcl sources except inside string literals. +- **Auto-fixes**: Run `tclint --fix` only when you have reviewed the resulting diff. + +## Toolchain & Dependencies +- Prefer the provided vendor modules under `src/vendor*` before fetching new dependencies. +- Use `tcl::tm::path add ` to surface project modules when writing new tooling. +- Keep compatibility with Tcl 8.6+; gate 9.0-specific features behind version checks. +- When optional compiled extensions (e.g., `twapi`, `tdom`) are necessary, guard `package require` calls with fallback messaging. + +## Repository Layout Primer +``` +src/ + bootsupport/modules/ # Early-load modules with minimal deps + modules/ # Main Punk modules (.tm) + lib/ # Classic Tcl libraries + scriptapps/ # Entry-point scripts for Punk apps + vendormodules*/ # Third-party modules bundled with repo +scriptlib/ # Shared utilities + tests +bin/ # Helper binaries/scripts +callbacks/, plugj.tcl, etc # Integration glue for host environments +src/vfs/* # Virtual file system images for builds +``` +Treat VFS directories as generated artifacts; edit them only when updating runtime payloads. + +## Imports & Package Management +- Always declare dependencies explicitly using `package require ` near file tops. +- Prefer fully-qualified namespaces when referencing external packages (`package require tcl::zlib`, `package require TclOO`). +- Organize custom modules as `namespace eval punk::` with filenames like `src/modules/punk/-.tm`. +- Use semantic versions that `package vcompare` can interpret; strip leading zeros. +- For optional features, probe with `if {[catch {package require foo}]} { ... }` and degrade gracefully. + +## Formatting & Layout Rules +- Opening braces stay on the same line for procs; multiline control structures may place braces on new lines for readability. +- Align continuations under their opening command; use explicit `\` when mapping to pipeline syntax is unclear. +- Keep pipelines readable by aligning `% var = ...` and `pipecase` segments vertically when possible. +- Document non-trivial procedures and exports with the standard header template (see below). + +## Naming Conventions +- **Procedures**: `lowercase_with_underscores` for internals, `camelCase` allowed for public APIs where existing patterns fit. +- **Variables**: `lowercase_with_underscores`; avoid single-letter names except for loop indices. +- **Namespaces**: Mirror directory structure; nested modules should reflect filesystem hierarchy. +- **Private helpers**: Prefix with `_` (e.g., `_resolve_stream`); do not export them. +- **Constants**: `UPPER_CASE_WITH_UNDERSCORES` declared via `namespace eval { variable CONSTANT value }` when practical. + +## Procedure Documentation Template +```tcl +# +# Args: +# arg1 - description +# arg2 - description +# Returns: +# Description of return value +proc procedure_name {arg1 arg2} { + # Implementation +} +``` +Update the template with concrete details whenever functions are user-facing or complex. + +## Error Handling & Logging +- Prefer `try { ... } on error {result options} { ... }` (Tcl 8.6+) for structured handling. +- Fallback pattern: + ```tcl + if {[catch {some_command} result]} { + puts stderr "Error: $result" + return -code error $result + } + ``` +- For Punk pipelines, wrap risky commands inside `pipecase` blocks and emit descriptive messages via `puts stderr` or Punk logging helpers. +- Never swallow errors silently; propagate with context so shell users see actionable details. + +## Pipeline & Functional Style Notes +- Use `% var = ...` bindings to capture intermediate values; keep names meaningful. +- `pipecase` should list specific patterns before catch-alls to avoid hidden matches. +- `fun name pattern { ... }` definitions should remain side-effect light; treat them as pure functions unless otherwise documented. +- Keep pipelines short and composable; extract into helper procs or `fun` definitions when they exceed ~10 logical steps. + +## Module Structure Expectations +```tcl +# Module description +package require + +namespace eval { + variable version + namespace export public_proc1 public_proc2 + + proc public_proc1 {args} { + # Implementation + } + + proc _private_helper {} { + # Private implementation + } +} +``` +Ensure module filenames include the version (`punk/console-0.1.1.tm`), and keep `namespace export` lists alphabetized for clarity. + +## Type & Data Handling +- Tcl is dynamically typed; emulate structural typing via argument validation at proc boundaries. +- Validate user inputs with `switch -exact`, `regexp`, or Punk pipeline predicates before mutation. +- Use dictionaries for structured data; avoid parallel lists. +- When bridging to binary data (e.g., ANSI/xbin parsing), document expected encodings and conversions. + +## Versioning & Releases +- Stick to semantic versioning (`major.minor.patch`). +- When referencing ranges, use bounded specs (e.g., `1.2.3-2.0.0`). +- Convert loose versions to bounded form in module metadata; helper utilities exist in boot modules for this purpose. +- Update `punk::libunknown` registries whenever adding/removing modules to keep discovery accurate. + +## Platform & Performance Notes +- Primary target: Windows (win32-x86_64). Validate code paths that rely on Windows-only packages. +- Secondary targets: Linux/macOS/FreeBSD; guard platform-specific calls with `if {$tcl_platform(os) eq "Windows"} {...}`. +- Favor compiled extensions (tcllibc, twapi) when available, but always provide scripted fallbacks. +- Be mindful of long-running pipelines; chunk work and avoid blocking the Punk shell UI thread. + +## Documentation & Comments +- Keep inline comments concise; describe intent, not mechanics. +- Update any relevant docs or usage notes in `scriptapps` when behavior changes. +- Mention environment variables or flags required to run new features. + +## Agent Workflow Tips +- No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` files exist as of this update. If they appear later, integrate their instructions here. +- Always re-run `tclint` and the most specific test affected by your changes before committing. +- When touching VFS payloads, describe regeneration steps inside commit messages and this guide if persistent. +- Favor incremental commits tied to logical units of work; avoid monolithic diffs mixing tooling and feature changes. + +## Final Submission Checklist +- [ ] Nested `AGENTS.md` files checked for scope-specific rules. +- [ ] `tcllint` (and `tcllint --fix` if needed) executed with clean results. +- [ ] Relevant tests (`tclsh scriptlib/tests/.tcl`) executed and passing. +- [ ] Build step (`tclsh make.tcl`) verified when touching build-critical code. +- [ ] Documentation/comments updated for new behavior or flags. +- [ ] Diffs reviewed to ensure no stray whitespace or debugging output remains. + +Adhering to these conventions keeps the ShellSpy/Punk Shell ecosystem consistent, portable, and friendly for future agentic collaborators. Happy hacking! diff --git a/bin/getzig.cmd b/bin/getzig.cmd index 68591b2d..d3c3f7f7 100644 --- a/bin/getzig.cmd +++ b/bin/getzig.cmd @@ -432,21 +432,27 @@ SETLOCAL EnableDelayedExpansion @REM batch file library functions @GOTO :endlib -@REM padding -@REM padding -@REM padding -@REM padding +@REM padding xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +@REM padding xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx %= ---------------------------------------------------------------------- =% @rem courtesy of dbenham :: Example usage @rem call :getUniqueFile "d:\test\myFile" ".txt" myFile @rem echo myFile="%myFile%" - +@rem 2025 - wmic deprecated :/ +@rem 2025 - output of 'wmic os get localDateTime' was something like: +@rem LocalDateTime +@rem 20251015234316.777000+660 +@rem !time! has a resolution of centiseconds. As we test in a loop for file existence, that should be ok. :getUniqueFile baseName extension rtnVar -setlocal +setlocal enabledelayedexpansion :getUniqueFileLoop -for /f "skip=1" %%A in ('wmic os get localDateTime') do for %%B in (%%A) do set "rtn=%~1_%%B%~2" +set "r=!date!_!time!" +set "r=%r::=.%" +set "r=%r: =%" +set "rtn=%~1_!r!%~2" +echo "### %rtn%" if exist "%rtn%" ( goto :getUniqueFileLoop ) else ( @@ -454,6 +460,7 @@ if exist "%rtn%" ( ) endlocal & set "%~3=%rtn%" exit /b + %= ---------------------------------------------------------------------- =% @REM padding @@ -1273,13 +1280,13 @@ fi # #mkdir -p ./zig -#tarball="zig-x86_64-windows-0.15.1.zip" -#tarball="zig-x86_64-freebsd-0.15.1.tar.xz" -tarball="zig-x86_64-linux-0.15.1.tar.xz" +#tarball="zig-x86_64-windows-0.15.2.zip" +#tarball="zig-x86_64-freebsd-0.15.2.tar.xz" +tarball="zig-x86_64-linux-0.15.2.tar.xz" automation_name="punkshell+julian@precisium.com.au_target_by_latency" uristring="https://ziglang.org" -full_uristring="${uristring}/download/0.15.1/${tarball}?source=${automation_name}" +full_uristring="${uristring}/download/0.15.2/${tarball}?source=${automation_name}" echo "Unimplemented: Download from ${full_uristring} and extract manually" #wget $full_uristring -O ./zig/zig-linux-x86_64-0.10.1.tar.xz #tar -xf ./zig/zig-linux-x86_64-0.10.1.tar.xz -C ./zig --strip-components=1 @@ -1598,7 +1605,9 @@ if (-not(Test-Path -Path $toolsfolder -PathType Container)) { $zigfolder = Join-Path $toolsfolder -ChildPath "zig" $zigexe = Join-Path $zigfolder -ChildPath "zig.exe" # $releasearchive = "zig-x86_64-windows-0.15.1.zip" ;#zip on windows, tarball on every other platform -$releasearchive = "zig-x86_64-windows-0.16.0-dev.254+6dd0270a1.zip" +#$releasearchive = "zig-x86_64-windows-0.16.0-dev.254+6dd0270a1.zip" +#$releasearchive = "zig-x86_64-windows-0.16.0-dev.2193+fc517bd01.zip" +$releasearchive = "zig-x86_64-windows-0.15.2.zip" Write-Output "powershell version: $($PSVersionTable.PSVersion)" if (Get-Command $zigexe -ErrorAction SilentlyContinue) { @@ -1997,3 +2006,4 @@ no script engine should try to run me + diff --git a/src/bootsupport/modules/include_modules.config b/src/bootsupport/modules/include_modules.config index 0ea2f344..8ea31ad3 100644 --- a/src/bootsupport/modules/include_modules.config +++ b/src/bootsupport/modules/include_modules.config @@ -19,6 +19,7 @@ set bootsupport_modules [list\ src/vendormodules metaface\ src/vendormodules modpod\ src/vendormodules overtype\ + src/vendormodules packagetest\ src/vendormodules pattern\ src/vendormodules patterncmd\ src/vendormodules patternlib\ diff --git a/src/bootsupport/modules/packagetest-0.1.7.tm b/src/bootsupport/modules/packagetest-0.1.7.tm new file mode 100644 index 0000000000000000000000000000000000000000..658d45a4e571209317a9621e1b6a1f037186fe5f GIT binary patch literal 12090 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 C6_b(x literal 0 HcmV?d00001 diff --git a/src/bootsupport/modules/patterncipher-0.1.1.tm b/src/bootsupport/modules/patterncipher-0.1.1.tm index 62b03cbc..0aa12476 100644 --- a/src/bootsupport/modules/patterncipher-0.1.1.tm +++ b/src/bootsupport/modules/patterncipher-0.1.1.tm @@ -37,7 +37,6 @@ package provide patterncipher [namespace eval patterncipher { package require ascii85 ;#tcllib package require pattern -::pattern::init ;# initialises (if not already) namespace eval ::patterncipher { namespace eval algo::txt { diff --git a/src/bootsupport/modules/punk/lib-0.1.5.tm b/src/bootsupport/modules/punk/lib-0.1.5.tm index db369f06..5138ac6d 100644 --- a/src/bootsupport/modules/punk/lib-0.1.5.tm +++ b/src/bootsupport/modules/punk/lib-0.1.5.tm @@ -2370,6 +2370,54 @@ namespace eval punk::lib { append body [info body is_list_all_ni_list2] proc is_list_all_ni_list2 {a b} $body } + proc is_cachedlist_all_ni_list {a b} { + upvar 0 ::punk::lib::caches::funcs_ni_list funcs + if {[info exists funcs($a)]} { + return [[set funcs($a)] $b] + } + set keybytes [encoding convertto utf-8 $a] + set key [binary encode base64 $keybytes] ;#one single-line base64 string + + set expression "" + foreach t $a { + #append expression "({$t} ni \$b) && " + append expression "{$t} ni \$b && " + } + set expression [string trimright $expression " &"] ;#trim trailing spaces and ampersands + proc ::punk::lib::caches::ni_list_$key {b} [string map [list @expression@ $expression] { + return [expr {@expression@}] + }] + + set funcs($a) ::punk::lib::caches::ni_list_$key + return [punk::lib::caches::ni_list_$key $b] + } + proc is_cachedlist_all_ni_list2 {a b} { + upvar 0 ::punk::lib::caches::funcs_ni_list funcs + if {[info exists funcs($a)]} { + return [[set funcs($a)] $b] + } + set keybytes [encoding convertto utf-8 $a] + set key [binary encode base64 $keybytes] ;#one single-line base64 string + + set d [dict create] + foreach x $a { + dict set d $x "" + } + #constructing a switch statement could be an option + # - but would need to avoid using escapes in order to get a jump-table + # - this would need runtime mapping of values - unlikely to be a win + proc ::punk::lib::caches::ni_list_$key {b} [string map [list @d@ $d] { + foreach x $b { + if {[::tcl::dict::exists {@d@} $x]} { + return 0 + } + } + return 1 + }] + + set funcs($a) ::punk::lib::caches::ni_list_$key + return [punk::lib::caches::ni_list_$key $b] + } namespace eval argdoc { variable PUNKARGS @@ -5389,6 +5437,10 @@ tcl::namespace::eval punk::lib::system { #[list_end] [comment {--- end definitions namespace punk::lib::system ---}] } +tcl::namespace::eval punk::lib::caches { + +} + tcl::namespace::eval punk::lib::debug { proc showdict {args} {} } diff --git a/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm b/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm index bcf2221f..59f23842 100644 --- a/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm +++ b/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm @@ -170,7 +170,16 @@ namespace eval punk::mix::commandset::module { @values -min 1 -max 1 module -type string -help\ "Name of module, possibly including a namespace and/or version number - e.g mynamespace::mymodule-1.0" + e.g mynamespace::mymodule-1.0 + + Some templates may require a prefixing namespace in order to function + correctly. e.g punk.test module names should be of the form + test::mymodule or test::mymodule::mycomponent + where the modules under test are mymodule and mymodule::mycomponent. + For example with test module test::a::b::c + The 'pkg' and 'pkgunprefixed' placeholders (surrounded by % char) are + filled with test::a::b::c and a::b::c respectively. + " }] proc new {args} { set year [clock format [clock seconds] -format %Y] @@ -222,6 +231,9 @@ namespace eval punk::mix::commandset::module { } else { set modulename $module } + #normalize modulename to remove any leading :: in case it was supplied that way + set modulename [string trimleft $modulename :] + punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" if {[regexp {[A-Z]} $module]} { @@ -410,7 +422,11 @@ namespace eval punk::mix::commandset::module { #for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern #Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens - set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version] + #JJJ + set pkg_parts [punk::ns::nsparts $modulename] ;#(modulename known not to have leading :: at this point) + set pkg_unprefixed [join [lrange $pkg_parts 1 end] ::] + #pkg_unprefixed may be empty - irrelevant for most templates but not ok for punk.test template - but where to reject? review + set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename pkgunprefixed $pkg_unprefixed year $year license $opt_license authors $opt_authors version $infile_version] set strmap [list] foreach {tag val} $tagnames { lappend strmap %$tag% $val diff --git a/src/bootsupport/modules/punk/repl-0.1.2.tm b/src/bootsupport/modules/punk/repl-0.1.2.tm index 0272500d..486720e8 100644 --- a/src/bootsupport/modules/punk/repl-0.1.2.tm +++ b/src/bootsupport/modules/punk/repl-0.1.2.tm @@ -479,7 +479,13 @@ proc repl::start {inchan args} { puts stderr "-->repl::start active on $inchan $args replthread:[thread::id] codethread:$codethread" set prompt_config [punk::repl::get_prompt_config] doprompt "P% " - chan event $inchan readable [list [namespace current]::repl_handler $inchan $prompt_config] + if {[llength $input_chunks_waiting($inchan)]} { + set readmore 0 + uplevel #0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + #after 0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + } + set readmore 1 + chan event $inchan readable [list [namespace current]::repl_handler $inchan $readmore $prompt_config] set reading 1 #catch { @@ -530,6 +536,22 @@ proc repl::start {inchan args} { #puts stderr "__> returning 0" return 0 } + +#put a script into the waiting buffer for evaluation +proc repl::submit {inputchan script} { + set prompt_config [punk::repl::get_prompt_config] + upvar ::punk::console::input_chunks_waiting input_chunks_waiting + if {[info exists input_chunks_waiting($inputchan)] && [llength $input_chunks_waiting($inputchan)]} { + set last [lindex $input_chunks_waiting($inputchan) end] + append last $script + lset input_chunks_waiting($inputchan) end $last + } else { + set input_chunks_waiting($inputchan) [list $script] + } + + #set readmore 0 + #after idle [list after 0 [list ::repl::repl_handler $inputchan $readmore $prompt_config]] +} proc repl::post_operations {} { if {[info exists ::repl::post_script] && [string length $::repl::post_script]} { #put aside post_script so the script has the option to add another post_script and restart the repl @@ -1384,7 +1406,10 @@ proc punk::repl::repl_handler_restorechannel_if_not_eof {inputchan previous_inpu } return [chan conf $inputchan] } -proc repl::repl_handler {inputchan prompt_config} { +proc repl::repl_handler {inputchan readmore prompt_config} { + #readmore set to zero used to process input_chunks_waiting without reading inputchan, + # and without rescheduling reader + # -- review variable in_repl_handler set in_repl_handler [list $inputchan $prompt_config] @@ -1451,115 +1476,132 @@ proc repl::repl_handler {inputchan prompt_config} { set waitingchunk [lindex $waitinglines end] # -- #set chunksize [gets $inputchan chunk] - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- - if {$chunksize > 0} { - if {[string index $chunk end] eq "\n"} { - lappend stdinlines $waitingchunk[string range $chunk 0 end-1] - #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" - - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - set input_chunks_waiting($inputchan) [list $allwaiting] - lappend input_chunks_waiting($inputchan) $chunk - } + if {!$readmore} { + set chunk "" + set chunksize 0 + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + set input_chunks_waiting($inputchan) [list $waitingchunk] } else { - #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' - if {[chan blocked $inputchan]} { - #REVIEW - - #todo - figure out why we're here. - #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) - #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? - ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows - set outconf [chan configure stdout] - set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] - if {"windows" eq $::tcl_platform(platform)} { - set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + set chunk [read $inputchan] + set chunksize [string length $chunk] + if {$chunksize > 0} { + if {[string index $chunk end] eq "\n"} { + lappend stdinlines $waitingchunk[string range $chunk 0 end-1] + #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" + + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] } else { - set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" + set input_chunks_waiting($inputchan) [list $allwaiting] + lappend input_chunks_waiting($inputchan) $chunk } - set cols "" - set rows "" - if {[dict exists $outconf -winsize]} { - lassign [dict get $outconf -winsize] cols rows - } else { - #fallback - try external executable. Which is a bit ugly - #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) - - #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 - #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! - #stty -a is 400ms+ vs 500us+ on FreeBSD - + } else { + if {[chan blocked $inputchan]} { + #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' + #REVIEW - + #todo - figure out why we're here. + #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) + #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? + ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows + set outconf [chan configure stdout] + set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] if {"windows" eq $::tcl_platform(platform)} { - set tputcmd [auto_execok tput] - if {$tputcmd ne ""} { - if {![catch {exec {*}$tputcmd cols lines} values]} { - lassign $values cols rows - } - } + set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + } else { + set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" } + set cols "" + set rows "" + if {[dict exists $outconf -winsize]} { + lassign [dict get $outconf -winsize] cols rows + } else { + #fallback1 query terminal + if {![catch {punk::console::get_size} sdict]} { + set cols [dict get $sdict columns] + set rows [dict get $sdict rows] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + + #fallback2 - try external executable. Which is a bit ugly + #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) + + #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 + #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! + #stty -a is 400ms+ vs 500us+ on FreeBSD - if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { - #same for all platforms? tested on windows, wsl, FreeBSD - #exec stty -a gives a result on the first line like: - #speed xxxx baud; rows rr; columns cc; - #review - more robust parsing - do we know it's first line? - set sttycmd [auto_execok stty] - if {$sttycmd ne ""} { - #the more parseable: stty -g doesn't give rows/columns - if {![catch {exec {*}$sttycmd -a} result]} { - lassign [split $result \n] firstline - set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? - set rowinfo [lsearch -index end -inline $lineparts rows] - if {[llength $rowinfo] == 2} { - set rows [lindex $rowinfo 0] + if {"windows" eq $::tcl_platform(platform)} { + set tputcmd [auto_execok tput] + if {$tputcmd ne ""} { + if {![catch {exec {*}$tputcmd cols lines} values]} { + lassign $values cols rows + } } - set colinfo [lsearch -index end -inline $lineparts columns] - if {[llength $colinfo] == 2} { - set cols [lindex $colinfo 0] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + #same for all platforms? tested on windows, wsl, FreeBSD + #exec stty -a gives a result on the first line like: + #speed xxxx baud; rows rr; columns cc; + #review - more robust parsing - do we know it's first line? + set sttycmd [auto_execok stty] + if {$sttycmd ne ""} { + #the more parseable: stty -g doesn't give rows/columns + if {![catch {exec {*}$sttycmd -a} result]} { + lassign [split $result \n] firstline + set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? + set rowinfo [lsearch -index end -inline $lineparts rows] + if {[llength $rowinfo] == 2} { + set rows [lindex $rowinfo 0] + } + set colinfo [lsearch -index end -inline $lineparts columns] + if {[llength $colinfo] == 2} { + set cols [lindex $colinfo 0] + } + } } } } } - } - if {[string is integer -strict $cols] && [string is integer -strict $rows]} { - #got_dimensions - todo - try spinner? - #puts -nonewline stdout [punk::ansi::move $rows 4]$msg - #use cursorsave_ version which avoids get_cursor_pos_list call - set msglen [ansistring length $msg] - punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 - } else { - #no mechanism to get console dimensions - #we are reduced to continuously spewing lines. - puts stderr $msg - } + if {[string is integer -strict $cols] && [string is integer -strict $rows]} { + #got_dimensions - todo - try spinner? + #puts -nonewline stdout [punk::ansi::move $rows 4]$msg + #use cursorsave_ version which avoids get_cursor_pos_list call + set msglen [ansistring length $msg] + punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 + } else { + #no mechanism to get console dimensions + #we are reduced to continuously spewing lines. + puts stderr $msg + } - after 100 + after 100 + } + set input_chunks_waiting($inputchan) [list $allwaiting] } - set input_chunks_waiting($inputchan) [list $allwaiting] } - + # -- } else { - punk::repl::repl_handler_checkchannel $inputchan - punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan - # -- --- --- - #set chunksize [gets $inputchan chunk] - # -- --- --- - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- --- --- - if {$chunksize > 0} { - #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" - set ln $chunk ;#temp - #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] - if {[string index $ln end] eq "\n"} { - lappend stdinlines [string range $ln 0 end-1] - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - lappend input_chunks_waiting($inputchan) $ln + if {$readmore} { + punk::repl::repl_handler_checkchannel $inputchan + punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan + # -- --- --- + #set chunksize [gets $inputchan chunk] + # -- --- --- + set chunk [read $inputchan] + set chunksize [string length $chunk] + # -- --- --- + if {$chunksize > 0} { + #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" + set ln $chunk ;#temp + #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] + if {[string index $ln end] eq "\n"} { + lappend stdinlines [string range $ln 0 end-1] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + } else { + lappend input_chunks_waiting($inputchan) $ln + } } } } @@ -1582,19 +1624,21 @@ proc repl::repl_handler {inputchan prompt_config} { } if {$continue} { - if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { - chan configure $inputchan -blocking 0 - chan configure $inputchan -translation lf - } - set chunk [read $inputchan] - #we expect a chan configured with -blocking 0 to be blocked immediately after reads - #test - just bug console for now - try to understand when/how/if a non blocking read occurs. - if {![chan blocked $inputchan]} { - puts stderr "repl_handler->$inputchan not blocked after read" - } + if {$readmore} { + if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { + chan configure $inputchan -blocking 0 + chan configure $inputchan -translation lf + } + set chunk [read $inputchan] + #we expect a chan configured with -blocking 0 to be blocked immediately after reads + #test - just bug console for now - try to understand when/how/if a non blocking read occurs. + if {![chan blocked $inputchan]} { + puts stderr "repl_handler->$inputchan not blocked after read" + } - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + } while {[llength $input_chunks_waiting($inputchan)]} { set chunkzero [lpop input_chunks_waiting($inputchan) 0] if {$chunkzero eq ""} {continue} ;#why empty waiting - and is there any point passing on? @@ -1604,33 +1648,35 @@ proc repl::repl_handler {inputchan prompt_config} { } } - if {![chan eof $inputchan]} { - ################################################################################## - #Re-enable channel read handler only if no waiting chunks - must process in order - ################################################################################## - if {![llength $input_chunks_waiting($inputchan)]} { - chan event $inputchan readable [list ::repl::repl_handler $inputchan $prompt_config] + if {$readmore} { + if {![chan eof $inputchan]} { + ################################################################################## + #Re-enable channel read handler only if no waiting chunks - must process in order + ################################################################################## + if {![llength $input_chunks_waiting($inputchan)]} { + chan event $inputchan readable [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } else { + #review + #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" + after idle [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } + #################################################### } else { - #review - #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" - after idle [list ::repl::repl_handler $inputchan $prompt_config] - } - #################################################### - } else { - #repl_handler_checkchannel $inputchan - chan event $inputchan readable {} - set reading 0 - #target is the 'main' interp in codethread. - #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) - # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 - - thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread - if {$::tcl_interactive} { - rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" - #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + #repl_handler_checkchannel $inputchan + chan event $inputchan readable {} + set reading 0 + #target is the 'main' interp in codethread. + #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) + # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 + + thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread + if {$::tcl_interactive} { + rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" + #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + } + set [namespace current]::done 1 + after 1 [list repl::reopen_stdin] } - set [namespace current]::done 1 - after 1 [list repl::reopen_stdin] } set in_repl_handler [list] } diff --git a/src/bootsupport/modules/punk/repo-0.1.1.tm b/src/bootsupport/modules/punk/repo-0.1.1.tm index 5d2a2725..16f6f1cb 100644 --- a/src/bootsupport/modules/punk/repo-0.1.1.tm +++ b/src/bootsupport/modules/punk/repo-0.1.1.tm @@ -218,7 +218,7 @@ namespace eval punk::repo { if {$fossilcmd eq "commit"} { if {[llength [file split $fosroot]]} { if {[file exists [file join $fosroot src/buildsuites]]} { - puts stderr "Todo - check buildsites/suite/projects for current branch/tag and update download_and_build_config" + puts stderr "Todo - check buildsuites/suite/projects for current branch/tag and update download_and_build_config" } } } elseif {$fossilcmd in [list "info" "status"]} { diff --git a/src/bootsupport/modules/shellfilter-0.2.1.tm b/src/bootsupport/modules/shellfilter-0.2.1.tm index 8e59cf0b..2eb2f8fa 100644 --- a/src/bootsupport/modules/shellfilter-0.2.1.tm +++ b/src/bootsupport/modules/shellfilter-0.2.1.tm @@ -2472,7 +2472,14 @@ namespace eval shellfilter { set exitinfo [list error "$errMsg" errorCode $::errorCode errorInfo "$::errorInfo"] } } + #puts "shellfilter::run finished call" + #------------------------- + #warning - without flush stdout - we can get hang, but only on some terminals + # - mechanism for this problem not understood! + flush stdout + flush stderr + #------------------------- #the previous redirections on the underlying inchan/outchan/errchan items will be restored from the -aside setting during removal #Remove execution-time Tees from stack @@ -2480,6 +2487,7 @@ namespace eval shellfilter { shellfilter::stack::remove stderr $id_err #shellfilter::stack::remove stderr $id_in + #puts stderr "shellfilter::run complete..." #chan configure stderr -buffering line #flush stdout diff --git a/src/bootsupport/modules/shellrun-0.1.1.tm b/src/bootsupport/modules/shellrun-0.1.1.tm index 478c70fa..c3f7ab10 100644 --- a/src/bootsupport/modules/shellrun-0.1.1.tm +++ b/src/bootsupport/modules/shellrun-0.1.1.tm @@ -187,10 +187,10 @@ namespace eval shellrun { #--------------------------------------------------------------------------------------------- set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none ] #--------------------------------------------------------------------------------------------- - foreach id $idlist_stderr { shellfilter::stack::remove stderr $id } + #puts stderr "shellrun::run exitinfo: $exitinfo" flush stderr flush stdout diff --git a/src/bootsupport/modules/test/tomlish-1.1.5.tm b/src/bootsupport/modules/test/tomlish-1.1.5.tm index 3ae60d426cf6b63988005e9d06eb1aac81a2c04f..f4f2b48410448782e6616be9dba89d649618f96a 100644 GIT binary patch delta 2687 zcmZ9MdpuO@8pmheVK9b4CKA&WsfOgPQSO&Qg;SyAGG%N+#JC+Xu}Q@aJtapsmoAjc zFrjkEy`&;{Nh%$k-R02MRy*R1Me}K&_0Rf!zt8hN>s{;fS#RoHLg`%s?+6|;2vpdi zK!nTqXbLMNBruvD6B88d5ea^R$OeRuh5r)V!CX{?-%3=$lNR=m(db9pcN^af^2c1rHg@%$9f^PLbqJ{mNBrTbe3QCDqg8n}d zsYPj}rfl2^*UG`baw&02*RP$cduXrj6u%)d${OE#D@VCrm~`N_er+%ha*&l7d$Mby zwPa~<_n!5;@7TU=vt4Sqa+z#$hfhj9DZf_pscdf04PKtx3DE?rfEsdZ(BN2#!sjogvvyyH z+Q(n^w!4}&XVq{iMv7~Rx6`v*hXT$wZdZ+%uv$km4uafgc&4JUI(KBl@{X`BM(VKf9XH~}QQW0Zd(tgfC+dz4Z{L+nxm@~%iL-O1 zzQa}PCpqR>PACS{*%iEebe`#|`lroAz2c5%YcsCJw5pp*g+4r58*ntXcc(l)y;I`c za7x*0OP|ej^JH#c*DJ0zKhQsEC}z~2vR(XZ!Tn^f;*eFzpPOq;P90H594Xw?FE>sZ zxMsqdA|w>;?l5j~u+nQLn$g4eGgEQ1ewGc+pXUaz6RPFbuf~P1EW6M9xVNLJC~yrJi^WLi!rp_MY2H&#PpPXBNe%It* zzngr|xLj#l5REum+s8s0_>HCv_S#st6hwenM}i|W~MY52NWBf;uSi*04$ zHHXpHXX2xa^YhX)-sn_uEj-R{Jh#{OQ%!EzKF^PvMnB&(x?K}IIHuj%VZ5RG*Z%w% zJDm?X#d-nHDjfYPoS9=5({JLNjz>6KNSIvthl|e6#J=O()kk9`n_1%*C&v|(xFjyr z_$K9Dux(IX$+hv7^t`5hk?*%oWqD#@PZ;5`@}tSgOzyY9eq&x*btAXGCvJALskEwt zf9_Wcvo&cKg6>u1hFrsKVBDNQ5mPA9{cEgihhwi3yiZ7pv^RK)?=+`}^Jcs?ch7C= z4wN{UrNq~asp^_;!X>wUwL zR;D^AoqILgY}d=%cIk!0%;T@ql1>f;5#W_8LLN8F4P@|bA&QCT?pdSMd#-5I2`{Q%@3sie$YTjxeu}s0sIXe z5aa8ioP`*~w?_sFn0?0&$M=BuEe>L+0PB`DbTY{hXCwoL6%6$Lhmwc80zAHAu|ows zYss*)l7_TbV#?{Z1k_g=p)IR^LMhl;rH2Ak7nMA`tYRQ#wME>5`hG-lki1KQ&$so_ zfz^xEd8Gnyhk*>$e^O3}x(35erWm}mq(IeO2Fh6blTyUCMzofb5_~h^B@c(I6HtbR zu+hB62-)ZhG0Ra3qVLJTs~RO_XCTyDYt50Tp%9CmB;Z9Y15u2G%F|gE+N#MAT1Ufg zgN(X#bk;;zoVX_`n94vUraviD)NLljn0g -CQ>Nlb)OCiQJn4?`*KjBi?T}uVS zMj8^c7HSty3h){k=+uIuH(4Q?4OXQ$Ed#EXV9(7mFw~@k$`*8eGZmbg8K~73>#A?w zie7EOV$J~yc(iDv?5#q4-NHm}c8f@bh*k!^4RTw5L;rEWq_!^=PTXf8?lvJxKhQ(; z?ZOF>zfj@k0|wICfhpcYX;{~$jW#$6vAB(ijGPux9?Tyy(4RkW3#xS%RE%LdKmqLA z8R&}(Sacgfal1cCcg3RPBYkA%wirc$8%l!qM+`KzptK%)ptwC)^-kC_7=Fw^;_d=8 z1^aMy2ztUmC+E=&9*0{9kPNFkXh>@xrV2Y`;aikE%swGPX@@C#xS+K<4N>KHFp-#S(@=C<$d*KuEi<-=7AYzhPf_!$Zp$m0 zRy9#sB8rkFx@9fgii#Gc?kz3%I8CMB`R9Dz@AEw0?{m)UoO7;}ysMO4luVkyiilbr z5*8i|M<@(PKy#sq#055Tz_S}noTLn$$OKlB9pE_GPAe>gmE<#4%?eHk69@&Y>3H|* zuo$3eG2j+WOIZ*V8xs^fPV187E{ zMPkcHLu7m!sL`~eN~cn#n?fUv1*#MZ_-JW<*C*9x66%=Nng1VHm2QF$Op|32xBg<% zZ?s((_pQkJQzwj0k&#hXkdc`IRjX#C8Ve)NEbEHwa#r0qu-vUtCTwqR*4C2Hy(}vm z*-yO|p`T``)@JJP6mrVC9vnBOjl4SOovUF`7q%_0$UTN5=x=ynHvJeS+5gSl%syw= zC!X?q^?VHnA`T{r?`bWd8&WFm|26V`79!zUv2{kf&2H#{|^ z$;9AMl2Wljn#(?KziWn)1C$7nKp@sr@x#_(qu$N8Mkw8(s+g2PIF!7{$-w17;`7N=te zM>-=r`Xy^d-kwY0{X2SFxA(?d&3W7Qm2XtAdTKzm&d+?;Sd>#c=!J$vu%I z&1X&@*=urdxEt8EoZVzn&y9cRWbAux?GwfRm>K629!TCfFg%6WAa}rju=N^})lYrp z_^u_C(i5xdX)8`Mr`MJllqsFr8Nb)~x!SArlzCCM&8~)RA?uzb_x0@}C)BR-|EQ)Q zK4T&RS(HgyiXm9%gh&r>==%;e||k?2kA>V_nN2hYeylu3@Pc$ zEHyq~SM*{&1cR0r9-ha-E{@4>m zHh$$(!ciQ7rrR%jgUj>iHOie{6-WI>cPyK{H;8$0QF7hX0>3@Kb5+E9mfoZvnZ4@K zaA*%&-<~w^=^ys)O10Gd@(?E;M`?=-_hn{7f4N(rc~A>?h2;cwafr5vvoCnS=c(w5CH~xjhpzcP2T-)jX_pOOWU$*`~45i z-cJAgbn?56^TMe%Tu1Q#>i?L{EEIPtGb6IG-h#~G29QkmkSQdE2a}b#+MJsez zR~nTdp@IQ%B~0Wxl~7k~Rp50Ah61KZQ43NpVQ8x!fmwD+P;x~T)Guozi|JCO0oj)^ z^zs`Tm8yc~6$_N4Ph2%BW#SDOX(9AoIf#sA67qH_2Z?`^Mip3D#zwO+0(;9Wk=Rfg zG>hJ1Nqk=awR{tC(nT9-$6ZnUMnFPE{Ie=0Z!AJD66};nFn> zh0m9w3(|EYN)!E6ApM3a6#6SG%Zxh;#xA=7zs3exI!Pl9{=Gg4zSL-=TVv{Xun(jX z&0DW)qleB?r9w=jbc2aLxZuhgufRwb;QWoh2TR=puIu4R?BVs|FDa!vA z4R3OgwL4)&YZ%aU6GP4mapc3{P$gJ=3qvl8e&E?Kd3;9bFB%`5N`vfEf~`9|7lgAH*2iR8iE*460ac;y7WcQGVd`J<|LgALJ~`4r)ZK0NNp*I91fe?lF=Vg_{jn6+pW<*>xo#_&OsSM zB6eh{0j~o?*W(GCkjQ}BI1VQfxZjKhyiR=BO$0s{sX}chMiRhqXFTf4BxF|ML@4eu kL3X>Pr~z}kF~rEmUj#D{b=#u#c|??dz@nz_8~X-buildversion.txt +# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.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 %pkg% 999999.0a1.0 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + +package require Tcl 8.6- + +tcl::namespace::eval %pkg% { + variable PUNKARGS + variable pkg %pkg% + variable version + set version 999999.0a1.0 + + package require packagetest + packagetest::makeAPI %pkg% $version %pkgunprefixed%; #will package provide %pkg% $version + package forget %pkgunprefixed% + package require %pkgunprefixed% +} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval %pkg% { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)%pkg%" + @package -name "%pkg%" -help\ + "Test suites for %pkgunprefixed% module" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return %pkg% + } + 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 %pkg% + test suite for %pkgunprefixed% module + } \n] + } + proc get_topic_License {} { + return "MIT" + } + proc get_topic_Version {} { + return "$::%pkg%::version" + } + proc get_topic_Contributors {} { + set authors {{ Julian Noble}} + 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 "::%pkg%::about" + dict set overrides @cmd -name "%pkg%::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About %pkg% module + }] \n] + dict set overrides topic -choices [list {*}[%pkg%::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [%pkg%::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 ::%pkg%::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::%pkg%::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 ::%pkg% +} +# ----------------------------------------------------------------------------- + +package provide %pkg% [tcl::namespace::eval %pkg% { + variable pkg %pkg% + variable version + set version 999999.0a1.0 +}] +## Ready +return diff --git a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/utility/scriptappwrappers/multishell.cmd b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/utility/scriptappwrappers/multishell.cmd index dbe4c1d4..abed8cf5 100644 --- a/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/utility/scriptappwrappers/multishell.cmd +++ b/src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/utility/scriptappwrappers/multishell.cmd @@ -432,10 +432,8 @@ SETLOCAL EnableDelayedExpansion @REM batch file library functions @GOTO :endlib -@REM padding -@REM padding -@REM padding -@REM padding +@REM padding xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +@REM padding xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx %= ---------------------------------------------------------------------- =% @rem courtesy of dbenham @@ -1617,3 +1615,4 @@ no script engine should try to run me #> + diff --git a/src/modules/punk/mix/commandset/module-999999.0a1.0.tm b/src/modules/punk/mix/commandset/module-999999.0a1.0.tm index 011ae58e..39819b6a 100644 --- a/src/modules/punk/mix/commandset/module-999999.0a1.0.tm +++ b/src/modules/punk/mix/commandset/module-999999.0a1.0.tm @@ -170,7 +170,16 @@ namespace eval punk::mix::commandset::module { @values -min 1 -max 1 module -type string -help\ "Name of module, possibly including a namespace and/or version number - e.g mynamespace::mymodule-1.0" + e.g mynamespace::mymodule-1.0 + + Some templates may require a prefixing namespace in order to function + correctly. e.g punk.test module names should be of the form + test::mymodule or test::mymodule::mycomponent + where the modules under test are mymodule and mymodule::mycomponent. + For example with test module test::a::b::c + The 'pkg' and 'pkgunprefixed' placeholders (surrounded by % char) are + filled with test::a::b::c and a::b::c respectively. + " }] proc new {args} { set year [clock format [clock seconds] -format %Y] @@ -222,6 +231,9 @@ namespace eval punk::mix::commandset::module { } else { set modulename $module } + #normalize modulename to remove any leading :: in case it was supplied that way + set modulename [string trimleft $modulename :] + punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" if {[regexp {[A-Z]} $module]} { @@ -410,7 +422,11 @@ namespace eval punk::mix::commandset::module { #for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern #Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens - set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version] + #JJJ + set pkg_parts [punk::ns::nsparts $modulename] ;#(modulename known not to have leading :: at this point) + set pkg_unprefixed [join [lrange $pkg_parts 1 end] ::] + #pkg_unprefixed may be empty - irrelevant for most templates but not ok for punk.test template - but where to reject? review + set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename pkgunprefixed $pkg_unprefixed year $year license $opt_license authors $opt_authors version $infile_version] set strmap [list] foreach {tag val} $tagnames { lappend strmap %$tag% $val diff --git a/src/modules/punk/net/vxlan-999999.0a1.0.tm b/src/modules/punk/net/vxlan-999999.0a1.0.tm new file mode 100644 index 00000000..f1598682 --- /dev/null +++ b/src/modules/punk/net/vxlan-999999.0a1.0.tm @@ -0,0 +1,365 @@ +# -*- tcl -*- +# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from -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) 2026 +# +# @@ Meta Begin +# Application punk::net::vxlan 999999.0a1.0 +# Meta platform tcl +# Meta license MIT +# @@ Meta End + + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Requirements +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + +package require Tcl 8.6- + + + +tcl::namespace::eval punk::net::vxlan { + variable PUNKARGS + + #todo - ipv6 - rename functions ipv4_vni_to_mcast ipv6_vni_to_mcast etc? + #IPv6 uses FF00::/8 + + lappend PUNKARGS [list { + @id -id "::punk::net::vxlan::vni_to_mcast" + @cmd -name "punk::net::vxlan::vni_to_mcast" -help\ + "Map a VXLAN VNI to a unique multicast address. + + The entire IPv4 multicast range is 224.0.0.0 - 239.255.255.255, + The upper end 239.0.0.0 - 239.255.255.255 is classified by + IANA as 'administratively scoped' (RFC 2365). + + The 239.0.0.0/8 range is 24 bits and *may* be available for VXLANs. + vni_to_mcast will map the VNI into an address in this /8 range. + + The range 239.192.0.0/14 is defined by RFC 2365 to be the + 'IPv4 Organization Local Scope' and it may be desirable to use + mappings that fall only within this range. + + Some vendors put restrictions on acceptable VNI values e.g + Cisco supports VNI values from 4096 to 16,777,215. + + 2 ranges within 239.0.0.0/8 are best avoided if it is desired + to reduce flooding by layer 2 switches and possible additional + processor load at VTEPs. + These are: + 239.0.0.0/24 (VNI 0 - 255) + and + 239.128.0.0/24 (VNI 8388608 - 8388863) + These happen to map to the same MAC address range (01:00:5e:00:00:xx) + as multicast addresses in the Link-Local Block (224.0.0.0/24) + These are commonly flooded to all ports on the switch even when IGMP + snooping is enabled (protocols such as OSPF would break if such flooding + wasn't done, as IGMP Membership Reports are normally not sent for multicast + traffic in the Link-Local Block). + " + @leaders -min 0 -max 0 + @opts -min 0 -max 0 + @values -min 1 -max 1 + vni -type integer -range {0 16777215} -help\ + "Integer representing a 24 bit VNI" + }] + proc vni_to_mcast {vni} { + if {![string is integer -strict $vni] || $vni < 0 || $vni > (2**24-1)} { + error "vni_to_mcast: VNI must be a 24bit integer i.e the range is 0 to 16777215" + } + set hex6 [format %6.6llx $vni] + set mcast "239." + foreach {h1 h2} [split $hex6 ""] { + append mcast [scan $h1$h2 %llx] . + } + set mcast [string range $mcast 0 end-1] + return $mcast + } + lappend PUNKARGS [list { + @id -id "::punk::net::vxlan::mcast_to_vni" + @cmd -name "punk::net::vxlan::mcast_to_vni" -help\ + "Return an integer VNI in the range 0 to 16777215" + @leaders -min 0 -max 0 + @opts -min 0 -max 0 + @values -min 1 -max 1 + mcastaddress -type string -help\ + "Multicast address within the 239.0.0.0/8 range. + See vni_to_mcast for notes about possible values + within the range to avoid." + }] + proc mcast_to_vni {mcastaddress} { + #todo - validate ipv4 + set addrparts [split $mcastaddress .] + set tailparts [lassign $addrparts p1] + if {$p1 ne "239"} { + error "mcast_to_vni: mcastaddress must be of the form 239.x.x.x" + } + #e.g mcastaddress: 239.188.97.78 + set hex "" + foreach tp $tailparts { + append hex [format %2.2llx $tp] + } + #e.g hex: bc614e + #e.g return: 12345678 + return [scan $hex %llx] + } + + #reference + #https://networklessons.com/multicast/multicast-ip-address-to-mac-address-mapping + + lappend PUNKARGS [list { + @id -id "::punk::net::vxlan::mcast_to_mac" + @cmd -name "punk::net::vxlan::mcast_to_mac" -help\ + "Return the MAC address this IPv4 multicast address + maps to. + Note that there will be a total of 32 addresses that + map to this same MAC address. + (see mac_to_mcast_list)" + @leaders -min 0 -max 0 + @opts -min 0 -max 0 + @values -min 1 -max 1 + mcastaddress -type string -help\ + "Multicast IPv4 address. + 224.0.0.0 to 239.255.255.255 + (224.0.0.0/4" + }] + proc mcast_to_mac {mcastaddress} { + set mac "01:00:5e:" ;#prefix for IANA reserved OUI covering the first 24 bits of 48bit mac address + #we can only use the last 23 bits from the mcastaddress + set addrparts [split $mcastaddress .] + set tailbin "" ;#binary representation of last 3 dotted parts + set p1 [lindex $addrparts 0] + if {$p1 < 224 || $p1 > 239} { + error "mcast_to_mac: address $mcastaddress does not seem to be an IPv4 multicast address" + } + foreach p [lrange $addrparts 1 end] { + append tailbin [format %8.8b $p] + } + # + set last23bits [string range $tailbin 1 end] + set tailbits "0$last23bits" + foreach {b0 b1 b2 b3 b4 b5 b6 b7} [split $tailbits ""] { + set nibble1 [scan $b0$b1$b2$b3 %b] + set nibble2 [scan $b4$b5$b6$b7 %b] + append mac "[format %x $nibble1][format %x $nibble2]:" + } + set mac [string range $mac 0 end-1] + #e.g mcastaddress: 224.132.6.17 + #result: 01:00:5e:04:06:11 + return $mac + } + #This is not a unique mapping there is 1:32 overlap + #because 5 bits are lost in the mapping + #ie there 32 multicast addresses mapping to the same mac + lappend PUNKARGS [list { + @id -id "::punk::net::vxlan::mac_to_mcast_list" + @cmd -name "punk::net::vxlan::mac_to_mcast_list" -help\ + "Return a list of the 32 multicast IPv4 addresses that + correspond to a multicast MAC address. + This is not a unique mapping because 5 bits are lost in + the process. + If a host is on a network with a lot of multicast traffic in + groups that happen to overlap with the same multicast address to MAC + mapping - there may be some additional overhead in ignoring non-relevant + frames." + @leaders -min 0 -max 0 + @opts -min 0 -max 0 + @values -min 1 -max 1 + mac -type string -help\ + "Mac address in the form 01:00:5e:xx:xx:xx or 01005exxxxxx. + The prefix 01:00:5e is the IANA reserved OUI for multicast MAC addresses. + (upper case versions of hex are also accepted)" + }] + proc mac_to_mcast_list {mac} { + #e.g 01:00:5e:0b:01:02 or 01005e0b0101 + #set bin_OUI "00000010000000001011110" + if {[string is xdigit -strict $mac] && [string length $mac] == 12} { + set mac_oui [string range $mac 0 5] + set mactailhex [string range $mac 6 end] + } else { + if {[string first : $mac] >=0} { + set macparts [split $mac :] + if {[llength $macparts] != 6} { + error "mac_to_mcast_list: mac address must have 6 parts (48bit mac address)" + } + set mac_oui [join [lrange $macparts 0 2] ""] + set mactailhex [join [lrange $macparts 3 end] ""] + } else { + error "mac_to_mcast_list: mac address must be in the form 01:00:5e:xx:xx:xx or 01005exxxxxx" + } + } + if {![string match -nocase 01005e* $mac_oui]} { + error "mac_to_mcast_list: mac address must begin with the reserved OUI 01:00:5e (or 01005e) for multicast adddresses" + } + set bin_tail "" + catch { + foreach hexdigit [split $mactailhex ""] { + set dec [scan $hexdigit %llx] + append bin_tail [format %4.4b $dec] + } + } + set last23bits [string range $bin_tail 1 end] + if {[string length $last23bits] != 23} { + error "mac_to_mcast_list: failed to convert mac:$mac to binary - check it is a properly formatted mac address" + } + #consider bytes b0 b1 b2 b3 + #last 2 bytes (b2, b3) will be the same for each resulting address + set last16bits [string range $last23bits 7 end] + set b2 [string range $last16bits 0 7] + set b3 [string range $last16bits 8 end] + set a2 [scan $b2 %b] + set a3 [scan $b3 %b] + + set top7of23 [string range $last23bits 0 6] + #first 2 bytes are 1110xxxx xnnnnnnn where the 7 n bits are the first 7 of the 23bits used from the tail, giving 32 possible values + set mcast_list [list] + for {set i 0} {$i <=31} {incr i} { + set varbits [format %5.5b $i] + set first2bytesbin "1110$varbits$top7of23" + set b0 [string range $first2bytesbin 0 7] + set b1 [string range $first2bytesbin 8 end] + lappend mcast_list "[scan $b0 %b].[scan $b1 %b].$a2.$a3" + } + if {[llength $mcast_list] != 32} { + error "mac_to_mcast_list: failed to properly calculate the 32 corresponding multicast addresses (length [llength $mcast_list] should be 32)" + } + return $mcast_list + } + +} + + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +# Secondary API namespace +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +tcl::namespace::eval punk::net::vxlan::lib { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + tcl::namespace::path [tcl::namespace::parent] +} +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + + + +#tcl::namespace::eval punk::net::vxlan::system { +#} + + +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::net::vxlan { + tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::net::vxlan" + @package -name "punk::net::vxlan" -help\ + "Package + Description" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::net::vxlan + } + 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 punk::net::vxlan + description to come.. + } \n] + } + proc get_topic_License {} { + return "MIT" + } + proc get_topic_Version {} { + return "$::punk::net::vxlan::version" + } + proc get_topic_Contributors {} { + set authors {{"Julian Noble" }} + 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 "::punk::net::vxlan::about" + dict set overrides @cmd -name "punk::net::vxlan::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::net::vxlan + }] \n] + dict set overrides topic -choices [list {*}[punk::net::vxlan::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::net::vxlan::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 ::punk::net::vxlan::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::net::vxlan::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 ::punk::net::vxlan +} +# ----------------------------------------------------------------------------- + +# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +## Ready +package provide punk::net::vxlan [tcl::namespace::eval punk::net::vxlan { + variable pkg punk::net::vxlan + variable version + set version 999999.0a1.0 +}] +return + diff --git a/src/modules/punk/net/vxlan-buildversion.txt b/src/modules/punk/net/vxlan-buildversion.txt new file mode 100644 index 00000000..f47d01c8 --- /dev/null +++ b/src/modules/punk/net/vxlan-buildversion.txt @@ -0,0 +1,3 @@ +0.1.0 +#First line must be a semantic version number +#all other lines are ignored. diff --git a/src/modules/punk/repl-999999.0a1.0.tm b/src/modules/punk/repl-999999.0a1.0.tm index e9ce7aef..efe4b7dd 100644 --- a/src/modules/punk/repl-999999.0a1.0.tm +++ b/src/modules/punk/repl-999999.0a1.0.tm @@ -479,7 +479,13 @@ proc repl::start {inchan args} { puts stderr "-->repl::start active on $inchan $args replthread:[thread::id] codethread:$codethread" set prompt_config [punk::repl::get_prompt_config] doprompt "P% " - chan event $inchan readable [list [namespace current]::repl_handler $inchan $prompt_config] + if {[llength $input_chunks_waiting($inchan)]} { + set readmore 0 + uplevel #0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + #after 0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + } + set readmore 1 + chan event $inchan readable [list [namespace current]::repl_handler $inchan $readmore $prompt_config] set reading 1 #catch { @@ -530,6 +536,22 @@ proc repl::start {inchan args} { #puts stderr "__> returning 0" return 0 } + +#put a script into the waiting buffer for evaluation +proc repl::submit {inputchan script} { + set prompt_config [punk::repl::get_prompt_config] + upvar ::punk::console::input_chunks_waiting input_chunks_waiting + if {[info exists input_chunks_waiting($inputchan)] && [llength $input_chunks_waiting($inputchan)]} { + set last [lindex $input_chunks_waiting($inputchan) end] + append last $script + lset input_chunks_waiting($inputchan) end $last + } else { + set input_chunks_waiting($inputchan) [list $script] + } + + #set readmore 0 + #after idle [list after 0 [list ::repl::repl_handler $inputchan $readmore $prompt_config]] +} proc repl::post_operations {} { if {[info exists ::repl::post_script] && [string length $::repl::post_script]} { #put aside post_script so the script has the option to add another post_script and restart the repl @@ -1384,7 +1406,10 @@ proc punk::repl::repl_handler_restorechannel_if_not_eof {inputchan previous_inpu } return [chan conf $inputchan] } -proc repl::repl_handler {inputchan prompt_config} { +proc repl::repl_handler {inputchan readmore prompt_config} { + #readmore set to zero used to process input_chunks_waiting without reading inputchan, + # and without rescheduling reader + # -- review variable in_repl_handler set in_repl_handler [list $inputchan $prompt_config] @@ -1451,115 +1476,132 @@ proc repl::repl_handler {inputchan prompt_config} { set waitingchunk [lindex $waitinglines end] # -- #set chunksize [gets $inputchan chunk] - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- - if {$chunksize > 0} { - if {[string index $chunk end] eq "\n"} { - lappend stdinlines $waitingchunk[string range $chunk 0 end-1] - #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" - - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - set input_chunks_waiting($inputchan) [list $allwaiting] - lappend input_chunks_waiting($inputchan) $chunk - } + if {!$readmore} { + set chunk "" + set chunksize 0 + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + set input_chunks_waiting($inputchan) [list $waitingchunk] } else { - #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' - if {[chan blocked $inputchan]} { - #REVIEW - - #todo - figure out why we're here. - #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) - #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? - ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows - set outconf [chan configure stdout] - set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] - if {"windows" eq $::tcl_platform(platform)} { - set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + set chunk [read $inputchan] + set chunksize [string length $chunk] + if {$chunksize > 0} { + if {[string index $chunk end] eq "\n"} { + lappend stdinlines $waitingchunk[string range $chunk 0 end-1] + #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" + + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] } else { - set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" + set input_chunks_waiting($inputchan) [list $allwaiting] + lappend input_chunks_waiting($inputchan) $chunk } - set cols "" - set rows "" - if {[dict exists $outconf -winsize]} { - lassign [dict get $outconf -winsize] cols rows - } else { - #fallback - try external executable. Which is a bit ugly - #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) - - #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 - #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! - #stty -a is 400ms+ vs 500us+ on FreeBSD - + } else { + if {[chan blocked $inputchan]} { + #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' + #REVIEW - + #todo - figure out why we're here. + #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) + #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? + ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows + set outconf [chan configure stdout] + set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] if {"windows" eq $::tcl_platform(platform)} { - set tputcmd [auto_execok tput] - if {$tputcmd ne ""} { - if {![catch {exec {*}$tputcmd cols lines} values]} { - lassign $values cols rows - } - } + set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + } else { + set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" } + set cols "" + set rows "" + if {[dict exists $outconf -winsize]} { + lassign [dict get $outconf -winsize] cols rows + } else { + #fallback1 query terminal + if {![catch {punk::console::get_size} sdict]} { + set cols [dict get $sdict columns] + set rows [dict get $sdict rows] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + + #fallback2 - try external executable. Which is a bit ugly + #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) + + #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 + #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! + #stty -a is 400ms+ vs 500us+ on FreeBSD - if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { - #same for all platforms? tested on windows, wsl, FreeBSD - #exec stty -a gives a result on the first line like: - #speed xxxx baud; rows rr; columns cc; - #review - more robust parsing - do we know it's first line? - set sttycmd [auto_execok stty] - if {$sttycmd ne ""} { - #the more parseable: stty -g doesn't give rows/columns - if {![catch {exec {*}$sttycmd -a} result]} { - lassign [split $result \n] firstline - set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? - set rowinfo [lsearch -index end -inline $lineparts rows] - if {[llength $rowinfo] == 2} { - set rows [lindex $rowinfo 0] + if {"windows" eq $::tcl_platform(platform)} { + set tputcmd [auto_execok tput] + if {$tputcmd ne ""} { + if {![catch {exec {*}$tputcmd cols lines} values]} { + lassign $values cols rows + } } - set colinfo [lsearch -index end -inline $lineparts columns] - if {[llength $colinfo] == 2} { - set cols [lindex $colinfo 0] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + #same for all platforms? tested on windows, wsl, FreeBSD + #exec stty -a gives a result on the first line like: + #speed xxxx baud; rows rr; columns cc; + #review - more robust parsing - do we know it's first line? + set sttycmd [auto_execok stty] + if {$sttycmd ne ""} { + #the more parseable: stty -g doesn't give rows/columns + if {![catch {exec {*}$sttycmd -a} result]} { + lassign [split $result \n] firstline + set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? + set rowinfo [lsearch -index end -inline $lineparts rows] + if {[llength $rowinfo] == 2} { + set rows [lindex $rowinfo 0] + } + set colinfo [lsearch -index end -inline $lineparts columns] + if {[llength $colinfo] == 2} { + set cols [lindex $colinfo 0] + } + } } } } } - } - if {[string is integer -strict $cols] && [string is integer -strict $rows]} { - #got_dimensions - todo - try spinner? - #puts -nonewline stdout [punk::ansi::move $rows 4]$msg - #use cursorsave_ version which avoids get_cursor_pos_list call - set msglen [ansistring length $msg] - punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 - } else { - #no mechanism to get console dimensions - #we are reduced to continuously spewing lines. - puts stderr $msg - } + if {[string is integer -strict $cols] && [string is integer -strict $rows]} { + #got_dimensions - todo - try spinner? + #puts -nonewline stdout [punk::ansi::move $rows 4]$msg + #use cursorsave_ version which avoids get_cursor_pos_list call + set msglen [ansistring length $msg] + punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 + } else { + #no mechanism to get console dimensions + #we are reduced to continuously spewing lines. + puts stderr $msg + } - after 100 + after 100 + } + set input_chunks_waiting($inputchan) [list $allwaiting] } - set input_chunks_waiting($inputchan) [list $allwaiting] } - + # -- } else { - punk::repl::repl_handler_checkchannel $inputchan - punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan - # -- --- --- - #set chunksize [gets $inputchan chunk] - # -- --- --- - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- --- --- - if {$chunksize > 0} { - #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" - set ln $chunk ;#temp - #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] - if {[string index $ln end] eq "\n"} { - lappend stdinlines [string range $ln 0 end-1] - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - lappend input_chunks_waiting($inputchan) $ln + if {$readmore} { + punk::repl::repl_handler_checkchannel $inputchan + punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan + # -- --- --- + #set chunksize [gets $inputchan chunk] + # -- --- --- + set chunk [read $inputchan] + set chunksize [string length $chunk] + # -- --- --- + if {$chunksize > 0} { + #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" + set ln $chunk ;#temp + #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] + if {[string index $ln end] eq "\n"} { + lappend stdinlines [string range $ln 0 end-1] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + } else { + lappend input_chunks_waiting($inputchan) $ln + } } } } @@ -1582,19 +1624,21 @@ proc repl::repl_handler {inputchan prompt_config} { } if {$continue} { - if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { - chan configure $inputchan -blocking 0 - chan configure $inputchan -translation lf - } - set chunk [read $inputchan] - #we expect a chan configured with -blocking 0 to be blocked immediately after reads - #test - just bug console for now - try to understand when/how/if a non blocking read occurs. - if {![chan blocked $inputchan]} { - puts stderr "repl_handler->$inputchan not blocked after read" - } + if {$readmore} { + if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { + chan configure $inputchan -blocking 0 + chan configure $inputchan -translation lf + } + set chunk [read $inputchan] + #we expect a chan configured with -blocking 0 to be blocked immediately after reads + #test - just bug console for now - try to understand when/how/if a non blocking read occurs. + if {![chan blocked $inputchan]} { + puts stderr "repl_handler->$inputchan not blocked after read" + } - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + } while {[llength $input_chunks_waiting($inputchan)]} { set chunkzero [lpop input_chunks_waiting($inputchan) 0] if {$chunkzero eq ""} {continue} ;#why empty waiting - and is there any point passing on? @@ -1604,33 +1648,35 @@ proc repl::repl_handler {inputchan prompt_config} { } } - if {![chan eof $inputchan]} { - ################################################################################## - #Re-enable channel read handler only if no waiting chunks - must process in order - ################################################################################## - if {![llength $input_chunks_waiting($inputchan)]} { - chan event $inputchan readable [list ::repl::repl_handler $inputchan $prompt_config] + if {$readmore} { + if {![chan eof $inputchan]} { + ################################################################################## + #Re-enable channel read handler only if no waiting chunks - must process in order + ################################################################################## + if {![llength $input_chunks_waiting($inputchan)]} { + chan event $inputchan readable [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } else { + #review + #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" + after idle [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } + #################################################### } else { - #review - #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" - after idle [list ::repl::repl_handler $inputchan $prompt_config] - } - #################################################### - } else { - #repl_handler_checkchannel $inputchan - chan event $inputchan readable {} - set reading 0 - #target is the 'main' interp in codethread. - #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) - # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 - - thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread - if {$::tcl_interactive} { - rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" - #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + #repl_handler_checkchannel $inputchan + chan event $inputchan readable {} + set reading 0 + #target is the 'main' interp in codethread. + #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) + # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 + + thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread + if {$::tcl_interactive} { + rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" + #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + } + set [namespace current]::done 1 + after 1 [list repl::reopen_stdin] } - set [namespace current]::done 1 - after 1 [list repl::reopen_stdin] } set in_repl_handler [list] } diff --git a/src/modules/punk/repo-999999.0a1.0.tm b/src/modules/punk/repo-999999.0a1.0.tm index 727b563d..060431fe 100644 --- a/src/modules/punk/repo-999999.0a1.0.tm +++ b/src/modules/punk/repo-999999.0a1.0.tm @@ -218,7 +218,7 @@ namespace eval punk::repo { if {$fossilcmd eq "commit"} { if {[llength [file split $fosroot]]} { if {[file exists [file join $fosroot src/buildsuites]]} { - puts stderr "Todo - check buildsites/suite/projects for current branch/tag and update download_and_build_config" + puts stderr "Todo - check buildsuites/suite/projects for current branch/tag and update download_and_build_config" } } } elseif {$fossilcmd in [list "info" "status"]} { diff --git a/src/modules/shellfilter-999999.0a1.0.tm b/src/modules/shellfilter-999999.0a1.0.tm index 5b8908c0..39c14a32 100644 --- a/src/modules/shellfilter-999999.0a1.0.tm +++ b/src/modules/shellfilter-999999.0a1.0.tm @@ -2472,7 +2472,14 @@ namespace eval shellfilter { set exitinfo [list error "$errMsg" errorCode $::errorCode errorInfo "$::errorInfo"] } } + #puts "shellfilter::run finished call" + #------------------------- + #warning - without flush stdout - we can get hang, but only on some terminals + # - mechanism for this problem not understood! + flush stdout + flush stderr + #------------------------- #the previous redirections on the underlying inchan/outchan/errchan items will be restored from the -aside setting during removal #Remove execution-time Tees from stack @@ -2480,6 +2487,7 @@ namespace eval shellfilter { shellfilter::stack::remove stderr $id_err #shellfilter::stack::remove stderr $id_in + #puts stderr "shellfilter::run complete..." #chan configure stderr -buffering line #flush stdout diff --git a/src/modules/shellrun-0.1.1.tm b/src/modules/shellrun-0.1.1.tm index 478c70fa..c3f7ab10 100644 --- a/src/modules/shellrun-0.1.1.tm +++ b/src/modules/shellrun-0.1.1.tm @@ -187,10 +187,10 @@ namespace eval shellrun { #--------------------------------------------------------------------------------------------- set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none ] #--------------------------------------------------------------------------------------------- - foreach id $idlist_stderr { shellfilter::stack::remove stderr $id } + #puts stderr "shellrun::run exitinfo: $exitinfo" flush stderr flush stdout diff --git a/src/modules/test/AGENTS.md b/src/modules/test/AGENTS.md new file mode 100644 index 00000000..218c4d30 --- /dev/null +++ b/src/modules/test/AGENTS.md @@ -0,0 +1,14 @@ +# test module information +--- +subfolders (that don't begin with a # or _ character) within this test folder form part of the Tcl namespace of the resulting modules that are produced from running ' src/make.tcl modules' + +The #modpod- folder for a module contain files that will stored in the final module's .tm file (zip based) which is built by make.tcl into the /modules/test folder (again with further subfolders depending on whether the module is namespaced) + +The final version of the built modules are determined from corresponding -buildversion.txt files placed at the same level as the corresponding #modpod--999999.0a1.0 folder + +example: A final installed module /modules/test/foo/baz/foobazzer-1.1.tm corresponds to the tcl module test::foo::baz::foobazzer and will have its source tests and associated files in the folder /src/modules/test/foo/baz/#modpod-foobazzer-999999.0a1.0 with a corresponding version number file at /src/modules/test/foo/baz/foobazzer-buildversion.txt + +A new testmodule for a package can be generated from the template template_test built into the punk::mix::templates package which is referenced as 'punk.test' when creating a new module using the 'dev module.new' command. This command is an alias for punk::mix::commandset::module::new. + + + diff --git a/src/modules/test/punk/#modpod-ansi-999999.0a1.0/ansi-999999.0a1.0.tm b/src/modules/test/punk/#modpod-ansi-999999.0a1.0/ansi-999999.0a1.0.tm index 4f88c2ba..e497f903 100644 --- a/src/modules/test/punk/#modpod-ansi-999999.0a1.0/ansi-999999.0a1.0.tm +++ b/src/modules/test/punk/#modpod-ansi-999999.0a1.0/ansi-999999.0a1.0.tm @@ -13,112 +13,26 @@ # Meta license MIT # @@ Meta End - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -# doctools header -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -#*** !doctools -#[manpage_begin shellspy_module_test::punk::ansi 0 999999.0a1.0] -#[copyright "2025"] -#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] -#[moddesc {-}] [comment {-- Description at end of page heading --}] -#[require test::punk::ansi] -#[keywords module] -#[description] -#[para] - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[section Overview] -#[para] overview of test::punk::ansi -#[subsection Concepts] -#[para] - - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Requirements -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[subsection dependencies] -#[para] packages used by test::punk::ansi -#[list_begin itemized] - package require Tcl 8.6- -#*** !doctools -#[item] [package {Tcl 8.6}] - -#*** !doctools -#[list_end] - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[section API] - tcl::namespace::eval test::punk::ansi { - # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - # Base namespace - # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - #*** !doctools - #[subsection {Namespace test::punk::ansi}] - #[para] Core API functions for test::punk::ansi - #[list_begin definitions] - variable PUNKARGS - variable pkg test::punk::ansi variable version set version 999999.0a1.0 - - package require packageTest - packageTest::makeAPI test::punk::ansi $version punk::ansi; #will package provide test::punk::args $version + package require packagetest + packagetest::makeAPI test::punk::ansi $version punk::ansi; #will package provide test::punk::args $version package forget punk::ansi package require punk::ansi - - - #*** !doctools - #[list_end] [comment {--- end definitions namespace test::punk::ansi ---}] } -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -# Secondary API namespace -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -tcl::namespace::eval test::punk::ansi::lib { - tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase - tcl::namespace::path [tcl::namespace::parent] - #*** !doctools - #[subsection {Namespace test::punk::ansi::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 test::punk::ansi::lib ---}] -} -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - - - - -# == === === === === === === === === === === === === === === +# == === === === === === === === === === === === === === === # Sample 'about' function with punk::args documentation -# == === === === === === === === === === === === === === === +# == === === === === === === === === === === === === === === tcl::namespace::eval test::punk::ansi { tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase variable PUNKARGS @@ -141,7 +55,7 @@ tcl::namespace::eval test::punk::ansi { set about_topics [list] foreach f $topic_funs { set tail [namespace tail $f] - lappend about_topics [string range $tail [string length get_topic_] end] + 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] @@ -149,10 +63,10 @@ tcl::namespace::eval test::punk::ansi { proc default_topics {} {return [list Description *]} # ------------------------------------------------------------- - # get_topic_ functions add more to auto-include in about topics + # get_topic_ functions add more to auto-include in about topics # ------------------------------------------------------------- proc get_topic_Description {} { - punk::args::lib::tstr [string trim { + punk::args::lib::tstr [string trim { package test::punk::ansi } \n] } @@ -179,9 +93,9 @@ tcl::namespace::eval test::punk::ansi { # we re-use the argument definition from punk::args::standard_about and override some items set overrides [dict create] dict set overrides @id -id "::test::punk::ansi::about" - dict set overrides @cmd -name "test::punk::ansi::about" + dict set overrides @cmd -name "test::punk::ansi::about" dict set overrides @cmd -help [string trim [punk::args::lib::tstr { - About test::punk::ansi + About test::punk::ansi }] \n] dict set overrides topic -choices [list {*}[test::punk::ansi::argdoc::about_topics] *] dict set overrides topic -choicerestricted 1 @@ -211,15 +125,11 @@ namespace eval ::punk::args::register { } # ----------------------------------------------------------------------------- -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Ready package provide test::punk::ansi [tcl::namespace::eval test::punk::ansi { variable pkg test::punk::ansi variable version set version 999999.0a1.0 }] +## Ready return -#*** !doctools -#[manpage_end] - diff --git a/src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/tests/define.test#..+args+define.test.fauxlink b/src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/tests/define.test#..+args+define.test.fauxlink new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/tests/opts.test#..+args+opts.test.fauxlink b/src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/tests/opts.test#..+args+opts.test.fauxlink new file mode 100644 index 00000000..e69de29b diff --git a/src/modules/test/punk/#modpod-args-999999.0a1.0/args-999999.0a1.0.tm b/src/modules/test/punk/#modpod-args-999999.0a1.0/args-999999.0a1.0.tm index 4afc180c..eae028f7 100644 --- a/src/modules/test/punk/#modpod-args-999999.0a1.0/args-999999.0a1.0.tm +++ b/src/modules/test/punk/#modpod-args-999999.0a1.0/args-999999.0a1.0.tm @@ -13,99 +13,21 @@ # Meta license MIT # @@ Meta End - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -# doctools header -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -#*** !doctools -#[manpage_begin shellspy_module_test::punk::args 0 999999.0a1.0] -#[copyright "2025"] -#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] -#[moddesc {-}] [comment {-- Description at end of page heading --}] -#[require test::punk::args] -#[keywords module] -#[description] -#[para] - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[section Overview] -#[para] overview of test::punk::args -#[subsection Concepts] -#[para] - - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Requirements -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[subsection dependencies] -#[para] packages used by test::punk::args -#[list_begin itemized] - package require Tcl 8.6- -#*** !doctools -#[item] [package {Tcl 8.6}] - - -#*** !doctools -#[list_end] - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[section API] - - - tcl::namespace::eval test::punk::args { - # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - # Base namespace - # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - #*** !doctools - #[subsection {Namespace test::punk::args}] - #[para] Core API functions for test::punk::args - #[list_begin definitions] - variable PUNKARGS variable pkg test::punk::args variable version set version 999999.0a1.0 - package require packageTest - packageTest::makeAPI test::punk::args $version punk::args; #will package provide test::punk::args $version + package require packagetest + packagetest::makeAPI test::punk::args $version punk::args; #will package provide test::punk::args $version package forget punk::args package require punk::args - - - - #*** !doctools - #[list_end] [comment {--- end definitions namespace test::punk::args ---}] } -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -# Secondary API namespace -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -#*** !doctools -#[section Internal] -#tcl::namespace::eval test::punk::args::system { - #*** !doctools - #[subsection {Namespace test::punk::args::system}] - #[para] Internal functions that are not part of the API - - - -#} # == === === === === === === === === === === === === === === @@ -119,7 +41,7 @@ tcl::namespace::eval test::punk::args { lappend PUNKARGS [list { @id -id "(package)test::punk::args" @package -name "test::punk::args" -help\ - "Test suites for punk::args" + "Test suites for punk::args module" }] namespace eval argdoc { @@ -220,6 +142,3 @@ package provide test::punk::args [tcl::namespace::eval test::punk::args { }] return -#*** !doctools -#[manpage_end] - diff --git a/src/modules/test/punk/#modpod-lib-999999.0a1.0/lib-999999.0a1.0.tm b/src/modules/test/punk/#modpod-lib-999999.0a1.0/lib-999999.0a1.0.tm index 08386ebf..21cb06c7 100644 --- a/src/modules/test/punk/#modpod-lib-999999.0a1.0/lib-999999.0a1.0.tm +++ b/src/modules/test/punk/#modpod-lib-999999.0a1.0/lib-999999.0a1.0.tm @@ -13,86 +13,19 @@ # Meta license MIT # @@ Meta End - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -# doctools header -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -#*** !doctools -#[manpage_begin shellspy_module_test::punk::lib 0 999999.0a1.0] -#[copyright "2025"] -#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] -#[moddesc {-}] [comment {-- Description at end of page heading --}] -#[require test::punk::lib] -#[keywords module] -#[description] -#[para] - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[section Overview] -#[para] overview of test::punk::lib -#[subsection Concepts] -#[para] - - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Requirements -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[subsection dependencies] -#[para] packages used by test::punk::lib -#[list_begin itemized] - package require Tcl 8.6- -#*** !doctools -#[item] [package {Tcl 8.6}] - - -#*** !doctools -#[list_end] - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - -#*** !doctools -#[section API] - - - tcl::namespace::eval test::punk::lib { - # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - # Base namespace - # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - #*** !doctools - #[subsection {Namespace test::punk::lib}] - #[para] Core API functions for test::punk::lib - #[list_begin definitions] - variable PUNKARGS - variable pkg test::punk::lib variable version set version 999999.0a1.0 - package require packageTest - packageTest::makeAPI test::punk::lib $version punk::lib; #will package provide test::punk::lib $version - + package require packagetest + packagetest::makeAPI test::punk::lib $version punk::lib; #will package provide test::punk::lib $version package forget punk::lib package require punk::lib - - - - #*** !doctools - #[list_end] [comment {--- end definitions namespace test::punk::lib ---}] } -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ - - -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -# Secondary API namespace -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # == === === === === === === === === === === === === === === @@ -106,7 +39,7 @@ tcl::namespace::eval test::punk::lib { lappend PUNKARGS [list { @id -id "(package)test::punk::lib" @package -name "test::punk::lib" -help\ - "Test suites for punk::lib" + "Test suites for punk::lib module" }] namespace eval argdoc { @@ -184,8 +117,7 @@ tcl::namespace::eval test::punk::lib { } } # end of sample 'about' function -# == === === === === === === === === === === === === === === - +# == === === === === === === === === === === === === === === # ----------------------------------------------------------------------------- # register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked @@ -198,14 +130,10 @@ namespace eval ::punk::args::register { } # ----------------------------------------------------------------------------- -# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Ready package provide test::punk::lib [tcl::namespace::eval test::punk::lib { variable pkg test::punk::lib variable version set version 999999.0a1.0 }] +## Ready return - -#*** !doctools -#[manpage_end] diff --git a/src/modules/test/punk/#modpod-ns-999999.0a1.0/ns-999999.0a1.0.tm b/src/modules/test/punk/#modpod-ns-999999.0a1.0/ns-999999.0a1.0.tm index feca648e..2308bfa0 100644 --- a/src/modules/test/punk/#modpod-ns-999999.0a1.0/ns-999999.0a1.0.tm +++ b/src/modules/test/punk/#modpod-ns-999999.0a1.0/ns-999999.0a1.0.tm @@ -28,8 +28,8 @@ tcl::namespace::eval test::punk::ns { variable version set version 999999.0a1.0 - package require packageTest - packageTest::makeAPI test::punk::ns $version punk::ns; #will package provide test::punk::ns $version + package require packagetest + packagetest::makeAPI test::punk::ns $version punk::ns; #will package provide test::punk::ns $version package forget punk::ns package require punk::ns diff --git a/src/modules/test/runtestmodules.tcl b/src/modules/test/runtestmodules.tcl new file mode 100644 index 00000000..b7846da6 --- /dev/null +++ b/src/modules/test/runtestmodules.tcl @@ -0,0 +1,166 @@ +#!punk902testrunner shellspy +#This script uses shellfilter::run calls under the hood - which probably requires a built punkshell binary to function properly. +#(plain tclsh may stall - todo - review reasons for this and whether shellfilter can be modified to support ordinary tclsh) +#A known working copy of a punk shell executable should be placed on the path and the shebang line updated to reflect this + + +package require punk +package require punk::args +punk::args::define { + @id -id (script)::runtestmodules + @cmd -name runtestmodules -help\ + "Run test:: modules that support the packagetest api + (have RUN command)" + -tcltestoptions -type list -default "" -help\ + "arguments that will be left in ::argv for tcltest + to handle" + @values -min 0 -max -1 + glob -type string -multiple 1 -optional 1 -help\ + " names or glob patterns of test modules to run. + Note that this script will search for all modules + within the test namespace that are known to the + current interpreter - not just those within the + current project." +} +set argd [punk::args::parse $::argv withid (script)::runtestmodules] +lassign [dict values $argd] leaders opts values received +set tcltestoptions [dict get $opts -tcltestoptions] +if {![dict exists $received glob]} { + set pkg_globs [list *] +} else { + set pkg_globs [dict get $values glob] +} + +set ::argv $tcltestoptions +set ::argc [llength $tcltestoptions] + + +#bogus require to ensure modules within path test have been scanned to be in Tcl's 'package ifneeded' in-memory database +catch {package require test::bogus666} +set tmlist [tcl::tm::list] +foreach tmfolder $tmlist { + set tfolder [file join $tmfolder test] + if {[file exists $tfolder]} { + puts stdout "checking tm test folder $tfolder" + set subfolders [glob -nocomplain -dir $tfolder -type d -tail *] + foreach sub $subfolders { + if {[string match #* $sub]} { + continue + } + puts stdout "bogus require of test::${sub}::bogus666" + catch {package require test::${sub}::bogus666} + } + } +} +set alltestpkgs [lsearch -all -inline [package names] test::*] +if {![llength $alltestpkgs]} { + puts stder "No packages matching test::* found" + exit 1 +} +if {[llength $pkg_globs] == 1 && [lindex $pkg_globs 0] eq "*"} { + set matchedtestpkgs $alltestpkgs +} else { + set matchedtestpkgs [list] + foreach pkg $alltestpkgs { + foreach g $pkg_globs { + if {[string match $g $pkg]} { + lappend matchedtestpkgs $pkg + break + } + } + } +} +if {![llength $matchedtestpkgs]} { + puts stderr "No test packages matched supplied glob patterns" + exit 1 +} +puts "matchedtestpkgs: $matchedtestpkgs" +set punktestpkgs [list] +foreach pkg $matchedtestpkgs { + if {![catch {package require $pkg}]} { + if {[info commands ::${pkg}::RUN] ne ""} { + lappend punktestpkgs $pkg + } + } else { + puts stderr "failed to load test package $pkg" + } +} +if {![llength $punktestpkgs]} { + puts stderr "No test packages with RUN command were able to be loaded" + exit 1 +} +set scriptname [file tail [info script]] +set results [dict create] +dict set results total 0 +dict set results passed 0 +dict set results skipped 0 +dict set results failed 0 +set pkgs_with_fails [list] +set pkgs_without_fails [list] +package require shellrun +puts "running tests in [llength $punktestpkgs] packages $punktestpkgs" +flush stderr +flush stdout +package require punk::ansi +foreach pkg $punktestpkgs { + puts stdout "running test pkg $pkg" + if {[catch { + #set result [shellrun::runout -tcl ${pkg}::RUN] + set result [shellrun::runx -tcl ${pkg}::RUN] + #set result [shellrun::runx ls] + } errM]} { + puts stderr "error calling 'runout -tcl ${pkg}::RUN' $errM"; flush stderr + set result {none ""} + } + puts stdout "executed ${pkg}::RUN" + flush stdout + set i 0 + dict for {what chunk} $result { + set chunk [string map [list \r\n \n] $chunk] + switch -- $what { + stdout { + foreach ln [split $chunk \n] { + incr i + if {[string match "Tests ended at*" $ln]} { + puts stdout " [punk::ansi::ansistring VIEW -lf 2 -cr 1 "$pkg $ln"]" + } elseif {[string match "*:*Total*Passed*Skipped*Failed*" $ln]} { + set fields [lrange $ln 1 end] + dict for {K v} $fields { + set k [string tolower $K] + dict incr results $k $v + if {$k eq "failed"} { + if {$v == 0} { + lappend pkgs_without_fails $pkg + } elseif {$v > 0} { + lappend pkgs_with_fails $pkg + } + } + } + puts stdout "$pkg $ln" + } else { + puts stdout " $ln" + #puts stdout "$i" + } + } + flush stdout + } + stderr { + puts stderr " [punk::ansi::ansistring VIEW -lf 2 -cr 1 $chunk]" + flush stderr + } + default { + puts stderr "<${what}> $chunk" + flush stderr + } + } + } + puts stdout "completed pkg test ${pkg}" +} +puts stdout "packages without failures: $pkgs_without_fails" +puts stdout "packages with failures: $pkgs_with_fails" +puts stdout "results: Total [dict get $results total] Passed [dict get $results passed] Skipped [dict get $results skipped] Failed [dict get $results failed]" +#after 5000 {set ::done true} +#vwait ::done +puts stdout "DONE" +#exit 0 + diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config index 0ea2f344..8ea31ad3 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config @@ -19,6 +19,7 @@ set bootsupport_modules [list\ src/vendormodules metaface\ src/vendormodules modpod\ src/vendormodules overtype\ + src/vendormodules packagetest\ src/vendormodules pattern\ src/vendormodules patterncmd\ src/vendormodules patternlib\ diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/packagetest-0.1.7.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/packagetest-0.1.7.tm new file mode 100644 index 0000000000000000000000000000000000000000..658d45a4e571209317a9621e1b6a1f037186fe5f GIT binary patch literal 12090 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 C6_b(x literal 0 HcmV?d00001 diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm index 62b03cbc..0aa12476 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm @@ -37,7 +37,6 @@ package provide patterncipher [namespace eval patterncipher { package require ascii85 ;#tcllib package require pattern -::pattern::init ;# initialises (if not already) namespace eval ::patterncipher { namespace eval algo::txt { diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm index db369f06..5138ac6d 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm @@ -2370,6 +2370,54 @@ namespace eval punk::lib { append body [info body is_list_all_ni_list2] proc is_list_all_ni_list2 {a b} $body } + proc is_cachedlist_all_ni_list {a b} { + upvar 0 ::punk::lib::caches::funcs_ni_list funcs + if {[info exists funcs($a)]} { + return [[set funcs($a)] $b] + } + set keybytes [encoding convertto utf-8 $a] + set key [binary encode base64 $keybytes] ;#one single-line base64 string + + set expression "" + foreach t $a { + #append expression "({$t} ni \$b) && " + append expression "{$t} ni \$b && " + } + set expression [string trimright $expression " &"] ;#trim trailing spaces and ampersands + proc ::punk::lib::caches::ni_list_$key {b} [string map [list @expression@ $expression] { + return [expr {@expression@}] + }] + + set funcs($a) ::punk::lib::caches::ni_list_$key + return [punk::lib::caches::ni_list_$key $b] + } + proc is_cachedlist_all_ni_list2 {a b} { + upvar 0 ::punk::lib::caches::funcs_ni_list funcs + if {[info exists funcs($a)]} { + return [[set funcs($a)] $b] + } + set keybytes [encoding convertto utf-8 $a] + set key [binary encode base64 $keybytes] ;#one single-line base64 string + + set d [dict create] + foreach x $a { + dict set d $x "" + } + #constructing a switch statement could be an option + # - but would need to avoid using escapes in order to get a jump-table + # - this would need runtime mapping of values - unlikely to be a win + proc ::punk::lib::caches::ni_list_$key {b} [string map [list @d@ $d] { + foreach x $b { + if {[::tcl::dict::exists {@d@} $x]} { + return 0 + } + } + return 1 + }] + + set funcs($a) ::punk::lib::caches::ni_list_$key + return [punk::lib::caches::ni_list_$key $b] + } namespace eval argdoc { variable PUNKARGS @@ -5389,6 +5437,10 @@ tcl::namespace::eval punk::lib::system { #[list_end] [comment {--- end definitions namespace punk::lib::system ---}] } +tcl::namespace::eval punk::lib::caches { + +} + tcl::namespace::eval punk::lib::debug { proc showdict {args} {} } diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm index bcf2221f..59f23842 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm @@ -170,7 +170,16 @@ namespace eval punk::mix::commandset::module { @values -min 1 -max 1 module -type string -help\ "Name of module, possibly including a namespace and/or version number - e.g mynamespace::mymodule-1.0" + e.g mynamespace::mymodule-1.0 + + Some templates may require a prefixing namespace in order to function + correctly. e.g punk.test module names should be of the form + test::mymodule or test::mymodule::mycomponent + where the modules under test are mymodule and mymodule::mycomponent. + For example with test module test::a::b::c + The 'pkg' and 'pkgunprefixed' placeholders (surrounded by % char) are + filled with test::a::b::c and a::b::c respectively. + " }] proc new {args} { set year [clock format [clock seconds] -format %Y] @@ -222,6 +231,9 @@ namespace eval punk::mix::commandset::module { } else { set modulename $module } + #normalize modulename to remove any leading :: in case it was supplied that way + set modulename [string trimleft $modulename :] + punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" if {[regexp {[A-Z]} $module]} { @@ -410,7 +422,11 @@ namespace eval punk::mix::commandset::module { #for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern #Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens - set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version] + #JJJ + set pkg_parts [punk::ns::nsparts $modulename] ;#(modulename known not to have leading :: at this point) + set pkg_unprefixed [join [lrange $pkg_parts 1 end] ::] + #pkg_unprefixed may be empty - irrelevant for most templates but not ok for punk.test template - but where to reject? review + set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename pkgunprefixed $pkg_unprefixed year $year license $opt_license authors $opt_authors version $infile_version] set strmap [list] foreach {tag val} $tagnames { lappend strmap %$tag% $val diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm index 0272500d..486720e8 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm @@ -479,7 +479,13 @@ proc repl::start {inchan args} { puts stderr "-->repl::start active on $inchan $args replthread:[thread::id] codethread:$codethread" set prompt_config [punk::repl::get_prompt_config] doprompt "P% " - chan event $inchan readable [list [namespace current]::repl_handler $inchan $prompt_config] + if {[llength $input_chunks_waiting($inchan)]} { + set readmore 0 + uplevel #0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + #after 0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + } + set readmore 1 + chan event $inchan readable [list [namespace current]::repl_handler $inchan $readmore $prompt_config] set reading 1 #catch { @@ -530,6 +536,22 @@ proc repl::start {inchan args} { #puts stderr "__> returning 0" return 0 } + +#put a script into the waiting buffer for evaluation +proc repl::submit {inputchan script} { + set prompt_config [punk::repl::get_prompt_config] + upvar ::punk::console::input_chunks_waiting input_chunks_waiting + if {[info exists input_chunks_waiting($inputchan)] && [llength $input_chunks_waiting($inputchan)]} { + set last [lindex $input_chunks_waiting($inputchan) end] + append last $script + lset input_chunks_waiting($inputchan) end $last + } else { + set input_chunks_waiting($inputchan) [list $script] + } + + #set readmore 0 + #after idle [list after 0 [list ::repl::repl_handler $inputchan $readmore $prompt_config]] +} proc repl::post_operations {} { if {[info exists ::repl::post_script] && [string length $::repl::post_script]} { #put aside post_script so the script has the option to add another post_script and restart the repl @@ -1384,7 +1406,10 @@ proc punk::repl::repl_handler_restorechannel_if_not_eof {inputchan previous_inpu } return [chan conf $inputchan] } -proc repl::repl_handler {inputchan prompt_config} { +proc repl::repl_handler {inputchan readmore prompt_config} { + #readmore set to zero used to process input_chunks_waiting without reading inputchan, + # and without rescheduling reader + # -- review variable in_repl_handler set in_repl_handler [list $inputchan $prompt_config] @@ -1451,115 +1476,132 @@ proc repl::repl_handler {inputchan prompt_config} { set waitingchunk [lindex $waitinglines end] # -- #set chunksize [gets $inputchan chunk] - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- - if {$chunksize > 0} { - if {[string index $chunk end] eq "\n"} { - lappend stdinlines $waitingchunk[string range $chunk 0 end-1] - #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" - - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - set input_chunks_waiting($inputchan) [list $allwaiting] - lappend input_chunks_waiting($inputchan) $chunk - } + if {!$readmore} { + set chunk "" + set chunksize 0 + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + set input_chunks_waiting($inputchan) [list $waitingchunk] } else { - #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' - if {[chan blocked $inputchan]} { - #REVIEW - - #todo - figure out why we're here. - #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) - #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? - ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows - set outconf [chan configure stdout] - set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] - if {"windows" eq $::tcl_platform(platform)} { - set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + set chunk [read $inputchan] + set chunksize [string length $chunk] + if {$chunksize > 0} { + if {[string index $chunk end] eq "\n"} { + lappend stdinlines $waitingchunk[string range $chunk 0 end-1] + #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" + + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] } else { - set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" + set input_chunks_waiting($inputchan) [list $allwaiting] + lappend input_chunks_waiting($inputchan) $chunk } - set cols "" - set rows "" - if {[dict exists $outconf -winsize]} { - lassign [dict get $outconf -winsize] cols rows - } else { - #fallback - try external executable. Which is a bit ugly - #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) - - #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 - #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! - #stty -a is 400ms+ vs 500us+ on FreeBSD - + } else { + if {[chan blocked $inputchan]} { + #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' + #REVIEW - + #todo - figure out why we're here. + #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) + #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? + ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows + set outconf [chan configure stdout] + set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] if {"windows" eq $::tcl_platform(platform)} { - set tputcmd [auto_execok tput] - if {$tputcmd ne ""} { - if {![catch {exec {*}$tputcmd cols lines} values]} { - lassign $values cols rows - } - } + set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + } else { + set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" } + set cols "" + set rows "" + if {[dict exists $outconf -winsize]} { + lassign [dict get $outconf -winsize] cols rows + } else { + #fallback1 query terminal + if {![catch {punk::console::get_size} sdict]} { + set cols [dict get $sdict columns] + set rows [dict get $sdict rows] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + + #fallback2 - try external executable. Which is a bit ugly + #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) + + #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 + #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! + #stty -a is 400ms+ vs 500us+ on FreeBSD - if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { - #same for all platforms? tested on windows, wsl, FreeBSD - #exec stty -a gives a result on the first line like: - #speed xxxx baud; rows rr; columns cc; - #review - more robust parsing - do we know it's first line? - set sttycmd [auto_execok stty] - if {$sttycmd ne ""} { - #the more parseable: stty -g doesn't give rows/columns - if {![catch {exec {*}$sttycmd -a} result]} { - lassign [split $result \n] firstline - set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? - set rowinfo [lsearch -index end -inline $lineparts rows] - if {[llength $rowinfo] == 2} { - set rows [lindex $rowinfo 0] + if {"windows" eq $::tcl_platform(platform)} { + set tputcmd [auto_execok tput] + if {$tputcmd ne ""} { + if {![catch {exec {*}$tputcmd cols lines} values]} { + lassign $values cols rows + } } - set colinfo [lsearch -index end -inline $lineparts columns] - if {[llength $colinfo] == 2} { - set cols [lindex $colinfo 0] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + #same for all platforms? tested on windows, wsl, FreeBSD + #exec stty -a gives a result on the first line like: + #speed xxxx baud; rows rr; columns cc; + #review - more robust parsing - do we know it's first line? + set sttycmd [auto_execok stty] + if {$sttycmd ne ""} { + #the more parseable: stty -g doesn't give rows/columns + if {![catch {exec {*}$sttycmd -a} result]} { + lassign [split $result \n] firstline + set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? + set rowinfo [lsearch -index end -inline $lineparts rows] + if {[llength $rowinfo] == 2} { + set rows [lindex $rowinfo 0] + } + set colinfo [lsearch -index end -inline $lineparts columns] + if {[llength $colinfo] == 2} { + set cols [lindex $colinfo 0] + } + } } } } } - } - if {[string is integer -strict $cols] && [string is integer -strict $rows]} { - #got_dimensions - todo - try spinner? - #puts -nonewline stdout [punk::ansi::move $rows 4]$msg - #use cursorsave_ version which avoids get_cursor_pos_list call - set msglen [ansistring length $msg] - punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 - } else { - #no mechanism to get console dimensions - #we are reduced to continuously spewing lines. - puts stderr $msg - } + if {[string is integer -strict $cols] && [string is integer -strict $rows]} { + #got_dimensions - todo - try spinner? + #puts -nonewline stdout [punk::ansi::move $rows 4]$msg + #use cursorsave_ version which avoids get_cursor_pos_list call + set msglen [ansistring length $msg] + punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 + } else { + #no mechanism to get console dimensions + #we are reduced to continuously spewing lines. + puts stderr $msg + } - after 100 + after 100 + } + set input_chunks_waiting($inputchan) [list $allwaiting] } - set input_chunks_waiting($inputchan) [list $allwaiting] } - + # -- } else { - punk::repl::repl_handler_checkchannel $inputchan - punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan - # -- --- --- - #set chunksize [gets $inputchan chunk] - # -- --- --- - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- --- --- - if {$chunksize > 0} { - #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" - set ln $chunk ;#temp - #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] - if {[string index $ln end] eq "\n"} { - lappend stdinlines [string range $ln 0 end-1] - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - lappend input_chunks_waiting($inputchan) $ln + if {$readmore} { + punk::repl::repl_handler_checkchannel $inputchan + punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan + # -- --- --- + #set chunksize [gets $inputchan chunk] + # -- --- --- + set chunk [read $inputchan] + set chunksize [string length $chunk] + # -- --- --- + if {$chunksize > 0} { + #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" + set ln $chunk ;#temp + #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] + if {[string index $ln end] eq "\n"} { + lappend stdinlines [string range $ln 0 end-1] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + } else { + lappend input_chunks_waiting($inputchan) $ln + } } } } @@ -1582,19 +1624,21 @@ proc repl::repl_handler {inputchan prompt_config} { } if {$continue} { - if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { - chan configure $inputchan -blocking 0 - chan configure $inputchan -translation lf - } - set chunk [read $inputchan] - #we expect a chan configured with -blocking 0 to be blocked immediately after reads - #test - just bug console for now - try to understand when/how/if a non blocking read occurs. - if {![chan blocked $inputchan]} { - puts stderr "repl_handler->$inputchan not blocked after read" - } + if {$readmore} { + if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { + chan configure $inputchan -blocking 0 + chan configure $inputchan -translation lf + } + set chunk [read $inputchan] + #we expect a chan configured with -blocking 0 to be blocked immediately after reads + #test - just bug console for now - try to understand when/how/if a non blocking read occurs. + if {![chan blocked $inputchan]} { + puts stderr "repl_handler->$inputchan not blocked after read" + } - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + } while {[llength $input_chunks_waiting($inputchan)]} { set chunkzero [lpop input_chunks_waiting($inputchan) 0] if {$chunkzero eq ""} {continue} ;#why empty waiting - and is there any point passing on? @@ -1604,33 +1648,35 @@ proc repl::repl_handler {inputchan prompt_config} { } } - if {![chan eof $inputchan]} { - ################################################################################## - #Re-enable channel read handler only if no waiting chunks - must process in order - ################################################################################## - if {![llength $input_chunks_waiting($inputchan)]} { - chan event $inputchan readable [list ::repl::repl_handler $inputchan $prompt_config] + if {$readmore} { + if {![chan eof $inputchan]} { + ################################################################################## + #Re-enable channel read handler only if no waiting chunks - must process in order + ################################################################################## + if {![llength $input_chunks_waiting($inputchan)]} { + chan event $inputchan readable [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } else { + #review + #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" + after idle [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } + #################################################### } else { - #review - #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" - after idle [list ::repl::repl_handler $inputchan $prompt_config] - } - #################################################### - } else { - #repl_handler_checkchannel $inputchan - chan event $inputchan readable {} - set reading 0 - #target is the 'main' interp in codethread. - #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) - # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 - - thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread - if {$::tcl_interactive} { - rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" - #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + #repl_handler_checkchannel $inputchan + chan event $inputchan readable {} + set reading 0 + #target is the 'main' interp in codethread. + #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) + # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 + + thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread + if {$::tcl_interactive} { + rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" + #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + } + set [namespace current]::done 1 + after 1 [list repl::reopen_stdin] } - set [namespace current]::done 1 - after 1 [list repl::reopen_stdin] } set in_repl_handler [list] } diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm index 5d2a2725..16f6f1cb 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm @@ -218,7 +218,7 @@ namespace eval punk::repo { if {$fossilcmd eq "commit"} { if {[llength [file split $fosroot]]} { if {[file exists [file join $fosroot src/buildsuites]]} { - puts stderr "Todo - check buildsites/suite/projects for current branch/tag and update download_and_build_config" + puts stderr "Todo - check buildsuites/suite/projects for current branch/tag and update download_and_build_config" } } } elseif {$fossilcmd in [list "info" "status"]} { diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm index 8e59cf0b..2eb2f8fa 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm @@ -2472,7 +2472,14 @@ namespace eval shellfilter { set exitinfo [list error "$errMsg" errorCode $::errorCode errorInfo "$::errorInfo"] } } + #puts "shellfilter::run finished call" + #------------------------- + #warning - without flush stdout - we can get hang, but only on some terminals + # - mechanism for this problem not understood! + flush stdout + flush stderr + #------------------------- #the previous redirections on the underlying inchan/outchan/errchan items will be restored from the -aside setting during removal #Remove execution-time Tees from stack @@ -2480,6 +2487,7 @@ namespace eval shellfilter { shellfilter::stack::remove stderr $id_err #shellfilter::stack::remove stderr $id_in + #puts stderr "shellfilter::run complete..." #chan configure stderr -buffering line #flush stdout diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellrun-0.1.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellrun-0.1.1.tm index 478c70fa..c3f7ab10 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellrun-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellrun-0.1.1.tm @@ -187,10 +187,10 @@ namespace eval shellrun { #--------------------------------------------------------------------------------------------- set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none ] #--------------------------------------------------------------------------------------------- - foreach id $idlist_stderr { shellfilter::stack::remove stderr $id } + #puts stderr "shellrun::run exitinfo: $exitinfo" flush stderr flush stdout diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.5.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/test/tomlish-1.1.5.tm index 3ae60d426cf6b63988005e9d06eb1aac81a2c04f..f4f2b48410448782e6616be9dba89d649618f96a 100644 GIT binary patch delta 2687 zcmZ9MdpuO@8pmheVK9b4CKA&WsfOgPQSO&Qg;SyAGG%N+#JC+Xu}Q@aJtapsmoAjc zFrjkEy`&;{Nh%$k-R02MRy*R1Me}K&_0Rf!zt8hN>s{;fS#RoHLg`%s?+6|;2vpdi zK!nTqXbLMNBruvD6B88d5ea^R$OeRuh5r)V!CX{?-%3=$lNR=m(db9pcN^af^2c1rHg@%$9f^PLbqJ{mNBrTbe3QCDqg8n}d zsYPj}rfl2^*UG`baw&02*RP$cduXrj6u%)d${OE#D@VCrm~`N_er+%ha*&l7d$Mby zwPa~<_n!5;@7TU=vt4Sqa+z#$hfhj9DZf_pscdf04PKtx3DE?rfEsdZ(BN2#!sjogvvyyH z+Q(n^w!4}&XVq{iMv7~Rx6`v*hXT$wZdZ+%uv$km4uafgc&4JUI(KBl@{X`BM(VKf9XH~}QQW0Zd(tgfC+dz4Z{L+nxm@~%iL-O1 zzQa}PCpqR>PACS{*%iEebe`#|`lroAz2c5%YcsCJw5pp*g+4r58*ntXcc(l)y;I`c za7x*0OP|ej^JH#c*DJ0zKhQsEC}z~2vR(XZ!Tn^f;*eFzpPOq;P90H594Xw?FE>sZ zxMsqdA|w>;?l5j~u+nQLn$g4eGgEQ1ewGc+pXUaz6RPFbuf~P1EW6M9xVNLJC~yrJi^WLi!rp_MY2H&#PpPXBNe%It* zzngr|xLj#l5REum+s8s0_>HCv_S#st6hwenM}i|W~MY52NWBf;uSi*04$ zHHXpHXX2xa^YhX)-sn_uEj-R{Jh#{OQ%!EzKF^PvMnB&(x?K}IIHuj%VZ5RG*Z%w% zJDm?X#d-nHDjfYPoS9=5({JLNjz>6KNSIvthl|e6#J=O()kk9`n_1%*C&v|(xFjyr z_$K9Dux(IX$+hv7^t`5hk?*%oWqD#@PZ;5`@}tSgOzyY9eq&x*btAXGCvJALskEwt zf9_Wcvo&cKg6>u1hFrsKVBDNQ5mPA9{cEgihhwi3yiZ7pv^RK)?=+`}^Jcs?ch7C= z4wN{UrNq~asp^_;!X>wUwL zR;D^AoqILgY}d=%cIk!0%;T@ql1>f;5#W_8LLN8F4P@|bA&QCT?pdSMd#-5I2`{Q%@3sie$YTjxeu}s0sIXe z5aa8ioP`*~w?_sFn0?0&$M=BuEe>L+0PB`DbTY{hXCwoL6%6$Lhmwc80zAHAu|ows zYss*)l7_TbV#?{Z1k_g=p)IR^LMhl;rH2Ak7nMA`tYRQ#wME>5`hG-lki1KQ&$so_ zfz^xEd8Gnyhk*>$e^O3}x(35erWm}mq(IeO2Fh6blTyUCMzofb5_~h^B@c(I6HtbR zu+hB62-)ZhG0Ra3qVLJTs~RO_XCTyDYt50Tp%9CmB;Z9Y15u2G%F|gE+N#MAT1Ufg zgN(X#bk;;zoVX_`n94vUraviD)NLljn0g -CQ>Nlb)OCiQJn4?`*KjBi?T}uVS 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 C6_b(x literal 0 HcmV?d00001 diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm index 62b03cbc..0aa12476 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/patterncipher-0.1.1.tm @@ -37,7 +37,6 @@ package provide patterncipher [namespace eval patterncipher { package require ascii85 ;#tcllib package require pattern -::pattern::init ;# initialises (if not already) namespace eval ::patterncipher { namespace eval algo::txt { diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm index db369f06..5138ac6d 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.5.tm @@ -2370,6 +2370,54 @@ namespace eval punk::lib { append body [info body is_list_all_ni_list2] proc is_list_all_ni_list2 {a b} $body } + proc is_cachedlist_all_ni_list {a b} { + upvar 0 ::punk::lib::caches::funcs_ni_list funcs + if {[info exists funcs($a)]} { + return [[set funcs($a)] $b] + } + set keybytes [encoding convertto utf-8 $a] + set key [binary encode base64 $keybytes] ;#one single-line base64 string + + set expression "" + foreach t $a { + #append expression "({$t} ni \$b) && " + append expression "{$t} ni \$b && " + } + set expression [string trimright $expression " &"] ;#trim trailing spaces and ampersands + proc ::punk::lib::caches::ni_list_$key {b} [string map [list @expression@ $expression] { + return [expr {@expression@}] + }] + + set funcs($a) ::punk::lib::caches::ni_list_$key + return [punk::lib::caches::ni_list_$key $b] + } + proc is_cachedlist_all_ni_list2 {a b} { + upvar 0 ::punk::lib::caches::funcs_ni_list funcs + if {[info exists funcs($a)]} { + return [[set funcs($a)] $b] + } + set keybytes [encoding convertto utf-8 $a] + set key [binary encode base64 $keybytes] ;#one single-line base64 string + + set d [dict create] + foreach x $a { + dict set d $x "" + } + #constructing a switch statement could be an option + # - but would need to avoid using escapes in order to get a jump-table + # - this would need runtime mapping of values - unlikely to be a win + proc ::punk::lib::caches::ni_list_$key {b} [string map [list @d@ $d] { + foreach x $b { + if {[::tcl::dict::exists {@d@} $x]} { + return 0 + } + } + return 1 + }] + + set funcs($a) ::punk::lib::caches::ni_list_$key + return [punk::lib::caches::ni_list_$key $b] + } namespace eval argdoc { variable PUNKARGS @@ -5389,6 +5437,10 @@ tcl::namespace::eval punk::lib::system { #[list_end] [comment {--- end definitions namespace punk::lib::system ---}] } +tcl::namespace::eval punk::lib::caches { + +} + tcl::namespace::eval punk::lib::debug { proc showdict {args} {} } diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm index bcf2221f..59f23842 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm @@ -170,7 +170,16 @@ namespace eval punk::mix::commandset::module { @values -min 1 -max 1 module -type string -help\ "Name of module, possibly including a namespace and/or version number - e.g mynamespace::mymodule-1.0" + e.g mynamespace::mymodule-1.0 + + Some templates may require a prefixing namespace in order to function + correctly. e.g punk.test module names should be of the form + test::mymodule or test::mymodule::mycomponent + where the modules under test are mymodule and mymodule::mycomponent. + For example with test module test::a::b::c + The 'pkg' and 'pkgunprefixed' placeholders (surrounded by % char) are + filled with test::a::b::c and a::b::c respectively. + " }] proc new {args} { set year [clock format [clock seconds] -format %Y] @@ -222,6 +231,9 @@ namespace eval punk::mix::commandset::module { } else { set modulename $module } + #normalize modulename to remove any leading :: in case it was supplied that way + set modulename [string trimleft $modulename :] + punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" if {[regexp {[A-Z]} $module]} { @@ -410,7 +422,11 @@ namespace eval punk::mix::commandset::module { #for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern #Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens - set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version] + #JJJ + set pkg_parts [punk::ns::nsparts $modulename] ;#(modulename known not to have leading :: at this point) + set pkg_unprefixed [join [lrange $pkg_parts 1 end] ::] + #pkg_unprefixed may be empty - irrelevant for most templates but not ok for punk.test template - but where to reject? review + set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename pkgunprefixed $pkg_unprefixed year $year license $opt_license authors $opt_authors version $infile_version] set strmap [list] foreach {tag val} $tagnames { lappend strmap %$tag% $val diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm index 0272500d..486720e8 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm @@ -479,7 +479,13 @@ proc repl::start {inchan args} { puts stderr "-->repl::start active on $inchan $args replthread:[thread::id] codethread:$codethread" set prompt_config [punk::repl::get_prompt_config] doprompt "P% " - chan event $inchan readable [list [namespace current]::repl_handler $inchan $prompt_config] + if {[llength $input_chunks_waiting($inchan)]} { + set readmore 0 + uplevel #0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + #after 0 [list [namespace current]::repl_handler $inchan $readmore $prompt_config] + } + set readmore 1 + chan event $inchan readable [list [namespace current]::repl_handler $inchan $readmore $prompt_config] set reading 1 #catch { @@ -530,6 +536,22 @@ proc repl::start {inchan args} { #puts stderr "__> returning 0" return 0 } + +#put a script into the waiting buffer for evaluation +proc repl::submit {inputchan script} { + set prompt_config [punk::repl::get_prompt_config] + upvar ::punk::console::input_chunks_waiting input_chunks_waiting + if {[info exists input_chunks_waiting($inputchan)] && [llength $input_chunks_waiting($inputchan)]} { + set last [lindex $input_chunks_waiting($inputchan) end] + append last $script + lset input_chunks_waiting($inputchan) end $last + } else { + set input_chunks_waiting($inputchan) [list $script] + } + + #set readmore 0 + #after idle [list after 0 [list ::repl::repl_handler $inputchan $readmore $prompt_config]] +} proc repl::post_operations {} { if {[info exists ::repl::post_script] && [string length $::repl::post_script]} { #put aside post_script so the script has the option to add another post_script and restart the repl @@ -1384,7 +1406,10 @@ proc punk::repl::repl_handler_restorechannel_if_not_eof {inputchan previous_inpu } return [chan conf $inputchan] } -proc repl::repl_handler {inputchan prompt_config} { +proc repl::repl_handler {inputchan readmore prompt_config} { + #readmore set to zero used to process input_chunks_waiting without reading inputchan, + # and without rescheduling reader + # -- review variable in_repl_handler set in_repl_handler [list $inputchan $prompt_config] @@ -1451,115 +1476,132 @@ proc repl::repl_handler {inputchan prompt_config} { set waitingchunk [lindex $waitinglines end] # -- #set chunksize [gets $inputchan chunk] - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- - if {$chunksize > 0} { - if {[string index $chunk end] eq "\n"} { - lappend stdinlines $waitingchunk[string range $chunk 0 end-1] - #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" - - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - set input_chunks_waiting($inputchan) [list $allwaiting] - lappend input_chunks_waiting($inputchan) $chunk - } + if {!$readmore} { + set chunk "" + set chunksize 0 + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + set input_chunks_waiting($inputchan) [list $waitingchunk] } else { - #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' - if {[chan blocked $inputchan]} { - #REVIEW - - #todo - figure out why we're here. - #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) - #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? - ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows - set outconf [chan configure stdout] - set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] - if {"windows" eq $::tcl_platform(platform)} { - set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + set chunk [read $inputchan] + set chunksize [string length $chunk] + if {$chunksize > 0} { + if {[string index $chunk end] eq "\n"} { + lappend stdinlines $waitingchunk[string range $chunk 0 end-1] + #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" + + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] } else { - set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" + set input_chunks_waiting($inputchan) [list $allwaiting] + lappend input_chunks_waiting($inputchan) $chunk } - set cols "" - set rows "" - if {[dict exists $outconf -winsize]} { - lassign [dict get $outconf -winsize] cols rows - } else { - #fallback - try external executable. Which is a bit ugly - #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) - - #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 - #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! - #stty -a is 400ms+ vs 500us+ on FreeBSD - + } else { + if {[chan blocked $inputchan]} { + #'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..' + #REVIEW - + #todo - figure out why we're here. + #can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos) + #punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances? + ## can't do this: set screeninfo [punk::console::get_size]; lassign $screeninfo _c cols _r rows + set outconf [chan configure stdout] + set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a] if {"windows" eq $::tcl_platform(platform)} { - set tputcmd [auto_execok tput] - if {$tputcmd ne ""} { - if {![catch {exec {*}$tputcmd cols lines} values]} { - lassign $values cols rows - } - } + set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}" + } else { + set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}" } + set cols "" + set rows "" + if {[dict exists $outconf -winsize]} { + lassign [dict get $outconf -winsize] cols rows + } else { + #fallback1 query terminal + if {![catch {punk::console::get_size} sdict]} { + set cols [dict get $sdict columns] + set rows [dict get $sdict rows] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + + #fallback2 - try external executable. Which is a bit ugly + #tput can't seem to get dimensions (on FreeBSD at least) when not run interactively - ie via exec. (always returns 80x24 no matter if run with <@stdin) + + #bizarrely - tput can work with exec on windows if it's installed e.g from msys2 + #but can be *slow* compared to unix e.g 400ms+ vs <2ms on FreeBSD ! + #stty -a is 400ms+ vs 500us+ on FreeBSD - if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { - #same for all platforms? tested on windows, wsl, FreeBSD - #exec stty -a gives a result on the first line like: - #speed xxxx baud; rows rr; columns cc; - #review - more robust parsing - do we know it's first line? - set sttycmd [auto_execok stty] - if {$sttycmd ne ""} { - #the more parseable: stty -g doesn't give rows/columns - if {![catch {exec {*}$sttycmd -a} result]} { - lassign [split $result \n] firstline - set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? - set rowinfo [lsearch -index end -inline $lineparts rows] - if {[llength $rowinfo] == 2} { - set rows [lindex $rowinfo 0] + if {"windows" eq $::tcl_platform(platform)} { + set tputcmd [auto_execok tput] + if {$tputcmd ne ""} { + if {![catch {exec {*}$tputcmd cols lines} values]} { + lassign $values cols rows + } } - set colinfo [lsearch -index end -inline $lineparts columns] - if {[llength $colinfo] == 2} { - set cols [lindex $colinfo 0] + } + + if {![string is integer -strict $cols] || ![string is integer -strict $rows]} { + #same for all platforms? tested on windows, wsl, FreeBSD + #exec stty -a gives a result on the first line like: + #speed xxxx baud; rows rr; columns cc; + #review - more robust parsing - do we know it's first line? + set sttycmd [auto_execok stty] + if {$sttycmd ne ""} { + #the more parseable: stty -g doesn't give rows/columns + if {![catch {exec {*}$sttycmd -a} result]} { + lassign [split $result \n] firstline + set lineparts [split $firstline {;}] ;#we seem to get segments that look well behaved enough to be treated as tcl lists - review - regex? + set rowinfo [lsearch -index end -inline $lineparts rows] + if {[llength $rowinfo] == 2} { + set rows [lindex $rowinfo 0] + } + set colinfo [lsearch -index end -inline $lineparts columns] + if {[llength $colinfo] == 2} { + set cols [lindex $colinfo 0] + } + } } } } } - } - if {[string is integer -strict $cols] && [string is integer -strict $rows]} { - #got_dimensions - todo - try spinner? - #puts -nonewline stdout [punk::ansi::move $rows 4]$msg - #use cursorsave_ version which avoids get_cursor_pos_list call - set msglen [ansistring length $msg] - punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 - } else { - #no mechanism to get console dimensions - #we are reduced to continuously spewing lines. - puts stderr $msg - } + if {[string is integer -strict $cols] && [string is integer -strict $rows]} { + #got_dimensions - todo - try spinner? + #puts -nonewline stdout [punk::ansi::move $rows 4]$msg + #use cursorsave_ version which avoids get_cursor_pos_list call + set msglen [ansistring length $msg] + punk::console::cursorsave_move_emitblock_return $rows [expr {$cols - $msglen -1}] $msg ;#supports also vt52 + } else { + #no mechanism to get console dimensions + #we are reduced to continuously spewing lines. + puts stderr $msg + } - after 100 + after 100 + } + set input_chunks_waiting($inputchan) [list $allwaiting] } - set input_chunks_waiting($inputchan) [list $allwaiting] } - + # -- } else { - punk::repl::repl_handler_checkchannel $inputchan - punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan - # -- --- --- - #set chunksize [gets $inputchan chunk] - # -- --- --- - set chunk [read $inputchan] - set chunksize [string length $chunk] - # -- --- --- - if {$chunksize > 0} { - #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" - set ln $chunk ;#temp - #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] - if {[string index $ln end] eq "\n"} { - lappend stdinlines [string range $ln 0 end-1] - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] - } else { - lappend input_chunks_waiting($inputchan) $ln + if {$readmore} { + punk::repl::repl_handler_checkchannel $inputchan + punk::repl::repl_handler_checkcontrolsignal_linemode $inputchan + # -- --- --- + #set chunksize [gets $inputchan chunk] + # -- --- --- + set chunk [read $inputchan] + set chunksize [string length $chunk] + # -- --- --- + if {$chunksize > 0} { + #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" + set ln $chunk ;#temp + #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] + if {[string index $ln end] eq "\n"} { + lappend stdinlines [string range $ln 0 end-1] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan line "" $stdinlines $prompt_config] + } else { + lappend input_chunks_waiting($inputchan) $ln + } } } } @@ -1582,19 +1624,21 @@ proc repl::repl_handler {inputchan prompt_config} { } if {$continue} { - if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { - chan configure $inputchan -blocking 0 - chan configure $inputchan -translation lf - } - set chunk [read $inputchan] - #we expect a chan configured with -blocking 0 to be blocked immediately after reads - #test - just bug console for now - try to understand when/how/if a non blocking read occurs. - if {![chan blocked $inputchan]} { - puts stderr "repl_handler->$inputchan not blocked after read" - } + if {$readmore} { + if {[dict get $original_input_conf -blocking] ne "0" || [dict get $original_input_conf -translation] ne "lf"} { + chan configure $inputchan -blocking 0 + chan configure $inputchan -translation lf + } + set chunk [read $inputchan] + #we expect a chan configured with -blocking 0 to be blocked immediately after reads + #test - just bug console for now - try to understand when/how/if a non blocking read occurs. + if {![chan blocked $inputchan]} { + puts stderr "repl_handler->$inputchan not blocked after read" + } - punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf - uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf + uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] + } while {[llength $input_chunks_waiting($inputchan)]} { set chunkzero [lpop input_chunks_waiting($inputchan) 0] if {$chunkzero eq ""} {continue} ;#why empty waiting - and is there any point passing on? @@ -1604,33 +1648,35 @@ proc repl::repl_handler {inputchan prompt_config} { } } - if {![chan eof $inputchan]} { - ################################################################################## - #Re-enable channel read handler only if no waiting chunks - must process in order - ################################################################################## - if {![llength $input_chunks_waiting($inputchan)]} { - chan event $inputchan readable [list ::repl::repl_handler $inputchan $prompt_config] + if {$readmore} { + if {![chan eof $inputchan]} { + ################################################################################## + #Re-enable channel read handler only if no waiting chunks - must process in order + ################################################################################## + if {![llength $input_chunks_waiting($inputchan)]} { + chan event $inputchan readable [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } else { + #review + #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" + after idle [list ::repl::repl_handler $inputchan $readmore $prompt_config] + } + #################################################### } else { - #review - #puts stderr "warning: after idle re-enable repl::repl_handler in thread: [thread::id]" - after idle [list ::repl::repl_handler $inputchan $prompt_config] - } - #################################################### - } else { - #repl_handler_checkchannel $inputchan - chan event $inputchan readable {} - set reading 0 - #target is the 'main' interp in codethread. - #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) - # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 - - thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread - if {$::tcl_interactive} { - rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" - #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + #repl_handler_checkchannel $inputchan + chan event $inputchan readable {} + set reading 0 + #target is the 'main' interp in codethread. + #(note bug where thread::send goes to code interp, but thread::send -async goes to main interp) + # https://core.tcl-lang.org/thread/tktview/0de73f04c7ce188b13a4 + + thread::send -async $::repl::codethread {set ::punk::repl::codethread::is_running 0} ;#to main interp of codethread + if {$::tcl_interactive} { + rputs stderr "\nrepl_handler EOF inputchannel:[chan conf $inputchan]" + #rputs stderr "\n|repl> ctrl-c EOF on $inputchan." + } + set [namespace current]::done 1 + after 1 [list repl::reopen_stdin] } - set [namespace current]::done 1 - after 1 [list repl::reopen_stdin] } set in_repl_handler [list] } diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm index 5d2a2725..16f6f1cb 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm @@ -218,7 +218,7 @@ namespace eval punk::repo { if {$fossilcmd eq "commit"} { if {[llength [file split $fosroot]]} { if {[file exists [file join $fosroot src/buildsuites]]} { - puts stderr "Todo - check buildsites/suite/projects for current branch/tag and update download_and_build_config" + puts stderr "Todo - check buildsuites/suite/projects for current branch/tag and update download_and_build_config" } } } elseif {$fossilcmd in [list "info" "status"]} { diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm index 8e59cf0b..2eb2f8fa 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.2.1.tm @@ -2472,7 +2472,14 @@ namespace eval shellfilter { set exitinfo [list error "$errMsg" errorCode $::errorCode errorInfo "$::errorInfo"] } } + #puts "shellfilter::run finished call" + #------------------------- + #warning - without flush stdout - we can get hang, but only on some terminals + # - mechanism for this problem not understood! + flush stdout + flush stderr + #------------------------- #the previous redirections on the underlying inchan/outchan/errchan items will be restored from the -aside setting during removal #Remove execution-time Tees from stack @@ -2480,6 +2487,7 @@ namespace eval shellfilter { shellfilter::stack::remove stderr $id_err #shellfilter::stack::remove stderr $id_in + #puts stderr "shellfilter::run complete..." #chan configure stderr -buffering line #flush stdout diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellrun-0.1.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellrun-0.1.1.tm index 478c70fa..c3f7ab10 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellrun-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellrun-0.1.1.tm @@ -187,10 +187,10 @@ namespace eval shellrun { #--------------------------------------------------------------------------------------------- set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punksh -inbuffering none -outbuffering none ] #--------------------------------------------------------------------------------------------- - foreach id $idlist_stderr { shellfilter::stack::remove stderr $id } + #puts stderr "shellrun::run exitinfo: $exitinfo" flush stderr flush stdout diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.5.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/test/tomlish-1.1.5.tm index 3ae60d426cf6b63988005e9d06eb1aac81a2c04f..f4f2b48410448782e6616be9dba89d649618f96a 100644 GIT binary patch delta 2687 zcmZ9MdpuO@8pmheVK9b4CKA&WsfOgPQSO&Qg;SyAGG%N+#JC+Xu}Q@aJtapsmoAjc zFrjkEy`&;{Nh%$k-R02MRy*R1Me}K&_0Rf!zt8hN>s{;fS#RoHLg`%s?+6|;2vpdi zK!nTqXbLMNBruvD6B88d5ea^R$OeRuh5r)V!CX{?-%3=$lNR=m(db9pcN^af^2c1rHg@%$9f^PLbqJ{mNBrTbe3QCDqg8n}d zsYPj}rfl2^*UG`baw&02*RP$cduXrj6u%)d${OE#D@VCrm~`N_er+%ha*&l7d$Mby zwPa~<_n!5;@7TU=vt4Sqa+z#$hfhj9DZf_pscdf04PKtx3DE?rfEsdZ(BN2#!sjogvvyyH z+Q(n^w!4}&XVq{iMv7~Rx6`v*hXT$wZdZ+%uv$km4uafgc&4JUI(KBl@{X`BM(VKf9XH~}QQW0Zd(tgfC+dz4Z{L+nxm@~%iL-O1 zzQa}PCpqR>PACS{*%iEebe`#|`lroAz2c5%YcsCJw5pp*g+4r58*ntXcc(l)y;I`c za7x*0OP|ej^JH#c*DJ0zKhQsEC}z~2vR(XZ!Tn^f;*eFzpPOq;P90H594Xw?FE>sZ zxMsqdA|w>;?l5j~u+nQLn$g4eGgEQ1ewGc+pXUaz6RPFbuf~P1EW6M9xVNLJC~yrJi^WLi!rp_MY2H&#PpPXBNe%It* zzngr|xLj#l5REum+s8s0_>HCv_S#st6hwenM}i|W~MY52NWBf;uSi*04$ zHHXpHXX2xa^YhX)-sn_uEj-R{Jh#{OQ%!EzKF^PvMnB&(x?K}IIHuj%VZ5RG*Z%w% zJDm?X#d-nHDjfYPoS9=5({JLNjz>6KNSIvthl|e6#J=O()kk9`n_1%*C&v|(xFjyr z_$K9Dux(IX$+hv7^t`5hk?*%oWqD#@PZ;5`@}tSgOzyY9eq&x*btAXGCvJALskEwt zf9_Wcvo&cKg6>u1hFrsKVBDNQ5mPA9{cEgihhwi3yiZ7pv^RK)?=+`}^Jcs?ch7C= z4wN{UrNq~asp^_;!X>wUwL zR;D^AoqILgY}d=%cIk!0%;T@ql1>f;5#W_8LLN8F4P@|bA&QCT?pdSMd#-5I2`{Q%@3sie$YTjxeu}s0sIXe z5aa8ioP`*~w?_sFn0?0&$M=BuEe>L+0PB`DbTY{hXCwoL6%6$Lhmwc80zAHAu|ows zYss*)l7_TbV#?{Z1k_g=p)IR^LMhl;rH2Ak7nMA`tYRQ#wME>5`hG-lki1KQ&$so_ zfz^xEd8Gnyhk*>$e^O3}x(35erWm}mq(IeO2Fh6blTyUCMzofb5_~h^B@c(I6HtbR zu+hB62-)ZhG0Ra3qVLJTs~RO_XCTyDYt50Tp%9CmB;Z9Y15u2G%F|gE+N#MAT1Ufg zgN(X#bk;;zoVX_`n94vUraviD)NLljn0g -CQ>Nlb)OCiQJn4?`*KjBi?T}uVS zMj8^c7HSty3h){k=+uIuH(4Q?4OXQ$Ed#EXV9(7mFw~@k$`*8eGZmbg8K~73>#A?w zie7EOV$J~yc(iDv?5#q4-NHm}c8f@bh*k!^4RTw5L;rEWq_!^=PTXf8?lvJxKhQ(; z?ZOF>zfj@k0|wICfhpcYX;{~$jW#$6vAB(ijGPux9?Tyy(4RkW3#xS%RE%LdKmqLA z8R&}(Sacgfal1cCcg3RPBYkA%wirc$8%l!qM+`KzptK%)ptwC)^-kC_7=Fw^;_d=8 z1^aMy2ztUmC+E=&9*0{9kPNFkXh>@xrV2Y`;aikE%swGPX@@C#xS+K<4N>KHFp-#S(@=C<$d*KuEi<-=7AYzhPf_!$Zp$m0 zRy9#sB8rkFx@9fgii#Gc?kz3%I8CMB`R9Dz@AEw0?{m)UoO7;}ysMO4luVkyiilbr z5*8i|M<@(PKy#sq#055Tz_S}noTLn$$OKlB9pE_GPAe>gmE<#4%?eHk69@&Y>3H|* zuo$3eG2j+WOIZ*V8xs^fPV187E{ zMPkcHLu7m!sL`~eN~cn#n?fUv1*#MZ_-JW<*C*9x66%=Nng1VHm2QF$Op|32xBg<% zZ?s((_pQkJQzwj0k&#hXkdc`IRjX#C8Ve)NEbEHwa#r0qu-vUtCTwqR*4C2Hy(}vm z*-yO|p`T``)@JJP6mrVC9vnBOjl4SOovUF`7q%_0$UTN5=x=ynHvJeS+5gSl%syw= zC!X?q^?VHnA`T{r?`bWd8&WFm|26V`79!zUv2{kf&2H#{|^ z$;9AMl2Wljn#(?KziWn)1C$7nKp@sr@x#_(qu$N8Mkw8(s+g2PIF!7{$-w17;`7N=te zM>-=r`Xy^d-kwY0{X2SFxA(?d&3W7Qm2XtAdTKzm&d+?;Sd>#c=!J$vu%I z&1X&@*=urdxEt8EoZVzn&y9cRWbAux?GwfRm>K629!TCfFg%6WAa}rju=N^})lYrp z_^u_C(i5xdX)8`Mr`MJllqsFr8Nb)~x!SArlzCCM&8~)RA?uzb_x0@}C)BR-|EQ)Q zK4T&RS(HgyiXm9%gh&r>==%;e||k?2kA>V_nN2hYeylu3@Pc$ zEHyq~SM*{&1cR0r9-ha-E{@4>m zHh$$(!ciQ7rrR%jgUj>iHOie{6-WI>cPyK{H;8$0QF7hX0>3@Kb5+E9mfoZvnZ4@K zaA*%&-<~w^=^ys)O10Gd@(?E;M`?=-_hn{7f4N(rc~A>?h2;cwafr5vvoCnS=c(w5CH~xjhpzcP2T-)jX_pOOWU$*`~45i z-cJAgbn?56^TMe%Tu1Q#>i?L{EEIPtGb6IG-h#~G29QkmkSQdE2a}b#+MJsez zR~nTdp@IQ%B~0Wxl~7k~Rp50Ah61KZQ43NpVQ8x!fmwD+P;x~T)Guozi|JCO0oj)^ z^zs`Tm8yc~6$_N4Ph2%BW#SDOX(9AoIf#sA67qH_2Z?`^Mip3D#zwO+0(;9Wk=Rfg zG>hJ1Nqk=awR{tC(nT9-$6ZnUMnFPE{Ie=0Z!AJD66};nFn> zh0m9w3(|EYN)!E6ApM3a6#6SG%Zxh;#xA=7zs3exI!Pl9{=Gg4zSL-=TVv{Xun(jX z&0DW)qleB?r9w=jbc2aLxZuhgufRwb;QWoh2TR=puIu4R?BVs|FDa!vA z4R3OgwL4)&YZ%aU6GP4mapc3{P$gJ=3qvl8e&E?Kd3;9bFB%`5N`vfEf~`9|7lgAH*2iR8iE*460ac;y7WcQGVd`J<|LgALJ~`4r)ZK0NNp*I91fe?lF=Vg_{jn6+pW<*>xo#_&OsSM zB6eh{0j~o?*W(GCkjQ}BI1VQfxZjKhyiR=BO$0s{sX}chMiRhqXFTf4BxF|ML@4eu kL3X>Pr~z}kF~rEmUj#D{b=#u#c|??dz@nz_8~Xpm1I>K zD1=I7R8+tFIR_E-9k1`}clzTv&vW0`{l515zG8zGd?XK`36KXGgM$3gWG~1DiGeV9 zggc4|5r`-P3g?b;SAzDSd?{!miUgs+8t}@Eg2s^1I0)?ZASpn2BIM^m($$4Uk?{}~ zPr;EPe=k@lctija*cO}!Ng83uC?XC)M*E>Kfe-};?<3(MvKN93p#sQ61d@z$ha|Ch zcLLsBk$^z@AUsiuekdXdjmJSAcnk~=O@aUd5(0~wdx9WAN@OfmncoidMBz|G1T2(7 zLgPGj0Yq4|t}X`crmKrZ_@G<>1{c~>ND=ShK|+zqfdmwUAR@g0Oh^yBhpl7)>5;M2 zKNhKk_JD#MkO(r;3ksqd6Ne{a5g7CV6y$)$dEg-ul87ddokHM~;RWEYphXg31EGjS zU`mNu7*wl}DQ>jKO3+>$!VOpy7}y;J?1x3;P_Q%#ct${wy_6ss0-i)d!)r(g4-~Kt zSVe$<+sH&Tk_=2o+AJ%v=+KZwzhEV#X7NENUr0g%3J!+E9jFSS0?;Hf37UKVzsD~D zEdsL`8gK`!t2>$q(cpl;aIov=@hsXryW9nVb9Vt@GrNa~$CIg>Xd=i6H0IZSr$quV z2w+K19OOVEV9;dhs(E3GPP1mG3Yd+`MZ2ld@VnIjV&%DzhEdPSTh@{!JcWp)Y4@jn z$_s^nxL<@I1VUj*sAWP-a!KsK79oKg15tyA{811Rfdl%%8H^whhgav2|G&=<> zt0TN@c}13gIvWSe+HC0#SbYo@glt>KTe?IMhr7%b( zC#NNdf61d&Q@EItU=^0iBO@`ox@0WeH%R1VM9moG#pU)sYy9|{O@*>T6~fdHM4%@$Zv1|Rl>k!Z~>FUB7!h0cV7s7jh zzD=bz5MVAqb9b1~%snE~DA%&OQG*38(JtV~*`A_IGdLAZkX*=OAWm@S0qarjiog{x zsan`PmYng&?g5+pcLTtZW_4U5Xl_0A@P%OiGdOHZ1XDdnEnP@F77N-u38LmE%+df6 zrJm;m0fHnU@u%>WL=ZhGaNPj(73PyjFF0;Nyx}1?&;WV5Gb)m&VW_Qw> zG*#I0Lfr8l>r4uG-I2rOt0;5o<;1n?)>A5Zk5(%r>u5jH*ina>QWT|h^02h11p zK>@%HO9SN3#Z*FBnHEYg%c3Qk1cXDw2@QVHg#Rh9U>tyEkqBrq3No@Zn~kgm0Xm=L zv!;gp;Q|1z2}CjlT#MjK5e^SrM{uXwL~)jx(H!c7!~274$)ebWLljRTLu6tg)o|qH ztpg*YX3p$QWm)gQi{Ovs5_`>)WoX_yf6HS~?BL5B3JdE)0v#DXn>_1!1msV|10Q=* zP$Uv0gCLU89^l#sfue|jyC|HP;Kl+xzaVx&%#bK3X!G)6MS~516DWWSNc;u^SkcJLXbo^DdcJZr)uX{}KcYhH5nu z8I;0bS5OkTei|4UEC}7%+6w8xZUL9!U-@7Fd4M(t?)r{6HxP=mD(Nn_cj2a4K_m(i zi9&&4gCnR-Fw0t4$$!P$uXKdw)mwa}owMu0??tK^!<8K__!NRC5#dhdi(u$M1bK}H z)en?^5O%bp?|}{g0Z2sxC-UD9T1qeGApH3a&8G_^e5o6ky)^!Q`TLh1USt9q2Y}|h zOLYfa?f=WeXB`hbM>7>w9%y|I3w}EN-FSSxh3>4H~b27kw;OM1o1zv<(@v?TB9V+~n zUS1;duPjm01A)P~0eTB36kPm4!p(80C6eGok+j&N1la<5L1isS!0SQxgiS$Z0a%nf zJcfXP-AIrkm_LGE2j0=*1TY!EsbOY7THX;$(+rLPc(5{GEdQE7@cy~V`x`%>SL)v^ z_f&#H@`NOQMJYUPofk7t4S?Bb+Reo$_?`=6&@X|{Y1su61%w}{ywu?!Z9e=)?0vz1%QgI0GJA^t01SkC`L z{f3WOzJ~w#&GPGio6rJB(XI*qmRXmWV(}S^f5HDlGav9EKtVD6HChz1+`+hlkB*Lx zg|1^m)c(e>8=qvr&y7}ebV}gqT>qJ^S~lR;r>LT&s-&U3B6VR}Z#HYq@A z$ROlJ3GTGoU^~KutN&$VV#rv|`B%p?SEM;@ICrl(>sZ@ADAlPCI`TN3Y2gqLPEO7; z<^gto)&Xg8c_vTkCmRm93ja*bysY6V!TDgUfumPIR=%uNS;yhc+lZxro{tbIJC;dmY0*ktAIL5DBKJ8P1zsNF!NiuHW+c|gp#AR*T_!`&79 zW8IHULx%)s@+uB_lsH#^WB>R%QmmWVnU3>|uKBAelVth~gQnd5BkFs868#LO*_*hT zJxg8a+Jqy-P8w0F`h-KAQ;y#Y;3|?3t$wucK<3ee#!{U~wB>oULiegEBl32O&HDPN zmp0Ib$6xDrt3jC3t>-9e0%tW8kkwvRO6Vz9=aU|Bfmf?#Plt0{9ox^nMwRmhOZOz> zE)Rj*j_>vAUB^A{CWK){B16Wt(^xHZm6?WfUO!&rE!k}%E?9dnCwiOCgVhK|EuJe! z=*oAqPj14Vb5U8{O0UFY+Ox%EYXAq`z)Xc&(+vx?Vp6l%hsSz+JLBV0G%#bH1B2BK zX_9X`2ZQUv4uxW0n9Nj%reBojnG(`ZvsSu<##@hx*^BR&5h>uXt~W5#f|%<*N`0#}#;c)Q9N9R&H@JBId^e8EvL-kY zcp3eJ$0)`XpUR=<1|O`$^8+x>yL$GGB<%OYcj`pi8bW6;z~ydl_j}%T;IHSgj3k%qT+j5nbPmDc6Kv{f8O5t_y6R_@Uao z2BpeepOH!As*9XVChI*-*C%HgS4YLOc*-As6onnMx?--X8)nNSpoUlBFh3Sz)_X&# z{zouV7k}+bch${PfSjUA<;ZdWhNR z{=};SqP9gZ+=!a8AIXnhLiCl7Ztd}L~Ya^osCPc#5+`RcrX@-UT#xKTJuVLTb#1%7N zBcv6~cn5bRq8x7&>)ti8hjNv&g_jrrPKn16%`4To~do42NUcwNpeU9A?D z8lg5A(ePwq>+btSJ6Z;fU+;?AD|#`kWqRgjz=51>yyj+7!WM~1SM#K09r;m?hm*KQ+%MKzrr#nxVCK*)Bw;wG~do|R#^SbL;!%80c!&pA$rydmby+hXJXs-qnHP0gii zg5pEkhYxKpN>0nvIDN^1{fv?BBLcr3TGCP4wEt?0`fWNBzo-U|b*urNt2658JzwSA zdwAcEeOqG6I=(Fy+scnwTdJMkdBtY9oJ~RGVh!Jn!U?e_bq$wxj0*YM4-K~MJQ%v} z)Aw5fqYWpTGhg0Dy5>3c8-AvHcJpDQ0b!!&c1mmU!EY;sEULK-dyCkP3y5%Sd6B#` zpQ6p^wEbP|z6lXAUg7Ac2<9*suA>r9?-i#h1)t&1GdQ4q9^1b;Z2cM$mdkd@8@6h? zdu6?^*-}0lpKz%5^bZGC!MZr7i6ou$@KagJ$~IOpj}2wHvsZEAW~6w8)f!Vh-1v4R zg;yo=PZA|anr-47LrH@#h99NrG_XgHi6k7w<@dq5-5;I|PT6M49%n>dm3Q`5@WrNYmXhVM%3-%TJ^VybD(GCq zo3~*lBZ7v}gMsX&8`^B`G*o>4nGs}rFlMFL_UiJb+mtB3uA;#`2OY?l3wVdQOSC0i zf*xKK(%YuQ;y7?tZ6NmLhGLe_hbwowM?I_lFt|}?)jySMYp&fAbxI;o%(T<6<5$cn zzA1)Bjv?4<`1qPHJQLXY)k1LJ^urI**j0InZlS`g?5!_^wUovCtNT^|i7raE@nXr! zeNq)j@z2>|dnkO=V0`tSZCN~R$$s(L$lGle#so1p!wZh*BV#)5GH4Jk*$@VAnsa{R z6^>9BWye7c9A2?E#0It|UKy`Bu#(rv^}IEE>RKf)?+UqsRo$smwk0!}vZ&Fx;t2=D zbSB@q`rwlr;-2V_S5|wDG#0wC%DH1F4v9JBr;N9!TP8(bwk`X4=KFp06VZD{{)6YByO9r|XxfDtCD_IexQo|g> zPj_&zu9-v#W$8T1(py263>cHj(($DmTF0kte{zhKSd}hb7mVp%-|pD6KLjRC>ynGF3EjQ-t<_N@f?RGL%a(o^VTS-sCD1R<(jO9 zAC~U+OgNDce#fGz&`79b^vwgqOb;|suB^C3>_JLbSJ&syBO@QGLnt9t;$OPHF-)9} zL9N)hS7mRAo6|$1)*WLMyBf!!Ly^0gELVrpcV%csIIp@GE;>`DYWVC(2E&aZ^L?e| zs&tY*KF)#qTEe#KrqPz2s>!Yb+fCo;=&s~Xe5v2xSHIW4{!-TE^e<04-R?Qp51{My zw9d#Czqh-Ev{`8$T~n`TuH5j>x>|jsZJG6>3kdnCo7kzB$F5v*QO@3jcvj@?VN~xK z|2-q|Wha2cx?0f{rPB+}h zH1tz=dO;XDXK% zOl`mB=XLc|?n?agp{gyJ+jiOXWV`MV&1)2jxndf_OUM`Fdz9`S!Nceg()285=%#qg zX_=C=^M+%B-tF{CO#C5Sr?*}TbYZkPn2qyn&T+ROBy6K^ExRy7?{EBC;O35>xy?Tf z(cQLP1!YkI>)eh=jg}df?+xlw)#ZMfzRHywb-5|N7dvvL|I7 zcMpN6U|BudYun()$%0`X(DvHY*0+-9?1r-&6I_xy3cCw>tP&69=BZnjGLQ9Ecm(!J zBz8C290=gj?XNmQNWWvW_kCHseUTJ?lcTle@XF*y-X}edjU-D=qfWbTk2eYCZHVC) zmXH5hDKv6(BlCI&+m$|XyA!kHRUSOPdUCUHdD3CCw+-}-O44`C_T0PBKg2F(>K62h zz17XxvbCp-jfc6cWqqri&bqvmgH4_N!(6(|Dqe_|)I?bkE{=!nA zKEi%$igvE;0q^JCv1y=Cpm+FP@~OnfYA6QTMEWy-YC3t;D7Z%7HS$T5`Wbtu`JK^< ziwczkE!?%2j*ML&E=XWuJn-nFdd$)K^%uVsx;~TlOJCRMo_>IJfTe9j@0F{6M9cnq zw95O3L)Z1o4odA7F8I8;m?+UX)V?dRXS=wrhD5SQHR;&f8C;qI6qyyjx;5S05$A z;Gom}pGTrHci%p!S2!6Onln+Cr&hp8DU{=F8y2>IDmeIqjoYT<%5b6f`@DGn&mSN| z@3mQL4ejY$2aTPDdem;%UlMmyI>0lV1PKXFMWSq=8=MyZfpNd z|K}#7jNK@iw{HRucm8Nxb2_j^wKS}Whiho}nZD1QE^i+*$Ht9@-M?b)a$!5`-joN3 z5j{oqaXw)^g(zwD#=G};1AV= zmG>@EOz_2Io~G{CpG~~>S6s%~ly7k{6+0{-$1^-^wR79>%~&%||KQAaqdQO6_+(AA zRBn1Mr+mg(3t7a`^x+imUezu_eq87-!%WAzUB~w%@)+!~(l&pc)2kMAGQT4AUV;R3 zx*em>R*_Ihr!H<~(GT9O>rBbMSuTfvng@umGksy!<5uo|t2T+Zk(Aq+kg%nrd(Adz zuw2LbAs0P^$(Sd}GOFLixb9R~PuI7bA5B&{riX~Um_#G`Vz8Q5Yc3AIJ$pm{ZM;6bM7YP9kd%cq-+pU zIrAmql9s;rO50nEs7G`qrmNc2R$DP8JBmbJ%P4$R{;rN49fDfy6n3^BGrzUjz>`+fbc+njvauM$5n*GTbBGoB$n z4tm|IACS1IxYt~&HNok7D{JzZflsV*U%ONGCFMuP{^;I`a&y;-IA8R@ZHr&SXv9VR zpbGXT(|Y5>VNQcel7mU!w~Oh;Ubd%jMkfYlVULfTu3D#SrI9*{-eZ0T+n`S%UDQxy zzaCbQ*Pt-C>Xuy}FN39UzZEuEu;%N>PlHpq6VjfO-q)WCtT!qitT4NA=kfJ+&2>}0 za{02?$oxIM17Dd-UyQWY4+Z^v`}np2!kB;P)T=`2yEiRNO-gtvH7S#m%6HHQCPYTO zRWCzP3S1v7jSX1Q?Pn`wVzEwfA^169?q|6(yyaG(`_pCqCY#3 z^6FK=mR{BEPKPEpzc1r_UnYOIv~j?MZP+Tw!Z(8bC(E{Q^l;YXyY2StH7~E!ENbkJ zf54JbD(+P9PRQwsepol}s_Lrdh^`2gjZ(Ec`}(1*N~Ag9M(^@0%O+Jzd;s5BsC~D|VW@x=hNAM}mM}-cYj|4g6{KaHCGd2kY2fLlS zqhb)hd9rE6ccxXx-};73u!SRC(g~xtc#H2%2Itd%>@C_nw4QDJ!P$~88<^4&*LPj` znOk<_cE2WT$8_Lp!q?)X5oY(BdGc3GzFlJ#XjP6s6*>LljA4aYb90#UsmZG;8|adY zci=7ZYl4$(rQ3BToHHz)o^~i7{4%VoJn{N?)Xz;WlznKX>Lj{hwqv!^<@>9fi+P^% zcI;w!>*W|8Zq|Fb_JgcQu;1>$+X4o0jos%*ev-FqY#tMl-GvmI$^G%ZxN}!k>ZQ%M zO+UmUd0!og(z=V!Nhrztw%KwAJp&)zpQnys@VMafpUogGcQCDlH`9ZEj^LXNA4B(l DsWfhc diff --git a/src/vendormodules/packageTest-0.1.5.tm b/src/vendormodules/packageTest-0.1.5.tm deleted file mode 100644 index 3aba1ee9a55795a713f2423a33d4e2c2277ff70a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11963 zcmch6c|4Tg+rK4Clr0q!cPL{Q5o4Y7DJ8oi`7$hK39CutdDFEo(y^72{_0fPxFTC zFa(G|LSu0hh)lteaYQT*s|wlR_R#SZ92LTWHQ<#89Z#U)i4fT9NmYhO6lkv}6^VpJ z(MXUViB6!|%lr0wFpP-bW=tG;cHw!UfPMXbcU9 zg=GCmSTYH#LPleJ(Ox)}y*LULPa;B|BmxW%PlW&iD%uY>{RB;gRB3)pWqv!*3rECJ z(6CTC6;Jd+0*J6^B$9ylKqCFnzBo64!HxA4QXzSIQgJj|AQ=atDHv}66VeCoVJjIz z`ZPc0A9GZ~dqTlZ7&Hyz4FxleNhDGH&;)!C4sya1JxLH1L&1}2E)4i&cmen;c#Z_v zKsX8om{Mj62Gc4ux(DmADzuY`_5c4rR>w1uG#pg%85*fn;Q$efyv_PE3Vx0eC8n3QfQN zzsD~F%>gqP8gK`!D;7_ISa85!MA-E+c;;-LTJDA>V%8d%bc2su&71U!woYDSof%aqxf0;ZyJ&TeKj{BAXXSZz9_Vbs&|7PKUlM5ka_ z+Wl#t+H4^p?&lz2KsW*ww?K%=&Wjz`B2CF3K0rs%yl-r z1^&$4w&{Pp%@A^$b@-n^!Y9IU1D82D)I4?J*YF2kf`a5W{dK`6NDhcuaLK}|P9Ge+ z@M}*J1&0Q;B>*6Ug5kvoxd4Sf8gAC|O$)>TOOYx2GON>6a>GIw49Dpm@HYX31!xN_ zI|VGLBfM;3MHYTK6$cC2Z2k^deF6qe#ksL6u^ScV<%c7J;^YKZI-(aSkpzM-`@VGgn`T8EYt)F zn&<`E-fZ-&((N)U4}UuY0uki`>B-VT(SnN(jsl1Q>|-nhi=XE+SZVm<0rJ9k!MF3{ zX8w~oW|Nzv#~;%Gc9bWbK$v1BkdqGy&=4mKi3r+1G*<|-Z-D*`OLl>fNLWL7FU0IZ zBu~({nbZaX%mrwUg$d2{BMOUhEvOqaSl|-v29BKSDQYZ(GtmSqGUfttfjbXakHe|} zSHPrdcJr8b#vi)}Z1Ud?085(Eah{;*^~}R(gZ-bu;hQIz={aWU!jSy@K%1vR%-n=o z8X%&~^IRZ6kYr^36uz=(iWeQO8-TvTd=ldg$1R9A66688AYe-%$KlsZUOdfjkw|l9 zZ^Y3s^FYDc%Hq&)=uWW$Dw0K!r*|=NkwL)F;OB4@EGz_&RM3w#)HO6!)OC^2Tp?3C zSxuTLY+)f-5>cK8#}y<$i;KtuVvZw1G{6esQuzxJ6%a@&2~etO><|bp0uD{3L0&Ke z_Jb)A_{QQ0=s+q2h`2wVh$Z<`Au<6?1C1s040&@3t$Ps2edt8G(}H7h`8 zl6=b4kUv}iz%_wFql0S^d?_N5fa}OurcG3)m>J8VzC@BgxR%U`T{uKZbQ(mX1Tqar zTi7}dUXpomf^=6~!XGiZU&AaJ7vY#aYqG_d2=Oy4o*Lz{8QjB+#XV*E!CFc_xQ zs5DRte_cVT;QDE3XgDi$r)n#t54#0ihJWRQA>;|#9JuQ{6FopEPN{@kXz%PzQ-Y{; z3>M< z2h|Uhe-L)8qVI_h00GEE0w?m{51LOerXl?K4a=vqBYeIa7rZq7e);>49-d}K%rKe0 zYs2Fqz_CDzFo!VAhQ*>UklZX%_h+mNyOObR6_5qBh-K zxShhS5GES{9hk7qjGe%h9E=~}D>;jk?8OoBU>PW=zvg6s{lL-7+6ue~x8eouG&NNC zUwV0-$iK2gSx+>9-~s3@oKSG_2MIULq2@_~6GhfylPY8n=mnFtU;wWN-4iwilLh$U zu<#fH0(PT9Dq#KydL4MjiW9(O0H=nT0d-+V%uh2o0^q^QOtJiH0>S&IEAMaod`7AN zvfMKX3e^je`4y$`xOGO%3^f2|V`(=XpWu5sj6uHyK4)bYOcc<2LFHu*2U+vk-==)i zb!hr~>H_ZvV1S&J+2#p|6>F}`5;=REi>82JoDr`wWp(Dz5spfh=|OBVXU15_BM|UJ zA|3dCYOYIW)>q&vW|5moJoutR05d1mKc^au_BW(>>m$=<^2~C|9{h0;KP1_-Z@pdr5KfhUc{cjUm;3(EL;lDELJX6d)W9~2be`sa`9t0>TroTpu5>{rMi$vMj z*m&8L5K)dkfk{&8;3wIdjZGCio$fzVRm%?C`c%|a*Q;u&&3giRW0k&d;+ZX%o{cLZ zdD}xA63n_xHh&>Yk5&&&agnb(GjEBqTA2KK^6E;3bW7=?2r&I`+o{ zJz8+{LZ`{8mSas57yO;VCF6 zSk5!Zzhd#A{2C=LFZn0yg4|dAJds|y!AnN4ajbSp-%16g@@6$1rx&jdRW%aZNhvt- zdwr{$xC+-RNku&nC4b>+<8VivK8zU9HI7Tr`tZc4xMxkPU~>8?gcPQ+AJMxw#`)G` zdL#QKgnQ?@57FM@_pIODf0NyRsPxAY#2BX=h~=TPB6u z_x+Ikp*IvNHko@l)U(9(#y7S82ZBbWg4fxk#^_1aZ=;nt^%+U#6>VNQKVFRP`?NAi zR-n=2CR@(NB}P!haL~Pt->$1JJN6E8{6~LZcF{+}#qx=mJpzw1OdmOvu6h$O6qgbb z6jXO3-+0Hl53%W3;|`A_n@6y!+IWYWeg9@Tx4gM^9cqyJ;LWgA=NI?xtB_IjkfI2e zzUDbyuNrctD=_BPP{Sd4uBS+Wi6N5CvJ{#tU+dNfxk{~DN~1TaA2_yEJeHiY`ZhN~ z4$H&tlfGiqzjO0VcgYM+XSt)52cr&;iLupxCE=2&saWTiCyaV>0}&_Xq}A{py-oE^ zjCkFxbwy2Iqqu%@JiQ+Mg~6$}(_Ze4R*+7v^R7od=X#AUY`SQLv=KiS!$=?)3q(CL zw~lbRujy`Rx&N@Qn4-rb!&M=+T<%ndkXi6U5O5e>E?Fs)#k-?{U$vMxbN2q~1yI+q%!+|l+zLpi6 zEgw)ZK42N|5@6om_PSQWPMR-jd*jjV%BaYF%hw7w?qbVc{OnOzO1#s@eM70_owWu{ODyYBtrC}+-?<>dk$f=1*;95yfu2u$Zjf}luajS- zZj7&l-b6x21p=?8U+h?QZ@|Z<|9H)V%2B%lw#n|Y8v{CC?*w=3N*|Jk6sX-n#Up!D zxrW3=G6NGMl^gorclqB4$i4RVaHJ6NZJ2CQZUK89Zck9t2ewjKu}6AF@rW~l0h2A^ z5`NwZBibmoAFqFKw}~B~W!&Ki9O9eMZcyOF6lg5Y$#M@WN;DL|+;Zghx`&U#9v92% zd5LC=K0jx|>szD4BkJ(_?fqVj$pp{pmv3!Pm4-WSH$2mD<5^&uKzQE2ntGpP|T zcXk~4vE_(l-gAK$R!QX_rHI0~;X#5-s?BlhO4V&AlrsBXMj~=<3QTfowQ@@DO3Kh} zXm&Tjti~?VT#9R48ecwgB<-|Q^2w&i`)5Q8^zFD#ef~;+w*tk!N#P953|iUU}h2M<*U)!X6TgVk2g+Q zy#Dh#K4P!uRB^~L#q<-*qw6^GfqZ?J1&d#Ab=CvPZ!=p@f4KgO#3Mg6gSg*Xvig(*TDDYL9Ie5h@(N*(8WoKf*AW?O zl+=&rY^>gTEd9*u6)^}kUBrQu7$4!>46NxXY<{cnatEYcxWbp+D~v8g$J-}&X!T!c zZaRBvl0$c3!e6Z;er2-J&CuFvi)%IEgY1{+>4}Wq;VYqPnJ(WyF5Zx`Z%x>3r{yAQ zNa(P)Op)qZ*}PJM24E$$YpI=nWz4^ z+{yIQ=A{oGak;gx4-;$pu)@PUr=--|z3uSV^`a?#*^W84(DtDtqOXgcn$AvM8gjRl zZ;x4+sncZG(gb8{mD`YNJ9y}DtXlhH`eNV*he&_H{!%J3buk!UW6$Q~Ho}s6{H{dTI z4Ge_e4%gFDicg0;84&if7rgF#Zhzt%e(jRvF#X`=MP|k|)+U`FL_$giJ5+>JsO;Q} z(PeVOONKkmmMmWW2`!PK^KXX!BART#m|TX=9=0J#(T%$!j(3ieQ6Ek_SW{3R0Vm;wC(*NRW-p(_VzTbL#|8DAC#IbUvK<0B8}t9 z(Dq$r73l%{^^Z1cG9 zS~G~R(bqn$Sp3G}8pe)$drWnW{&uz6*S0q_b?wV-|2>OV`h3;z^RvSj3f$B(ZO~7P zd^}M#UJ2jRlCI_#x1!jr*i5^mJNB*4H5-&uxMbonTBe&yyGKkAbL#KSPut2Hx*m-j zua7gQ_YGG1`M%%1(@}-kGI8GCejp|KZLrCj@;<}LeO)mJolCNA6F48;!0*ppW347X6Y8Fgkgt%|`3ES9_Mvc+V#~>1l&`%xV`QnBkniu4I7shaQlm6^k}Z1oPl zVq+(3ucRCARsPpYZO_9--OmEX#Tx2biE&(#VFfBH4Xs3uA5@S>Zw#yNFI_b$@la}6 zWMOq|j1+E762dClp)N1qYMrS(@9?6DKyulRys{qR{gH-%7ovq#u9s~);|L8WFMA`h z>D}pSC5E53UfSz@@z^UEiBw{a^#x5i0NsIoQ>T^hh)00v6 zGo3j*CZ?Lsn%UJtXe7UaY&?9Jt8C)xc?23B4* z`+4r+Ph)(CeS3a+RDh&MgxqMkamCKy_Vq~NXQ@lwg>j{Iff22rXqTJLCS(LLI^;iM zv~HcrPVMh7y<-!n-n3=YJ>zS(6)8%t0i)LxDBYvN8I|JuO+MGh-I6(2mSbOy4(|Ua z+RY|eQn-suQMS4<+Gk(uA;?SM8Qkct*V4}|a%SBb-9)$Jj|)5UyR4H!&*f@bmGO-A zUG@y@lS%4$U>6i1gnV8VK~BAe+WDqDVRw-nNzd8VYMA@PgJn;;oF7oFv`}ph-yZ3S z<*tieu}Uf7>vf5dtGYaEIqbQ8<1Ldi6Vw|YT|8>Es^WOqwpX?64^-uEZL_(5_W2OM zw5dmMFMqR#t5tJXIiCnmdHvdE2OY`WNHR9 zh915~Ejq7!eXw5mc0t5g<#2u?FK5ud?=)i%)T}-KrO^GU(%w|b2iVl0#e=*pBl^AW z{)g%vYw+rCnuaP3%0uKVSLJ^&DyGP^4Yislb!}aPl>Kq^MQo8BPAL3S+Re_TPrr&X z5*J+xf=O`^g_jDEyJsJcZ$9I!6$6@_`-1E#y7bM z{vX~##y;X1;>Nq#n_rr^N_1&l*H4yV`+GCB?1CyRF6zAeu55Se`i=sn6w9XvpYVRmLD5VvAp+8@EW<>k>6hYt zFoly1``4bZxg8#9aMlnVu`AW_UV23Sf%jQDTL!<-DP6)BGeb7xen!2J4I9y0qR%c> zwCwmP#XVLJ1|QdY=Iyt;OpzityX|ZiFWcY8|J^(O#K$_vi~e!7c3sMLJI^=lLJW0U zJdmZ*AzT>Cf$Q=Z0*C89;ksBRX?D|J@-s>kfqUMgY!UMEb* zR%&33hrIvXs~xp3D$lw3A2T_sN#ht;zrXF$!{q^yCozWHTnbQg`i6V8>u$8<3QDGb z$b_+k!0~OMpR_$GD`{+7%+;GR z>4@ZQk(;X;Hm8Z|4&9Y-kR7h(TCWna!FxO%vOzqBv#F*cliG37}1)48Ho^? z#kFg>%d`5&?8h>RZH-TtlIlV+BkHxoNAUI0%TU+E|8Zn%j3+Ga3^My>{9p-3VVP1Q z^2lmchjC5UpTX~5J%3S~?p$GSqgmN$hlu75_{Znyi9LrEO9zHUE5@;0C4OqIrX#*e z_e-tZZOu>+Iv0Xj)oq&-8TThb|?+W1ewOLqNyuV(h)ErQ8uwB>hiL#-9XY2CI z#!8<<*mh`?8D%7ItlBEo6S4KmbBW^390o#df~oHLcvb1^36|0Ldoe+~Ud1=qt=n=4 zW7;{aybkMMPt5Bqe{n4%2CKga(LsNsL8;IGG$JwfuAJ??ZlZ?Z#Z=nr@?;a9W9t$P zs>~HCUsv)F1+vfK*zMKFKc0MX9lg?QUn$#mAN{`ACLtXo*B_HTe(whAU6=YV&X&7B zKB=v+G2n3W>DcFn7F=5QPBiY<>!SXI+JaYiiZR|AF}}6Sk3Q!x5J?%wId0W=1~k%BV$lMt%17yY*fFuDrIOwX@1&LuY!y^0f0+T8GrCB33^h(76D|~98s4D+5R=Buu-0qr?v)J zRaHA&w)5=i+zVWt$i@qg(vRKN6l++c=R-qvxLiy-As*!Fo$<_ioG?(naipz$V~ys^ z?w3a^YL9cLUp?uq9%K1Fz6y^DEiv&3s&lIf?CaR5DmhViQ0an!w2$$=!4bir&m2{X zFO_bDp}fT25MSMVW%~AQz_m@|j?%i$H#{=YHXq`+6vG)o<#w)iE^Y&THSeS{7CQs&>hH$0uZNTx~Qn2+ZpL*|;%hr(?L9X4qAZ ztg(=y6D8FJ2R?14CmJIq?9pf&ox-u;?L6t-wyyk#;`eB1zxrm>E!-fuuVVY7ChnX0 zH{Tb~ZApW}@dhnQFFO`}$;fILGaDUnD*7R4Wya1S%J%2!BN#x=`uxvkkyd70-0)_0 N@Xr~1?}LwF`+wp)XJY^W diff --git a/src/vendormodules/packagetest-0.1.7.tm b/src/vendormodules/packagetest-0.1.7.tm new file mode 100644 index 0000000000000000000000000000000000000000..658d45a4e571209317a9621e1b6a1f037186fe5f GIT binary patch literal 12090 zcmch7c|4R+`?q~5dlZsxOHy{Jm@JW!PzptiG0b8ZGt6QqOUP0oLY9;*YY{0SQkE9l zkwl?Ht4c}gJ@*Vn)bIH{@B4YDKZg50=Q`K=y}swtB52k}&Y$23;Rr-5G%N;^ z^B`k9$ru$+w2M0$k5%!;Qm6zn3Br+yuzCb41T>(cJ+L!R&{Rm3=D{%Lj|1^o5|)C7 zh0>`65*`6mghe9|M1nH{;emF?qJR!4=2J+8jKfi}G@73$7D7{8T!EU9E_e@nNgvXs zc`$yMV-f)e1=zcwX)dl%0K=IiGQ|T;B=} zRh|$C7mY%2p#hgs4c5rcIW%a_Z)i_X5QcyNAQaYpAq`>sb99E~&9wr40-^@H21B4gOx3_oBsfEU={8RYyanrvoxg=C z;LkY1(#SZl!G%tP!-7O27ZeG!K%q$(6iC`>RVZXKjiC!uFbz#0{#I+g5F#4H6iC}ZTgV?ti%7NBzz(qFc^yD4d+=5zlJ~X5<^Jd=${LW zAbB8W!6gfuI&*Nq!mn{;3Kk9O-xp{G1;C3H<$c%u)p4_y8!Z3{Ohcx_!a$JeDuado zwZdk05kW}<=w|xKcR?HBr3)Lf@YCr$SkPw+{g>)w z$^$hDKorJMD#LN}YRos7^0f96{LYw61RM5>p7GNo$6~WQQK(P1)&4*p!42sQt!MF3%cm9((4wRo` z$6sp;4jGP4Bu>*2$li?%_=mjo?u0Xq zWZoG;5S9o6@%Nma?EqNPw2kuw!68~u+5eq7i{=StM2>-nE@Tf6&|#?%qe@|V1{f&A zSR@1(lC13C(pL^m!P8*`0lXC^k}j@r-hzB1L(Tw}09^vr4!>p);u&I#Kv*!q3QKdD zrxa|h92O0y?sPMzBADEGW*0**PmnM)_&J;f3kw146ab{Ay5>3+bsYpWSIG2ECTcQ- zEi42>CatEyc?GSW#m`m)Mvf&xG(Zbs(EP0t6);FD8IYtI-64=%L@b(0gYYo*^?;cO z_{Ly~Xg?|h7`P9CgdzJ-Ax|Qj1~3p%Za`m2WReP_N1=fUYZlhb+7)K)U?b?fbPvFv z{6LEWLYUrHQv(5jbz?GE&~dC~QAJ zLH-V_vTAC~R8j-!%`7xoh(v%38vMo-{M+H~mAkPn3nVvMI_sZ>Z2 zO`#ER;Jya|P(;K16fR6~WdV_&mAfEksB|o3uy9&2)dq@t(g7=w{fGL$WBN}I{3jCr z?q~p_6pF=P`^YbCLChd+`xUN*Z<{j;%zQJvMx_BL z{Cx+dg8QewzW%J#oyJy37Y+-!1^=!GeFz8I9Ju5=keop(PMd^SXz%Pz(}JjU7Z)rR zOc@*iHo<&rHj@9&x8LOm{W5Rvop#2Y+2322G-gi$t}75RQ3OVLEa)e? zClL!qZIA}w;egwKu0p2(n(<#oeSY)&CvB9&p@~Fiz<1#agS!E!z!_4tptf>5jZ~q1 zfNwBpj0+(8pq;|LVbB5(Fsgwc07p_G6)=ee%@4d|CJvxBfMml|feOv%j&NDdFFbkX z^yfFg{IjIs{WDnn2V?(b)qmIk82p%uhh%@}Ej)SsCFU1d0DfbdH^)aT&p1@F0kkeykK+tAX z{kMWl5RwP3TrjI)8~#}*@B;Y5xMyM@`Sz`ZG7?$(@0ny=O7Dn~Izgc+wU*L6~)&H5B=lN#tp+P~@6P7h)I@ZWcS)d>Q#jEG#@M!V($|>q}Ejy}+*qRxB*4;OR^gn+7`g z>Zqcws-e13ZQc{mU?1vE`Sff^yfS)r^QsqqoxDTZb!TnPcl#f!d|7Src-dtigVsQQ zQQkHOtK1#&7sT|Y-fdjRVi?b{rj7otccLo6m?b=|F_69g(4_*^o8jUH+;Zb>M-wwv z2a9-mrybH&3e(+jj5qeufeyRio872k^7pS_wSzJBP6u8X~n_(W}AYFFb-?)16o^S1WT^Y>YJwin_rk4`voXn$v;vYvx1j77)z zAd|MliP!J;trQw5xaqDiW^v0-ic>cOs;QpxdCE{INqRE6xnM%E3u{)j>L zHF9gP;`c9WTVX%hn0aMTd0^Dq*zbggXM2j;vn%00qS_Dm|HO~K_?&(~CM)o#-sClE zKPze*@ArD!v#h&TcIIcqo;jeWzw+m%GC6O@mSBPHXT^UsN+xxf)!*&wkrj5AHjX_M z^)4<=U*VaPr*b=5a*!ao*!yOXZH-*i$*imj@(!W!yR@Oz?htW<$!QBse6g0Yvwrbz zRl*l1hxoFMc9pfayM-jQui^cYT670xoqBd@r6RvmE`1aae_AZIi*kXE9PdVXDQLIV zI#{mUr;p6m3UeF`lZjK_Zzb5-D?@D9X5=lL{b67DRHOLt%Q$vgNGyj)wWW|)ZQRej z#u6LXG`IK!evHSRa!T}%?AR(iG`2jhWha;aSLz#leajQMWfgUzt9Au*Ve5{5-NL!a zW|zE%BwEVq)=l&~D|N+D2kWL=d0Q4=jd5@*k>8oE$J-sarSg(-z9M&Mxn0iChwe_- zH|;^MFYV|goZ(ujQZ3h9zddWfExPY^NzD8Bv*=TPBF42Afm<<|US1rssu9wyj$2WF z!~9&yrU~~#FN_#8|9o*l@(zoIf>R=5f8e=dq(%3hcJ-ms`7ylwhAfVkcN;epQ;|ANc!vV}9z6v4z@S&sDg zE}jvu*gM!7jylD?ol=$!_(m*&T}?@shR?WUyf;2^VhPJqm-mLO z2frN|j9jvzw6%Ro5hv8vzodh1EicE4>Ki+_its!vVSo6E`);ncO&pqBS-rTwMco^I zD81H|O{I8qOK!<>!kUVE&GQJycr~Jw z{2H&k%Zb-Vel2NxfC<~Z;(h&yWXYMcv2jip%5K#SefWlRs%d$)>e~Y$zhln1Vj;Io z1BORg)`mf%wQ}#XH+;$^qVTWu5_f)(4_K@l;G=nl9zkcx$3gZ&5i)8T+nWmS4D7)3 zX0Q=XQY%`j8V9j=n#7@)gQ)?_Ijiq<38g-E^(1CnsGBDq(%`t(S+u%zG&R>*xbgW> zd)v;XaixofbyD(W<^6YmdwPkSoKc(pDrED6%}CW9?N^`F$6=&HU)T$+^A=eZYhC_~ zqY_f9$ko?1eO9jcT&nV*?LLfrmQd^ynZ-)YsSfh2QE%5<(ziY0mEL<*)at!r>SeZr zS0V&|CIl6yC+vWpuC=_PShziRDQ10M`h#zp%`Im67+OvZU;6o5qSVs!Qz>p{F%PAd zp3&*MsL{8&Dnx(CzUY41q8H}((eE9NxYo#@TH>T`YEb#L&#LCwhhvMV54CcH*Lmv$>08S3T(A9@c$HA-@&t}Ql7ml^`{wg7jrU${9dG;>??@7p;xH1}R({AzNHf>G z!um#)=579>&0>MAs12{5e|)$KM{?lmNwYqNQY|af5B9wteDUJk3uYy-4e z^r+C7>#B7w)DHyPjBl>J@fVV>uv*+$F62}}KOyu`r(^Sz&;seED| zbB(VB(mu%Dt$9%W=KGaUWOlDWMEtNHqAqeDyLAy&g}f*S!mGzMM3iXp6p@#2F3jU* zQ>>~HI8&C|9UDVwD>IadW9-29)g_K%NhYch2cjpJ7`e;Q_n7TS~@0 zty(H(emHd{zM#REvdUIZ8wgbq+fl- z4mUb3V3&}aRNmP$rOcian!0aAmv`ssVh0Cmo>Kc>id|%lZQ(lTbPt=l&IE-p&S`aF zNey9gqS_F7bxm$6T7%4WFW_eWb;OG=x?CsA*M}`)XHC*7SbyYab7WI~Qa9e#;NVlk zt31|5U3&z_nqTiL8s`+0L0HFZ-QUG$ZyUlvO6Ga1uw)hYn^3VPiKCp=@h;q2*SiE= zWEa`(chZZEfz%TV)8hE>T9s+ZuAJTlZ@SGU@rxyT>Z$^jh(FW9I#a#x4QdobWOZI3 zmabQ_3u7y+`f)aBNmZCMQpVDYCt%%=&JU<3Ym3%3E%{(*QZ_{+eA6quPPY=Q%_s;v z_ImM#hgO?2>JA;}ga(RKKYQ^Wuyo0gJanRlCH$axg}fWhZN!X^Rcx=QQnvOTSzQhq zXWIC&kmn5o*;YbNFueOmhTlpDv=p^e749eHMR~;S@x7qeE0N8wJhD?N(F=X?@V7k^ z-8WE5zG~T@x8^AzrCSq(M!UEMyQ(KgR;r~7dUG4yeWKk-A=%Bm+k^F_gm@EM*Hzw+Rcx|35M8AjWu9Tq=S{i2 zHh1?e$9Kw~Bu*$qRXn?Wdtl=Gix)WmVCu)-YBGtmm*Z!1#^x_}{H2h|qWl}XA~#yu z4(oIw%$-QJSFWOXExkhpHaHrV6O zmx!MtIx9D}AF-_(7#>cw4E|Db%pTuscQUMD!*_RNjEw)Q<#A#v*}TeAc!t>%)bb zV2?wj`%x92Qt=tFSE?qBa+`xZ(HCE_mAjj6Pj8X?R^Xu&IUqwFJbv5$@_O<0;lf*4 z4v(Z3l(t+jaofGZ>^iM;yQY84;JCBS^`*ynO`R^gm)erNtHgR}ci$}9D=0hAw@83h zNdd2)i~UkGVr(4Olb`4K;eN6~#x_&)L^i#kk9!JBg~XBJoI+J=2`If+wJQ{$MB~ab zHG!x)>P>#XCqLtLXltX#KkoIinVPDNp}bbOWPQ9ytbnKW967J5+jUu8mgRTNlt2T=;U2 zi+AbvU@OU3&4@F;<=n^?0U91FQ?g<|s{Dwdcaculdt#UHsgaDxNPoVoM?coHhzg2t zJ&G(tEe^ZYv!1Ot^U_JqYHN}5kjkZya+mlIjy=VMj0xV7B6(h#II%R~tsl3O!#9=n zNf#anZthL+mAbe+uW!%J-a?k{&ep>B#ZtIFNz=vmo2DG=$CTN7bgh3R?r`IHm&@iG z_W6M8T9Lh6inf?HFi%Osx@*8sOfpxt&+Gh71ew19>%cgMZ9k;yCR-T=SMsHV`p!`g-y4X&J zi+;?I8qTtd*A~aBrG3)59_;_BfX{Df)Sd27L;jmbcy2m&tyx8ElOjab6soW56tnSI zAzn4izcoGDJ#Q1GzcD;lRi_G>ALNqjrc{$|CX*tR{nVc26oza^m zO5EF}%6%X6eu>ROTzVk0jDM3=spZd@wfah3?P0z0CYs&3j$t}sv=!V^#P)c*kJ|j- z-bk!Sz&EXIy|-7x@o9>+K|UtUza zc*!jN-bEjy)%%57Wkk)Fr>onhe~}lk>RT@G4SlW<*NHjJ)}S0DlpmNBdRllg)FiK4 ztTdCVZT&&1`l8lA##m2wCr|RX$g`uV;7BW+D9xMasUF3P?0|G{T=vOxD7o9zj8y2+Y51;~44p4nQ#W9D8v zFAGH3DEE4|RapB;>`l==`Dn>`=})F%!?yleAC*ttVBypsQC1&nPMzpkcHDLcE4Q>j z%Gkl>h(y*;=hr1!?iHPE6-RNm4Ae`y+Wl;~?~q++$oiV=gU9R6xRi=k!(|+eiTo#Q zzKS8`Ij%lVUsw8~IlrA}2JUZr9vi6=EBd=T{lMIU$Y!?eCof6rvwD(RBPC7y>EI2K; zx?S&a;^nj(HxA!j#A&tsMxevfN!eFRe_l4dkX(G(a93j8%gR0tUcZ*Kx+A$kPG@(Y z80BoHcfD#hOXotEjC!%iT+qHVbhMv?*pwE1(<`szg?to*FFe@lvaiZxMCsfSjTT=~ z5z6L`+|6KRmY1ZE(e{<;h}$ z`|i>D!xK3&X|aa85Kqo=^ltsQS}aR_sO5o|&V`k1MOf@ZyXN{uLU~(Tqcyq9v>!A# z$Yk%@8Ej&_rcA^!;r{1Uev2KskRqQEOZ*b?&=NDJ!*`A>5A?4s-~Rbz!|rhRrX#@u zIJIYpgAFSTYklRrUg?gLZKhPsj~#PJR@f2elnKIvH00tK>+kSJEp0g;6C<~bg1Y|x z(K5-4HO_bHGWRxJ8|^7A47D+RxzUQAT~>Ue`lEMmRBFAKd1}za1H~5;(b>Hjy0=+h z@~4MaK26ZIv<_ui@4fYO_}8}wdv%vg_ANu{qk?~UogRfiiXY95~0i^yN@^?5NUl`@Euvqumo2>@v zE>FEM;L#yn$XCK=l2W`_jn>-=-;{#`Q*TnzmMFJS)6ctP%KT z1@R15OZn6ZHbK8kY3F7_2q*DDh|Om+&vVwN-lrC4UHRJ47Q5@%vVF(zzq-aX<~_2_ zNlvFJ`T$GbRrM;XZ{s;7VUnrgZc`X9Q{1(K<*Igf{SPa2zcPFsy$IEpaSe}5m3ofm z`=0c^tqQS{CZ+YZm{#pL7@D`%M)&yo^N+o#2Faa>Ca?a6#dTISt*_VpQ` zr1i`F_IcS|E_SWUxgSUl_o_Sp@%ZD05Ok2wq2Oe*H;2ts>s;D=Uq&_7K1Le)zq$9e zwYouS@`!k{VB9?~prk?FO57 z^i%R(1O=?!Nq2MASBE<2y4w}?*)^zU2lT6$kK zZ&mu1@CGqYp2cSe6dZZ4r&X7wD16r-pL}iI)yUF;Fv_th-=1L8SbS7jV<#p>q`~_R zUCMA5+6fDdY8BnRu$cUre~X5?Oa04@JbNOtT*ZQ4D!tng(YSG7^F2MyKO4Apv2eyu70uq_M0ZLr_=NSn{uiD$SwpZM~lv!uVDE-rc`sGm2Xso3kfwC1+b zr?Jn`4eUdQF87%#qNK(REX`QiL|Ohmz62A@S)c#iFw@eElMCL=3jR5O?>hJxmj44g C6_b(x literal 0 HcmV?d00001 diff --git a/src/vendormodules/pattern/IPatternBuilder-2.0.tm b/src/vendormodules/pattern/IPatternBuilder-2.0.tm new file mode 100644 index 00000000..35a56b5d --- /dev/null +++ b/src/vendormodules/pattern/IPatternBuilder-2.0.tm @@ -0,0 +1,326 @@ +package provide pattern::IPatternBuilder 2.0 + +#Definition of pattern interface with interface ID 'PatternBuilder' +#Execution context: pp::Obj${OID}::_meta namespace (varspace _meta) + +namespace eval pattern { +} + +package require TclOO + +oo::class create ::pattern::IPatternBuilder +oo::define ::pattern::IPatternBuilder { + variable o_OID +} + +oo::define ::pattern::IPatternBuilder method ID {} { + my variable o_OID + puts "IPatternBuilder returning id $o_OID" + return $o_OID +} +oo::define ::pattern::IPatternBuilder method do {script} { + eval $script +} +oo::define ::pattern::IPatternBuilder method test {} { + return [my ID] +} +oo::define ::pattern::IPatternBuilder method test2 {} { + return [my PatternBuilder.ID] +} +oo::define ::pattern::IPatternBuilder method test3 {} { + return "info level 0 = \[[info level 0]\]" +} + +oo::define ::pattern::IPatternBuilder forward test4 my API(varspace_meta)getmap + +#gather all varspaces from interfaces on this object +oo::define ::pattern::IPatternBuilder method (GET)Varspaces {} { + my variable o_OID + #set builtin_varspaces [list _ref _meta _main _iface _apimanager] + set raw_nslist [namespace children ::pp::Obj${o_OID}] + set spaces [list] + foreach ns $raw_nslist { + lappend spaces [namespace tail $ns] + } + return [concat main $spaces] +} + +#gather all varspaces from patterns +oo::define ::pattern::IPatternBuilder method (GET)PatternVarspaces {} { + +} + + +oo::define ::pattern::IPatternBuilder method Constructor {arglist body} { + my variable o_OID _ID_ o_pattern_apis o_interface_default_api + set invocants [dict get $_ID_ i] + + set apiname [set ::pp::Obj${o_OID}::_meta::o_interface_default_api] + + set istack [dict get $o_pattern_apis $apiname] + + set iid_top [lindex $patterns end] ;#!todo - choose 'open' interface to expand. + + set iface ::p::ifaces::>$iid_top + + if {(![string length $iid_top]) || ([$iface . isClosed])} { + #no existing pattern - create a new interface + set iid_top [expr {$::p::ID + 1}] ;#PREDICT the next object's id + #set iid_top [::p::get_new_object_id] + + #the >interface constructor takes a list of IDs for o_usedby + set iface [::pp::>interface .. Create ::pp::ifaces::>$iid_top [set usedby [list $OID]] ] + + dict set o_pattern_apis $apiname [concat $patterns $iid_top] + } + set IID $iid_top + + namespace upvar ::pp::Obj${IID}::_iface o_open o_open o_constructor o_constructor o_varspace o_varspace + + + # examine the existing command-chain + set maxversion [::p::predator::method_chainhead $IID (CONSTRUCTOR)] + set headid [expr {$maxversion + 1}] + set THISNAME (CONSTRUCTOR).$headid ;#first version will be $method.1 + + #set next [::p::predator::next_script $IID (CONSTRUCTOR) $THISNAME $_ID_] + + #set varspaces [::pattern::varspace_list] + set processed [dict create {*}[::p::predator::expand_var_statements $body $o_varspace]] + + if {[llength [dict get $processed explicitvars]]} { + set body [dict get $processed body] + } else { + set varDecls [::p::predator::runtime_vardecls] + set body $varDecls\n[dict get $processed body] + #puts stderr "\t runtime_vardecls in Constructor $varDecls" + } + + #set body [string map [::list @OID@ "\[lindex \[dict get \$_ID_ i this\] 0 0\]" @this@ "\[lindex \[dict get \[set ::p::\[lindex \[dict get \$_ID_ i this\] 0 0\]::_meta::map\] invocantdata \] 3\]" @next@ $next] $body\n] + set body [string map [::list @OID@ "\[dict get \[lindex \[dict get \$_ID_ i this\] 0\] id\]" @this@ "\[dict get \[lindex \[dict get \$_ID_ i this\] 0\] id\]" @next@ error] $body\n] + + #puts stderr ---- + #puts stderr $body + #puts stderr ---- + + + + #proc ::p::${IID}::_iface::(CONSTRUCTOR).$headid [concat _ID_ $arglist] $body + #interp alias {} ::p::${IID}::_iface::(CONSTRUCTOR) {} ::p::${IID}::_iface::(CONSTRUCTOR).$headid + + + + set o_constructor [list $arglist $body] + set o_open 1 + + return +} + + + + + +oo::define ::pattern::IPatternBuilder method Method {method arglist bodydef args} { + my variable o_OID _ID_ + + set invocants [dict get $_ID_ i] + set invocant_signature [list] ; + ;# we sort when calculating the sig.. so a different key order will produce the same signature - !todo - this is probably desirable but review anyway. + foreach role [lsort [dict keys $invocants]] { + lappend invocant_signature $role [llength [dict get $invocants $role]] + } + #note: it's expected that by far the most common 'invocant signature' will be {this 1} - which corresponds to a standard method dispatch on a single invocant object - the 'subject' (aka 'this') + + set IID [::pp::predator::get_possibly_new_open_interface $o_OID] + + error "unimplemented" + +} + +oo::define ::pattern::IPatternBuilder method INFO {INVOCANTS} { + my variable _ID_ o_interface_apis o_pattern_apis o_invocantrecord o_interface_default_api o_pattern_default_api + + puts stderr "\tINVOCANTS:$INVOCANTS\n" + puts stderr "\t[info level 0]\n" + set result "" + append result "_ID_: $_ID_\n" + + set invocants [dict get $_ID_ i] + set invocant_roles [dict keys $invocants] + append result "invocant roles: $invocant_roles\n" + set total_invocants 0 + foreach key $invocant_roles { + incr total_invocants [llength [dict get $invocants $key]] + } + + append result "invocants: ($total_invocants invocant(s) in [llength $invocant_roles] role(s)) \n" + foreach key $invocant_roles { + append result "\t-------------------------------\n" + append result "\trole: $key\n" + set role_members [dict get $invocants $key] ;#usually the role 'this' will have 1 member - but roles can have any number of invocants + append result "\t Raw data for this role: $role_members\n" + append result "\t Number of invocants in this role: [llength $role_members]\n" + foreach member $role_members { + #set OID [lindex [dict get $invocants $key] 0 0] + set OID [lindex $member 0] + set OID [dict get $member id] + append result "\t\tOID: $OID\n" + #lassign $o_invocantrecord _OID namespace default_method cmd _wrapped + dict update o_invocantrecord id _OID ns namespace defaultmethod default_method object cmd {} + append result "\t\tNamespace: $namespace\n" + append result "\t\tDefault method: $default_method\n" + append result "\t\tCommand: $cmd\n" + append result "\t\tClass: [info object class $cmd]\n" + + append result "\t\tDefault API: $o_interface_default_api\n" + append result "\t\tinterface apis: \n" + foreach key [dict keys $o_interface_apis] { + append result "\t\t\tapi:'$key'\n" + append result "\t\t\t\t[dict get $o_interface_apis $key]\n" + } + #append result "\t\tDefault pattern API: $o_pattern_default_api\n" + append result "\t\tpattern apis: \n" + foreach key [dict keys $o_pattern_apis] { + append result "\t\t\tapi:'$key'\n" + append result "\t\t\t\t[dict get $o_pattern_apis $key]\n" + } + + } + append result "\n" + append result "\t-------------------------------\n" + } + + + + return $result +} + +oo::define ::pattern::IPatternBuilder method IFINFO {{api "default"}} { + my variable _ID_ o_OID o_interface_apis o_pattern_apis o_interface_default_api + if {$api eq "default"} { + set api $o_interface_default_api} + + if {$api eq "*"} { + set apilist [dict keys $o_interface_apis] + } else { + set apilist [list $api] + } + + puts stderr "\t _ID_ --$_ID_--" + set invocants [dict get $_ID_ i] + + foreach a $apilist { + puts stderr "\t------------------------------\n" + puts stderr "\t API:'$a'\n" + puts stderr "\t------------------------------\n" + set interfaces [dict get $o_interface_apis $a] + set IFID [lindex $interfaces 0] + if {![llength $interfaces]} { + puts stderr "No interfaces present for api:'$a'" + } else { + foreach IFID $interfaces { + set iface ::pp::ifaces::>$IFID + puts stderr "$iface : [$iface --]" + puts stderr "\tis open: [set ::pp::I${IFID}::_iface::o_open]" + set variables [set ::pp::I${IFID}::_iface::o_variables] + puts stderr "\tvariables: $variables" + } + } + puts stderr "\t------------------------------\n" + } +} + + +oo::define ::pattern::IPatternBuilder method Create {target_spec args} { + my variable _ID_ o_OID o_invocantrecord o_interface_apis o_pattern_apis o_interface_default_api + set invocants [dict get $_ID_ i] + set invocant_roles [dict keys $invocants] ;#usually the only invocant role present will be 'this' (single dispatch case) + + set api $o_interface_default_api + lassign $o_invocantrecord o_OID parent_ns parent_defaultmethod parent_object_command + set interfaces [dict get $o_interface_apis $api] ;#level-0 interfaces + set patterns [dict get $o_pattern_apis $api] ;#level-1 interfaces + + #set parent_patterndefaultmethod [dict get $map patterndata patterndefaultmethod] + + #todo - change to dict of interface stacks + set IFID0 [lindex $interfaces 0] + set IFID1 [lindex $patterns 0] ;#1st pattern + + if {[llength $target_spec] ==1} { + set child $target_spec + set targets [list $child {}] + } else { + set targets $target_spec + } + + + + + set target_objects [list] + foreach child [dict keys $targets] { + set target_spec_dict [dict get $targets $child] + if {![string match {::*} $child]} { + if {[set ns [uplevel 1 {namespace current}]] eq "::"} { + set child ::$child + } else { + set child ${ns}::$child + } + } + #add > character if not already present + set child [namespace qualifiers $child]::>[string trimleft [namespace tail $child] >] + + #maintain a record of interfaces created so that we can clean-up if we get an error during any of the Constructor calls. + set new_interfaces [list] + if {![llength $patterns]} { + #puts stderr "===> WARNING: no level-1 interfaces (patterns) on object $parent_object_command when creating $child" + set patterns [list [set iid [::pp::get_new_object_id]]] + lappend new_interfaces [::pp::func::new_object ::pp::ifaces::>$iid $iid] + } + + if {![llength [info commands $child]]} { + #usual case - target/child does not exist + set is_new_object 1 + if {[dict exists $target_spec_dict -id]} { + ::pp::func::new_object $child [dict get $target_spec_dict -id] + } else { + ::pp::func::new_object $child + } + set child_ID [$child ## varspace_meta . ID] + #set childmapdata [set ::pp::Obj${child_ID}::_meta::map] + #upvar #0 ::pp::Obj${child_ID}::_meta::map CHILDMAP + + $child ## varspace_meta . (SET)interfaces $patterns + + set ifaces_added $patterns + } else { + #child exists - overlay + set is_new_object 0 + set existing_interfaces [$child ## varspace_meta . (GET)interfaces] + set ifaces_added [list] + foreach p $patterns { + if {$p ni $existing_interfaces} { + lappend ifaces_added $p + } + } + if {[llength $ifaces_added]} { + $child ## varspace_meta . (SET)interfaces [concat $existing_interfaces $ifaces_added] + } + } + + #only set the child's defaultmethod value if the parent_patterndefaultmethod is not empty + #if {$parent_patterndefaultmethod ne ""} { + # $child ## PatternInternal . (SET)default_method $parent_patterndefaultmethod + #} + + lappend target_objects $child + } + return $target_objects +} + + +namespace eval ::pattern { + set tmp_methods [info class methods ::pattern::IPatternBuilder -private] ;#-private returns all user-defined methods above + oo::define ::pattern::IPatternBuilder export {*}$tmp_methods + unset tmp_methods +} \ No newline at end of file diff --git a/src/vendormodules/pattern/IPatternInterface-2.0.tm b/src/vendormodules/pattern/IPatternInterface-2.0.tm new file mode 100644 index 00000000..c4303c8d --- /dev/null +++ b/src/vendormodules/pattern/IPatternInterface-2.0.tm @@ -0,0 +1,43 @@ +package provide pattern::IPatternInterface 2.0 + +#Definition of pattern interface with interface ID 'Define' +#Execution context: pp::Obj${OID}::_meta namespace (varspace _meta) + +namespace eval pattern { +} + +package require TclOO + +oo::class create ::pattern::IPatternInterface + +oo::define ::pattern::IPatternInterface { + variable o_open +} + +oo::define ::pattern::IPatternInterface method isOpen {} { + my variable o_open + return $o_open +} +oo::define ::pattern::IPatternInterface method isClosed {} { + my variable o_open + return [expr {!$o_open}] +} + +oo::define ::pattern::IPatternInterface method getcmd {args} { + return $args +} +oo::define ::pattern::IPatternInterface method run {args} { + uplevel 1 {*}$args +} + +#this is not the object's implemented varspaces - it is the varspace list for the interface specification this object represents +oo::define ::pattern::IPatternInterface method Varspaces {args} { + tailcall my API(varspace_iface)(GET)interface_varspaces +} + + +namespace eval ::pattern { + set tmp_methods [info class methods ::pattern::IPatternInterface -private] ;#-private returns all user-defined methods above + oo::define ::pattern::IPatternInterface export {*}$tmp_methods + unset tmp_methods +} \ No newline at end of file diff --git a/src/vendormodules/pattern/IPatternSystem-2.0.tm b/src/vendormodules/pattern/IPatternSystem-2.0.tm new file mode 100644 index 00000000..68663e62 --- /dev/null +++ b/src/vendormodules/pattern/IPatternSystem-2.0.tm @@ -0,0 +1,122 @@ + +# +# +package provide pattern::IPatternSystem 2.0 + +#Definition of pattern interface with interface ID 'PatternSystem' +#Execution context: pp::Obj${OID}::_meta namespace (varspace _meta) + +namespace eval pattern { +} + +package require TclOO + +oo::class create ::pattern::IPatternSystem + +oo::define ::pattern::IPatternSystem { + variable o_OID +} + +oo::define ::pattern::IPatternSystem method ID {} { + my variable o_OID + puts "IPatternSystem returning id $o_OID" + return $o_OID +} + + +oo::define ::pattern::IPatternSystem method get_possibly_new_open_interface {{apiname default}} { + my variable o_OID _ID_ o_interface_apis + + + if {$apiname eq "default"} { + set apiname [set ::pp::Obj${o_OID}::_meta::o_default_interface_api] + } + if {![dict exists $o_interface_apis $apiname]} { + error "get_possibly_new_open_interface Unable to find api:'$apiname'" + } + + set interfaces [dict get $o_interface_apis $apiname] + set iid_top [lindex $interfaces end] + set iface ::pp::ifaces::>$iid_top + if {(![string length $iid_top]) || ([$iface . isClosed])} { + #no existing pattern - create a new interface + set iid_top [expr {$::pp::ID + 1}] ;#PREDICT the next object's id + #puts stderr ">>>>creating new interface $iid_top" + set iface [::pp::>interface .. Create ::pp::ifaces::>$iid_top $o_OID] + + dict set o_interface_apis $apiname [concat $interfaces $iid_top] + } + return $iid_top +} + + +oo::define ::pattern::IPatternSystem method add_pattern_interface {iid {apiname default}} { + my variable _ID_ o_pattern_apis + #puts stderr "!!!!!!!!!!!!!!! add_pattern_interface $iid" + if {![string is integer -strict $iid]} { + error "add_pattern_interface adding interface by name not yet supported. Please use integer id" + } + + + if {$apiname eq "default"} { + set apiname [set ::pp::Obj${o_OID}::_meta::o_default_pattern_api] + } + if {![dict exists $o_pattern_apis $apiname]} { + error "add_interface Unable to find api:'$apiname'" + } + + + #set invocants [dict get $_ID_ i] + + set istack [dict get $o_pattern_apis $apiname] + + #it is theoretically possible to have the same interface present multiple times in an iStack. + # #!todo -review why/whether this is useful. should we disallow it and treat as an error? + dict set o_pattern_apis $apiname [concat $istack $iid] + + + + +} + + + +#!todo - update usedby ?? +oo::define ::pattern::IPatternSystem method add_interface {iid {apiname default}} { + my variable o_OID _ID_ o_interface_apis + if {![string is integer -strict $iid]} { + error "adding interface by name not yet supported. Please use integer id" + } + + + if {$apiname eq "default"} { + set apiname [set ::pp::Obj${o_OID}::_meta::o_default_interface_api] + } + if {![dict exists $o_interface_apis $apiname]} { + error "add_interface Unable to find api:'$apiname'" + } + + + lassign [dict get $_ID_ i this] list_of_invocants_for_role_this ;#Although there is normally only 1 'this' element - it is a 'role' and the structure is nonetheless a list. + set this_invocant [lindex $list_of_invocants_for_role_this 0] + lassign $this_invocant OID _etc + + set istack [dict get $o_interface_apis $apiname] + dict set o_interface_apis $apiname [concat $istack $iid] + + return [dict get $o_interface_apis $apiname] +} + + +oo::define ::pattern::IPatternSystem method INVOCANTDATA {} { + my variable _ID_ + #same as a call to: >object .. + return $_ID_ +} + + +namespace eval ::pattern { + set tmp_methods [info class methods ::pattern::IPatternSystem -private] ;#-private returns all user-defined methods above + oo::define ::pattern::IPatternSystem export {*}$tmp_methods + unset tmp_methods +} \ No newline at end of file diff --git a/src/vendormodules/pattern/ms-1.0.12.tm b/src/vendormodules/pattern/ms-1.0.12.tm new file mode 100644 index 00000000..09a4a005 --- /dev/null +++ b/src/vendormodules/pattern/ms-1.0.12.tm @@ -0,0 +1,330 @@ +#JMN 2007 +#public domain + +#experimental +#VERY incomplete + +package require pattern +package require patternlib +package require struct::set + +package provide pattern::ms [namespace eval ::pattern::ms { + variable version + set version 1.0.12 +}] + + +#-------------------------------------------------- +namespace eval ::pattern::ms { + ::>pattern .. Create >IEnumerable + >IEnumerable .. PatternVariable i ;#current index + >IEnumerable .. PatternProperty Current + >IEnumerable .. PatternPropertyRead Current {} { + var o_list i + return [lindex $o_list $i] + } + >IEnumerable .. PatternMethod MoveNext {} { + var i + incr i + } + >IEnumerable .. PatternMethod Reset {} { + var i + set i 0 + } +} + +#-------------------------------------------------- +namespace eval ::pattern::ms { + ::>pattern .. Create >Enumerator + >Enumerator .. PatternVariable o_enumerable + + >Enumerator .. Constructor {IEnumerable_object} { + var o_enumerable + set o_enumerable $IEnumerable_object + } + + >Enumerator .. PatternMethod atEnd {} { + var i o_list + return [expr {$i >= ([llength $o_list] -1)} ] + } + >Enumerator .. PatternMethod moveNext {} { + var i + incr i + } + >Enumerator .. PatternMethod moveFirst {} { + var i + set i 0 + } + >Enumerator .. PatternMethod item {} { + var i o_list + return [lindex $o_list $i] + } +} + + +#-------------------------------------------------- +namespace eval ::pattern::ms { + ::>pattern .. Create >textstream + >textstream .. PatternVariable o_fd ;#file descriptor + + >textstream .. Constructor {args} { + set opts [dict merge { + -mode r + } $args] + + if {([dict get $opts -mode] eq "r") && ![file exists [dict get $opts -path]]} { + error "file [dict get $opts -path] not found" + } + + set o_fd [open [dict get $opts -path] [dict get $opts -mode]] + return + } + + >textstream .. PatternMethod Write {data} { + var o_fd + puts -nonewline $o_fd $data + } + >textstream .. PatternMethod WriteLine {{line ""}} { + var o_fd + puts $o_fd $line + } + >textstream .. PatternMethod WriteBlankLines {howmany} { + var o_fd + #!todo - work out proper line-ending and write in single call. + if {$howmany > 0} { + for {set i 0} {$i < $howmany} {incr i} { + puts $o_fd "" + } + } + } + >textstream .. PatternMethod Read {{numbytes ""}} { + var o_fd + if {[string length $numbytes]} { + return [read $o_fd $numbytes] + } else { + return [read $o_fd] + } + } + >textstream .. PatternMethod ReadLine {} { + var o_fd + return [gets $o_fd] + } + >textstream .. PatternMethod ReadAll {} { + var o_fd + return [read $o_fd] ;#don't use size argument - we can't be sure it hasn't changed since opening (?) + } + >textstream .. PatternMethod Skip {numchars} { + var o_fd + seek $o_fd $numchars current + } + >textstream .. PatternMethod SkipLine {} { + var o_fd + gets $o_fd + return + } + + >textstream .. PatternMethod Close {} { + var o_fd + close $o_fd + } +} + +#------------------------------------------------------------------------------ +# https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/file-object +namespace eval ::pattern::ms { + ::>pattern .. Create >fso_file + >fso_file .. PatternVariable o_path + >fso_file .. Constructor {args} { + var this o_path + set this @this@ + + set opts [dict merge { + + } $args] + + if {![file exists [dict get $opts -path]]} { + error "cannot find file '[dict get $opts -path]'" + } + if {![file isfile [dict get $opts -path]]} { + error "path '[dict get $opts -path]' does not appear to be a file" + } + + set o_path [dict get $opts -path] + + return + } + >fso_file .. PatternProperty Name + >fso_file .. PatternPropertyRead Name {} { + var o_path + return [file tail $o_path] ;#??? + } + >fso_file .. PatternPropertyWrite Name {newname} { + var o_path + file rename $o_path [file dirname $o_path]/$newname + return + } + >fso_file .. PatternProperty Path + >fso_file .. PatternPropertyRead Path {} { + var o_path + return $o_path + } + +} + + +#------------------------------------------------------------------------------ +namespace eval ::pattern::ms { + ::>pattern .. Create >fso_folder + >fso_folder .. PatternVariable o_path + >fso_folder .. PatternVariable o_files ;#collection + + >fso_folder .. Constructor {args} { + var this ns o_path o_files + + set this @this@ + set ns [$this .. Namespace] + + set opts [dict merge { + + } $args] + + if {![file exists [dict get $opts -path]]} { + error "cannot find folder '[dict get $opts -path]'" + } + if {![file isdirectory [dict get $opts -path]]} { + error "path '[dict get $opts -path]' does not appear to be a folder" + } + + set o_path [dict get $opts -path] + + set o_files [::patternlib::>collection .. Create ${ns}::>col_files] + + return + } + + + #!todo - what happens to the object? destroy it? + >fso_folder .. PatternMethod Delete {{force 0}} { + var this o_path + if {$force} { + file delete -force $o_path + } else { + file delete $o_path + } + + #?? + # $this .. Destroy + return + } + + >fso_folder .. PatternProperty DateCreated + >fso_folder .. PatternPropertyRead DateCreated {} { + var o_path + file stat $o_path info + return $info(ctime) + } + >fso_folder .. PatternProperty DateLastAccessed + >fso_folder .. PatternPropertyRead DateLastAccessed {} { + var o_path + return [file atime $o_path] + } + >fso_folder .. PatternProperty DateLastModified + >fso_folder .. PatternPropertyRead DateLastModified {} { + var o_path + return [file mtime $o_path] + } + + >fso_folder .. PatternProperty Files + >fso_folder .. PatternPropertyRead Files {} { + var ns o_path objectcounter o_files + set filenames [glob -dir $o_path -type f -tail *] + lappend filenames {*}[glob -dir $o_path -types {f hidden} -tail *] + + set NEW [::pattern::ms::>fso_file .. Create .] + set files [list] + + set superfluous [struct::set difference [$o_files . names] $filenames] + foreach doomed $superfluous { + set f [$o_files . item $doomed] + $f .. Destroy + $o_files . del $doomed + } + + set missing [struct::set difference $filenames [$o_files . names]] + foreach fname $missing { + if {[catch { + set fobj [$NEW ${ns}::>fl_[incr objectcounter] -path $o_path/$fname] + } errM]} { + #There can exist characterSpecial files such as 'nul' that aren't identified as file or directory by Tcl 'file isfile' or 'file isdirectory' + # yet were picked up by glob + #(these shouldn't really exist - but can be accidentally created) + #we don't want an error in creating an >fso_file for this to stop us accessing any other files in the folder + #but we should at least be loud about it by emitting the error to stderr + puts stder " + } + $o_files . add [$NEW ${ns}::>fl_[incr objectcounter] -path $o_path/$fname] $fname + } + + return [$o_files . items] + } +} + + +#------------------------------------------------------------------------------ +#vba and vb6 File System Object (used the same COM component - Microsoft Scripting Runtime library scrrun.dll) +namespace eval ::pattern::ms { + ::>pattern .. Create >fso + + >fso .. PatternVariable objectcounter ;# + >fso .. Constructor {args} { + var this ns objectcounter + set this @this@ + set ns [$this .. Namespace] + + set objectcounter 0 + } + + >fso .. PatternMethod CreateTextFile {path {bool 1}} { + var ns objectcounter + set ts [::pattern::ms::>textstream .. Create ${ns}::>ts_[incr objectcounter] -path $path -mode w] + return $ts + } + + >fso .. PatternMethod OpenTextFile {path mode {bool 1}} { + var ns objectcounter + + switch -- [string tolower $mode] { + 1 - + forreading { + set md r + } + 2 - + forwriting { + set md w + } + 8 - + forappending { + set md a + } + default { + error "unknown file mode - $mode" + } + } + set ts [::pattern::ms::>textstream .. Create ${ns}::>ts_[incr objectcounter] -mode $md -path $path] + return $ts + } + + >fso .. PatternMethod GetFolder {path} { + var ns objectcounter + + set fld [::pattern::ms::>fso_folder .. Create ${ns}::>fld_[incr objectcounter] -path $path] + return $fld + } + >fso .. PatternProperty Drives + >fso .. PatternPropertyRead Drives {} { + var ns + error "unimplemented" + #todo >fso_drive object and collection + } +} + diff --git a/src/vendormodules/pattern2-2.0.tm b/src/vendormodules/pattern2-2.0.tm new file mode 100644 index 00000000..28d415ab --- /dev/null +++ b/src/vendormodules/pattern2-2.0.tm @@ -0,0 +1,911 @@ +#PATTERN +# - A prototype-based Object system. +# +# Julian Noble 2003 +# License: Public domain +# + +# "I need pattern" - Lexx Series 1 Episode 3 - Eating Pattern. +# +# +# Pattern uses a mixture of class-based and prototype-based object instantiation. +# +# A pattern object has 'properties' and 'methods' +# The system makes a distinction between them with regards to the access syntax for write operations, +# and yet provides unity in access syntax for read operations. +# e.g >object . myProperty +# will return the value of the property 'myProperty' +# >ojbect . myMethod +# will return the result of the method 'myMethod' +# contrast this with the write operations: +# set [>object . myProperty .] blah +# >object . myMethod blah +# however, the property can also be read using: +# set [>object . myProperty .] +# Note the trailing . to give us a sort of 'reference' to the property. +# this is NOT equivalent to +# set [>object . myProperty] +# This last example is of course calling set against a standard variable whose name is whatever value is returned by reading the property +# i.e it is equivalent in this case to: set blah + +#All objects are represented by a command, the name of which contains a leading ">". +#Any commands in the interp which use this naming convention are assumed to be a pattern object. +#Use of non-pattern commands containing this leading character is not supported. (Behaviour is undefined) + +#All user-added properties & methods of the wrapped object are accessed +# using the separator character "." +#Metamethods supplied by the patterm system are accessed with the object command using the metamethod separator ".." +# e.g to instantiate a new object from an existing 'pattern' (the equivalent of a class or prototype) +# you would use the 'Create' metamethod on the pattern object like so: +# >MyFactoryClassOrPrototypeLikeThing .. Create >NameOfNewObject +# '>NameOfNewObject' is now available as a command, with certain inherited methods and properties +# of the object it was created from. ( + + +#The use of the access-syntax separator character "." allows objects to be kept +# 'clean' in the sense that the only methods &/or properties that can be called this way are ones +# the programmer(you!) put there. Existing metamethods such as 'Create' are accessed using a different syntax +# so you are free to implement your own 'Create' method on your object that doesn't conflict with +# the metamethod. + +#Chainability (or how to violate the Law of Demeter!) +#The . access-syntax gives TCL an OO syntax more closely in line with many OO systems in other +# languages such as Python & VB, and allows left to right keyboard-entry of a deeply nested object-reference +# structure, without the need to regress to enter matching brackets as is required when using +# standard TCL command syntax. +# ie instead of: +# [[[object nextObject] getItem 4] getItem [chooseItemNumber]] doSomething +# we can use: +# >object . nextObject . getItem 4 . getItem [chooseItemNumber] . doSomething +# +# This separates out the object-traversal syntax from the TCL command syntax. + +# . is the 'traversal operator' when it appears between items in a commandlist +# . is the 'reference operator' when it is the last item in a commandlist +# , is the 'index traversal operator' (or 'nest operator') - mathematically it marks where there is a matrix 'partition'. +# It marks breaks in the multidimensional structure that correspond to how the data is stored. +# e.g obj . arraydata x y , x1 y1 z1 +# represents an element of a 5-dimensional array structured as a plane of cubes +# e.g2 obj . arraydata x y z , x1 y1 +# represents an element of a 5-dimensional array structured as a cube of planes +# The underlying storage for e.g2 might consist of something such as a Tcl array indexed such as cube($x,$y,$z) where each value is a patternlib::>matrix object with indices x1 y1 +# .. is the 'meta-traversal operator' when it appears between items in a commandlist +# .. is the 'meta-info operator'(?) when it is the last item in a commandlist + + +#!todo - Duck Typing: http://en.wikipedia.org/wiki/Duck_typing +# implement iStacks & pStacks (interface stacks & pattern stacks) + +#see also: Using namsepace ensemble without a namespace: http://wiki.tcl.tk/16975 + + +#------------------------------------------------------------ +# System objects. +#------------------------------------------------------------ +#::pp::Obj-1 ::p::internals::>metaface +#::pp::Obj0 ::p::ifaces::>null +#::pp::Obj1 ::>pattern +#------------------------------------------------------------ + +#TODO + +#investigate use of [namespace path ... ] to resolve command lookup (use it to chain iStacks?) + + +#CHANGES +#2018-09 - v 1.2.2 +# varied refactoring +# Changed invocant datastructure curried into commands (the _ID_ structure) +# Changed MAP structure to dict +# Default Method no longer magic "item" - must be explicitly set with .. DefaultMethod (or .. PatternDefaultMethod for patterns) +# updated test suites +#2018-08 - v 1.2.1 +# split ::p::predatorX functions into separate files (pkgs) +# e.g patternpredator2-1.0.tm +# patternpredator1-1.0 - split out but not updated/tested - probably obsolete and very broken +# +#2017-08 - v 1.1.6 Fairly big overhaul +# New predator function using coroutines +# Added bang operator ! +# Fixed Constructor chaining +# Added a few tests to test::pattern +# +#2008-03 - preserve ::errorInfo during var writes + +#2007-11 +#Major overhaul + new functionality + new tests v 1.1 +# new dispatch system - 'predator'. +# (preparing for multiple interface stacks, multiple invocants etc) +# +# +#2006-05 +# Adjusted 'var' expansion to use the new tcl8.5 'namespace upvar $ns v1 n1 v2 n2 ... ' feature. +# +#2005-12 +# Adjusted 'var' expansion in method/constructor etc bodies to be done 'inline' where it appears rather than aggregated at top. +# +# Fixed so that PatternVariable default applied on Create. +# +# unified interface/object datastructures under ::p:::: instead of seperate ::p::IFACE:::: +# - heading towards multiple-interface objects +# +#2005-10-28 +# 1.0.8.1 passes 80/80 tests +# >object .. Destroy - improved cleanup of interfaces & namespaces. +# +#2005-10-26 +# fixes to refsync (still messy!) +# remove variable traces on REF vars during .. Destroy +# passes 76/76 +# +#2005-10-24 +# fix objectRef_TraceHandler so that reading a property via an object reference using array syntax will call a PropertyRead function if defined. +# 1.0.8.0 now passes 75/76 +# +#2005-10-19 +# Command alias introduced by @next@ is now placed in the interfaces namespace. (was unnamespaced before) +# changed IFACE array names for level0 methods to be m-1 instead of just m. (now consistent with higher level m-X names) +# 1.0.8.0 (passes 74/76) +# tests now in own package +# usage: +# package require test::pattern +# test::p::list +# test::p::run ?nameglob? ?-version ? +# +#2005-09?-12 +# +# fixed standalone 'var' statement in method bodies so that no implicit variable declarations added to proc. +# fixed @next@ so that destination method resolved at interface compile time instead of call time +# fixed @next@ so that on Create, .. PatternMethod x overlays existing method produced by a previous .. PatternMethod x. +# (before, the overlay only occured when '.. Method' was used to override.) +# +# +# miscellaneous tidy-ups +# +# 1.0.7.8 (passes 71/73) +# +#2005-09-10 +# fix 'unknown' system such that unspecified 'unknown' handler represented by lack of (unknown) variable instead of empty string value +# this is so that a mixin with an unspecified 'unknown' handler will not undo a lowerlevel 'unknown' specificier. +# +#2005-09-07 +# bugfix indexed write to list property +# bugfix Variable default value +# 1.0.7.7 (passes 70/72) +# fails: +# arrayproperty.test - array-entire-reference +# properties.test - property_getter_filter_via_ObjectRef +# +#2005-04-22 +# basic fix to PatternPropertyRead dispatch code - updated tests (indexed case still not fixed!) +# +# 1.0.7.4 +# +#2004-11-05 +# basic PropertyRead implementation (non-indexed - no tests!) +# +#2004-08-22 +# object creation speedups - (pattern::internals::obj simplified/indirected) +# +#2004-08-17 +# indexed property setter fixes + tests +# meta::Create fixes - state preservation on overlay (correct constructor called, property defaults respect existing values) +# +#2004-08-16 +# PropertyUnset & PatternPropertyUnset metaMethods (filter method called on property unset) +# +#2004-08-15 +# reference syncing: ensure writes to properties always trigger traces on property references (+ tests) +# - i.e method that updates o_myProp var in >myObj will cause traces on [>myObj . myProp .] to trigger +# - also trigger on curried traces to indexed properties i.e list and array elements. +# - This feature presumably adds some overhead to all property writes - !todo - investigate desirability of mechanism to disable on specific properties. +# +# fix (+ tests) for ref to multiple indices on object i.e [>myObj key1 key2 .] +# +#2004-08-05 +# add PropertyWrite & PatternPropertyWrite metaMethods - (filter method called on property write) +# +# fix + add tests to support method & property of same name. (method precedence) +# +#2004-08-04 +# disallow attempt to use method reference as if it were a property (raise error instead of silently setting useless var) +# +# 1.0.7.1 +# use objectref array access to read properties even when some props unset; + test +# unset property using array access on object reference; + test +# +# +#2004-07-21 +# object reference changes - array property values appear as list value when accessed using upvared array. +# bugfixes + tests - properties containing lists (multidimensional access) +# +#1.0.7 +# +#2004-07-20 +# fix default property value append problem +# +#2004-07-17 +# add initial implementation of 'Unknown' and 'PatternUnknown' meta-methods +# ( +# +#2004-06-18 +# better cleanup on '>obj .. Destroy' - recursively destroy objects under parents subnamespaces. +# +#2004-06-05 +# change argsafety operator to be anything with leading - +# if standalone '-' then the dash itself is not added as a parameter, but if a string follows '-' +# i.e tkoption style; e.g -myoption ; then in addition to acting as an argsafety operator for the following arg, +# the entire dash-prefixed operator is also passed in as an argument. +# e.g >object . doStuff -window . +# will call the doStuff method with the 2 parameters -window . +# >object . doStuff - . +# will call doStuff with single parameter . +# >object . doStuff - -window . +# will result in a reference to the doStuff method with the argument -window 'curried' in. +# +#2004-05-19 +#1.0.6 +# fix so custom constructor code called. +# update Destroy metamethod to unset $self +# +#1.0.4 - 2004-04-22 +# bug fixes regarding method specialisation - added test +# +#------------------------------------------------------------ + +package provide pattern2 [namespace eval pattern {variable version; set version 2.0}] +package require patterncmd ;#utility/system diagnostic commands (may be used by metaface lib etc) +package require cmdline + +package require patterndispatcher + +namespace eval pattern { + variable initialised 0 +} + + + +namespace eval pp { + #this is also the interp alias namespace. (object commands created here , then renamed into place) + #the object aliases are named as incrementing integers.. !todo - consider uuids? + variable ID 0 + namespace eval func {} +} + + +#!store all interface objects here? +namespace eval ::pp::ifaces {} + + +proc ::pp::assert {condition errmsg} { + if {![uplevel 1 expr $condition]} { + return -code error "assertion failed. condition:'$condition' msg:'$errmsg'" + } +} +#proc ::pp::assert args {} + +proc ::pp::get_new_object_id {} { + tailcall incr ::pp::ID + #tailcall ::pattern::new_uuid +} + + +#create a new minimal object - with no interfaces or patterns. +proc ::pp::func::new_object {obj {OID ""}} { + puts stderr "(::pp::func::new_object) obj:$obj OID:$OID" + + if {[string range $obj 0 1] ne "::"} { + set nsbase [uplevel 1 [list namespace current]] + if {$nsbase eq "::"} { + set obj ::$obj + } else { + set obj ${nsbase}::$obj + } + } + + if {[info object isa object $obj]} { + puts stderr "(::pp::func::new_object) Object $obj already exists " + } + + if {$OID eq ""} { + set OID [::pp::get_new_object_id] + } + + set main_ns ::pp::Obj${OID} + if {[namespace exists $main_ns]} { + error "(::pp::func::new_object) Cannot create Object with id:'$OID' - corresponding namespace already exists" + } + + + + + set default_method {} + set object_command $obj + #set INVOCANTRECORD [list $OID $main_ns $default_method $object_command {}] + + set invocantD [list id $OID ns $main_ns defaultmethod $default_method object $object_command] + + # _ID_ structure + #set _InvocantData_ [dict create i [dict create this [list $INVOCANTRECORD]] context ""] + set _InvocantData_ [dict create i [dict create this [list $invocantD]] context ""] + + #must create main varspace first as it is also the parent namespace for all varspaces + set vs_main [::pp::varspace_main create ::pp::Obj${OID} [set varspacename ""] $_InvocantData_] + + set vs_meta [::pp::varspace_meta create ::pp::Obj${OID}::_meta _meta $_InvocantData_] + + + puts stderr "\t(::pp::func::new_object) --- about to call pp::dispatcher create $obj $_InvocantData_ main" + pp::dispatcher create $obj $_InvocantData_ "main" + + return $obj +} + +proc ::pp::func::new_dispatcher {obj _InvocantData_ apiname} { + pp::dispatcher create $obj $_InvocantData_ $apiname +} + + + +#aliased from ::p::${OID}:: +# called when no DefaultMethod has been set for an object, but it is called with indices e.g >x something +proc ::pp::func::no_default_method {_ID_ args} { + puts stderr "no_default_method _ID_:'$_ID_' args:'$args'" + lassign [lindex [dict get $_ID_ i this] 0] OID alias default_method object_command wrapped + tailcall error "No default method on object $object_command. (To get or set, use: $object_command .. DefaultMethod ?methodname?)" +} + + + +#>x .. Create >y +# ".." is special case equivalent to "._." +# (whereas in theory it would be ".default.") +# "." is equivalent to ".default." is equivalent to ".default.default." (...) + +#>x ._. Create >y +#>x ._.default. Create >y ??? +# +# + +# create object using 'blah' as source interface-stack ? +#>x .blah. .. Create >y +#>x .blah,_. ._. Create .iStackDestination. >y + + + +# +# ">x .blah,_." is a reference(cast) to >x that contains only the iStacks in the order listed. i.e [list blah _] +# the 1st item, blah in this case becomes the 'default' iStack. +# +#>x .*. +# cast to object with all iStacks +# +#>x .*,!_. +# cast to object with all iStacks except _ +# +# --------------------- +#!todo - MultiMethod support via transient and persistent object conglomerations. Operators '&' & '@' +# - a persistent conglomeration will have an object id (OID) and thus associated namespace, whereas a transient one will not. +# +#eg1: >x & >y . some_multi_method arg arg +# this is a call to the MultiMethod 'some_multi_method' with 2 objects as the invocants. ('>x & >y' is a transient conglomeration of the two objects) +# No explicit 'invocation role' is specified in this call - so it gets the default role for multiple invocants: 'these' +# The invocant signature is thus {these 2} +# (the default invocation role for a standard call on a method with a single object is 'this' - with the associated signature {this 1}) +# Invocation roles can be specified in the call using the @ operator. +# e.g >x & >y @ points . some_multi_method arg arg +# The invocant signature for this is: {points 2} +# +#eg2: {*}[join $objects &] @ objects & >p @ plane . move $path +# This has the signature {objects n plane 1} where n depends on the length of the list $objects +# +# +# To get a persistent conglomeration we would need to get a 'reference' to the conglomeration. +# e.g set pointset [>x & >y .] +# We can now call multimethods on $pointset +# + + + + + if {[namespace which ::pp::ifaces>null] eq ""} { + set ::pp::ID 4 ;#0,1,2,3 reserved for null interface,>pattern, >ifinfo & ::p::>interface + + #OID = 0 + ::pp::func::new_object ::pp::ifaces::>null 0 + + ::pp::func::new_object ::>pattern 1 + + #'class' for ::pp::ifaces::>x instances + ::pp::func::new_object ::pp::>interface 3 + + } + +#NOOP - for compatibility with libraries which still call it +proc ::pattern::init {args} { +} + + +proc ::pattern::initXXX {args} { + if {[set ::pattern::initialised]} { + if {[llength $args]} { + #if callers want to avoid this error, they can do their own check of $::pattern::initialised + error "pattern package is already initialised. Unable to apply args: $args" + } else { + return 1 + } + } + set ::pp::ID 4 ;#0,1,2,3 reserved for null interface,>pattern, >ifinfo & ::p::>interface + + #OID = 0 + ::pp::func::new_object ::pp::ifaces::>null 0 + + ::pp::func::new_object ::>pattern 1 + + #'class' for ::pp::ifaces::>x instances + ::pp::func::new_object ::pp::>interface 3 + + #::pp::>interface ## PatternSystem . add_pattern_interface 2 + + #add to constructor? + #::pp::Obj${o_OID}::_iface API(PatternInternal)add_tcloo_interface_on_api "varspace_iface" "pattern::IPatternInterface" + + set ::pattern::initialised 1 +} + + +# >pattern has object ID 1 +# meta interface has object ID 0 +proc ::pattern::init2 args { + if {[set ::pattern::initialised]} { + if {[llength $args]} { + #if callers want to avoid this error, they can do their own check of $::pattern::initialised + error "pattern package is already initialised. Unable to apply args: $args" + } else { + return 1 + } + } + set ::pp::ID 4 ;#0,1,2,3 reserved for null interface,>pattern, >ifinfo & ::p::>interface + + #create metaface - IID = -1 - also OID = -1 + # all objects implement this special interface - accessed via the .. operator. + package require metaface + + + + #OID = 0 + ::pp::func::new_object ::p::ifaces::>null 0 + + #? null object has itself as level0 & level1 interfaces? + #set ::p::ifaces::>null [list [list 0 ::p::ifaces::>null item] [list [list 0] [list 0]] [list {} {}]] + + #null interface should always have 'usedby' members. It should never be extended. + array set ::p::0::_iface::o_usedby [list i-1 ::p::internals::>metaface i0 ::p::ifaces::>null i1 ::>pattern] ;#'usedby' array + set ::p::0::_iface::o_open 0 + + set ::p::0::_iface::o_constructor [list] + set ::p::0::_iface::o_variables [list] + set ::p::0::_iface::o_properties [dict create] + set ::p::0::_iface::o_methods [dict create] + set ::p::0::_iface::o_varspace "" + set ::p::0::_iface::o_varspaces [list] + array set ::p::0::_iface::o_definition [list] + set ::p::0::_iface::o_propertyunset_handlers [dict create] + + + + + ############################### + # OID = 1 + # >pattern + ############################### + ::pp::func::new_object ::>pattern 1 + + + + set _self ::pattern + + #set IFID [::p::internals::new_interface 1] ;#level 0 interface usedby object 1 + #set IFID_1 [::p::internals::new_interface 1] ;#level 1 interface usedby object 1 + + + + #1)this object references its interfaces + #lappend ID $IFID $IFID_1 + + + #set body [string map [::list @self@ ::>pattern @_self@ ::pattern @self_ID@ 0 @itemCmd@ item] $::p::internals::OBJECTCOMMAND] + #proc ::>pattern args $body + + + + + ####################################################################################### + #OID = 2 + # >ifinfo interface for accessing interfaces. + # + ::p::internals::new_object ::p::ifaces::>2 "" 2 ;#>ifinfo object + set ::p::2::_iface::o_constructor [list] + set ::p::2::_iface::o_variables [list] + set ::p::2::_iface::o_properties [dict create] + set ::p::2::_iface::o_methods [dict create] + set ::p::2::_iface::o_varspace "" + set ::p::2::_iface::o_varspaces [list] + array set ::p::2::_iface::o_definition [list] + set ::p::2::_iface::o_open 1 ;#open for extending + + ::p::ifaces::>2 .. AddInterface 2 + + #Manually create a minimal >ifinfo implementation using the same general pattern we use for all method implementations + #(bootstrap because we can't yet use metaface methods on it) + + + + proc ::p::2::_iface::isOpen.1 {_ID_} { + return $::p::2::_iface::o_open + } + interp alias {} ::p::2::_iface::isOpen {} ::p::2::_iface::isOpen.1 + + proc ::p::2::_iface::isClosed.1 {_ID_} { + return [expr {!$::p::2::_iface::o_open}] + } + interp alias {} ::p::2::_iface::isClosed {} ::p::2::_iface::isClosed.1 + + proc ::p::2::_iface::open.1 {_ID_} { + set ::p::2::_iface::o_open 1 + } + interp alias {} ::p::2::_iface::open {} ::p::2::_iface::open.1 + + proc ::p::2::_iface::close.1 {_ID_} { + set ::p::2::_iface::o_open 0 + } + interp alias {} ::p::2::_iface::close {} ::p::2::_iface::close.1 + + + #proc ::p::2::_iface::(GET)properties.1 {_ID_} { + # set ::p::2::_iface::o_properties + #} + #interp alias {} ::p::2::_iface::(GET)properties {} ::p::2::_iface::(GET)properties.1 + + #interp alias {} ::p::2::properties {} ::p::2::_iface::(GET)properties + + + #proc ::p::2::_iface::(GET)methods.1 {_ID_} { + # set ::p::2::_iface::o_methods + #} + #interp alias {} ::p::2::_iface::(GET)methods {} ::p::2::_iface::(GET)methods.1 + #interp alias {} ::p::2::methods {} ::p::2::_iface::(GET)methods + + + + + + #link from object to interface (which in this case are one and the same) + + #interp alias {} ::p::2::isOpen {} ::p::2::_iface::isOpen [::p::ifaces::>2 --] + #interp alias {} ::p::2::isClosed {} ::p::2::_iface::isClosed [::p::ifaces::>2 --] + #interp alias {} ::p::2::open {} ::p::2::_iface::open [::p::ifaces::>2 --] + #interp alias {} ::p::2::close {} ::p::2::_iface::close [::p::ifaces::>2 --] + + interp alias {} ::p::2::isOpen {} ::p::2::_iface::isOpen + interp alias {} ::p::2::isClosed {} ::p::2::_iface::isClosed + interp alias {} ::p::2::open {} ::p::2::_iface::open + interp alias {} ::p::2::close {} ::p::2::_iface::close + + + #namespace eval ::p::2 "namespace export $method" + + ####################################################################################### + + + + + + + set ::pattern::initialised 1 + + + ::p::internals::new_object ::p::>interface "" 3 + #create a convenience object on which to manipulate the >ifinfo interface + #set IF [::>pattern .. Create ::p::>interface] + set IF ::p::>interface + + + #!todo - put >ifinfo on a separate pStack so that end-user can more freely treat interfaces as objects? + # (or is forcing end user to add their own pStack/iStack ok .. ?) + # + ::p::>interface .. AddPatternInterface 2 ;# + + ::p::>interface .. PatternVarspace _iface + + ::p::>interface .. PatternProperty methods + ::p::>interface .. PatternPropertyRead methods {} { + varspace _iface + var {o_methods mmm} + return $mmm + } + ::p::>interface .. PatternProperty properties + ::p::>interface .. PatternPropertyRead properties {} { + varspace _iface + var o_properties + return $o_properties + } + ::p::>interface .. PatternProperty variables + + ::p::>interface .. PatternProperty varspaces + + ::p::>interface .. PatternProperty definition + + ::p::>interface .. Constructor {{usedbylist {}}} { + #var this + #set this @this@ + #set ns [$this .. Namespace] + #puts "-> creating ns ${ns}::_iface" + #namespace eval ${ns}::_iface {} + + varspace _iface + var o_constructor o_variables o_properties o_methods o_definition o_usedby o_varspace o_varspaces + + set o_constructor [list] + set o_variables [list] + set o_properties [dict create] + set o_methods [dict create] + set o_varspaces [list] + array set o_definition [list] + + foreach usedby $usedbylist { + set o_usedby(i$usedby) 1 + } + + + } + ::p::>interface .. PatternMethod isOpen {} { + varspace _iface + var o_open + + return $o_open + } + ::p::>interface .. PatternMethod isClosed {} { + varspace _iface + var o_open + + return [expr {!$o_open}] + } + ::p::>interface .. PatternMethod open {} { + varspace _iface + var o_open + set o_open 1 + } + ::p::>interface .. PatternMethod close {} { + varspace _iface + var o_open + set o_open 0 + } + ::p::>interface .. PatternMethod refCount {} { + varspace _iface + var o_usedby + return [array size o_usedby] + } + + set ::p::2::_iface::o_open 1 + + + + + + uplevel #0 {package require patternlib} + return 1 +} + + + +#detect attempt to treat a reference to a method as a property +proc ::pp::func::commandrefMisuse_TraceHandler {OID field args} { + #puts "commandrefMisuse_TraceHandler fired OID:$OID field:$field args:$args" + lassign [lrange $args end-2 end] vtraced vidx op + #NOTE! cannot rely on vtraced as it may have been upvared + + switch -- $op { + write { + error "$field is not a property" "property ref write failure for property $field (OID: $OID refvariable: [lindex $args 0])" + } + unset { + #!todo - monitor stat of Tcl bug# 1911919 - when/(if?) fixed - reinstate 'unset' trace + #trace add variable $traced {read write unset} [concat ::pp::func::commandrefMisuse_TraceHandler $OID $field $args] + + #!todo - don't use vtraced! + trace add variable $vtraced {read write unset array} [concat ::pp::func::commandrefMisuse_TraceHandler $OID $field $args] + + #pointless raising an error as "Any errors in unset traces are ignored" + #error "cannot unset. $field is a method not a property" + } + read { + error "$field is not a property (args $args)" "property ref read failure for property $field (OID: $OID refvariable: [lindex $args 0])" + } + array { + error "$field is not a property (args $args)" "property ref use as array failure for property $field (OID: $OID refvariable: [lindex $args 0])" + #error "unhandled operation in commandrefMisuse_TraceHandler - got op:$op expected read,write,unset. OID:$OID field:$field args:$args" + } + } + + return +} + + + + +#!todo - review calling-points for make_dispatcher.. probably being called unnecessarily at some points. +# +# The 'dispatcher' is an object instance's underlying object command. +# + +#proc ::p::make_dispatcher {obj ID IFID} { +# proc [string map {::> ::} $obj] {{methprop INFO} args} [string map [::list @IID@ $IFID @oid@ $ID] { +# ::p::@IID@ $methprop @oid@ {*}$args +# }] +# return +#} + + + + +################################################################################################################################################ +################################################################################################################################################ +################################################################################################################################################ + + +#force 1 will extend an interface even if shared. (??? why is this necessary here?) +#if IID empty string - create the interface. +proc ::pp::func::expand_interface {IID {force 0}} { + #puts stdout ">>> expand_interface $IID [info level -1]<<<" + if {![string length $IID]} { + set iid [expr {$::p::ID + 1}] + ::pp::>interface .. Create ::pp::ifaces::>$iid + return $iid + } else { + if {[set ::pp::Obj${IID}::_iface::o_open]} { + #interface open for extending - shared or not! + return $IID + } + error "temporary error. Interface can't be expanded. Not implemented" + if {[array size ::pp::Obj${IID}::_iface::o_usedby] > 1} { + #upvar #0 ::p::${IID}::_iface::o_usedby prev_usedby + + #oops.. shared interface. Copy before specialising it. + set prev_IID $IID + + set IID [expr {$::pp::ID + 1}] + ::pp::>interface .. Create ::pp::ifaces::>$IID + + ::pp::func::linkcopy_interface $prev_IID $IID + #assert: prev_usedby contains at least one other element. + } + #whether copied or not - mark as open for extending. + set ::pp::Obj${IID}::_iface::o_open 1 + return $IID + } +} + +#params: old - old (shared) interface ID +# new - new interface ID +proc ::pp::func::linkcopy_interface {old new} { + #puts stderr " ** ** ** linkcopy_interface $old $new" + set ns_old ::pp::Obj${old}::_iface + set ns_new ::pp::Obj${new}::_iface + + + + foreach nsmethod [info commands ${ns_old}::*.1] { + #puts ">>> adding $nsmethod to iface $new" + set tail [namespace tail $nsmethod] + set method [string range $tail 0 end-2] ;#strip .1 + + if {![llength [info commands ${ns_new}::$method]]} { + + set oldhead [interp alias {} ${ns_old}::$method] ;#the 'head' of the cmdchain that it actually points to ie $method.$x where $x >=1 + + #link from new interface namespace to existing one. + #(we assume that since ${ns_new}::$method didn't exist, that all the $method.$x chain slots are empty too...) + #!todo? verify? + #- actual link is chainslot to chainslot + interp alias {} ${ns_new}::$method.1 {} $oldhead + + #!todo - review. Shouldn't we be linking entire chain, not just creating a single .1 pointer to the old head? + + + #chainhead pointer within new interface + interp alias {} ${ns_new}::$method {} ${ns_new}::$method.1 + + namespace eval $ns_new "namespace export $method" + + #if {[string range $method 0 4] ni {(GET) (SET) (UNSE (CONS }} { + # lappend ${ns_new}::o_methods $method + #} + } else { + if {$method eq "(VIOLATE)"} { + #ignore for now + #!todo + continue + } + + #!todo - handle how? + #error "command $cmd already exists in interface $new" + + #warning - existing chainslot will be completely shadowed by linked method. + # - existing one becomes unreachable. #!todo review!? + + + error "linkcopy_interface $old -> $new - chainslot shadowing not implemented (method $method already exists on target interface $new)" + + } + } + + + #foreach propinf [set ${ns_old}::o_properties] { + # lassign $propinf prop _default + # #interp alias {} ${ns_new}::(GET)$prop {} ::p::predator::getprop $prop + # #interp alias {} ${ns_new}::(SET)$prop {} ::p::predator::setprop $prop + # lappend ${ns_new}::o_properties $propinf + #} + + + set ${ns_new}::o_variables [set ${ns_old}::o_variables] + set ${ns_new}::o_properties [set ${ns_old}::o_properties] + set ${ns_new}::o_methods [set ${ns_old}::o_methods] + set ${ns_new}::o_constructor [set ${ns_old}::o_constructor] + + + set ::pp::${old}::_iface::o_usedby(i$new) linkcopy + + + #obsolete.? + #array set ::p::${new}:: [array get ::p::${old}:: ] + + + + #!todo - is this done also when iface compiled? + #namespace eval ::p::${new}::_iface {namespace ensemble create} + + + #puts stderr "copy_interface $old $new" + + #assume that the (usedby) data is now obsolete + #???why? + #set ${ns_new}::(usedby) [::list] + + #leave ::(usedby) reference in place for caller to change as appropriate - 'copy' + + return +} +################################################################################################################################################ +################################################################################################################################################ +################################################################################################################################################ + +#pattern::init + +#return $::pattern::version + +proc pp::repl {} { + set command "" + set prompt "% " + puts -nonewline stdout $prompt + flush stdout + while {[gets stdin line] >=0} { + append command "\n$line" + if {[info complete $command]} { + catch {uplevel #0 $command} result + puts stdout $result + set command "" + set prompt "% " + } else { + set prompt "(cont)% " + } + puts -nonewline stdout $prompt + flush stdout + } +} + +if {[info exists ::argv0] && [file dirname [file normalize [info script]/ ]] eq [file dirname [file normalize $argv0/]]} { + pp::repl +} + diff --git a/src/vendormodules/patterncipher-0.1.1.tm b/src/vendormodules/patterncipher-0.1.1.tm index 62b03cbc..0aa12476 100644 --- a/src/vendormodules/patterncipher-0.1.1.tm +++ b/src/vendormodules/patterncipher-0.1.1.tm @@ -37,7 +37,6 @@ package provide patterncipher [namespace eval patterncipher { package require ascii85 ;#tcllib package require pattern -::pattern::init ;# initialises (if not already) namespace eval ::patterncipher { namespace eval algo::txt { diff --git a/src/vendormodules/patterndispatcher-1.2.4.tm b/src/vendormodules/patterndispatcher-1.2.4.tm index 14194aee..373fe6d9 100644 --- a/src/vendormodules/patterndispatcher-1.2.4.tm +++ b/src/vendormodules/patterndispatcher-1.2.4.tm @@ -13,6 +13,7 @@ namespace eval pp { set no_operators_in_args [string trimright $no_operators_in_args " &"] ;#trim trailing spaces and ampersands #set no_operators_in_args {({.} ni $args) && ({,} ni $args) && ({..} ni $args)} + #todo - compare performance against algorithm in punk::lib::is_cachedlist_all_ni_list } package require TclOO diff --git a/src/vendormodules/test/pattern-1.2.8.tm b/src/vendormodules/test/pattern-1.2.8.tm new file mode 100644 index 0000000000000000000000000000000000000000..b5cb7026c5e1a0fbdd2df2ad549e8f1743d15add GIT binary patch literal 54941 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 -CQ>Nlb)OCiQJn4?`*KjBi?T}uVS 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 CgbK0% diff --git a/src/vfs/_config/project_main.tcl b/src/vfs/_config/project_main.tcl index bcf54540..40446f82 100644 --- a/src/vfs/_config/project_main.tcl +++ b/src/vfs/_config/project_main.tcl @@ -1,4 +1,6 @@ +#source is at /src/vfs/_config/project_main.tcl + #This main script will consume a first argument of the form dev|os|internal # or any dash-delimited combination such as dev-os # diff --git a/src/vfs/_config/punk_main.tcl b/src/vfs/_config/punk_main.tcl index bc853604..14ad0e1c 100644 --- a/src/vfs/_config/punk_main.tcl +++ b/src/vfs/_config/punk_main.tcl @@ -1,4 +1,6 @@ +#source is at src/vfs/_config/punk_main.tcl + #This main script will consume a first argument of the form dev|os|internal # or any dash-delimited combination such as dev-os #