how to write a tcl procedure with default values and options? - tcl

I am trying to write a tcl procedure which does the following -
proc myProc {arg1 def1} {arg2 def2} {
...
...
}
tcl> myProc -arg1 val1 -arg2 val2
arg1 variable has val1
arg2 variable has val2
tcl> myProc -arg1 val1
arg1 variable has val1
arg2 variable has def2
tcl> myProc -?
myProc -arg1 <value1> -arg2 <value2>
arg1 - required argument [default value is 10]
arg2 - optional argument [default value is 20]
help - print this message
? - print this message
Is this possible in tcl?
I looked up some of the questions that have been asked and what I see is this question. This does partially what I require but I couldn't find anything that would help me solve my problem. Please guide me!

Read the proc man page carefully: the list of arguments has to be a single list. You were thinking about this:
% proc myproc {{a 42} {b 54}} { puts "a=$a b=$b" }
% myproc
a=42 b=54
% myproc 1
a=1 b=54
% myproc 1 2
a=1 b=2
Note that the first argument is assigned to a -- you cannot provide a value for b and use the default value for a with this method.
To use command-line-like options, the simplest way is this:
% proc myproc {args} {
array set data [list -a 42 -b 54 {*}$args]
puts "a=$data(-a) b=$data(-b)"
}
% myproc
a=42 b=54
% myproc -a 1
a=1 b=54
% myproc -b 2
a=42 b=2
% myproc -b 2 -a 3
a=3 b=2
% myproc -c 4
a=42 b=54
One problem with this method is that you must pass an even number of arguments, or array set will throw an error:
% myproc 12
list must have an even number of elements

You can use Tcllib module cmdline. On wiki you can find example how to use this module with proc.
proc printdata args {
array set param [::cmdline::getoptions args {\
{page.arg 1 "current page"}
{pages.arg 1 "number of pages"}
} "printdata ?options? Data"]
if {1 != [llength $args]} {
return -code error "No data given"
}
set param(data) [lindex $args 0]
# processing here
parray param
}
% printdata -pages 2 -- "--Head data to print--"
param(data) = --Head data to print--
param(page) = 1
param(pages) = 2
% printdata -?
printdata ?options? Data
-page value current page <1>
-pages value number of pages <1>
-help Print this message
-? Print this message

I wrote a utility function for this years ago. The arguments have the same syntax as proc but it generates procs that work the way you describe. The code is simple:
proc optproc {name args script} {
proc $name args [
string map [list ARGS $args SCRIPT $script] {
foreach var {ARGS} {
set [lindex $var 0] [lindex $var 1]
}
foreach {var val} $args {
set [string trim $var -] $val
}
SCRIPT
}
]
}
What the code does above is basically call proc with two foreach loops injected to the function body to process the arguments.
With this you can declare your function like this:
optproc myProc {{arg1 def1} {arg2 def2}} {
# you can use arg1 and arg2 just like a regular proc:
puts $arg1
puts $arg2
}
Which you can then use the way you wanted:
myProc ;# default values for arg1 and arg2
myProc -arg1 foo ;# default values for arg2
myProc -arg2 bar ;# default values for arg1
myProc -arg1 foo -arg2 bar
For more info and discussion on this you can read the wiki page: http://wiki.tcl.tk/20066
For even more flexible proc argument processing you can use a while loop: http://wiki.tcl.tk/16032

Thank you all for your valuable inputs. I have written the following code which helps me achieve what I want -
Code
proc argsParser args {
set args [string map { - "" } $args]
if {[expr [llength $args] % 2] != 0 } {
puts "Wrong Arguments :: $args "
return "For Help :: argsParser -?";
}
switch $args {
h -
help -
? { puts "Usage :: argParser -arg1 val1 -arg2 val2"
puts "arg1 :: Number"
puts "arg2 :: Number"
}
default {
set arg1 10 ;
set arg2 20 ;
set args [string map { - "" } $args]
for {set i 0} {$i < [llength $args]} { incr i 2} {
set a [lindex $args $i]
if { $a != "arg1" && $a != "arg2" } {
return "Unknown Args :: $a - For Help :: argsParser -?";
}
set b [lindex $args [expr $i+1]]
set $a $b
}
puts "arg1 - $arg1"
puts "arg2 - $arg2"
}
}
}
Result
tcl> argsParser -arg1 20 -arg4 40
Unknown Args :: arg4 - For Help :: argsParser -?
tcl> argsParser -arg1 20 -arg2 40
arg1 - 20
arg2 - 40

Related

