Julian Noble
12 months ago
42 changed files with 18119 additions and 375 deletions
After Width: | Height: | Size: 277 KiB |
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@ |
|||||||
#!/bin/sh |
#!/bin/sh |
||||||
# -*- tcl -*- \ |
# -*- tcl -*- \ |
||||||
# 'build.tcl' name as required by kettle |
# 'build.tcl' name as required by kettle |
||||||
# Can be run directly - but also using `pmix Kettle ...` or `pmix KettleShell ...` |
# Can be run directly - but also using `pmix Kettle ...` or `pmix KettleShell ...`\ |
||||||
exec ./kettle -f "$0" "${1+$@}" |
exec ./kettle -f "$0" "${1+$@}" |
||||||
kettle doc |
kettle doc |
||||||
|
@ -0,0 +1,84 @@ |
|||||||
|
INSTALLER -tsiso 2023-11-30T01:40:19 -ts 1701268819673094 -name make.tcl -keep_events 5 { |
||||||
|
EVENT -tsiso_begin 2023-11-30T01:40:19 -ts_begin 1701268819676147 -tsiso_end {} -ts_end {} -id 250ad5e3-c95e-4833-addf-1282d09c9fec -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T01:47:15 -ts_begin 1701269235368667 -tsiso_end {} -ts_end {} -id 473193f2-54d2-44e8-a31a-9650c20177b5 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T01:53:57 -ts_begin 1701269637315528 -tsiso_end {} -ts_end {} -id 0984f805-501d-4f53-ba65-9fd68222a994 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T01:54:41 -ts_begin 1701269681466076 -tsiso_end {} -ts_end {} -id 94ea851c-85e5-4c48-b793-37b521ecb209 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T02:00:53 -ts_begin 1701270053672048 -tsiso_end {} -ts_end {} -id 1e060522-28a2-4712-a0f9-78ecc279c4d6 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T02:01:16 -ts_begin 1701270076820494 -tsiso_end {} -ts_end {} -id 5ce76b29-2b9a-4652-8c51-4f0281752381 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T02:06:29 -ts_begin 1701270389366390 -tsiso_end {} -ts_end {} -id 5271c70f-3a87-4a53-9c46-7b064b2bd43f -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-11-30T02:16:17 -ts_begin 1701270977456325 -tsiso_end {} -ts_end {} -id c84fbf6e-7aae-44b4-9f2b-d99615b76a81 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-05T04:22:54 -ts_begin 1701710574869059 -tsiso_end {} -ts_end {} -id 08ed1a89-fbb6-4cee-a543-e7b6f69663ae -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T01:45:19 -ts_begin 1701787519119661 -tsiso_end {} -ts_end {} -id 95cbdbe1-b100-4ed6-9202-3fa1dbbe7137 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T02:32:50 -ts_begin 1701790370423077 -tsiso_end {} -ts_end {} -id 9ba7b31c-9d08-4919-b475-3683fce42a37 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T03:36:28 -ts_begin 1701794188149001 -tsiso_end {} -ts_end {} -id 52ae56d6-8032-4855-88ee-5e71801b2846 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T05:31:47 -ts_begin 1701801107537126 -tsiso_end {} -ts_end {} -id 92f7f018-6b16-469e-9336-0d4a3b9bf75a -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T05:45:26 -ts_begin 1701801926154241 -tsiso_end {} -ts_end {} -id 9aa987b8-46d5-4059-9b5f-ba1fc8e9c841 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T05:55:36 -ts_begin 1701802536235596 -tsiso_end {} -ts_end {} -id 51123563-1b90-4437-b6e6-e85b1f8b9239 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T05:58:41 -ts_begin 1701802721245826 -tsiso_end {} -ts_end {} -id d67b0687-4760-4340-8022-0ffa2e69f2b2 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
EVENT -tsiso_begin 2023-12-06T06:09:27 -ts_begin 1701803367522663 -tsiso_end {} -ts_end {} -id 35fd839e-2ef6-4391-b2ec-809149cbb0b2 -source ../../../.. -targets . -types {} -config {-make-step sync_templates} |
||||||
|
} |
||||||
|
FILEINFO -targets {} -keep_installrecords 3 -keep_skipped 1 -keep_inprogress 2 { |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T01:40:19 -ts 1701268819677101 -installer make.tcl -eventid 250ad5e3-c95e-4833-addf-1282d09c9fec -metadata_us 21289 -ts_start_transfer 1701268819698390 -transfer_us 891 -elapsed_us 22180 { |
||||||
|
SOURCE -type file -path ../../../../build.tcl -cksum 8ab5fbcfe246195a43a7ba884d3088dbced18640 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 9411 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T01:40:19 -ts 1701268819704081 -installer make.tcl -eventid 250ad5e3-c95e-4833-addf-1282d09c9fec -metadata_us 16366 -ts_start_transfer 1701268819720447 -transfer_us 705 -elapsed_us 17071 { |
||||||
|
SOURCE -type file -path layouts/project/src/build.tcl -cksum 5f647ac1fbff3cb74f42a48bbef5239730a90054 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3516 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T01:40:19 -ts 1701268819725576 -installer make.tcl -eventid 250ad5e3-c95e-4833-addf-1282d09c9fec -metadata_us 21854 -ts_start_transfer 1701268819747430 -transfer_us 735 -elapsed_us 22589 { |
||||||
|
SOURCE -type file -path ../../../../make.tcl -cksum 0e44e25f9127c61faeb1946e2f2c7adfc6cfa585 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 10241 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T01:40:19 -ts 1701268819752520 -installer make.tcl -eventid 250ad5e3-c95e-4833-addf-1282d09c9fec -metadata_us 18713 -ts_start_transfer 1701268819771233 -transfer_us 715 -elapsed_us 19428 { |
||||||
|
SOURCE -type file -path layouts/project/src/make.tcl -cksum ca1412aac730e464406363d5fe90416cf66ce4a1 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 5116 |
||||||
|
} |
||||||
|
} |
||||||
|
FILEINFO -targets layouts/project/src/build.tcl -keep_installrecords 3 -keep_skipped 1 -keep_inprogress 2 { |
||||||
|
INSTALL-INPROGRESS -tsiso 2023-11-30T01:47:15 -ts 1701269235369501 -installer make.tcl -eventid 473193f2-54d2-44e8-a31a-9650c20177b5 -tempcontext {tag EVENT -tsiso_begin 2023-11-30T01:47:15 -ts_begin 1701269235368667 -tsiso_end {} -ts_end {} -id 473193f2-54d2-44e8-a31a-9650c20177b5 -source ../../../.. -targets . -types {} -config {-make-step sync_templates}} |
||||||
|
INSTALL-INPROGRESS -tsiso 2023-11-30T01:53:57 -ts 1701269637316371 -installer make.tcl -eventid 0984f805-501d-4f53-ba65-9fd68222a994 -tempcontext {tag EVENT -tsiso_begin 2023-11-30T01:53:57 -ts_begin 1701269637315528 -tsiso_end {} -ts_end {} -id 0984f805-501d-4f53-ba65-9fd68222a994 -source ../../../.. -targets . -types {} -config {-make-step sync_templates}} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T01:54:41 -ts 1701269681466949 -installer make.tcl -eventid 94ea851c-85e5-4c48-b793-37b521ecb209 -metadata_us 23683 -ts_start_transfer 1701269681490632 -transfer_us 2738 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/buildx.tcl": no such file or directory} -elapsed_us 26421 { |
||||||
|
SOURCE -type missing -path ../../../../buildx.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 8987 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T02:00:53 -ts 1701270053672988 -installer make.tcl -eventid 1e060522-28a2-4712-a0f9-78ecc279c4d6 -metadata_us 23887 -ts_start_transfer 1701270053696875 -transfer_us 2757 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/buildx.tcl": no such file or directory} -elapsed_us 26644 { |
||||||
|
SOURCE -type missing -path ../../../../buildx.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 9065 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T02:01:16 -ts 1701270076821516 -installer make.tcl -eventid 5ce76b29-2b9a-4652-8c51-4f0281752381 -metadata_us 24281 -ts_start_transfer 1701270076845797 -transfer_us 2813 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/buildx.tcl": no such file or directory} -elapsed_us 27094 { |
||||||
|
SOURCE -type missing -size {} -path ../../../../buildx.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 9039 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T02:06:29 -ts 1701270389367455 -installer make.tcl -eventid 5271c70f-3a87-4a53-9c46-7b064b2bd43f -metadata_us 24977 -ts_start_transfer 1701270389392432 -transfer_us 2918 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/buildx.tcl": no such file or directory} -elapsed_us 27895 { |
||||||
|
SOURCE -type missing -size {} -path ../../../../buildx.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 9034 |
||||||
|
} |
||||||
|
INSTALL-RECORD -tsiso 2023-11-30T02:16:17 -ts 1701270977457421 -installer make.tcl -eventid c84fbf6e-7aae-44b4-9f2b-d99615b76a81 -metadata_us 26164 -ts_start_transfer 1701270977483585 -transfer_us 3773 -note test -elapsed_us 29937 { |
||||||
|
SOURCE -type file -size 195 -path ../../../../build.tcl -cksum 8ab5fbcfe246195a43a7ba884d3088dbced18640 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 9681 |
||||||
|
} |
||||||
|
INSTALL-RECORD -tsiso 2023-12-05T04:22:54 -ts 1701710574870134 -installer make.tcl -eventid 08ed1a89-fbb6-4cee-a543-e7b6f69663ae -metadata_us 25456 -ts_start_transfer 1701710574895590 -transfer_us 4425 -note test -elapsed_us 29881 { |
||||||
|
SOURCE -type file -size 196 -path ../../../../build.tcl -cksum 54fc5a072dc4627d1df737eecc8daed2fdd17f4d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 9776 |
||||||
|
} |
||||||
|
INSTALL-SKIPPED -tsiso 2023-12-06T06:09:27 -ts 1701803367523924 -installer make.tcl -eventid 35fd839e-2ef6-4391-b2ec-809149cbb0b2 -elapsed_us 22312 { |
||||||
|
SOURCE -type file -size 196 -path ../../../../build.tcl -cksum 54fc5a072dc4627d1df737eecc8daed2fdd17f4d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 9830 |
||||||
|
} |
||||||
|
} |
||||||
|
FILEINFO -targets layouts/project/src/make.tcl -keep_installrecords 3 -keep_skipped 1 -keep_inprogress 2 { |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T01:54:41 -ts 1701269681498040 -installer make.tcl -eventid 94ea851c-85e5-4c48-b793-37b521ecb209 -metadata_us 23162 -ts_start_transfer 1701269681521202 -transfer_us 2474 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/makex.tcl": no such file or directory} -elapsed_us 25636 { |
||||||
|
SOURCE -type missing -path ../../../../makex.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 8978 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T02:00:53 -ts 1701270053704394 -installer make.tcl -eventid 1e060522-28a2-4712-a0f9-78ecc279c4d6 -metadata_us 23411 -ts_start_transfer 1701270053727805 -transfer_us 2522 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/makex.tcl": no such file or directory} -elapsed_us 25933 { |
||||||
|
SOURCE -type missing -path ../../../../makex.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 9024 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T02:01:16 -ts 1701270076853426 -installer make.tcl -eventid 5ce76b29-2b9a-4652-8c51-4f0281752381 -metadata_us 23643 -ts_start_transfer 1701270076877069 -transfer_us 2566 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/makex.tcl": no such file or directory} -elapsed_us 26209 { |
||||||
|
SOURCE -type missing -size {} -path ../../../../makex.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 8991 |
||||||
|
} |
||||||
|
INSTALL-FAILED -tsiso 2023-11-30T02:06:29 -ts 1701270389400265 -installer make.tcl -eventid 5271c70f-3a87-4a53-9c46-7b064b2bd43f -metadata_us 23863 -ts_start_transfer 1701270389424128 -transfer_us 2604 -note {copy failed with err: error copying "C:/repo/jn/shellspy/src/makex.tcl": no such file or directory} -elapsed_us 26467 { |
||||||
|
SOURCE -type missing -size {} -path ../../../../makex.tcl -cksum <PATHNOTFOUND> -cksum_all_opts {-cksum_content 1 -cksum_meta auto -cksum_acls 0 -cksum_usetar auto -cksum_algorithm sha1} -changed 1 -metadata_us 9005 |
||||||
|
} |
||||||
|
INSTALL-RECORD -tsiso 2023-12-06T01:45:19 -ts 1701787519148901 -installer make.tcl -eventid 95cbdbe1-b100-4ed6-9202-3fa1dbbe7137 -metadata_us 26024 -ts_start_transfer 1701787519174925 -transfer_us 4325 -note test -elapsed_us 30349 { |
||||||
|
SOURCE -type file -size 32642 -path ../../../../make.tcl -cksum 80105c381fa3db05833f44b716c1536fef128d84 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 10482 |
||||||
|
} |
||||||
|
INSTALL-RECORD -tsiso 2023-12-06T02:32:50 -ts 1701790370452196 -installer make.tcl -eventid 9ba7b31c-9d08-4919-b475-3683fce42a37 -metadata_us 26602 -ts_start_transfer 1701790370478798 -transfer_us 4392 -note test -elapsed_us 30994 { |
||||||
|
SOURCE -type file -size 32922 -path ../../../../make.tcl -cksum 7aea3c018ce954a67ce8254c88e07407e008247c -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 10680 |
||||||
|
} |
||||||
|
INSTALL-RECORD -tsiso 2023-12-06T03:36:28 -ts 1701794188178099 -installer make.tcl -eventid 52ae56d6-8032-4855-88ee-5e71801b2846 -metadata_us 26790 -ts_start_transfer 1701794188204889 -transfer_us 4285 -note test -elapsed_us 31075 { |
||||||
|
SOURCE -type file -size 32956 -path ../../../../make.tcl -cksum dda7ebdcf186a5bd8e7f9c72a8e2bc892620fcab -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 11017 |
||||||
|
} |
||||||
|
INSTALL-SKIPPED -tsiso 2023-12-06T06:09:27 -ts 1701803367551725 -installer make.tcl -eventid 35fd839e-2ef6-4391-b2ec-809149cbb0b2 -elapsed_us 22232 { |
||||||
|
SOURCE -type file -size 32956 -path ../../../../make.tcl -cksum dda7ebdcf186a5bd8e7f9c72a8e2bc892620fcab -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 10590 |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@ |
|||||||
#!/bin/sh |
#!/bin/sh |
||||||
# -*- tcl -*- \ |
# -*- tcl -*- \ |
||||||
exec kettle -f "$0" "${1+$@}" |
# 'build.tcl' name as required by kettle |
||||||
kettle tcl |
# Can be run directly - but also using `pmix Kettle ...` or `pmix KettleShell ...`\ |
||||||
|
exec ./kettle -f "$0" "${1+$@}" |
||||||
kettle doc |
kettle doc |
||||||
|
@ -0,0 +1,47 @@ |
|||||||
|
|
||||||
|
/bin/ |
||||||
|
/lib/ |
||||||
|
#The directory for compiled/built Tcl modules |
||||||
|
/modules/ |
||||||
|
/vendorbuilds/ |
||||||
|
|
||||||
|
#Temporary files e.g from tests |
||||||
|
/tmp/ |
||||||
|
|
||||||
|
/logs/ |
||||||
|
**/_aside/ |
||||||
|
**/_build/ |
||||||
|
scratch* |
||||||
|
|
||||||
|
#Built documentation |
||||||
|
/html/ |
||||||
|
/man/ |
||||||
|
/md/ |
||||||
|
/doc/ |
||||||
|
|
||||||
|
/test* |
||||||
|
|
||||||
|
|
||||||
|
#Built tclkits (if any) |
||||||
|
punk*.exe |
||||||
|
tcl*.exe |
||||||
|
|
||||||
|
#ignore fossil database files (but keep .fossil-settings and .fossil-custom in repository even if fossil not being used at your site) |
||||||
|
_FOSSIL_ |
||||||
|
.fos |
||||||
|
.fslckout |
||||||
|
*.fossil |
||||||
|
|
||||||
|
#miscellaneous editor files etc |
||||||
|
*.swp |
||||||
|
|
||||||
|
|
||||||
|
todo.txt |
||||||
|
|
||||||
|
zig-cache/ |
||||||
|
zig-out/ |
||||||
|
/release/ |
||||||
|
/debug/ |
||||||
|
/build/ |
||||||
|
/build-*/ |
||||||
|
/docgen_tmp/ |
@ -0,0 +1,13 @@ |
|||||||
|
%project% |
||||||
|
============================== |
||||||
|
|
||||||
|
+ |
||||||
|
+ |
||||||
|
|
||||||
|
|
||||||
|
About |
||||||
|
------------------------------ |
||||||
|
|
||||||
|
+ |
||||||
|
+ |
||||||
|
+ |
@ -0,0 +1,11 @@ |
|||||||
|
Tcl Module Source files for the project. |
||||||
|
Consider using the punkshell pmix facility to create and manage these. |
||||||
|
|
||||||
|
pmix::newmodule <name> will create a basic .tm module template and assist in versioning. |
||||||
|
|
||||||
|
Tcl modules can be namespaced. |
||||||
|
For example |
||||||
|
> pmix::newmodule mymodule::utils |
||||||
|
will create the new module under src/modules/mymodule/utils |
||||||
|
|
||||||
|
|
@ -0,0 +1 @@ |
|||||||
|
package ifneeded app-sample 0.1 [list source [file join $dir sample.tcl]] |
@ -0,0 +1,8 @@ |
|||||||
|
namespace eval sample { |
||||||
|
proc main {} { |
||||||
|
puts stdout "[namespace current] argc $::argc argv $::argv" |
||||||
|
puts stdout "[namespace current] done" |
||||||
|
} |
||||||
|
main |
||||||
|
} |
||||||
|
package provide app-sample 0.1 |
@ -0,0 +1,2 @@ |
|||||||
|
package ifneeded app-sampleshell 0.1 [list source [file join $dir repl.tcl]] |
||||||
|
|
@ -0,0 +1,111 @@ |
|||||||
|
package provide app-punk 1.0 |
||||||
|
|
||||||
|
|
||||||
|
#punk linerepl launcher |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------ |
||||||
|
#Module loading |
||||||
|
#------------------------------------------------------------------------------ |
||||||
|
#If the current directory contains .tm files when the punk repl starts - then it will attempt to preference them |
||||||
|
# - but first add our other known relative modules paths - as it won't make sense to use current directory as a modulpath if it's an ancestor of one of these.. |
||||||
|
|
||||||
|
set original_tm_list [tcl::tm::list] |
||||||
|
tcl::tm::remove {*}$original_tm_list |
||||||
|
|
||||||
|
#tm list first added end up later in the list - and then override earlier ones if version the same - so add pwd-relative 1st to give higher priority |
||||||
|
#1 |
||||||
|
if {[file exists [pwd]/modules]} { |
||||||
|
catch {tcl::tm::add [pwd]/modules} |
||||||
|
} |
||||||
|
|
||||||
|
#2) |
||||||
|
if {[string match "*.vfs/*" [info script]]} { |
||||||
|
#src/xxx.vfs/lib/app-punk/repl.tcl |
||||||
|
#we assume if calling directly into .vfs that the user would prefer to use src/modules - so go up 4 levels |
||||||
|
set modulefolder [file dirname [file dirname [file dirname [file dirname [info script]]]]]/modules |
||||||
|
|
||||||
|
} else { |
||||||
|
# .../bin/punkXX.exe look for ../modules (i.e modules folder at same level as bin folder) |
||||||
|
set modulefolder [file dirname [file dirname [info nameofexecutable]]]/modules |
||||||
|
} |
||||||
|
|
||||||
|
if {[file exists $modulefolder]} { |
||||||
|
tcl::tm::add $modulefolder |
||||||
|
} else { |
||||||
|
puts stderr "Warning unable to find module folder at: $modulefolder" |
||||||
|
} |
||||||
|
|
||||||
|
#libs are appended to end - so add higher prioriy libraries last (opposite to modules) |
||||||
|
#auto_path - add exe-relative after exe-relative path |
||||||
|
set libfolder [file dirname [file dirname [info nameofexecutable]]]/lib |
||||||
|
if {[file exists $libfolder]} { |
||||||
|
lappend ::auto_path $libfolder |
||||||
|
} |
||||||
|
if {[file exists [pwd]/lib]} { |
||||||
|
lappend ::auto_path [pwd]/lib |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#2) |
||||||
|
#now add current dir (if no conflict with above) |
||||||
|
set currentdir_modules [glob -nocomplain -dir [pwd] -type f -tail *.tm] |
||||||
|
set tcl_core_packages [list tcl::zlib zlib tcl::oo TclOO tcl::tommath tcl::zipfs Tcl Tk] |
||||||
|
if {[llength $currentdir_modules]} { |
||||||
|
#only forget all *unloaded* package names if we are started in a .tm containing folder |
||||||
|
foreach pkg [package names] { |
||||||
|
if {$pkg in $tcl_core_packages} { |
||||||
|
continue |
||||||
|
} |
||||||
|
if {![llength [package versions $pkg]]} { |
||||||
|
#puts stderr "Got no versions for pkg $pkg" |
||||||
|
continue |
||||||
|
} |
||||||
|
if {![string length [package provide $pkg]]} { |
||||||
|
package forget $pkg |
||||||
|
} |
||||||
|
} |
||||||
|
catch {tcl::tm::add [pwd]} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#puts stdout "$::auto_path" |
||||||
|
package require Thread |
||||||
|
#These are strong dependencies |
||||||
|
# - the repl requires Threading and punk,shellfilter,shellrun to call and display properly. |
||||||
|
# tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list |
||||||
|
set required [list\ |
||||||
|
shellfilter |
||||||
|
shellrun\ |
||||||
|
punk\ |
||||||
|
] |
||||||
|
|
||||||
|
catch { |
||||||
|
foreach pkg $required { |
||||||
|
package forget $pkg |
||||||
|
package require $pkg |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#restore module paths |
||||||
|
set tm_list_now [tcl::tm::list] |
||||||
|
foreach p $original_tm_list { |
||||||
|
if {$p ni $tm_list_now} { |
||||||
|
#the prior tm paths go to the head of the list. |
||||||
|
#They are processed first.. but an item of same version later in the list will override one at the head. |
||||||
|
tcl::tm::add $p |
||||||
|
} |
||||||
|
} |
||||||
|
#------------------------------------------------------------------------------ |
||||||
|
|
||||||
|
foreach pkg $required { |
||||||
|
package require $pkg |
||||||
|
} |
||||||
|
|
||||||
|
package require punk::repl |
||||||
|
repl::start stdin |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,23 @@ |
|||||||
|
|
||||||
|
if {[catch {package require starkit}]} { |
||||||
|
#presumably running the xxx.vfs/main.tcl script using a non-starkit tclsh that doesn't have starkit lib available.. lets see if we can move forward anyway |
||||||
|
lappend ::auto_path [file join [file dirname [info script]] lib] |
||||||
|
} else { |
||||||
|
starkit::startup |
||||||
|
} |
||||||
|
|
||||||
|
#when run as a tclkit - the exe is mounted as a dir and Tcl's auto_execok doesn't find it |
||||||
|
set thisexe [file tail [info nameofexecutable]] |
||||||
|
set thisexeroot [file rootname $thisexe] |
||||||
|
set ::auto_execs($thisexeroot) [info nameofexecutable] |
||||||
|
if {$thisexe ne $thisexeroot} { |
||||||
|
set ::auto_execs($thisexe) [info nameofexecutable] |
||||||
|
} |
||||||
|
|
||||||
|
if {[llength $::argv]} { |
||||||
|
package require app-sample |
||||||
|
} else { |
||||||
|
package require app-sampleshell |
||||||
|
repl::start stdin |
||||||
|
} |
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,710 @@ |
|||||||
|
# vim: set ft=tcl |
||||||
|
# |
||||||
|
#purpose: handle the run commands that call shellfilter::run |
||||||
|
#e.g run,runout,runerr,runx |
||||||
|
|
||||||
|
package require shellfilter |
||||||
|
package require punk::ansi |
||||||
|
|
||||||
|
#NOTE: the run,runout,runerr,runx commands only produce an error if the command didn't run. |
||||||
|
# - If it did run, but there was a non-zero exitcode it is up to the application to check that. |
||||||
|
#This is deliberate, but means 'catch' doesn't catch errors within the command itself - the exitcode has to be checked. |
||||||
|
#The user can always use exec for different process error semantics (they don't get exitcode with exec) |
||||||
|
|
||||||
|
namespace eval shellrun { |
||||||
|
variable runout |
||||||
|
variable runerr |
||||||
|
|
||||||
|
#do we need these? |
||||||
|
variable punkout |
||||||
|
variable punkerr |
||||||
|
|
||||||
|
#some ugly coupling with punk/punk::config for now |
||||||
|
#todo - something better |
||||||
|
if {[info exists ::punk::config::running]} { |
||||||
|
upvar ::punk::config::running conf |
||||||
|
set syslog_stdout [dict get $conf syslog_stdout] |
||||||
|
set syslog_stderr [dict get $conf syslog_stderr] |
||||||
|
set logfile_stdout [dict get $conf logfile_stdout] |
||||||
|
set logfile_stderr [dict get $conf logfile_stderr] |
||||||
|
} else { |
||||||
|
lassign [list "" "" "" ""] syslog_stdout syslog_stderr logfile_stdout logfile_stderr |
||||||
|
} |
||||||
|
set outdevice [shellfilter::stack::new punkout -settings [list -tag "punkout" -buffering none -raw 1 -syslog $syslog_stdout -file $logfile_stdout]] |
||||||
|
set out [dict get $outdevice localchan] |
||||||
|
set errdevice [shellfilter::stack::new punkerr -settings [list -tag "punkerr" -buffering none -raw 1 -syslog $syslog_stderr -file $logfile_stderr]] |
||||||
|
set err [dict get $errdevice localchan] |
||||||
|
|
||||||
|
namespace import ::punk::ansi::a+ |
||||||
|
namespace import ::punk::ansi::a |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#repltelemetry - additional/alternative display info used in a repl context i.e info directed towards the screen |
||||||
|
#todo - package up in repltelemetry module and rewrite proc based on whether the module was found/loaded. |
||||||
|
#somewhat strong coupling to punk - but let's try to behave decently if it's not loaded |
||||||
|
#The last_run_display is actually intended for the repl - but is resident in the punk namespace with a view to the possibility of a different repl being in use. |
||||||
|
proc set_last_run_display {chunklist} { |
||||||
|
#chunklist as understood by the |
||||||
|
if {![info exists ::punk::repltelemetry_emmitters]} { |
||||||
|
namespace eval ::punk { |
||||||
|
variable repltelemetry_emmitters |
||||||
|
set repltelemetry_emmitters "shellrun" |
||||||
|
} |
||||||
|
} else { |
||||||
|
if {"shellrun" ni $::punk::repltelemetry_emmitters} { |
||||||
|
lappend punk::repltelemetry_emmitters "shellrun" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#most basic of validity tests here.. just that it is a list (can be empty). We don't want to duplicate or over-constrain the way repls/shells/terminals interpet the info |
||||||
|
if {[catch {llength $chunklist} errMsg]} { |
||||||
|
error "set_last_run_display expects a list. Value supplied doesn't appear to be a well formed tcl list. '$errMsg'" |
||||||
|
} |
||||||
|
#todo - |
||||||
|
set ::punk::last_run_display $chunklist |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#maintenance: similar used in punk::ns & punk::winrun |
||||||
|
#todo - take runopts + aliases as args |
||||||
|
proc get_run_opts {arglist} { |
||||||
|
if {[catch { |
||||||
|
set callerinfo [info level -1] |
||||||
|
} errM]} { |
||||||
|
set caller "" |
||||||
|
} else { |
||||||
|
set caller [lindex $callerinfo 0] |
||||||
|
} |
||||||
|
|
||||||
|
#we provide -nonewline even for 'run' even though run doesn't deliver stderr or stdout to the tcl return value |
||||||
|
#This is for compatibility with other runX commands, and the difference is also visible when calling from repl. |
||||||
|
set known_runopts [list "-echo" "-e" "-nonewline" "-n" "-tcl"] |
||||||
|
set aliases [list "-e" "-echo" "-echo" "-echo" "-n" "-nonewline" "-nonewline" "-nonewline" "-tcl" "-tcl"] ;#include map to self |
||||||
|
set runopts [list] |
||||||
|
set cmdargs [list] |
||||||
|
set idx_first_cmdarg [lsearch -not $arglist "-*"] |
||||||
|
set runopts [lrange $arglist 0 $idx_first_cmdarg-1] |
||||||
|
set cmdargs [lrange $arglist $idx_first_cmdarg end] |
||||||
|
foreach o $runopts { |
||||||
|
if {$o ni $known_runopts} { |
||||||
|
error "$caller: Unknown runoption $o - known options $known_runopts" |
||||||
|
} |
||||||
|
} |
||||||
|
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||||
|
return [list runopts $runopts cmdargs $cmdargs] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
proc run {args} { |
||||||
|
set_last_run_display [list] |
||||||
|
|
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
|
||||||
|
if {"-nonewline" in $runopts} { |
||||||
|
set nonewline 1 |
||||||
|
} else { |
||||||
|
set nonewline 0 |
||||||
|
} |
||||||
|
set idlist_stderr [list] |
||||||
|
#we leave stdout without imposed ansi colouring - because the source may be colourised |
||||||
|
#stderr might have source colouring - but it usually doesn't seem to, and the visual distiction of red stderr is very handy for the run command. |
||||||
|
#A further enhancement could be to detect well-known options such as --color and/or use a configuration for specific commands that have useful colourised stderr, |
||||||
|
#but defaulting stderr to red is a pretty reasonable compromise. |
||||||
|
#Note that the other run commands, runout,runerr, runx don't emit in real-time - so for those commands there may be options to detect and/or post-process stdout and stderr. |
||||||
|
#TODO - fix. This has no effect because the repl adds an ansiwrap transform |
||||||
|
# what we probably want to do is 'aside' that transform for runxxx commands only. |
||||||
|
#lappend idlist_stderr [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}] |
||||||
|
|
||||||
|
set callopts [dict create] |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
dict set callopts -tclscript 1 |
||||||
|
} |
||||||
|
#--------------------------------------------------------------------------------------------- |
||||||
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punk -inbuffering none -outbuffering none ] |
||||||
|
#--------------------------------------------------------------------------------------------- |
||||||
|
|
||||||
|
foreach id $idlist_stderr { |
||||||
|
shellfilter::stack::remove stderr $id |
||||||
|
} |
||||||
|
|
||||||
|
flush stderr |
||||||
|
flush stdout |
||||||
|
|
||||||
|
if {[dict exists $exitinfo error]} { |
||||||
|
error "[dict get $exitinfo error]\n$exitinfo" |
||||||
|
} |
||||||
|
|
||||||
|
return $exitinfo |
||||||
|
} |
||||||
|
|
||||||
|
proc runout {args} { |
||||||
|
set_last_run_display [list] |
||||||
|
variable runout |
||||||
|
variable runerr |
||||||
|
set runout "" |
||||||
|
set runerr "" |
||||||
|
|
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
|
||||||
|
if {"-nonewline" in $runopts} { |
||||||
|
set nonewline 1 |
||||||
|
} else { |
||||||
|
set nonewline 0 |
||||||
|
} |
||||||
|
|
||||||
|
#puts stdout "RUNOUT cmdargs: $cmdargs" |
||||||
|
|
||||||
|
#todo add -data boolean and -data lastwrite to -settings with default being -data all |
||||||
|
# because sometimes we're only interested in last char (e.g to detect something was output) |
||||||
|
|
||||||
|
#set outvar_stackid [shellfilter::stack::add commandout tee_to_var -action float -settings {-varname ::runout}] |
||||||
|
# |
||||||
|
#when not echoing - use float-locked so that the repl's stack is bypassed |
||||||
|
if {"-echo" in $runopts} { |
||||||
|
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float-locked -settings {-varname ::shellrun::runout}] |
||||||
|
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float-locked -settings {-varname ::shellrun::runerr}] |
||||||
|
#set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action sink-locked -settings {-varname ::shellrun::runerr}] |
||||||
|
} else { |
||||||
|
set stdout_stackid [shellfilter::stack::add stdout var -action float-locked -settings {-varname ::shellrun::runout}] |
||||||
|
set stderr_stackid [shellfilter::stack::add stderr var -action float-locked -settings {-varname ::shellrun::runerr}] |
||||||
|
} |
||||||
|
|
||||||
|
set callopts "" |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
append callopts " -tclscript 1" |
||||||
|
} |
||||||
|
|
||||||
|
#shellfilter::run [lrange $args 1 end] -teehandle punk -outchan stdout -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler |
||||||
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punk -inbuffering none -outbuffering none ] |
||||||
|
|
||||||
|
flush stderr |
||||||
|
flush stdout |
||||||
|
|
||||||
|
shellfilter::stack::remove stdout $stdout_stackid |
||||||
|
shellfilter::stack::remove stderr $stderr_stackid |
||||||
|
|
||||||
|
#shellfilter::stack::remove commandout $outvar_stackid |
||||||
|
|
||||||
|
if {[dict exists $exitinfo error]} { |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
|
||||||
|
} else { |
||||||
|
#we must raise an error. |
||||||
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||||
|
# |
||||||
|
set msg "" |
||||||
|
append msg [dict get $exitinfo error] |
||||||
|
append msg "\n(add -tcl option to run as a tcl command/script instead of an external command)" |
||||||
|
error $msg |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set chunklist [list] |
||||||
|
|
||||||
|
#exitcode not part of return value for runout - colourcode appropriately |
||||||
|
set n [a] |
||||||
|
set c "" |
||||||
|
if [dict exists $exitinfo exitcode] { |
||||||
|
set code [dict get $exitinfo exitcode] |
||||||
|
if {$code == 0} { |
||||||
|
set c [a+ green] |
||||||
|
} else { |
||||||
|
set c [a+ white bold] |
||||||
|
} |
||||||
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
||||||
|
} elseif [dict exists $exitinfo error] { |
||||||
|
set c [a+ yellow bold] |
||||||
|
lappend chunklist [list "info" "${c}error [dict get $exitinfo error]$n"] |
||||||
|
lappend chunklist [list "info" "errorCode [dict get $exitinfo errorCode]"] |
||||||
|
#lappend chunklist [list "info" "errorInfo [list [dict get $exitinfo errorInfo]]"] |
||||||
|
lappend chunklist [list "info" errorInfo] |
||||||
|
lappend chunklist [list "stderr" [dict get $exitinfo errorInfo]] |
||||||
|
} else { |
||||||
|
set c [a+ Yellow red bold] |
||||||
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set chunk "[a+ red bold]stderr[a]" |
||||||
|
lappend chunklist [list "info" $chunk] |
||||||
|
|
||||||
|
set chunk "" |
||||||
|
if {[string length $::shellrun::runerr]} { |
||||||
|
if {$nonewline} { |
||||||
|
set e [string trimright $::shellrun::runerr \r\n] |
||||||
|
} else { |
||||||
|
set e $::shellrun::runerr |
||||||
|
} |
||||||
|
#append chunk "[a+ red light]$e[a]\n" |
||||||
|
append chunk "[a+ red light]$e[a]" |
||||||
|
} |
||||||
|
lappend chunklist [list stderr $chunk] |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lappend chunklist [list "info" "[a+ white bold]stdout[a]"] |
||||||
|
set chunk "" |
||||||
|
if {[string length $::shellrun::runout]} { |
||||||
|
if {$nonewline} { |
||||||
|
set o [string trimright $::shellrun::runout \r\n] |
||||||
|
} else { |
||||||
|
set o $::shellrun::runout |
||||||
|
} |
||||||
|
append chunk "$o" |
||||||
|
} |
||||||
|
lappend chunklist [list result $chunk] |
||||||
|
|
||||||
|
|
||||||
|
set_last_run_display $chunklist |
||||||
|
|
||||||
|
if {$nonewline} { |
||||||
|
return [string trimright $::shellrun::runout \r\n] |
||||||
|
} else { |
||||||
|
return $::shellrun::runout |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
proc runerr {args} { |
||||||
|
set_last_run_display [list] |
||||||
|
variable runout |
||||||
|
variable runerr |
||||||
|
set runout "" |
||||||
|
set runerr "" |
||||||
|
|
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
|
||||||
|
if {"-nonewline" in $runopts} { |
||||||
|
set nonewline 1 |
||||||
|
} else { |
||||||
|
set nonewline 0 |
||||||
|
} |
||||||
|
|
||||||
|
set callopts "" |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
append callopts " -tclscript 1" |
||||||
|
} |
||||||
|
if {"-echo" in $runopts} { |
||||||
|
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float-locked -settings {-varname ::shellrun::runerr}] |
||||||
|
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float-locked -settings {-varname ::shellrun::runout}] |
||||||
|
} else { |
||||||
|
set stderr_stackid [shellfilter::stack::add stderr var -action float-locked -settings {-varname ::shellrun::runerr}] |
||||||
|
set stdout_stackid [shellfilter::stack::add stdout var -action float-locked -settings {-varname ::shellrun::runout}] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||||
|
shellfilter::stack::remove stderr $stderr_stackid |
||||||
|
shellfilter::stack::remove stdout $stdout_stackid |
||||||
|
|
||||||
|
|
||||||
|
flush stderr |
||||||
|
flush stdout |
||||||
|
|
||||||
|
#we raise an error because an error during calling is different to collecting stderr from a command, and the caller should be able to wrap in a catch |
||||||
|
# to determine something other than just a nonzero exit code or output on stderr. |
||||||
|
if {[dict exists $exitinfo error]} { |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
|
||||||
|
} else { |
||||||
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||||
|
error [dict get $exitinfo error] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
set chunklist [list] |
||||||
|
|
||||||
|
set n [a] |
||||||
|
set c "" |
||||||
|
if [dict exists $exitinfo exitcode] { |
||||||
|
set code [dict get $exitinfo exitcode] |
||||||
|
if {$code == 0} { |
||||||
|
set c [a+ green] |
||||||
|
} else { |
||||||
|
set c [a+ white bold] |
||||||
|
} |
||||||
|
|
||||||
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
||||||
|
|
||||||
|
} elseif [dict exists $exitinfo error] { |
||||||
|
set c [a+ yellow bold] |
||||||
|
lappend chunklist [list "info" "error [dict get $exitinfo error]"] |
||||||
|
lappend chunklist [list "info" "errorCode [dict get $exitinfo errorCode]"] |
||||||
|
lappend chunklist [list "info" "errorInfo [list [dict get $exitinfo errorInfo]]"] |
||||||
|
} else { |
||||||
|
set c [a+ Yellow red bold] |
||||||
|
lappend chunklist [list "info" "$c$exitinfo$n"] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
lappend chunklist [list "info" "[a+ white bold]stdout[a]"] |
||||||
|
set chunk "" |
||||||
|
if {[string length $::shellrun::runout]} { |
||||||
|
if {$nonewline} { |
||||||
|
set o [string trimright $::shellrun::runout \r\n] |
||||||
|
} else { |
||||||
|
set o $::shellrun::runout |
||||||
|
} |
||||||
|
append chunk "[a+ white light]$o[a]\n" ;#this newline is the display output separator - always there whether data has trailing newline or not. |
||||||
|
} |
||||||
|
lappend chunklist [list stdout $chunk] |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set chunk "[a+ red bold]stderr[a]" |
||||||
|
lappend chunklist [list "info" $chunk] |
||||||
|
|
||||||
|
set chunk "" |
||||||
|
if {[string length $::shellrun::runerr]} { |
||||||
|
if {$nonewline} { |
||||||
|
set e [string trimright $::shellrun::runerr \r\n] |
||||||
|
} else { |
||||||
|
set e $::shellrun::runerr |
||||||
|
} |
||||||
|
append chunk "$e" |
||||||
|
} |
||||||
|
lappend chunklist [list resulterr $chunk] |
||||||
|
|
||||||
|
|
||||||
|
set_last_run_display $chunklist |
||||||
|
|
||||||
|
if {$nonewline} { |
||||||
|
return [string trimright $::shellrun::runerr \r\n] |
||||||
|
} |
||||||
|
return $::shellrun::runerr |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc runx {args} { |
||||||
|
set_last_run_display [list] |
||||||
|
variable runout |
||||||
|
variable runerr |
||||||
|
set runout "" |
||||||
|
set runerr "" |
||||||
|
|
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
|
||||||
|
if {"-nonewline" in $runopts} { |
||||||
|
set nonewline 1 |
||||||
|
} else { |
||||||
|
set nonewline 0 |
||||||
|
} |
||||||
|
|
||||||
|
#shellfilter::stack::remove stdout $::repl::id_outstack |
||||||
|
|
||||||
|
if {"-echo" in $runopts} { |
||||||
|
#float to ensure repl transform doesn't interfere with the output data |
||||||
|
set stderr_stackid [shellfilter::stack::add stderr tee_to_var -action float -settings {-varname ::shellrun::runerr}] |
||||||
|
set stdout_stackid [shellfilter::stack::add stdout tee_to_var -action float -settings {-varname ::shellrun::runout}] |
||||||
|
} else { |
||||||
|
#set stderr_stackid [shellfilter::stack::add stderr var -action sink-locked -settings {-varname ::shellrun::runerr}] |
||||||
|
#set stdout_stackid [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::shellrun::runout}] |
||||||
|
|
||||||
|
#float above the repl's tee_to_var to deliberately block it. |
||||||
|
#a var transform is naturally a junction point because there is no flow-through.. |
||||||
|
# - but mark it with -junction 1 just to be explicit |
||||||
|
set stderr_stackid [shellfilter::stack::add stderr var -action float-locked -junction 1 -settings {-varname ::shellrun::runerr}] |
||||||
|
set stdout_stackid [shellfilter::stack::add stdout var -action float-locked -junction 1 -settings {-varname ::shellrun::runout}] |
||||||
|
} |
||||||
|
|
||||||
|
set callopts "" |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
append callopts " -tclscript 1" |
||||||
|
} |
||||||
|
#set exitinfo [shellfilter::run $cmdargs -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||||
|
set exitinfo [shellfilter::run $cmdargs {*}$callopts -teehandle punk -inbuffering none -outbuffering none] |
||||||
|
|
||||||
|
shellfilter::stack::remove stdout $stdout_stackid |
||||||
|
shellfilter::stack::remove stderr $stderr_stackid |
||||||
|
|
||||||
|
|
||||||
|
flush stderr |
||||||
|
flush stdout |
||||||
|
|
||||||
|
if {[dict exists $exitinfo error]} { |
||||||
|
if {"-tcl" in $runopts} { |
||||||
|
|
||||||
|
} else { |
||||||
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||||
|
error [dict get $exitinfo error] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#set x [shellfilter::stack::add stdout var -action sink-locked -settings {-varname ::repl::runxoutput}] |
||||||
|
|
||||||
|
set chunk "" |
||||||
|
if {[string length $::shellrun::runout]} { |
||||||
|
if {$nonewline} { |
||||||
|
set o [string trimright $::shellrun::runout \r\n] |
||||||
|
} else { |
||||||
|
set o $::shellrun::runout |
||||||
|
} |
||||||
|
set chunk $o |
||||||
|
} |
||||||
|
set chunklist [list] |
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
lappend chunklist [list "result" stdout] ;#key 'stdout' forms part of the resulting dictionary output |
||||||
|
lappend chunklist [list "info" "[a+ white bold]stdout[a]"] |
||||||
|
lappend chunklist [list result $chunk] ;#value corresponding to 'stdout' key in resulting dict |
||||||
|
|
||||||
|
|
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
set chunk "[a+ red bold]stderr[a]" |
||||||
|
lappend chunklist [list "result" $chunk] |
||||||
|
lappend chunklist [list "info" stderr] |
||||||
|
|
||||||
|
set chunk "" |
||||||
|
if {[string length $::shellrun::runerr]} { |
||||||
|
if {$nonewline} { |
||||||
|
set e [string trimright $::shellrun::runerr \r\n] |
||||||
|
} else { |
||||||
|
set e $::shellrun::runerr |
||||||
|
} |
||||||
|
set chunk $e |
||||||
|
} |
||||||
|
#stderr is part of the result |
||||||
|
lappend chunklist [list "resulterr" $chunk] |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set n [a] |
||||||
|
set c "" |
||||||
|
if {[dict exists $exitinfo exitcode]} { |
||||||
|
set code [dict get $exitinfo exitcode] |
||||||
|
if {$code == 0} { |
||||||
|
set c [a+ green] |
||||||
|
} else { |
||||||
|
set c [a+ yellow bold] |
||||||
|
} |
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
lappend chunklist [list "result" exitcode] |
||||||
|
lappend chunklist [list "info" "exitcode $code"] |
||||||
|
lappend chunklist [list "result" "$c$code$n"] |
||||||
|
set exitdict [list exitcode $code] |
||||||
|
} elseif {[dict exists $exitinfo result]} { |
||||||
|
# presumably from a -tcl call |
||||||
|
set val [dict get $exitinfo result] |
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
lappend chunklist [list "result" result] |
||||||
|
lappend chunklist [list "info" result] |
||||||
|
lappend chunklist [list "result" $val] |
||||||
|
set exitdict [list result $val] |
||||||
|
} elseif {[dict exists $exitinfo error]} { |
||||||
|
# -tcl call with error |
||||||
|
#set exitdict [dict create] |
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
lappend chunklist [list "result" error] |
||||||
|
lappend chunklist [list "info" error] |
||||||
|
lappend chunklist [list "result" [dict get $exitinfo error]] |
||||||
|
|
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
lappend chunklist [list "result" errorCode] |
||||||
|
lappend chunklist [list "info" errorCode] |
||||||
|
lappend chunklist [list "result" [dict get $exitinfo errorCode]] |
||||||
|
|
||||||
|
lappend chunklist [list "info" " "] |
||||||
|
lappend chunklist [list "result" errorInfo] |
||||||
|
lappend chunklist [list "info" errorInfo] |
||||||
|
lappend chunklist [list "result" [dict get $exitinfo errorInfo]] |
||||||
|
|
||||||
|
set exitdict $exitinfo |
||||||
|
} else { |
||||||
|
#review - if no exitcode or result. then what is it? |
||||||
|
lappend chunklist [list "info" exitinfo] |
||||||
|
set c [a+ yellow bold] |
||||||
|
lappend chunklist [list result "$c$exitinfo$n"] |
||||||
|
set exitdict [list exitinfo $exitinfo] |
||||||
|
} |
||||||
|
|
||||||
|
set_last_run_display $chunklist |
||||||
|
|
||||||
|
#set ::repl::result_print 0 |
||||||
|
#return [lindex [list [list stdout $::runout stderr $::runerr {*}$exitinfo] [shellfilter::stack::remove stdout $x][puts -nonewline stdout $pretty][set ::repl::output ""]] 0] |
||||||
|
|
||||||
|
|
||||||
|
if {$nonewline} { |
||||||
|
return [list {*}$exitdict stdout [string trimright $::shellrun::runout \r\n] stderr [string trimright $::shellrun::runerr \r\n]] |
||||||
|
} |
||||||
|
#always return exitinfo $code at beginning of dict (so that punk unknown can interpret the exit code as a unix-style bool if double evaluated) |
||||||
|
return [list {*}$exitdict stdout $::shellrun::runout stderr $::shellrun::runerr] |
||||||
|
} |
||||||
|
|
||||||
|
#an experiment |
||||||
|
# |
||||||
|
#run as raw string instead of tcl-list - no variable subst etc |
||||||
|
# |
||||||
|
#dummy repl_runraw that repl will intercept |
||||||
|
proc repl_runraw {args} { |
||||||
|
error "runraw: only available in repl as direct call - not from script" |
||||||
|
} |
||||||
|
#we can only call runraw with a single (presumably braced) string if we want to use it from both repl and tcl scripts (why? todo with unbalanced quotes/braces?) |
||||||
|
proc runraw {commandline} { |
||||||
|
#runraw fails as intended - because we can't bypass exec/open interference quoting :/ |
||||||
|
set_last_run_display [list] |
||||||
|
variable runout |
||||||
|
variable runerr |
||||||
|
set runout "" |
||||||
|
set runerr "" |
||||||
|
|
||||||
|
#return [shellfilter::run [lrange $args 1 end] -teehandle punk -inbuffering none -outbuffering none -stdinhandler ::repl::repl_handler] |
||||||
|
puts stdout ">>runraw got: $commandline" |
||||||
|
|
||||||
|
#run always echoes anyway.. as we aren't diverting stdout/stderr off for capturing |
||||||
|
#for consistency with other runxxx commands - we'll just consume it. (review) |
||||||
|
|
||||||
|
set reallyraw 1 |
||||||
|
if {$reallyraw} { |
||||||
|
set wordparts [regexp -inline -all {\S+} $commandline] |
||||||
|
set runwords $wordparts |
||||||
|
} else { |
||||||
|
#shell style args parsing not suitable for windows where we can't assume matched quotes etc. |
||||||
|
package require string::token::shell |
||||||
|
set parts [string token shell -indices -- $commandline] |
||||||
|
puts stdout ">>shellparts: $parts" |
||||||
|
set runwords [list] |
||||||
|
foreach p $parts { |
||||||
|
set ptype [lindex $p 0] |
||||||
|
set pval [lindex $p 3] |
||||||
|
if {$ptype eq "PLAIN"} { |
||||||
|
lappend runwords [lindex $p 3] |
||||||
|
} elseif {$ptype eq "D:QUOTED"} { |
||||||
|
set v {"} |
||||||
|
append v $pval |
||||||
|
append v {"} |
||||||
|
lappend runwords $v |
||||||
|
} elseif {$ptype eq "S:QUOTED"} { |
||||||
|
set v {'} |
||||||
|
append v $pval |
||||||
|
append v {'} |
||||||
|
lappend runwords $v |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
puts stdout ">>runraw runwords: $runwords" |
||||||
|
set runwords [lrange $runwords 1 end] |
||||||
|
|
||||||
|
puts stdout ">>runraw runwords: $runwords" |
||||||
|
#set args [lrange $args 1 end] |
||||||
|
#set runwords [lrange $wordparts 1 end] |
||||||
|
|
||||||
|
set known_runopts [list "-echo" "-e" "-terminal" "-t"] |
||||||
|
set aliases [list "-e" "-echo" "-echo" "-echo" "-t" "-terminal" "-terminal" "-terminal"] ;#include map to self |
||||||
|
set runopts [list] |
||||||
|
set cmdwords [list] |
||||||
|
set idx_first_cmdarg [lsearch -not $runwords "-*"] |
||||||
|
set runopts [lrange $runwords 0 $idx_first_cmdarg-1] |
||||||
|
set cmdwords [lrange $runwords $idx_first_cmdarg end] |
||||||
|
|
||||||
|
foreach o $runopts { |
||||||
|
if {$o ni $known_runopts} { |
||||||
|
error "runraw: Unknown runoption $o" |
||||||
|
} |
||||||
|
} |
||||||
|
set runopts [lmap o $runopts {dict get $aliases $o}] |
||||||
|
|
||||||
|
set cmd_as_string [join $cmdwords " "] |
||||||
|
puts stdout ">>cmd_as_string: $cmd_as_string" |
||||||
|
|
||||||
|
if {"-terminal" in $runopts} { |
||||||
|
#fake terminal using 'script' command. |
||||||
|
#not ideal: smushes stdout & stderr together amongst other problems |
||||||
|
set tcmd [shellfilter::get_scriptrun_from_cmdlist_dquote_if_not $cmdwords] |
||||||
|
puts stdout ">>tcmd: $tcmd" |
||||||
|
set exitinfo [shellfilter::run $tcmd -teehandle punk -inbuffering line -outbuffering none ] |
||||||
|
set exitinfo "exitcode not-implemented" |
||||||
|
} else { |
||||||
|
set exitinfo [shellfilter::run $cmdwords -teehandle punk -inbuffering line -outbuffering none ] |
||||||
|
} |
||||||
|
|
||||||
|
if {[dict exists $exitinfo error]} { |
||||||
|
#todo - check errorInfo makes sense.. return -code? tailcall? |
||||||
|
error [dict get $exitinfo error] |
||||||
|
} |
||||||
|
set code [dict get $exitinfo exitcode] |
||||||
|
if {$code == 0} { |
||||||
|
set c [a+ green] |
||||||
|
} else { |
||||||
|
set c [a+ white bold] |
||||||
|
} |
||||||
|
puts stderr $c |
||||||
|
return $exitinfo |
||||||
|
} |
||||||
|
|
||||||
|
proc sh_run {args} { |
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
#e.g sh -c "ls -l *" |
||||||
|
#we pass cmdargs to sh -c as a list, not individually |
||||||
|
tailcall shellrun::run {*}$runopts sh -c $cmdargs |
||||||
|
} |
||||||
|
proc sh_runout {args} { |
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
tailcall shellrun::runout {*}$runopts sh -c $cmdargs |
||||||
|
} |
||||||
|
proc sh_runerr {args} { |
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
tailcall shellrun::runerr {*}$runopts sh -c $cmdargs |
||||||
|
} |
||||||
|
proc sh_runx {args} { |
||||||
|
set splitargs [get_run_opts $args] |
||||||
|
set runopts [dict get $splitargs runopts] |
||||||
|
set cmdargs [dict get $splitargs cmdargs] |
||||||
|
tailcall shellrun::runx {*}$runopts sh -c $cmdargs |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval shellrun { |
||||||
|
interp alias {} run {} shellrun::run |
||||||
|
interp alias {} sh_run {} shellrun::sh_run |
||||||
|
interp alias {} runout {} shellrun::runout |
||||||
|
interp alias {} sh_runout {} shellrun::sh_runout |
||||||
|
interp alias {} runerr {} shellrun::runerr |
||||||
|
interp alias {} sh_runerr {} shellrun::sh_runerr |
||||||
|
interp alias {} runx {} shellrun::runx |
||||||
|
interp alias {} sh_runx {} shellrun::sh_runx |
||||||
|
|
||||||
|
interp alias {} runraw {} shellrun::runraw |
||||||
|
|
||||||
|
|
||||||
|
#the shortened versions deliberately don't get pretty output from the repl |
||||||
|
interp alias {} r {} shellrun::run |
||||||
|
interp alias {} ro {} shellrun::runout |
||||||
|
interp alias {} re {} shellrun::runerr |
||||||
|
interp alias {} rx {} shellrun::runx |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
namespace eval shellrun { |
||||||
|
proc test_cffi {} { |
||||||
|
package require test_cffi |
||||||
|
cffi::Wrapper create ::shellrun::kernel32 [file join $env(windir) system32 Kernel32.dll] |
||||||
|
::shellrun::kernel32 stdcall CreateProcessA |
||||||
|
#todo - stuff. |
||||||
|
return ::shellrun::kernel32 |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
package provide shellrun [namespace eval shellrun { |
||||||
|
variable version |
||||||
|
set version 0.1 |
||||||
|
}] |
@ -0,0 +1,698 @@ |
|||||||
|
#package require logger |
||||||
|
|
||||||
|
package provide shellthread [namespace eval shellthread { |
||||||
|
variable version |
||||||
|
set version 1.6 |
||||||
|
}] |
||||||
|
|
||||||
|
|
||||||
|
package require Thread |
||||||
|
|
||||||
|
namespace eval shellthread { |
||||||
|
|
||||||
|
proc iso8601 {{tsmicros ""}} { |
||||||
|
if {$tsmicros eq ""} { |
||||||
|
set tsmicros [clock micros] |
||||||
|
} else { |
||||||
|
set microsnow [clock micros] |
||||||
|
if {[string length $tsmicros] != [string length $microsnow]} { |
||||||
|
error "iso8601 requires 'clock micros' or empty string to create timestamp" |
||||||
|
} |
||||||
|
} |
||||||
|
set seconds [expr {$tsmicros / 1000000}] |
||||||
|
return [clock format $seconds -format "%Y-%m-%d_%H-%M-%S"] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace eval shellthread::worker { |
||||||
|
variable settings |
||||||
|
variable sysloghost_port |
||||||
|
variable sock |
||||||
|
variable logfile "" |
||||||
|
variable fd |
||||||
|
variable client_ids [list] |
||||||
|
variable ts_start_micros |
||||||
|
variable errorlist [list] |
||||||
|
variable inpipe "" |
||||||
|
|
||||||
|
proc bgerror {args} { |
||||||
|
variable errorlist |
||||||
|
lappend errorlist $args |
||||||
|
} |
||||||
|
proc send_errors_now {tidcli} { |
||||||
|
variable errorlist |
||||||
|
thread::send -async $tidcli [list shellthread::manager::report_worker_errors [list worker_tid [thread::id] errors $errorlist]] |
||||||
|
} |
||||||
|
proc add_client_tid {tidcli} { |
||||||
|
variable client_ids |
||||||
|
if {$tidcli ni $client_ids} { |
||||||
|
lappend client_ids $tidcli |
||||||
|
} |
||||||
|
} |
||||||
|
proc init {tidclient start_m settingsdict} { |
||||||
|
variable sysloghost_port |
||||||
|
variable logfile |
||||||
|
variable settings |
||||||
|
interp bgerror {} shellthread::worker::bgerror |
||||||
|
package require overtype |
||||||
|
variable client_ids |
||||||
|
variable ts_start_micros |
||||||
|
lappend client_ids $tidclient |
||||||
|
set ts_start_micros $start_m |
||||||
|
|
||||||
|
set defaults [list -raw 0 -file "" -syslog "" -direction out] |
||||||
|
set settings [dict merge $defaults $settingsdict] |
||||||
|
|
||||||
|
set syslog [dict get $settings -syslog] |
||||||
|
if {[string length $syslog]} { |
||||||
|
lassign [split $syslog :] s_host s_port |
||||||
|
set sysloghost_port [list $s_host $s_port] |
||||||
|
} else { |
||||||
|
set sysloghost_port "" |
||||||
|
} |
||||||
|
if {[catch {package require udp} errm]} { |
||||||
|
#disable rather than bomb and interfere with any -file being written |
||||||
|
set sysloghost_port "" |
||||||
|
} |
||||||
|
|
||||||
|
set logfile [dict get $settings -file] |
||||||
|
} |
||||||
|
|
||||||
|
proc start_pipe_read {source readchan args} { |
||||||
|
#assume 1 inpipe for now |
||||||
|
variable inpipe |
||||||
|
variable sysloghost_port |
||||||
|
variable logfile |
||||||
|
set defaults [dict create -buffering \uFFFF ] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
if {[dict exists $opts -readbuffering]} { |
||||||
|
set readbuffering [dict get $opts -readbuffering] |
||||||
|
} else { |
||||||
|
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||||
|
#get buffering setting from the channel as it was set prior to thread::transfer |
||||||
|
set readbuffering [chan configure $readchan -buffering] |
||||||
|
} else { |
||||||
|
set readbuffering [dict get $opts -buffering] |
||||||
|
chan configure $readchan -buffering $readbuffering |
||||||
|
} |
||||||
|
} |
||||||
|
if {[dict exists $opts -writebuffering]} { |
||||||
|
set writebuffering [dict get $opts -writebuffering] |
||||||
|
} else { |
||||||
|
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||||
|
set writebuffering line |
||||||
|
#set writebuffering [chan configure $writechan -buffering] |
||||||
|
} else { |
||||||
|
set writebuffering [dict get $opts -buffering] |
||||||
|
#can configure $writechan -buffering $writebuffering |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
chan configure $readchan -translation lf |
||||||
|
|
||||||
|
if {$readchan ni [chan names]} { |
||||||
|
error "shellthread::worker::start_pipe_read - inpipe not configured. Use shellthread::manager::set_pipe_read_from_client to thread::transfer the pipe end" |
||||||
|
} |
||||||
|
set inpipe $readchan |
||||||
|
#::shellthread::worker::log $inpipe 0 - $source - info "START PIPE READ HELLO\n" line |
||||||
|
chan configure $readchan -blocking 0 |
||||||
|
#::shellthread::worker::log $inpipe 0 - $source - info "START PIPE READ HELLO2 readbuffering: $readbuffering syslog $sysloghost_port filename $logfile" line |
||||||
|
|
||||||
|
set waitvar ::shellthread::worker::wait($inpipe,[clock micros]) |
||||||
|
chan event $readchan readable [list apply {{chan source waitfor readbuffering writebuffering} { |
||||||
|
if {$readbuffering eq "line"} { |
||||||
|
set chunksize [chan gets $chan chunk] |
||||||
|
if {$chunksize >= 0} { |
||||||
|
if {![chan eof $chan]} { |
||||||
|
::shellthread::worker::log pipe 0 - $source - info $chunk\n $writebuffering |
||||||
|
} else { |
||||||
|
::shellthread::worker::log pipe 0 - $source - info $chunk $writebuffering |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
set chunk [chan read $chan] |
||||||
|
::shellthread::worker::log pipe 0 - $source - info $chunk $writebuffering |
||||||
|
} |
||||||
|
if {[chan eof $chan]} { |
||||||
|
chan event $chan readable {} |
||||||
|
set $waitfor "pipe" |
||||||
|
chan close $chan |
||||||
|
} |
||||||
|
}} $readchan $source $waitvar $readbuffering $writebuffering] |
||||||
|
#::shellthread::worker::log $inpipe 0 - $source - info "START PIPE READ HELLO3 vwaiting on $waitvar\n" line |
||||||
|
vwait $waitvar |
||||||
|
} |
||||||
|
|
||||||
|
proc start_pipe_write {source writechan args} { |
||||||
|
variable outpipe |
||||||
|
set defaults [dict create -buffering \uFFFF ] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
|
||||||
|
#todo! |
||||||
|
set readchan stdin |
||||||
|
|
||||||
|
if {[dict exists $opts -readbuffering]} { |
||||||
|
set readbuffering [dict get $opts -readbuffering] |
||||||
|
} else { |
||||||
|
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||||
|
set readbuffering [chan configure $readchan -buffering] |
||||||
|
} else { |
||||||
|
set readbuffering [dict get $opts -buffering] |
||||||
|
chan configure $readchan -buffering $readbuffering |
||||||
|
} |
||||||
|
} |
||||||
|
if {[dict exists $opts -writebuffering]} { |
||||||
|
set writebuffering [dict get $opts -writebuffering] |
||||||
|
} else { |
||||||
|
if {[dict get $opts -buffering] eq "\uFFFF"} { |
||||||
|
#nothing explicitly set - take from transferred channel |
||||||
|
set writebuffering [chan configure $writechan -buffering] |
||||||
|
} else { |
||||||
|
set writebuffering [dict get $opts -buffering] |
||||||
|
can configure $writechan -buffering $writebuffering |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {$writechan ni [chan names]} { |
||||||
|
error "shellthread::worker::start_pipe_write - outpipe not configured. Use shellthread::manager::set_pipe_write_to_client to thread::transfer the pipe end" |
||||||
|
} |
||||||
|
set outpipe $writechan |
||||||
|
chan configure $readchan -blocking 0 |
||||||
|
chan configure $writechan -blocking 0 |
||||||
|
set waitvar ::shellthread::worker::wait($outpipe,[clock micros]) |
||||||
|
|
||||||
|
chan event $readchan readable [list apply {{chan writechan source waitfor readbuffering} { |
||||||
|
if {$readbuffering eq "line"} { |
||||||
|
set chunksize [chan gets $chan chunk] |
||||||
|
if {$chunksize >= 0} { |
||||||
|
if {![chan eof $chan]} { |
||||||
|
puts $writechan $chunk |
||||||
|
} else { |
||||||
|
puts -nonewline $writechan $chunk |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
set chunk [chan read $chan] |
||||||
|
puts -nonewline $writechan $chunk |
||||||
|
} |
||||||
|
if {[chan eof $chan]} { |
||||||
|
chan event $chan readable {} |
||||||
|
set $waitfor "pipe" |
||||||
|
chan close $writechan |
||||||
|
if {$chan ne "stdin"} { |
||||||
|
chan close $chan |
||||||
|
} |
||||||
|
} |
||||||
|
}} $readchan $writechan $source $waitvar $readbuffering] |
||||||
|
|
||||||
|
vwait $waitvar |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
proc _initsock {} { |
||||||
|
variable sysloghost_port |
||||||
|
variable sock |
||||||
|
if {[string length $sysloghost_port]} { |
||||||
|
if {[catch {fconfigure $sock} state]} { |
||||||
|
set sock [udp_open] |
||||||
|
fconfigure $sock -buffering none -translation binary |
||||||
|
fconfigure $sock -remote $sysloghost_port |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
proc _reconnect {} { |
||||||
|
variable sock |
||||||
|
catch {close $sock} |
||||||
|
_initsock |
||||||
|
return [fconfigure $sock] |
||||||
|
} |
||||||
|
|
||||||
|
proc send_info {client_tid ts_sent source msg} { |
||||||
|
set ts_received [clock micros] |
||||||
|
set lag_micros [expr {$ts_received - $ts_sent}] |
||||||
|
set lag [expr {$lag_micros / 1000000.0}] ;#lag as x.xxxxxx seconds |
||||||
|
|
||||||
|
log $client_tid $ts_sent $lag $source - info $msg line 1 |
||||||
|
} |
||||||
|
proc log {client_tid ts_sent lag source service level msg writebuffering {islog 0}} { |
||||||
|
variable sock |
||||||
|
variable fd |
||||||
|
variable sysloghost_port |
||||||
|
variable logfile |
||||||
|
variable settings |
||||||
|
|
||||||
|
set logchunk $msg |
||||||
|
|
||||||
|
if {![dict get $settings -raw]} { |
||||||
|
set tail_crlf 0 |
||||||
|
set tail_lf 0 |
||||||
|
set tail_cr 0 |
||||||
|
#for cooked - always remove the trailing newline before splitting.. |
||||||
|
# |
||||||
|
#note that if we got our data from reading a non-line-buffered binary channel - then this naive line splitting will not split neatly for mixed line-endings. |
||||||
|
# |
||||||
|
#Possibly not critical as cooked is for logging and we are still preserving all \r and \n chars - but review and consider implementing a better split |
||||||
|
#but add it back exactly as it was afterwards |
||||||
|
#we can always split on \n - and any adjacent \r will be preserved in the rejoin |
||||||
|
set lastchar [string range $logchunk end end] |
||||||
|
if {[string range $logchunk end-1 end] eq "\r\n"} { |
||||||
|
set tail_crlf 1 |
||||||
|
set logchunk [string range $logchunk 0 end-2] |
||||||
|
} else { |
||||||
|
if {$lastchar eq "\n"} { |
||||||
|
set tail_lf 1 |
||||||
|
set logchunk [string range $logchunk 0 end-1] |
||||||
|
} elseif {$lastchar eq "\r"} { |
||||||
|
#\r line-endings are obsolete..and unlikely... and ugly as they can hide characters on the console. but we'll pass through anyway. |
||||||
|
set tail_cr 1 |
||||||
|
set logchunk [string range $logchunk 0 end-1] |
||||||
|
} else { |
||||||
|
#possibly a single line with no linefeed.. or has linefeeds only in the middle |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {$ts_sent != 0} { |
||||||
|
set micros [lindex [split [expr {$ts_sent / 1000000.0}] .] end] |
||||||
|
set time_info [::shellthread::iso8601 $ts_sent].$micros |
||||||
|
#set time_info "${time_info}+$lag" |
||||||
|
set lagfp "+[format %f $lag]" |
||||||
|
} else { |
||||||
|
#from pipe - no ts_sent/lag info available |
||||||
|
set time_info "" |
||||||
|
set lagfp "" |
||||||
|
} |
||||||
|
|
||||||
|
set idtail [string range $client_tid end-8 end] ;#enough for display purposes id - mostly zeros anyway |
||||||
|
set col0 [string repeat " " 9] |
||||||
|
set col1 [string repeat " " 27] |
||||||
|
set col2 [string repeat " " 11] |
||||||
|
set col3 [string repeat " " 20] |
||||||
|
#do not columnize the final data column or append to tail - or we could muck up the crlf integrity |
||||||
|
|
||||||
|
lassign [list [overtype::left $col0 $idtail] [overtype::left $col1 $time_info] [overtype::left $col2 $lagfp] [overtype::left $col3 $source]] c0 c1 c2 c3 |
||||||
|
|
||||||
|
#split on \n no matter the actual line-ending in use |
||||||
|
#shouldn't matter as long as we don't add anything at the end of the line other than the raw data |
||||||
|
#ie - don't quote or add spaces |
||||||
|
set lines [split $logchunk \n] |
||||||
|
|
||||||
|
set i 1 |
||||||
|
set outlines [list] |
||||||
|
foreach ln $lines { |
||||||
|
if {$i == 1} { |
||||||
|
lappend outlines "$c0 $c1 $c2 $c3 $ln" |
||||||
|
} else { |
||||||
|
lappend outlines "$c0 $c1 $col2 $c3 $ln" |
||||||
|
} |
||||||
|
incr i |
||||||
|
} |
||||||
|
if {$tail_lf} { |
||||||
|
set logchunk "[join $outlines \n]\n" |
||||||
|
} elseif {$tail_crlf} { |
||||||
|
set logchunk "[join $outlines \r\n]\r\n" |
||||||
|
} elseif {$tail_cr} { |
||||||
|
set logchunk "[join $outlines \r]\r" |
||||||
|
} else { |
||||||
|
#no trailing linefeed |
||||||
|
set logchunk [join $outlines \n] |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
#set logchunk "[overtype::left $col0 $idtail] [overtype::left $col1 $time_info] [overtype::left $col2 "+$lagfp"] [overtype::left $col3 $source] $msg" |
||||||
|
} |
||||||
|
|
||||||
|
if {[string length $sysloghost_port]} { |
||||||
|
_initsock |
||||||
|
catch {puts -nonewline $sock $logchunk} |
||||||
|
} |
||||||
|
#todo - sockets etc? |
||||||
|
if {[string length $logfile]} { |
||||||
|
#todo - setting to maintain open filehandle and reduce io. |
||||||
|
# possible settings for buffersize - and maybe logrotation, although this could be left to client |
||||||
|
#for now - default to safe option of open/close each write despite the overhead. |
||||||
|
set fd [open $logfile a] |
||||||
|
chan configure $fd -translation auto -buffering $writebuffering |
||||||
|
#whether line buffered or not - by now our logchunk includes newlines |
||||||
|
puts -nonewline $fd $logchunk |
||||||
|
close $fd |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
# - withdraw just this client |
||||||
|
proc finish {tidclient} { |
||||||
|
variable client_ids |
||||||
|
if {($tidclient in $clientids) && ([llength $clientids] == 1)} { |
||||||
|
terminate $tidclient |
||||||
|
} else { |
||||||
|
set posn [lsearch $client_ids $tidclient] |
||||||
|
set client_ids [lreplace $clientids $posn $posn] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#allow any client to terminate |
||||||
|
proc terminate {tidclient} { |
||||||
|
variable sock |
||||||
|
variable client_ids |
||||||
|
if {$tidclient in $client_ids} { |
||||||
|
catch {close $sock} |
||||||
|
set client_ids [list] |
||||||
|
return 1 |
||||||
|
} else { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
namespace eval shellthread::manager { |
||||||
|
variable workers [dict create] |
||||||
|
variable worker_errors [list] |
||||||
|
|
||||||
|
variable free_threads [list] |
||||||
|
#variable log_threads |
||||||
|
|
||||||
|
#new datastructure regarding workers and sourcetags required. |
||||||
|
#one worker can service multiple sourcetags - but each sourcetag may be used by multiple threads too. |
||||||
|
#generally each thread will use a specific sourcetag - but we may have pools doing similar things which log to same destination. |
||||||
|
# |
||||||
|
#As a convention we may use a sourcetag for the thread which started the worker that isn't actually used for logging - but as a common target for joins |
||||||
|
#If the thread which started the thread calls leave_worker with that 'primary' sourcetag it means others won't be able to use that target - which seems reasonable. |
||||||
|
#If another thread want's to maintain joinability beyond the span provided by the starting client, |
||||||
|
#it can join with both the primary tag and a tag it will actually use for logging. |
||||||
|
#A thread can join the logger with any existingtag - not just the 'primary' |
||||||
|
#(which is arbitrary anyway. It will usually be the first in the list - but may be unsubscribed by clients and disappear) |
||||||
|
proc join_worker {existingtag sourcetaglist} { |
||||||
|
set client_tid [thread::id] |
||||||
|
#todo - allow a source to piggyback on existing worker by referencing one of the sourcetags already using the worker |
||||||
|
} |
||||||
|
#it is up to caller to use a unique sourcetag (e.g by prefixing with own thread::id etc) |
||||||
|
# This allows multiple threads to more easily write to the same named sourcetag if necessary |
||||||
|
# todo - change sourcetag for a list of tags which will be handled by the same thread. e.g for multiple threads logging to same file |
||||||
|
# |
||||||
|
# todo - some protection mechanism for case where target is a file to stop creation of multiple worker threads writing to same file. |
||||||
|
# Even if we use open fd,close fd wrapped around writes.. it is probably undesirable to have multiple threads with same target |
||||||
|
# On the other hand socket targets such as UDP can happily be written to by multiple threads. |
||||||
|
# For now the mechanism is that a call to new_worker (rename to open_worker?) will join the same thread if a sourcetag matches.. |
||||||
|
# but, as sourcetags can get removed(unsubbed via leave_worker) this doesn't guarantee two threads with same -file settings won't fight. |
||||||
|
# Also.. the settingsdict is ignored when joining with a tag that exists.. this is problematic.. e.g logrotation where previous file still being written by existing worker |
||||||
|
# todo - rename 'sourcetag' concept to 'targettag' ?? the concept is a mixture of both.. it is somewhat analagous to a syslog 'facility' |
||||||
|
# probably new_worker should disallow auto-joining and we allow different workers to handle same tags simultaneously to support overlap during logrotation etc. |
||||||
|
proc new_worker {sourcetaglist {settingsdict {}}} { |
||||||
|
variable workers |
||||||
|
set ts_start [clock micros] |
||||||
|
set tidclient [thread::id] |
||||||
|
set sourcetag [lindex $sourcetaglist 0] ;#todo - use all |
||||||
|
|
||||||
|
if {[dict exists $workers $sourcetag]} { |
||||||
|
set winfo [dict get $workers $sourcetag] |
||||||
|
if {[thread::exists [dict get $winfo tid]]} { |
||||||
|
#add our client-info to existing worker thread |
||||||
|
dict lappend winfo list_client_tids $tidclient |
||||||
|
dict set workers $sourcetag $winfo ;#writeback |
||||||
|
return [dict get $winfo tid] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#check if there is an existing unsubscribed thread first |
||||||
|
variable free_threads |
||||||
|
if {[llength $free_threads]} { |
||||||
|
#todo - re-use from tail - as most likely to have been doing similar work?? review |
||||||
|
|
||||||
|
set free_threads [lassign $free_threads tidworker] |
||||||
|
#todo - keep track of real ts_start of free threads... kill when too old |
||||||
|
set winfo [dict create tid $tidworker list_client_tids [list $tidclient] ts_start $ts_start ts_end_list [list]] |
||||||
|
puts stderr "shellfilter::new_worker Re-using free worker thread: $tidworker with tag $sourcetag" |
||||||
|
dict set workers $sourcetag $winfo |
||||||
|
return $tidworker |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#set ts_start [::shellthread::iso8601] |
||||||
|
set tidworker [thread::create -preserved] |
||||||
|
set init_script [string map [list %ts_start% $ts_start %mp% [tcl::tm::list] %ap% $::auto_path %tidcli% $tidclient %sd% $settingsdict] { |
||||||
|
#set tclbase [file dirname [file dirname [info nameofexecutable]]] |
||||||
|
#set tcllib $tclbase/lib |
||||||
|
#if {$tcllib ni $::auto_path} { |
||||||
|
# lappend ::auto_path $tcllib |
||||||
|
#} |
||||||
|
|
||||||
|
set ::settingsinfo [dict create %sd%] |
||||||
|
#if the executable running things is something like a tclkit, |
||||||
|
# then it's likely we will need to use the caller's auto_path and tcl::tm::list to find things |
||||||
|
#The caller can tune the thread's package search by providing a settingsdict |
||||||
|
if {![dict exists $::settingsinfo tcl_tm_list]} { |
||||||
|
::tcl::tm::add %mp% |
||||||
|
} else { |
||||||
|
tcl::tm::remove {*}[tcl::tm::list] |
||||||
|
::tcl::tm::add {*}[dict get $::settingsinfo tcl_tm_list] |
||||||
|
} |
||||||
|
if {![dict exists $::settingsinfo auto_path]} { |
||||||
|
set ::auto_path [list %ap%] |
||||||
|
} else { |
||||||
|
set ::auto_path [dict get $::settingsinfo auto_path] |
||||||
|
} |
||||||
|
|
||||||
|
package require Thread |
||||||
|
package require shellthread |
||||||
|
if {![catch {::shellthread::worker::init %tidcli% %ts_start% $::settingsinfo} errmsg]} { |
||||||
|
unset ::settingsinfo |
||||||
|
set ::shellthread_init "ok" |
||||||
|
} else { |
||||||
|
unset ::settingsinfo |
||||||
|
set ::shellthread_init "err $errmsg" |
||||||
|
} |
||||||
|
}] |
||||||
|
|
||||||
|
thread::send -async $tidworker $init_script |
||||||
|
#thread::send $tidworker $init_script |
||||||
|
set winfo [dict create tid $tidworker list_client_tids [list $tidclient] ts_start $ts_start ts_end_list [list]] |
||||||
|
dict set workers $sourcetag $winfo |
||||||
|
return $tidworker |
||||||
|
} |
||||||
|
|
||||||
|
proc set_pipe_read_from_client {tag_pipename worker_tid rchan args} { |
||||||
|
variable workers |
||||||
|
if {![dict exists $workers $tag_pipename]} { |
||||||
|
error "workerthread::manager::set_pipe_read_from_client source/pipename $tag_pipename not found" |
||||||
|
} |
||||||
|
set match_worker_tid [dict get $workers $tag_pipename tid] |
||||||
|
if {$worker_tid ne $match_worker_tid} { |
||||||
|
error "workerthread::manager::set_pipe_read_from_client source/pipename $tag_pipename workert_tid mismatch '$worker_tid' vs existing:'$match_worker_tid'" |
||||||
|
} |
||||||
|
#buffering set during channel creation will be preserved on thread::transfer |
||||||
|
thread::transfer $worker_tid $rchan |
||||||
|
#start_pipe_read will vwait - so we have to send async |
||||||
|
thread::send -async $worker_tid [list ::shellthread::worker::start_pipe_read $tag_pipename $rchan] |
||||||
|
#client may start writing immediately - but presumably it will buffer in fifo2 |
||||||
|
} |
||||||
|
|
||||||
|
proc set_pipe_write_to_client {tag_pipename worker_tid wchan args} { |
||||||
|
variable workers |
||||||
|
if {![dict exists $workers $tag_pipename]} { |
||||||
|
error "workerthread::manager::set_pipe_write_to_client pipename $tag_pipename not found" |
||||||
|
} |
||||||
|
set match_worker_tid [dict get $workers $tag_pipename tid] |
||||||
|
if {$worker_tid ne $match_worker_tid} { |
||||||
|
error "workerthread::manager::set_pipe_write_to_client pipename $tag_pipename workert_tid mismatch '$worker_tid' vs existing:'$match_worker_tid'" |
||||||
|
} |
||||||
|
#buffering set during channel creation will be preserved on thread::transfer |
||||||
|
thread::transfer $worker_tid $wchan |
||||||
|
thread::send -async $worker_tid [list ::shellthread::worker::start_pipe_write $tag_pipename $wchan] |
||||||
|
} |
||||||
|
|
||||||
|
proc write_log {source msg args} { |
||||||
|
variable workers |
||||||
|
set ts_micros_sent [clock micros] |
||||||
|
set defaults [list -async 1 -level info] |
||||||
|
set opts [dict merge $defaults $args] |
||||||
|
|
||||||
|
if {[dict exists $workers $source]} { |
||||||
|
set tidworker [dict get $workers $source tid] |
||||||
|
if {![thread::exists $tidworker]} { |
||||||
|
set tidworker [new_worker $source] |
||||||
|
} |
||||||
|
} else { |
||||||
|
#auto create with no requirement to call new_worker.. warn? |
||||||
|
set tidworker [new_worker $source] |
||||||
|
} |
||||||
|
set client_tid [thread::id] |
||||||
|
if {[dict get $opts -async]} { |
||||||
|
thread::send -async $tidworker [list ::shellthread::worker::send_info $client_tid $ts_micros_sent $source $msg] |
||||||
|
} else { |
||||||
|
thread::send $tidworker [list ::shellthread::worker::send_info $client_tid $ts_micros_sent $source $msg] |
||||||
|
} |
||||||
|
} |
||||||
|
proc report_worker_errors {errdict} { |
||||||
|
variable workers |
||||||
|
set reporting_tid [dict get $errdict worker_tid] |
||||||
|
dict for {src srcinfo} $workers { |
||||||
|
if {[dict get $srcinfo tid] eq $reporting_tid} { |
||||||
|
dict set srcinfo errors [dict get $errdict errors] |
||||||
|
dict set workers $src $srcinfo ;#writeback updated |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#aka leave_worker |
||||||
|
#Note that the tags may be on separate workertids, or some tags may share workertids |
||||||
|
proc unsubscribe {sourcetaglist} { |
||||||
|
variable workers |
||||||
|
#workers structure example: |
||||||
|
#[list sourcetag1 [list tid <tidworker> list_client_tids <clients>] ts_start <ts_start> ts_end_list {}] |
||||||
|
variable free_threads |
||||||
|
set mytid [thread::id] ;#caller of shellthread::manager::xxx is the client thread |
||||||
|
|
||||||
|
set subscriberless_tags [list] |
||||||
|
foreach source $sourcetaglist { |
||||||
|
if {[dict exists $workers $source]} { |
||||||
|
set list_client_tids [dict get $workers $source list_client_tids] |
||||||
|
if {[set posn [lsearch $list_client_tids $mytid]] >= 0} { |
||||||
|
set list_client_tids [lreplace $list_client_tids $posn $posn] |
||||||
|
dict set workers $source list_client_tids $list_client_tids |
||||||
|
} |
||||||
|
if {![llength $list_client_tids]} { |
||||||
|
lappend subscriberless_tags $source |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#we've removed our own tid from all the tags - possibly across multiplew workertids, and possibly leaving some workertids with no subscribers for a particular tag - or no subscribers at all. |
||||||
|
|
||||||
|
set subscriberless_workers [list] |
||||||
|
set shuttingdown_workers [list] |
||||||
|
foreach deadtag $subscriberless_tags { |
||||||
|
set workertid [dict get $workers $deadtag tid] |
||||||
|
set worker_tags [get_worker_tagstate $workertid] |
||||||
|
set subscriber_count 0 |
||||||
|
set kill_count 0 ;#number of ts_end_list entries - even one indicates thread is doomed |
||||||
|
foreach taginfo $worker_tags { |
||||||
|
incr subscriber_count [llength [dict get $taginfo list_client_tids]] |
||||||
|
incr kill_count [llength [dict get $taginfo ts_end_list]] |
||||||
|
} |
||||||
|
if {$subscriber_count == 0} { |
||||||
|
lappend subscriberless_workers $workertid |
||||||
|
} |
||||||
|
if {$kill_count > 0} { |
||||||
|
lappend shuttingdown_workers $workertid |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#if worker isn't shutting down - add it to free_threads list |
||||||
|
foreach workertid $subscriberless_workers { |
||||||
|
if {$workertid ni $shuttingdown_workers} { |
||||||
|
if {$workertid ni $free_threads} { |
||||||
|
lappend free_threads $workertid |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#todo |
||||||
|
#unsub this client_tid from the sourcetags in the sourcetaglist. if no more client_tids exist for sourcetag, remove sourcetag, |
||||||
|
#if no more sourcetags - add worker to free_threads |
||||||
|
} |
||||||
|
proc get_worker_tagstate {workertid} { |
||||||
|
variable workers |
||||||
|
set taginfo_list [list] |
||||||
|
dict for {source sourceinfo} $workers { |
||||||
|
if {[dict get $sourceinfo tid] eq $workertid} { |
||||||
|
lappend taginfo_list $sourceinfo |
||||||
|
} |
||||||
|
} |
||||||
|
return $taginfo_list |
||||||
|
} |
||||||
|
|
||||||
|
#instruction to shut-down the thread that has this source. |
||||||
|
proc close_worker {source {timeout 2500}} { |
||||||
|
variable workers |
||||||
|
variable worker_errors |
||||||
|
variable free_threads |
||||||
|
set ts_now [clock micros] |
||||||
|
#puts stderr "close_worker $source" |
||||||
|
if {[dict exists $workers $source]} { |
||||||
|
set tidworker [dict get $workers $source tid] |
||||||
|
if {$tidworker in $freethreads} { |
||||||
|
#make sure a thread that is being closed is removed from the free_threads list |
||||||
|
set posn [lsearch $freethreads $tidworker] |
||||||
|
set freethreads [lreplace $freethreads $posn $posn] |
||||||
|
} |
||||||
|
set mytid [thread::id] |
||||||
|
set client_tids [dict get $workers $source list_client_tids] |
||||||
|
if {[set posn [lsearch $client_tids $mytid]] >= 0} { |
||||||
|
set client_tids [lreplace $client_tids $posn $posn] |
||||||
|
#remove self from list of clients |
||||||
|
dict set workers $source list_client_tids $client_tids |
||||||
|
} |
||||||
|
set ts_end_list [dict get $workers $source ts_end_list] ;#ts_end_list is just a list of timestamps of closing calls for this source - only one is needed to close, but they may all come in a flurry. |
||||||
|
if {[llength $ts_end_list]} { |
||||||
|
set last_end_ts [lindex $ts_end_list end] |
||||||
|
if {[expr {(($tsnow - $last_end_ts) / 1000) >= $timeout}]} { |
||||||
|
lappend ts_end_list $ts_now |
||||||
|
dict set workers $source ts_end_list $ts_end_list |
||||||
|
} else { |
||||||
|
#existing close in progress.. assume it will work |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if {[thread::exists $tidworker]} { |
||||||
|
#puts stderr "shellthread::manager::close_worker: thread $tidworker for source $source still running.. terminating" |
||||||
|
set timeoutarr($source) 0 |
||||||
|
after $timeout [list set timeoutarr($source) 2] |
||||||
|
|
||||||
|
thread::send -async $tidworker [list shellthread::worker::send_errors_now [thread::id]] |
||||||
|
thread::send -async $tidworker [list shellthread::worker::terminate [thread::id]] timeoutarr($source) |
||||||
|
|
||||||
|
#thread::send -async $tidworker [string map [list %tidclient% [thread::id]] { |
||||||
|
# shellthread::worker::terminate %tidclient% |
||||||
|
#}] timeoutarr($source) |
||||||
|
|
||||||
|
vwait timeoutarr($source) |
||||||
|
#puts stderr "shellthread::manager::close_worker: thread $tidworker for source $source DONE1" |
||||||
|
|
||||||
|
thread::release $tidworker |
||||||
|
#puts stderr "shellthread::manager::close_worker: thread $tidworker for source $source DONE2" |
||||||
|
if {[dict exists $workers $source errors]} { |
||||||
|
set errlist [dict get $workers $source errors] |
||||||
|
if {[llength $errlist]} { |
||||||
|
lappend worker_errors [list $source [dict get $workers $source]] |
||||||
|
} |
||||||
|
} |
||||||
|
dict unset workers $source |
||||||
|
} else { |
||||||
|
#thread may have been closed by call to close_worker with another source with same worker |
||||||
|
#clear workers record for this source |
||||||
|
#REVIEW - race condition for re-creation of source with new workerid? |
||||||
|
#check that record is subscriberless to avoid this |
||||||
|
if {[llength [dict get $workers $source list_client_tids]] == 0} { |
||||||
|
dict unset workers $source |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#puts stdout "close_worker $source - end" |
||||||
|
} |
||||||
|
|
||||||
|
#worker errors only available for a source after close_worker called on that source |
||||||
|
#It is possible for there to be multiple entries for a source because new_worker can be called multiple times with same sourcetag, |
||||||
|
# e.g if a thread |
||||||
|
proc get_and_clear_errors {source} { |
||||||
|
variable worker_errors |
||||||
|
set source_errors [lsearch -all -inline -index 0 $worker_errors $source] |
||||||
|
set worker_errors [lsearch -all -inline -index 0 -not $worker_errors $source] |
||||||
|
return $source_errors |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,49 +0,0 @@ |
|||||||
# -*- tcl -*- |
|
||||||
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
|
||||||
# |
|
||||||
# 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) 2023 |
|
||||||
# |
|
||||||
# @@ Meta Begin |
|
||||||
# Application %pkg% 999999.0a1.0 |
|
||||||
# Meta platform tcl |
|
||||||
# Meta license <unspecified> |
|
||||||
# @@ Meta End |
|
||||||
|
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
foreach base [tcl::tm::list] { |
|
||||||
set nsprefix "";#in case sourced directly and not in any of the .tm paths |
|
||||||
if {[string match -nocase ${base}* [info script]]} { |
|
||||||
set nsprefix [string trimleft [join [lrange [file split [string range [info script] [string length $base]+1 end]] 0 end-1] ::]:: ::] |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
namespace eval [lassign [split [file rootname [file tail [info script] ]] -] pkgtail verparts]${nsprefix}$pkgtail { |
|
||||||
#-------------------------------------- |
|
||||||
#Do not put any 'package require' statements above this block. (globals nsprefix,pkgtail,verparts still set) |
|
||||||
variable pkg "${::nsprefix}${::pkgtail}[unset ::nsprefix; unset ::pkgtail]" |
|
||||||
variable version [join $::verparts -][unset ::verparts] |
|
||||||
#-------------------------------------- |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Requirements |
|
||||||
##e.g package require frobz |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace eval [namespace current]::lib { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
|
||||||
## Ready |
|
||||||
uplevel #0 [list package provide $pkg $version] |
|
||||||
} |
|
||||||
return |
|
||||||
|
|
@ -0,0 +1,52 @@ |
|||||||
|
# -*- tcl -*- |
||||||
|
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt |
||||||
|
# |
||||||
|
# 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) %year% |
||||||
|
# |
||||||
|
# @@ Meta Begin |
||||||
|
# Application %pkg% 999999.0a1.0 |
||||||
|
# Meta platform tcl |
||||||
|
# Meta license %license% |
||||||
|
# @@ Meta End |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Requirements |
||||||
|
##e.g package require frobz |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
namespace eval %pkg% { |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ |
||||||
|
## Ready |
||||||
|
package provide %pkg% [namespace eval %pkg% { |
||||||
|
variable pkg %pkg% |
||||||
|
variable version |
||||||
|
set version 999999.0a1.0 |
||||||
|
}] |
||||||
|
return |
Loading…
Reference in new issue