blob: 364282b78e840a3418aaf59cc0537598564a4cf6 [file] [log] [blame]
Michael Walsh08a66762018-02-15 17:14:02 -06001#!/usr/bin/wish
2
3# This file provides many valuable parm and argument processing procedures
4# such as longoptions, pos_parms, gen_get_options, etc.
5
6my_source [list escape.tcl data_proc.tcl print.tcl]
7
8
9proc get_arg_req { opt_name } {
10
11 # Determine whether the given opt_name is "optional", "required" or
12 # "not_allowed" and return that result.
13
14 # Note: This procedure assumes that global list longoptions has been
15 # initialized via a call to the longoptions procedure.
16
17 # Description of argument(s):
18 # opt_name The name of the option including its
19 # requirement indicator as accepted by the
20 # bash getopt longoptions parameter: No
21 # colon means the option takes no argument,
22 # one colon means the option requires an
23 # argument and two colons indicate that an
24 # argument is optional (the value of the
25 # option will be 1 if no argument is
26 # specified.
27
28 global longoptions
29
30 if { [lsearch -exact $longoptions "${opt_name}::"] != -1 } {
31 return optional
32 }
33 if { [lsearch -exact $longoptions "${opt_name}:"] != -1 } {
34 return required
35 }
36 return not_allowed
37
38}
39
40
41proc longoptions { args } {
42
43 # Populate the global longoptions list and set global option variable
44 # defaults.
45
46 # Description of argument(s):
47 # args Each arg is comprised of 1) the name of
48 # the option 2) zero, one or 2 colons to
49 # indicate whether the corresponding
50 # argument value is a) not required, b)
51 # required or c) optional 3) Optionally, an
52 # equal sign followed by a default value for
53 # the parameter.
54
55 # Example usage:
56 # longoptions parm1 parm2: parm3:: test_mode:=0 quiet:=0
57
58 global longoptions
59
60 set debug 0
61 foreach arg $args {
62 # Create an option record which is a 2-element list consisting of the
63 # option specification and a possible default value. Example:;
64 # opt_rec:
65 # opt_rec[0]: test_mode:
66 # opt_rec[1]: 0
67 set opt_rec [split $arg =]
68 # opt_spec will include any colons that may have been specified.
69 set opt_spec [lindex $opt_rec 0]
70 # Add the option spec to the global longoptions list.
71 lappend_unique longoptions $opt_spec
72 # Strip the colons to get the option name.
73 set opt_name [string trimright $opt_spec ":"]
74 # Get the option's default value, if any.
75 set opt_default_value [lindex $opt_rec 1]
76 set arg_req [get_arg_req $opt_name]
77 if { $arg_req == "not_allowed" && $opt_default_value == "" } {
78 # If this parm takes no arg and no default was specified by the user,
79 # we will set the default to 0.
80 set opt_default_value 0
81 }
82 # Set a global variable whose name is identical to the option name. Set
83 # the default value if there is one.
84 set cmd_buf "global ${opt_name} ; set ${opt_name} {${opt_default_value}}"
85 dpissuing
86 eval $cmd_buf
87 }
88
89}
90
91
92proc pos_parms { args } {
93
94 # Populate the global pos_parms list and set global option variable defaults.
95
96 # Description of argument(s):
97 # args Each arg is comprised of the name of a
98 # positional parm and a possible initial
99 # value.
100
101 # Example usage:
102 # pos_parms user_name=mike
103
104 global pos_parms
105
106 set pos_parms [list]
107 set debug 0
108 foreach arg $args {
109 dprint_var arg
110 # Create an option record which is a 2-element list consisting of the
111 # option specification and a possible default value. Example:;
112 # opt_rec:
113 # opt_rec[0]: test_mode:
114 # opt_rec[1]: 0
115 set parm_rec [split $arg =]
116 dprint_list parm_rec
117 # parm_spec will include any colons that may have been specified.
118 set parm_name [lindex $parm_rec 0]
119 dprint_var parm_name
120 # Add the option spec to the global pos_parms list.
121 lappend pos_parms $parm_name
122 # Get the option's default value, if any.
123 set parm_default_value [lindex $parm_rec 1]
124 dprint_var parm_default_value
125 # Set a global variable whose name is identical to the option name. Set
126 # the default value if there is one.
127 set cmd_buf "global ${parm_name} ; set ${parm_name}"
128 append cmd_buf " {${parm_default_value}}"
129 dpissuing
130 eval $cmd_buf
131 }
132
133}
134
135
136proc gen_get_options { argv } {
137
138 # Get the command line options/arguments and use them to set the
139 # corresponding global option variable names.
140
141 # Note: This procedure assumes that global list longoptions has been
142 # initialized via a call to the longoptions procedure and that global
143 # pos_parms has been initialized via a call to the pos_parms procdure.
144 # These data structures indicates what options and arguments are supported
145 # by the calling program.
146
147 # Note: If the last var_name in pos_parms ends in "_list", then the caller
148 # can specify as many parms as they desire and they will all be appended to
149 # the variable in question.
150
151 # Description of argument(s):
152 # argv The argv array that is set for this
153 # program.
154
155 # Example call:
156 # gen_get_options $argv
157
158 global longoptions
159 global pos_parms
160 global program_name
161
162 set debug 0
163
164 set len_pos_parms [llength $pos_parms]
165
166 dprint_list longoptions
167 dprint_list pos_parms
168 dprint_var len_pos_parms
169
170 # Rather than write the algorithm from scratch, we will call upon the bash
171 # getopt program to help us. This program has several advantages:
172 # - It will reject illegal options
173 # - It supports different posix input styles (e.g. -option <arg> vs
174 # --option=<arg>).
175 # - It allows the program's caller to abbreviate option names provided that
176 # there is no ambiguity.
177
178 # Convert curly braces to single quotes. This includes escaping existing
179 # quotes in the argv string. This will allow us to use the result in a bash
180 # command string. Example: {--parm3=Kathy's cat} will become
181 # '--parm3=Kathy'\''s cat'.
182 dprint_var argv
183 set bash_args [curly_braces_to_quotes $argv]
184 set cmd_buf "getopt --name=${program_name} -a --longoptions=\"help"
185 append cmd_buf " ${longoptions}\" --options=\"-h\" -- ${bash_args}"
186 dpissuing
187 if { [ catch {set OPT_LIST [eval exec bash -c {$cmd_buf}]} result ] } {
188 puts stderr $result
189 exit 1
190 }
191
192 set OPT_LIST [quotes_to_curly_braces $OPT_LIST]
193 set cmd_buf "set opt_list \[list $OPT_LIST\]"
194 dpissuing
195 eval $cmd_buf
196
197 dprint_list opt_list
198
199 set longopt_regex {\-[-]?[^- ]+}
200 global help
201 global h
202 set help 0
203 set h 0
204 dprintn ; dprint_timen "Processing opt_list."
205 set pos_parm_ix 0
206 set current_longopt {}
207 foreach opt_list_entry $opt_list {
208 dprint_var opt_list_entry
209 if { $opt_list_entry == "--" } { break; }
210 if { $current_longopt != "" } {
211 dprint_var current_longopt
212 set cmd_buf "global ${current_longopt} ; set ${current_longopt}"
213 append cmd_buf " {${opt_list_entry}}"
214 dpissuing
215 eval $cmd_buf
216 set current_longopt {}
217 dprintn
218 continue
219 }
220 set is_option [regexp -expanded $longopt_regex ${opt_list_entry}]
221 dprint_var is_option
222 if { $is_option } {
223 regsub -all {^\-[-]?} $opt_list_entry {} opt_name
224 dprint_var opt_name
225 set arg_req [get_arg_req $opt_name]
226 dprint_var arg_req
227 if { $arg_req == "not_allowed" } {
228 set cmd_buf "global ${opt_name} ; set ${opt_name} 1"
229 dpissuing
230 eval $cmd_buf
231 } else {
232 set current_longopt [string trimleft $opt_list_entry "-"]
233 }
234 } else {
235 # Must be a positional parm.
236 if { $pos_parm_ix >= $len_pos_parms } {
237 set is_list [regexp -expanded "_list$" ${pos_parm_name}]
238 dprint_var is_list
239 if { $is_list } {
240 set cmd_buf "lappend ${pos_parm_name} {${opt_list_entry}}"
241 dpissuing
242 eval $cmd_buf
243 continue
244 }
245 append message "The caller has specified more positional parms than"
246 append message " are allowed by the program.\n"
247 append message [sprint_varx parm_value ${opt_list_entry} 2]
248 append message [sprint_list pos_parms 2]
249 print_error_report $message
250 exit 1
251 }
252 set pos_parm_name [lindex $pos_parms $pos_parm_ix]
253 set cmd_buf "global ${pos_parm_name} ; set ${pos_parm_name}"
254 append cmd_buf " {${opt_list_entry}}"
255 dpissuing
256 eval $cmd_buf
257 incr pos_parm_ix
258 }
259 dprintn
260 }
261
262 if { $h || $help } {
263 if { [info proc help] != "" } {
264 help
265 } else {
266 puts "No help text defined for this program."
267 }
268 exit 0
269 }
270
271}
272
273
274proc print_usage {} {
275
276 # Print usage help text line.
277
278 # Example:
279 # usage: demo.tcl [OPTIONS] [USERID] [FILE_LIST]
280
281 global program_name
282 global longoptions
283 global pos_parms
284
285 append buffer "usage: $program_name"
286
287 if { $longoptions != "" } {
288 append buffer " \[OPTIONS\]"
289 }
290
291 foreach parm $pos_parms {
292 set upper_parm [string toupper $parm]
293 append buffer " \[$upper_parm\]"
294 }
295
296 puts $buffer
297
298}
299
300
301proc print_option_help { option help_text { data_desc {} } { print_default {}}\
302 { width 30 } } {
303
304 # Print help text for the given option.
305
306 # Description of argument(s):
307 # option The option for which help text should be
308 # printed. This value should include a
309 # leading "--" to indicate that this is an
310 # optional rather than a positional parm.
311 # data_desc A description of the data (e.g. "dir
312 # path", "1,0", etc.)0
313 # print_default Indicates whether the current value of the
314 # global variable representing the option is
315 # to be printed as a default value. For
316 # example, if the option value is "--parm1",
317 # global value parm1 is "no" and
318 # print_default is set, the following phrase
319 # will be appended to the help text: The
320 # default value is "no".
321 # width The width of the arguments column.
322
323 set indent 2
324
325 # Get the actual opt_name by stripping leading dashes and trailing colons.
326 regsub -all {^\-[-]?} $option {} opt_name
327 regsub -all {:[:]?$} $opt_name {} opt_name
328
329 # Set defaults for args to this procedure.
330 set longopt_regex {\-[-]?[^- ]+}
331 set is_option [regexp -expanded $longopt_regex ${option}]
332 if { $is_option } {
333 # It is an option (vs positional parm).
334 # Does it take an argument?
335 set arg_req [get_arg_req $opt_name]
336 if { $arg_req == "not_allowed" } {
337 set data_desc_default ""
338 } else {
339 set data_desc_default "{$opt_name}"
340 }
341 } else {
342 # It's a positional parm.
343 set opt_name [string tolower $opt_name]
344 set data_desc_default ""
345 }
346
347 set_var_default data_desc $data_desc_default
348 set_var_default print_default 1
349
350 if { $print_default } {
351 # Access the global variable that represents the value of the option.
352 eval global $opt_name
353 set cmd_buf "set opt_value \${${opt_name}}"
354 eval $cmd_buf
355 set default_string " The default value is \"${opt_value}\"."
356 } else {
357 set default_string ""
358 }
359
360 if { $data_desc != "" } {
361 # Remove any curly braces and put them back on.
362 set data_desc "{[string trim $data_desc {{}}]}"
363 }
364
365 print_arg_desc "$option $data_desc" "${help_text}${default_string}" 2 $width
366
367}
368
369
370# Create help text variables for stock parms like quiet, debug and test_mode.
371set test_mode_help_text "This means that ${program_name} should go through"
372append test_mode_help_text " all the motions but not actually do anything"
373append test_mode_help_text " substantial. This is mainly to be used by the"
374append test_mode_help_text " developer of ${program_name}."
375set quiet_help_text "If this parameter is set to \"1\", ${program_name} will"
376append quiet_help_text " print only essential information, i.e. it will not"
377append quiet_help_text " echo parameters, echo commands, print the total run"
378append quiet_help_text " time, etc."
379set debug_help_text "If this parameter is set to \"1\", ${program_name} will"
380append debug_help_text " print additional debug information. This is mainly to"
381append debug_help_text " be used by the developer of ${program_name}."
382
383proc gen_print_help { { width 30 } } {
384
385 # Print general help text based on user's pos_parms and longoptions.
386
387 # Note: To use this procedure, the user must create a global help_dict
388 # containing entries for each of their options and one for the program as a
389 # whole. The keys of this dictionary are the option names and the values
390 # are lists whose values map to arguments from the print_option_help
391 # procedure:
392 # - help_text
393 # - data_desc (optional)
394 # - print_default (1 or 0 - default is 1)
395
396 # Example:
397 # set help_dict [dict create\
398 # ${program_name} [list "${program_name} will demonstrate..."]\
399 # userid [list "The userid of the caller."]\
400 # file_list [list "A list of files to be processed."]\
401 # flag [list "A flag to indicate that..."]\
402 # dir_path [list "The path to the directory containing the files."]\
403 # release [list "The code release."]\
404 # ]
405
406 global program_name
407 global longoptions
408 global pos_parms
409
410 global help_dict
411 global test_mode_help_text
412 global quiet_help_text
413 global debug_help_text
414
415 # Add help text for stock options to global help_dict.
416 dict set help_dict test_mode [list $test_mode_help_text "1,0"]
417 dict set help_dict quiet [list $quiet_help_text "1,0"]
418 dict set help_dict debug [list $debug_help_text "1,0"]
419
420 puts ""
421 print_usage
422
423 # Retrieve the general program help text from the help_dict and print it.
424 set help_entry [dict get $help_dict ${program_name}]
425 puts ""
426 puts [lindex $help_entry 0]
427
428 if { $pos_parms != "" } {
429 puts ""
430 puts "positional arguments:"
431 foreach option $pos_parms {
432 # Retrieve the print_option_help parm values from the help_dict and
433 # call print_option_help.
434 set help_entry [dict get $help_dict ${option}]
435 set help_text [lindex $help_entry 0]
436 set data_desc [lindex $help_entry 1]
437 set print_default [lindex $help_entry 2]
438 print_option_help [string toupper $option] $help_text $data_desc\
439 $print_default $width
440 }
441 }
442
443 if { $longoptions != "" } {
444 puts ""
445 puts "optional arguments:"
446 foreach option $longoptions {
447 set option [string trim $option ":"]
448 # Retrieve the print_option_help parm values from the help_dict and
449 # call print_option_help.
450 set help_entry [dict get $help_dict ${option}]
451 set help_text [lindex $help_entry 0]
452 set data_desc [lindex $help_entry 1]
453 set print_default [lindex $help_entry 2]
454 print_option_help "--${option}" $help_text $data_desc $print_default\
455 $width
456 }
457 }
458 puts ""
459
460}
461
462
463proc return_program_options {} {
464
465 # Return all the names of the global program options as a composite list.
466
467 global longoptions pos_parms
468
469 regsub -all {:} $longoptions {} program_options
470 eval lappend program_options $pos_parms
471
472 return $program_options
473
474}
475
476
477proc global_program_options {} {
478
479 # Make all program option global variables available to the calling function.
480 set program_options [return_program_options]
481 uplevel eval global $program_options
482
483}
484
485
486proc gen_pre_validation {} {
487
488 # Do generic post-validation processing. By "post", we mean that this is
489 # to be called from a validation function after the caller has done any
490 # validation desired. If the calling program passes exit_function and
491 # signal_handler parms, this function will register them. In other words,
492 # it will make the signal_handler functions get called for SIGINT and
493 # SIGTERM and will make the exit_function function run prior to the
494 # termination of the program.
495
496 # Make all program option global variables available to the calling function.
497 uplevel global_program_options
498
499}
500
501
502proc gen_post_validation {} {
503
504 # Do generic post-validation processing. By "post", we mean that this is
505 # to be called from a validation function after the caller has done any
506 # validation desired. If the calling program passes exit_function and
507 # signal_handler parms, this function will register them. In other words,
508 # it will make the signal_handler functions get called for SIGINT and
509 # SIGTERM and will make the exit_function function run prior to the
510 # termination of the program.
511
512 trap { exit_proc } [list SIGTERM SIGINT]
513
514}