What is the proper way to create hyphen parameters in TCL that are Boolean or have values? [duplicate]

I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.

Is it possible to pass argument by name in TCL procedure?

I have the following procedure:
proc test {a {b 10} {c 30}} {
puts "$a $b $c"
}
I would like to call the test procedure by passing value to argument a and c and keep the default value of the argument b. In other words, I want to pass arguments value by name.
Is it possible to do it in TCL?
No.
The usual method to do this is:
proc test { args } {
# set up the defaults
set a(-a) {}
set a(-b) 10
set a(-c) 30
set len [llength $args]
# some basic argument checking
if { $len > 6 || $len < 2 || $len % 2 != 0 } {
error "Invalid arguments"
}
array set a $args
...
}
set result [test -a 3 -c 40]

How to create Tcl Procedure with options?

I want to create tcl procedures with some options. I know procedures with arguments and optional arguments, but don't know options. For example, if I am calling my procedure arith in following three ways (-add for addition, -sub for subtraction):
1) arith 10 5
2) arith -add 10 5 or arith 10 5 -add
3) arith -sub 10 5 or arith 10 5 -sub
Respectively output should be 1) 15 (by default it should add), 2) 15, 3) 5
How to write this procedure in Tcl? I am new to tcl, please suggest me some online material or book for Tcl.
Complex argument parsing can be done with the cmdline package, which is part of Tcllib. The key command is ::cmdline::getoptions, which extracts the options from a variable and returns a dictionary describing them. It also modifies the variable so it contains just the arguments left over.
package require cmdline
proc arith args {
set options {
{op.arg "add" "operation to apply (defaults to 'add')"}
}
array set parsed [::cmdline::getoptions args $options]
if {[llength $args] != 2} {
return -code error "wrong # args: must be \"arith ?-op operation? x y\""
}
switch $parsed(op) {
add {return [::tcl::mathop::+ {*}$args]}
sub {return [::tcl::mathop::- {*}$args]}
default {
return -code error "Unknown -op \"$parsed(op)\": must be add or sub"
}
}
}
Demonstrating usage (including some error cases):
% arith
wrong # args: must be "arith ?-op operation? x y"
% arith 2 3
5
% arith -op sub 2 3
-1
% arith -op mult 2 3
Unknown -op "mult": must be add or sub
The main thing to be aware of is that the options descriptor takes the names of options without a leading - and with .arg on the end if you want to have an argument passed as well.
When it comes to options, it's a good idea to use even number of arguments
-op add -values {10 5}
-op sub -values {10 5}
With this, you can put the arguments into array as,
array set aArgs $args
where args is nothing but arguments passed to procedure.
proc arith {args} {
if {[catch {array set aArgs $args} err]} {
puts "Error : $err"
return 0
}
if {![info exists aArgs(-op)] || ![info exists aArgs(-values)] || [llength $aArgs(-values)]!=2} {
puts "Please pass valid args"
return 0
}
set x [lindex $aArgs(-values) 0]
set y [lindex $aArgs(-values) 1]
switch $aArgs(-op) {
"add" {
puts [expr {$x+$y}]
}
"sub" {
puts [expr {$x-$y}]
}
}
}
arith -op add -values {10 5}
In this example... I'm assuming that the operator is either the first or last option. The value would be -add or add. Just make the changes.
There is an optimization in the computation... to use lrange and ::mathop::.
proc arith args {
if {[llength $args] != 3} {
return -code error "wrong # args: must be \"arith ?-op operation? x y\""
}
set isadd [lsearch $args add]
set issub [lsearch $args sub]
if {$isadd != -1 && $issub == -1} {
return [expr [lindex $args 1] + [lindex $args [expr $isadd == 0 ? 2: 0]]]
}
if {$issub != -1 && $isadd == -1} {
return [expr [lindex $args [expr $issub == 0 ? 1: 0]] - [lindex $args [expr $issub == 0 ? 2: 1]]]
}
return -code error "Unknown -op must be add or sub"
}
example:
#arith add 1 2 3
#arith add sub 2
puts [arith add 1 2]
puts [arith 1 2 add]
puts [arith sub 1 2]
puts [arith 1 2 sub]
The two error examples were commented out because I need a catch instead... but it really depends on the big picture and how reusable it's intended to be.
proc arith args {
if {[llength $args] != 3} {
return -code error "wrong # args: must be \"arith ?-op operation? x y\""
}
set isadd [lsearch $args -add]
set issub [lsearch $args -sub]
if {$isadd != -1 && $issub == -1} {
return [::tcl::mathop::+ {*}[lrange $args [expr $isadd == 0] [expr ($isadd == 0) + 1]]]
}
if {$issub != -1 && $isadd == -1} {
return [::tcl::mathop::- {*}[lrange $args [expr $issub == 0] [expr ($issub == 0) + 1]]]
}
return -code error "Unknown -op must be add or sub"
}

