TCL: use "help" option from cmdline package - tcl

In my TCL script I am trying to provide input argument which will be parsed using 'cmdline' package. I have defined the following in my script:
set run_options {
{input_1.arg 8 "first input argument"}
{input_2.arg 1 "second input argument"}
{msb "choose it if input data has msb first"}
{lsb "choose it if input data has lsb first"}
}
set my_usage ": \n tclsh my_src \[options] ...\noptions are:"
if {[catch {array set params [::cmdline::getoptions argv $run_options $my_usage]} msg]} {
puts "Error while parsing user options of $msg"
exit 1
}
everything look fine except that when running my script I figured out that the 'cmdline' package defines "help" option by default, but when I run my script with "-help" option the following is the output:
Error while parsing user options of my_src :
tclsh my_src [options] ...
options are:
-input_1 value first input argument <8>
-input_2 value second input argument <1>
-msb choose it if input data has msb first
-lsb choose it if input data has lsb first
-- Forcibly stop option processing
-help Print this message
-? Print this message
so, does anyone know how to use this automatically added "help" option without giving error message?

cmdline::getoptions doesn't differentiate between -help and an unknown option (An unrecognized option is treated like it was -?). If you want to have different messages for the two cases, you need to use one of the the lower level functions directly in a loop, or something like the following enhanced version of cmdline::getoptions:
#!/usr/bin/env tclsh
package require try ;# In case you're still using tcl 8.5 for some reason
package require cmdline 1.5
proc better_getoptions {arglistVar optlist usage} {
upvar 1 $arglistVar argv
# Warning: Internal cmdline function
set opts [::cmdline::GetOptionDefaults $optlist result]
while {[set err [::cmdline::getopt argv $opts opt arg]]} {
if {$err < 0} {
return -code error -errorcode {CMDLINE ERROR} $arg
}
set result($opt) $arg
}
if {[info exists result(?)] || [info exists result(help)]} {
return -code error -errorcode {CMDLINE USAGE} \
[::cmdline::usage $optlist $usage]
}
return [array get result]
}
set run_options {
{input_1.arg 8 "first input argument"}
{input_2.arg 1 "second input argument"}
{msb "choose it if input data has msb first"}
{lsb "choose it if input data has lsb first"}
}
set my_usage ": \n tclsh my_src \[options] ...\noptions are:"
try {
array set params [better_getoptions argv $run_options $my_usage]
puts "All options valid"
} trap {CMDLINE USAGE} {msg} {
puts stderr $msg
exit 0
} trap {CMDLINE ERROR} {msg} {
puts stderr "Error: $msg"
exit 1
}
Example usage:
$ ./example.tcl --help
cmd :
tclsh my_src [options] ...
options are:
-input_1 value first input argument <8>
-input_2 value second input argument <1>
-msb choose it if input data has msb first
-lsb choose it if input data has lsb first
-- Forcibly stop option processing
-help Print this message
-? Print this message
$ ./example.tcl -foo
Error: Illegal option "-foo"
$ ./example.tcl -input_1
Error: Option "input_1" requires an argument
$ ./example.tcl -msb
All options valid

Related

How to check the string value of an argument passed to expect script?

I'm writing a expect script that takes command line arguments. I would like to be able to detect whether the first argument is "--help" and print a Usage string then. Otherwise use the argument as a port number with a specific default (let's say 1818).
I tried this code that fails:
#!/usr/bin/expect
if {[llength $argv] != 1} {
puts "No Port number specified, defaulting to port 1818."
set port 1818
} else {
if {[lindex $argv 0] eq "--help"} {
puts "Usage: testit [--help] [port]"
exit
} else {
set port [lindex $argv 0]
}
}
The error is:
invalid command name "--help"
while executing
"--help"
invoked from within
"if {[llength $argv] != 1} {
puts "No Port number specified, defaulting to port 1818."
set port 1818
} else {
if {[lindex $argv 0] eq "--he..."
Obviously it is trying to interpret the content of the "--help" string while I'm trying to make the script compare the value of argument 0 to "--help".
What is wrong in the above logic or syntax?
I tried using other strings, like "help" instead of "--help" but the outcome is the same.
I'm not that familiar with expect and tcl, but I tried the expression in tclsh and the same thing happens there. So this issue has to do with invalid tcl code. The following tcsh session shows that the syntax if {$variable=="--help"} {...} is OK, but removing the white space in my string comparison attempt above does not solve the problem.
Here's the tcsh session:
% set v1 "--help"
--help
% if [v1 == "--help'] { puts "allo"}
extra characters after close-quote
% if [v1 == "--help"] { puts "allo"}
invalid command name "v1"
% if [$v1 == "--help"] { puts "allo"}
invalid command name "--help"
% if $v1 == help {puts "allo"}
invalid bareword "help"
in expression "--help";
should be "$help" or "{help}" or "help(...)" or ...
% if $v1 == "--help" {puts "allo"}
invalid bareword "help"
in expression "--help";
should be "$help" or "{help}" or "help(...)" or ...
% if {$v1=="--help"} {puts "allo"}
allo
%
The problem is this line:
puts "Usage: testit [--help] [port]"
And the problem with it is that [...] does command substitution in that situation. You need to add a couple of backslashes in there to prevent that, like this:
puts "Usage: testit \[--help] \[port]"
Or you can enclose the string in braces to inhibit all substitutions:
puts {Usage: testit [--help] [port]}
Either will work (and they'll get compiled to exactly the same thing so use whichever you prefer).

Tcl - How to get the list of all possible sub-commands/switches for a given tcl command (including user defined commands)

For a given tcl user-defined command (for instance, "user_command -switch_A -switch_B") I would like to get all the possible sub-commands/switches to be used with that command as a list.
Below is an example of the tcl regexp switches available:
Regexp valid switches
I would like to use it to compare "implemented and public sub-commands/switches VS documented and public sub-commands/switches" for a given program.
I can get the available commands from "info commands" command, but I'm struggling to get possible valid sub-commands/switches for a given command.
The normal way is to use a deliberately wrong option name (such as -!) and to catch and parse the result.
proc listOptions args {
try {
{*}$args -!
} on error msg {
if {[regexp {should be one of (.*)} $msg -> items]} {
return [string map {{, or } { } , {}} $items]
}
}
error "no option list from $args"
}
puts [listOptions chan configure stdout]
To find subcommands for commands that are implemented as namespace ensembles, you can do:
set cmd "string"
set map [namespace ensemble configure $cmd -map]
dict keys $map
# => bytelength cat compare equal first index is last length map match range repeat replace reverse tolower toupper totitle trim trimleft trimright wordend wordstart

Tcl:Is there a way to show which line in tcl throw an error?

When I source a tcl file, error will be report when run to a unknown command.
Tcl> source run.tcl
$inside tcl file$$> setup_desgin
Design setup...
Done
$inside tcl file$$> my_prove
Info: proving started
Info: ....
Info: ....
$inside tcl file$$> ::unknown my_pro
invalid command name "my_pro"
Is there a way to show the line number of the error line in tcl file as below?
Tcl> source run.tcl
$inside tcl file$$> setup_desgin
Design setup...
Done
$inside tcl file$$> my_prove
Info: proving started
Info: ....
Info: ....
$inside tcl file$$> ::unknown my_pro
invalid command name "my_pro" (run.tcl:5)
We want this because we may have a very big run.tcl with minions of line.
It's actually quite difficult to get this information in general. The accurate line number information is only available at present when the code is on the execution stack. Fortunately, you can find out what's going on with a leave-step trace (intercepting unknown would only work for unknown commands; tracing lets all errors be caught). In the code below, I've put it on eval, but in a more practical example you'd put it on source or something like that.
set errorlineSet 0
proc LEAVESTEP {cmd code result op args} {
global errorline errorlineSet
if {$code == 1} {
if {!$errorlineSet} {
set errorline [dict get [info frame -4] line]
}
set errorlineSet 1
} else {
set errorlineSet 0
}
}
try {
trace add execution eval leavestep LEAVESTEP
eval {
sdfgsldfjg; # This is line 17
}
} on error {msg opt} {
puts "ERROR: $msg (line: $errorline) (local line: [dict get $opt -errorline])"
} finally {
trace remove execution eval leavestep LEAVESTEP
}
When I save all that to a file and run it, it prints this:
ERROR: invalid command name "sdfgsldfjg" (line: 17) (local line: 3)
As you can see, there is also the -errorline key in the result option dictionary, but that's mapped to 3 in this case and that's rather misleading! It uses that value because of backward compatibility, but I'm not convinced that it is all that helpful.
You can redefine the unknown command and use the info frame command in it to get the location. Something like:
# Save the default unknown if you want
rename ::unknown ::original_unknown
proc ::unknown {name args} {
set caller [info frame -1]
dict with caller {
switch $type {
source {
puts stderr "invalid command name \"$name\" (in file $file:$line)"
}
proc {
if {[info exists lambda]} {
puts stderr "invalid command name \"$name\" (in lambda $lambda:$line)"
} else {
puts stderr "invalid command name \"$name\" (in proc $proc:$line)"
}
}
eval {
puts stderr "invalid command name \"$name\" (in eval {$cmd}:$line)"
}
precompiled {
puts stderr "Invalid command name \"$name\""
}
}
}
}
Example usage:
% source my_unknown.tcl
% source bad.tcl
invalid command name "put" (in file /home/shawn/src/bad.tcl:3)

Why is ::cmdline::getoptions throwing an error?

Why does the following code:
#!/usr/bin/env tclsh
package require cmdline;
set options {{d.arg "" "destination directory"}}
set usage ": $::argv0 \[options] filename ...\noptions:"
set params [::cmdline::getoptions ::argv $options $usage]
throw the following error upon execution of ./main.tcl -help?
main : ./main.tcl [options] filename ...
options:
-d value destination directory <>
-help Print this message
-? Print this message
while executing
"error [usage $optlist $usage]"
(procedure "::cmdline::getoptions" line 15)
invoked from within
"::cmdline::getoptions ::argv $options $usage"
invoked from within
"set params [::cmdline::getoptions ::argv $options $usage]"
(file "./main.tcl" line 8)
It should display the usage information, but I didn't expect the error afterwards. Did I do something wrong?
From what I understand from the docs (emphasis mine):
The options -?, -help, and -- are implicitly understood. The first two abort option processing by throwing an error and force the generation of the usage message, whereas the the last aborts option processing without an error, leaving all arguments coming after for regular processing, even if starting with a dash.
using -help or -? will always throw an error.
Further down in the docs you can see an example where try { ... } trap { ... } is being used in conjunction with ::cmdline::getoptions, which might be how you might want to do it:
try {
array set params [::cmdline::getoptions ::argv $options $usage]
} trap {CMDLINE USAGE} {msg o} {
# Trap the usage signal, print the message, and exit the application.
# Note: Other errors are not caught and passed through to higher levels!
puts $msg
exit 1
}

Why can't I access errorInfo and errorCode

I have the following code:
$ cat ~/tmp/2.tcl
set zero 0
proc p1 {} {
if {[catch {expr 1/$zero} err]} {
puts "errorCode=$errorCode"
puts "errorInfo=$errorInfo"
}
}
p1
When I source it, I get error accessing errorCode:
$ tclsh ~/tmp/2.tcl
can't read "errorCode": no such variable
while executing
"puts "errorCode=$errorCode""
(procedure "p1" line 3)
invoked from within
"p1"
(file "~/tmp/2.tcl" line 9)
I tried changing to $::errorCode, but did not help.
Can you see what is wrong?
The errorInfo and errorCode variables are globals. You should either use the global command to bring them into scope or use their fully-qualified names (i.e., precede with ::).
It might be easier to pick the information out of the result options dictionary (a new feature in 8.5).
Starting from Tcl 8.5 [catch] doesn't set the errorCode and errorInfo global variables. (As Donal has pointed out, it still does, so they can be accessed as $::errorCode and $::errorInfo). And in addition it puts their values into a dictionary which name is to be specified as the third argument. The following code
#!/usr/bin/tclsh
set zero 0
proc p1 {} {
if {[catch {expr 1/$zero} err opts] == 1} {
puts "errorCode=[dict get $opts -errorcode]"
puts "errorInfo=[dict get $opts -errorinfo]"
}
}
p1
prints
errorCode=NONE
errorInfo=can't read "zero": no such variable
while executing
"expr 1/$zero"
in Tcl 8.5.19, and
errorCode=TCL READ VARNAME
errorInfo=can't read "zero": no such variable
while executing
"expr 1/$zero"
in Tcl8.6.6.
You'd probably want to use $::zero in the division after which the result would be
errorCode=ARITH DIVZERO {divide by zero}
errorInfo=divide by zero
while executing
"expr 1/$::zero"