how to create tcl proc with hyphen flag arguments

Im searching all over the internet , i guess im searching not the right keywords
i tried most of them :)
i want to create in tcl/bash a proc with hyphen flags to get arguments with flags from the user
ex.
proc_name -color red -somethingselse black
It's very easy to do, actually. This code allows abbreviated option names, flag options (-quxwoo in the example) and the ability to stop reading options either with a -- token or with a non-option argument appearing. In the example, unknown option names raise errors. After passing the option-parsing loop, args contains the remaining command-line arguments (not including the -- token if it was used).
proc foo args {
array set options {-bargle {} -bazout vampires -quxwoo 0}
while {[llength $args]} {
switch -glob -- [lindex $args 0] {
-bar* {set args [lassign $args - options(-bargle)]}
-baz* {set args [lassign $args - options(-bazout)]}
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
-- {set args [lrange $args 1 end] ; break}
-* {error "unknown option [lindex $args 0]"}
default break
}
}
puts "options: [array get options]"
puts "other args: $args"
}
foo -barg 94 -quxwoo -- abc def
# => options: -quxwoo 1 -bazout vampires -bargle 94
# => other args: abc def
This is how it works. First set default values for the options:
array set options {-bargle {} -bazout vampires -quxwoo 0}
Then enter a loop that processes the arguments, if there are any (left).
while {[llength $args]} {
During each iteration, look at the first element in the argument list:
switch -glob -- [lindex $args 0] {
String-match ("glob") matching is used to make it possible to have abbreviated option names.
If a value option is found, use lassign to copy the value to the corresponding member of the options array and to remove the first two elements in the argument list.
-bar* {set args [lassign $args - options(-bargle)]}
If a flag option is found, set the corresponding member of the options array to 1 and remove the first element in the argument list.
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
If the special -- token is found, remove it from the argument list and exit the option-processing loop.
-- {set args [lrange $args 1 end] ; break}
If an option name is found that hasn't already been dealt with, raise an error.
-* {error "unknown option [lindex $args 0]"}
If the first argument doesn't match any of the above, we seem to have run out of option arguments: just exit the loop.
default break
Documentation: array, break, error, lassign, lindex, llength, proc, puts, set, switch, while
With array set, we can assign the parameters and their values into an array.
proc getInfo {args} {
# Assigning key-value pair into array
# If odd number of arguments passed, then it should throw error
if {[catch {array set aInfo $args} msg]} {
return $msg
}
parray aInfo; # Just printing for your info
}
puts [getInfo -name Dinesh -age 25 -id 974155]
will produce the following output
aInfo(-age) = 25
aInfo(-id) = 974155
aInfo(-name) = Dinesh
The usual way to handle this in Tcl is by slurping the values into an array or dictionary and then picking them out of that. It doesn't offer the greatest amount of error checking, but it's so easy to get working.
proc myExample args {
# Set the defaults
array set options {-foo 0 -bar "xyz"}
# Read in the arguments
array set options $args
# Use them
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
Doing error checking takes more effort. Here's a simple version
proc myExample args {
array set options {-foo 0 -bar "xyz"}
if {[llength $args] & 1} {
return -code error "must have even number of arguments in opt/val pairs"
}
foreach {opt val} $args {
if {![info exist options($opt)]} {
return -code error "unknown option \"$opt\""
}
set options($opt) $val
}
# As before...
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
# And here are the errors it spits out...
myExample -spregr sgkjfd
# unknown option "-spregr"
myExample -foo
# must have even number of arguments in opt/val pairs
#flag defaults
set level 1
set inst ""
# Parse Flags
while {[llength $args]} {
set flag [lindex $args 0]
#puts "flag: ($flag)"
switch -glob $flag {
-level {
set level [lindex $args 1]
set args [lrange $args 2 end]
puts "level:($level) args($args)"
} -inst {
set autoname 0
set inst [lindex $args 1]
set args [lrange $args 2 end]
puts "inst:($inst) args($args)"
} -h* {
#help
puts "USAGE:"
exit 1
} -* {
# unknown option
error "unknown option [lindex $args 0]"
} default break
}
}
# remaining arguments
set filename "$args"
puts "filename: $args"

How can I safely deal with optional parameters

I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.