first version.
[build-srcpkg.git] / shlib / args.shlib
blob7fbe9d026ed8e007ebcf750349069b0959c305bb
1 #!/bin/bash
2 ############################################################
3 # source: args.shlib
4 # author: CottonCandyOwner(CottonCandyOwner@126.com)
5 # date: 2021-09-16
6 ############################################################
7 # Copyright (C) 2022- Free Software Foundation, Inc.
8 # This configure script is free software; the Free Software
9 # Foundation gives unlimited permission to copy, distribute
10 # and modify it.
11 ############################################################
12 # note:
13 # it is a shlib for processing of program arguments.
14 # there are several part of args.shlib for args processing.
15 # @ several args desc-str-line. it describe a cmdline optoin
16 # by string syntax.
17 # @ desc-str dispatching result vars. it's the env var generated
18 # in desc-str-line processing. and it's used for actual args
19 # dispatching.
20 # @ action function. it is included in desc-str-line for the
21 # option. when this option apeard in args, this function will
22 # be invoked to implement corresponding feature. if a option
23 # bring with a paramter arg, it would be the first paramter
24 # of action function. if the action function is not used by
25 # programer, use the env var defined in desc-str. when the
26 # value is 'enable', it means this option is used in cmdline.
27 # it's usefull for multi paramter processing.
28 # @ paramter helper. it's a part of usage string. invoking
29 # helper_gen to output the string.
30 # it's a usefull feature for developer on args processing.
31 # and the developer get benifit by using this shlib is that:
32 # @ just care about option name, and the output resualt of
33 # vars and action functions and desc-info. those info is
34 # associate by this shlib automatically.
35 # @ in normal developping, programer is writing code for feature
36 # implement, and append helper info at last. it may be cause
37 # a problem that the option implement is not syncronized to
38 # code implement. in desc-str-line, the paramter and implement
39 # action function can be seen directly by developer.
40 ############################################################
43 #include stdio.shlib
44 #include dbgout.shlib
48 # todo:
49 # @ code-trim: func name, dbg-info.
50 # @ combine param/vname/onproc into one opt.
51 # @ right boundary align.
53 # @ gen var define in c & sh
54 # @ insert dispathed var into src file.
55 # @ use imi file instead of var define in src file.
56 # @ cmdline hint append.
57 # @ option mutex.
59 # @ subcmd
62 # @ the processing of description string bundary implemented by fold.
63 # but it works only in english. make some patch for it, and let it
64 # working in utf-8 charset.
65 # @ the right side limit will truncate the words in desc string.
66 # impove with fold. english words would not be truncated.
70 # @ append blank line display desc-str-line "|blank" in desc-str.
71 # @ add string after 'blank', and displayed for option category.
72 # @ multiple desc-str.
73 # @ dispatch debug info output by --debug option, or DBGOUTD_OUTPUT=1.
74 # @ the old version only dispaly strings without blank. now it can display
75 # english string with blanks.
79 # CODE DEBUG METHOD:
80 # @ add OptDescParamPrint() after opt_desc_str_dispatch(), to judge if parameter desc
81 # string is correct.
82 # @ if there are many opts in program, divid into several desc_str for debugging.
83 # @ if helper info display incorrect, read func helper_gen().
84 # @ if opt does not work, read the func prog_opt_proc(),
88 # NOTE:
89 # @ display width auto-adapt to term_width, it can be disabled by setting
90 # fixed value in program, or add an ENVAR to disable this feature.
93 # this cmd enable alias feature in script. it is disabled in default.
94 shopt -s expand_aliases
97 ##############################
98 # section: public comment info
99 ##############################
103 ##############################
104 # section: variable define
105 ##############################
108 # example:
109 # usage_desc_str="
110 # prog $0 '<prog_desc>'
111 # param -<pchar> --<long-opt> ---<cmd-opt> =<value> %<pname> !<pproc> '<pdesc-str>'
115 # member variable of option paramter var.
116 p_prog=
117 p_progdesc=
119 # vars for getopt.
120 p_shortparam=
121 p_longparam=
122 p_cmdparam=
125 p_pcnt=
127 # variable of dispatched paramter describe string
129 # those variable is used as an array.
130 # if it is a digital indexed array, it must be the variable used to store dispatched paramter.
131 # if it is a name string indexed array, it must be the variable updated by runtime arguments.
133 p_char=
134 p_longopt=
135 p_cmd=
136 p_value=
137 p_name=
138 p_proc=
139 p_desc=
141 # array of shell script can not use string indexed array mixed with array.
142 # before declare an array, unset it first.
143 # TBD: this code just running at once, when multiple desc-str is used.
144 unset p_char2idx
145 unset p_longopt2idx
146 unset p_cmd2idx
147 # not used currently.
148 unset p_name2idx
150 declare -g -A -x p_char2idx
151 declare -g -A -x p_longopt2idx
152 declare -g -A -x p_cmd2idx
153 declare -g -A -x p_name2idx
155 export p_char2idx
156 export p_longopt2idx
157 export p_cmd2idx
158 export p_name2idx
160 declare -g -a actionlist
164 # option desciption framework for helper.
165 # -f, --file=<file-path> name paramter string. use this
166 # paramter as the operating file.
167 # |<<-1->|--------- 2 -------->|<-3->|<----- 4 ---—->|
168 # |<---------------------------- 5 ----------------------------->|
170 # 1:pname_blanks. blanks befor paramter name string.
171 pname_blanks=${pname_blanks:-1}
172 # 2:pname_width. paramter string width, and blank prefix width.
173 pname_width=30
174 # 3:desc_blanks. blanks before paramter describe string.
175 desc_blanks=1
176 # 4: calculated by 'term_width - desc_blanks - pname_width - pname_blanks'
177 # 5: term_width, getting from code.
181 # others.
183 helper=
186 ##############################
187 # section: private function
188 ##############################
190 __get_term_width ()
192 local f_ttyout=$(ps -p $$ -o tty | tail -n 1 | cut -d ' ' -f1 2>/dev/null)
194 test -n "$f_ttyout" && export term_width=$(stty -F /dev/$f_ttyout size 2>&1 | cut -d ' ' -f2 | tr -d "'")
196 echo $term_width
198 unset f_ttyout
202 # term display width
203 # get term width, to generate usage string adepted to screen size.
204 # when stdout is not a tty device, set a default value.
206 # this paramter is defined in other shlib
208 __get_tty_width ()
210 if test -z "$term_width"; then
211 term_width=$(__get_term_width)
212 if [[ ! "$term_width" =~ [^0-9] ]]; then
213 args_ttydev=$(ps -p $$ -o tty | tail -n 1 | cut -d ' ' -f1)
215 # args_ttydev=$(ps -ajx | cut -d ' ' -f2 | grep -i "`ps -ajx | grep -i "$$" | cut -d ' ' -f4 | head -n 1`" | cut -d ' ' -f5)
217 test -z "$args_ttydev" && args_ttydev=pts/0
218 args_ttydev="/dev/$args_ttydev"
219 export term_width=$(stty -F $args_ttydev size 2>&1 | cut -d ' ' -f2)
220 unset args_ttydev
222 if [[ $term_width =~ "[a-zA-Z]" ]]; then
223 export term_width=100
225 #else
226 # dbgoutd "defined term_width=$term_width\n"
230 __get_tty_width
233 # copy dbgout code to here, this shlib can be used indepently.
236 if [[ ! "$(type dbgout 2>&1)" =~ "is a function" && ! "$(type dbgout 2>&1)" =~ "is aliased" ]]; then
238 #dbgout_file=
239 #log_file=
241 dbgout_file=${dbgout_file:-&2}
242 log_file=${log_file:-/dev/null}
245 # 输出调试信息到管道文件和日志文件中
246 # dbgout <param-list>
247 dbgout ()
249 echo
250 echo -ne "$@" | tee -a $log_file >&2 #>$dbgout_file
255 # re-define here to disable dbgoutd.
256 # declare -g -x DBGOUTD_OUTPUT=1
257 # set DBGOUTD_OUTPUT to 1 as an exported global var, the dbgout info will be outputted.
258 # it's usefull for new option implement, especially in test code..
260 dbgoutdd ()
262 if test -n "$DBGOUTD_OUTPUT"; then
263 dbgoutd "$@"
268 ###########################################
269 # helper string output by dispatched vars.
272 # paramter descript string display is not a complex thing.
273 # there is something should be pay attension on, that the lenth
274 # limit on the right side. so we sould get the string lenth/width
275 # to limit it.
276 # there are three type of char, and the corresponding lenth
277 # and display width.
278 # @ ascii char, it takes one byte, and use one unit of display char.
279 # @ gbk char, it takes two byte, and use two unit of display char.
280 # @ utf8 char, it may takes three byte, and use two unit of display
281 # char.
282 # the problem is, if the char type is mixed with those three type,
283 # how to get the display char width?
284 # ascii & gbk char is equal to the byte size of char. utf8 should
285 # be translated to gbk encode. then, those three type of char display
286 # width is equal to byte lenth of char.
287 # invoke "iconv -f utf-8 -t GBK", translate utf8 to gbk, calculate
288 # byte lenth, then convert back to utf8 encode. the byte size that we
289 # getted is the display char width.
292 # improve for words bundary truncating.
293 __oneline_desc_output ()
295 local len=
296 local cnt=
297 local tmp=
298 local size=
300 offset=${offset:-0}
302 test "$offset" -ge "${#p_desc[$3]}" && return 0
304 # it's the length of string byte, but string length is the string char count.
305 len=$(( term_width - pname_width - desc_blanks ))
307 printf "%*s" $(( pname_width - $1 + desc_blanks )) " "
310 # todo: this cmd cost more cpu resource.
312 # $len is the dispaly width of description string area.
313 # first, traslate it to gbk, let the byte size equal to dispaly char width, and cut $len char that equal
314 # to the width of dispaly area, and get the byte $cnt. in utf-8 envronment, it maybe cut a double or triple
315 # byte char, and it will leading error dispaly. so $len is not the truncate length it should be. use echo
316 # cmd to output string again, it will filer the uncorrect char that may be truncated by 'cut', and count
317 # the bytes of string, it's the actual byte cnt we should use for 'fold' to cut the string.
318 tmp="`echo -ne "${p_desc[$3]:$offset}" | iconv -f utf-8 -t GBK 2> /dev/null | cut -c 1-$len`"
319 cnt=`echo -ne "$tmp" | wc -c`
321 # then, do tralate operation again, and intert with a fold cmd to cut it by $cnt size, get the first line
322 # of the string, that's the string should be dispalyed on screen.
323 # this method compactive with utf-8 char dispaly, and english words bundary truncate. but it cost a bit more
324 # cpu rate.
325 tmp="`echo -ne "${p_desc[$3]:$offset}" | iconv -f utf-8 -t GBK 2> /dev/null | fold -b -s -w $cnt - 2> /dev/null | iconv -f GBK -t utf-8 2> /dev/null`"
326 tmp="${tmp%%
329 test -z "$tmp" && return 0
331 # get the char cnt of tmp that to be dispalyed
332 size=${#tmp}
334 echo -ne "$tmp\n"
335 offset=$(( offset + size ))
337 return 1
341 # fsyntax: __rest_desc_output <width> <idx>
342 # fdesc: output multi-line string. if the first line is outputed, it
343 # display the rest string.
345 __rest_desc_output ()
347 local i=
349 # the string length of env var is the char number of string.
350 # the string size of env is the buffer byte count of string.
351 # if it is a gbk char string, 2 byte size equal to 2 char width.
352 # calculate string display width is count the byte of string transformed from
353 # utf-8 to gbk.
354 # len=$(( $term_width - $pname_blanks - $pname_width - $desc_blanks ))
356 for (( i=0; i<200; i++ )); do
357 __oneline_desc_output 0 $1 $2
358 test "$?" == 0 && break
359 done
361 test "$i" = 200 && err "err: $FUNCNAME(line$LINENO) loop exception.\n" && exit
364 ############################################################
365 # argument describe string and dispatching
366 # using regex to match string, instead of others.
367 # TBD: it can not be executed under non-bash shell program.
370 str_dispatch ()
372 local fmt
374 # [[ "${abc}" =~ \|([^|]+)\ \|-([^|]*)\ \|--([^|]*)\ \|---([^|]*)\ \|=[\<\|\[]?([^|]*)[\>\|\]]? ]] && echo ${BASH_REMATCH[@]}
375 # |param |- |--logfile |--- |= | param logfile
377 # \|([^|]+)\ \|-([^|]*)\ \|--([^|]*)\ \|---([^|]*)\ \|=[\<|\[]?([^\|\>]*)[\>|\]?\ \|%[\<|\[]?([^\|\>]*)[\>|\]?\ \|=[\<|\[]?([^\|\>]*)[\>|\]?[\>|\]]?\ \|[\']?([^\|\>]*)[\']?
379 # it's a fixed syntax for desc-str-line.
381 # eg:
382 # CONTENT="|param |-e |--envchk |---envchk |= |%<envchk> |&<on_envchk> |'execute environment check for compile.'"
383 # debug this format sting in several parts
385 fmt+="\|([[:alnum:]_]+)[[:space:]]*"
386 fmt+="\|-([[:alnum:]_]*)?[[:space:]]*"
387 fmt+="\|--([[:alnum:]-]*)?[[:space:]]*"
388 fmt+="\|---([[:alnum:]_]*)?[[:space:]]*"
389 fmt+="\|=([\<|\[]?[[:alnum:][:punct:]]*[]|\>]?)?[[:space:]]*"
390 fmt+="[\|]%[\<|\[]?([[:alnum:]_]*)?[]|\>]?[[:space:]]*"
391 fmt+="\|&[\<|\[]?([[:alnum:]_]*)?[]|\>]?[[:space:]]*"
392 fmt+="\|[']?([^\']*)?"
394 if [[ "${1}" =~ $fmt ]]; then
395 unset param
396 declare -g -a param
397 for ((x=0; $x < ${#BASH_REMATCH[@]}; x++)); do
398 param[$x]="${BASH_REMATCH[$x]}"
399 done
400 elif [[ "${1}" =~ ^[:blank:]*[\|](prog) ]]; then
401 unset param
402 declare -g -a param
403 param[0]="${BASH_REMATCH[0]}"
404 param[1]="${BASH_REMATCH[1]}"
405 elif [[ "${1}" =~ [\|](blank)[\ ]*[\']?([^\']*)?[\']? ]]; then
406 unset param
407 declare -g -a param
408 param[0]="${BASH_REMATCH[0]}"
409 param[1]="${BASH_REMATCH[1]}"
410 param[2]="${BASH_REMATCH[2]}"
411 else
412 unset param
413 declare -g -a param=""
417 desc_param_setting ()
419 test "${param:-111}" = 111 && return
421 test -n "${param[2]}" && p_char[$idx]=${param[2]:0:2} && p_char2idx["${param[2]:0:1}"]=$idx
422 test -n "${param[3]}" && p_longopt[$idx]=${param[3]} && p_longopt2idx["${param[3]}"]=$idx
423 test -n "${param[4]}" && p_cmd[$idx]=${param[4]} && p_cmd2idx["${param[4]}"]=$idx
425 # paramter value
426 if test "${param[5]:0:1}" = "<" ; then
427 p_char[$idx]+=":"
428 elif test "${param[5]:0:1}" = "[" ; then
429 p_char[$idx]+="::"
430 else
431 param[5]=""
433 param[5]=${param[5]:1:-1}
434 test -n "${param[5]}" && p_value[$idx]="${param[5]}"
436 # option name, proc function
437 test -n "${param[6]}" && p_name[$idx]=${param[6]} && p_name2idx["${param[6]}"]=$idx
438 test -n "${param[7]}" && p_proc[$idx]=${param[7]}
440 # desc string
441 test -n "${param[8]}" && p_desc[$idx]="${param[8]}"
443 dbgoutdd "============================================\n"
444 tmp=${p_char[$idx]}
445 dbgoutdd "p_char[$idx]=$tmp'\n"
446 tmp=${p_char[$idx]:0:1}
447 [[ -n "$tmp" && "$tmp" != ':' ]] && dbgoutdd "p_char2idx[$tmp]='${p_char2idx[$tmp]}'\n"
448 tmp=${p_longopt[$idx]}
449 dbgoutdd "p_longopt[$idx]='${tmp}'\n"
450 [[ -n "$tmp" ]] && dbgoutdd "p_longopt2idx[$tmp]='${p_longopt2idx[$tmp]}'\n"
451 tmp=${p_cmd[$idx]}
452 dbgoutdd "p_cmd[$idx]='${tmp}'\n"
453 [[ -n "$tmp" ]] && dbgoutdd "p_cmd2idx[$tmp]='${p_cmd2idx[$tmp]}'\n"
454 dbgoutdd "p_value[$idx]='${p_value[$idx]}'\n"
455 tmp=${p_name[$idx]}
456 dbgoutdd "p_name[$idx]='${tmp}'\n"
457 [[ -n "$tmp" ]] && dbgoutdd "p_name2idx[$tmp]='${p_name2idx[$tmp]}'\n"
458 dbgoutdd "p_proc[$idx]='${p_proc[$idx]}'\n"
459 dbgoutdd "p_desc[$idx]='${p_desc[$idx]}'\n"
461 declare -l OPT_PFX=" "
463 len=0
465 # XXX:
466 # due to p_char[] store short opt with ':', it might be only ':' without short opt char.
467 # maybe it should append these judement below.
468 # && "${p_char[$idx]:0:1}" != ':'
470 if [[ -n "${p_char[$idx]}" && "${p_char[$idx]:0:1}" != ':' ]]; then
471 content+="-${p_char[$idx]:0:1}"
472 p_shortparam+="|${p_char[$idx]}"
473 len=$(( $len + 2 ))
474 OPT_PFX=", "
475 else
476 OPT_PFX=" "
480 [[ $(( $len + ${#p_cmd[$idx]} + 2 )) -gt $pname_width ]] && content+="$(printf '\n%*s'  $pname_blanks ' ')" && len=0
481 if test -n "${p_cmd[$idx]}" ; then
482 content+="${OPT_PFX}${p_cmd[$idx]}"
483 p_cmdparam+="|${p_cmd[$idx]}"
484 len=$(( $len + ${#p_cmd[$idx]} + 2 ))
485 OPT_PFX=", "
488 [[ $(( $len + ${#p_longopt[$idx]} + 4 )) -gt $pname_width ]] && content+="$(printf '\n%*s'  $pname_blanks ' ')" && len=0
489 test -n "${p_longopt[$idx]}" && content+="${OPT_PFX}--${p_longopt[$idx]}" && p_longparam+="|${p_longopt[$idx]}${p_char[$idx]:1}" && len=$(( $len + ${#p_longopt[$idx]} + 4 ))
491 test "$(( $len + ${#p_value[$idx]} + 3 ))" -gt $pname_width && content+="$(printf '\n%*s'  $pname_blanks ' ')" && len=0
492 test -n "${p_value[$idx]}" && content+="=<${p_value[$idx]}>" && len=$(( $len + ${#p_value[$idx]} + 3 ))
497 ##############################
498 # section: public function
499 ##############################
502 # fsyntax: OptDescParamPrint
503 # fdesc: output environment variable of dispatched option describe string.
505 OptDescParamPrint ()
507 dbgout "##########################\n"
508 dbgout "p_prog=$p_prog\n"
509 dbgout "p_progdesc=$p_progdesc\n"
511 dbgout "p_shortparam=$p_shortparam\n"
512 dbgout "p_longparam=$p_longparam\n"
513 dbgout "p_cmdparam=$p_cmdparam\n"
515 dbgout "p_pcnt=$p_pcnt\n"
517 for (( i=0; i<$p_pcnt; i++ )); do
518 dbgout "# [$i]\n"
519 dbgout " p_char[$i]=${p_char[$i]}\n"
520 dbgout " p_longopt[$i]=${p_longopt[$i]}\n"
521 dbgout " p_cmd[$i]=${p_cmd[$i]}\n"
522 dbgout " p_value[$i]=${p_value[$i]}\n"
523 dbgout " p_name[$i]=${p_name[$i]}\n"
524 dbgout " p_proc[$i]=${p_proc[$i]}\n"
525 dbgout " p_desc[$i]=\"${p_desc[$i]}\"\n"
526 done
528 dbgout "pname_width=$pname_width\n"
529 dbgout "pname_blanks=$pname_blanks\n"
530 dbgout "desc_blanks=$desc_blanks\n"
532 dbgout "term_width=$term_width\n"
533 opt_helper
536 ########################################
537 # main feature of args.shlib:
538 # @ dispatching desc-str to env vars which has a pfx 'p_'.
539 # @ resovle run time program args by 'p_*' env vars, set corresponding var value,
540 # and invoke action proc function.
541 # @ generate helper string by 'p_*' env vars
545 # fsyntax: helper_gen
546 # fdesc: generate helper string by paramter, and output it to the variable which
547 # specified in paramter. invoke this function before opt_helper() dispaly helper.
549 helper_gen ()
551 local i
552 local acnt
553 local data
554 local param
555 local len
556 local maxlen
557 local output
559 acnt=$idx #${#p_char[@]}
560 offset=0
561 for (( i=0; $i <= $acnt; i++ )); do
562 len=0
563 offset=0
565 # no description is seems as a blank line tag.
566 test -z "${p_desc[$i]}" && echo && continue
568 # this is a category description string.
569 if [[ "${p_char[$i]}" == '::' || "${p_char[$i]}" == ':' || -z "${p_char[$i]}" ]]; then
570 [[ -z "${p_longopt[$i]}" && -z "${p_cmd[$i]}" ]] && echo -ne "${p_desc[$i]}\n" && continue
573 printf "%*s" $pname_blanks " "
574 len=$pname_blanks
576 # if test "$OPT_PFX" = " "; then
577 declare -l OPT_PFX=" "
580 # echo "-n"
581 # echo "-e"
582 # this two cmd display nothing on screen.
583 # this string is filtered automatically, it is treated as the paramter for echo.
584 # echo "-ea"
585 # but this is ok.
586 # so output this string by two step. one is '-', then is 'n' or 'e'
587 if test -n "${p_char[$i]:0:1}" && test "${p_char[$i]:0:1}" != ":" ; then
588 echo -ne "-"
589 echo -ne "${p_char[$i]:0:1}"
590 OPT_PFX=", "
591 else
592 echo -ne " "
594 len=$(( $len + 2 ))
596 if test -n ${p_cmd[$i]} && test "$(( $len + ${#p_cmd[$i]} + 2 ))" -gt $pname_width ; then
597 __oneline_desc_output $len $pname_width $i
598 printf "%*s" $pname_blanks " "
599 len=$pname_blanks
601 if test -n "${p_cmd[$i]}"; then
602 echo -ne "${OPT_PFX}${p_cmd[$i]}"
603 len=$(( $len + ${#p_cmd[$i]} + 2 ))
604 OPT_PFX=", "
607 if test "$(( $len + ${#p_longopt[$i]} + 4 ))" -gt $pname_width ; then
608 __oneline_desc_output $len $pname_width $i
609 printf "%*s" $pname_blanks " "
610 len=$pname_blanks
612 test -n "${p_longopt[$i]}" && echo -ne "${OPT_PFX}--${p_longopt[$i]}" && len=$(( $len + ${#p_longopt[$i]} + 4 ))
614 if test "$(( $len + ${#p_value[$i]} + 3 ))" -gt $pname_width ; then
615 __oneline_desc_output $len $pname_width $i
616 printf "%*s " $pname_blanks " "
617 len=$(( pname_blanks + 4 ))
619 if test "${p_char[$i]:1}" = "::" || test "${p_char[$i]}" = "::" ; then
620 test -n "${p_value[$i]}" && echo -ne "=[${p_value[$i]}]" && len=$(( $len + ${#p_value[$i]} + 3 ))
621 else
622 test -n "${p_value[$i]}" && echo -ne "=<${p_value[$i]}>" && len=$(( $len + ${#p_value[$i]} + 3 ))
625 test "$offset" -ge "${#p_desc[$i]}" && echo && continue
627 __oneline_desc_output $len $pname_width $i
629 # output rest lines.
630 __rest_desc_output $pname_width $i
631 done
635 # fsyntax: opt_helper <output-var>
636 # fsyntax: generate helper string by the dispatched var.
637 # if <output-var> is '-', output to stdout instead of var.
639 opt_helper ()
641 local tmp=$(__get_term_width)
643 if test -n "$tmp" && test "$term_width" -gt "$tmp" || test -z "$helper"; then
644 term_width=$tmp
645 helper="`helper_gen`"
648 # TBD: iconv here at a time to save time coast.
650 test -n "$helper" && echo "$helper" # | iconv -f GBK -t utf-8 -c -s 2>/dev/null
651 else
652 test -n "$helper" && echo "$helper" # | iconv -f GBK -t utf-8 -c -s 2>/dev/null
656 # TBD: init the value to 0 if first init.
657 # it is defined as a global var, used for multiple desc-str dispathing.
658 declare -g idx=0
661 # fsyntax: opt_desc_str_dispatch <desc_str_var_name>
662 # fdesc: description string dispatch to p_* variables.
664 opt_desc_str_dispatch ()
666 local i
667 local ARGS="\$$1"
668 local tmp
669 local content
670 local desc=
671 local cnt
672 local opt
673 local longopt
674 local subcmd
675 local width
676 local tmpfile=
678 # resolve command line descript string and save to p_ variables.
679 IFS_OLD="$IFS"
680 IFS="@"
681 # ARGS=( $(eval echo -ne "$ARGS" | tr '\n' '@' | tr -s ' ' ) )
683 # sed -E "s/[\r\n]/@/g"
684 # this cmd cannot replace \r with @.
685 # [https://blog.51cto.com/u_15127525/4013659]
686 # in sed, it process text every time in a line. newline is a spacial char.
687 # it will append trailing newline after one line data processing.
688 # to solv this problem, we should known about branch condition cmd, pattern
689 # space, and hold space.
690 # run sed cmd in two lines, use this cmd:
691 # sed -E ":a;N;s/[\r\n]/@/g;ta"
692 # sed -E ':a;N;s/[\n\r]{1,2}[\|]/@\|/g;ta'
693 ARGS=( $(eval echo -ne "$ARGS" | sed -E 's/^[\|]/@\|/g' ) )
694 IFS="$IFS_OLD"
695 # declare -p ARGS >&2
696 # dbgoutdd "ARGS=${ARGS[@]}\n"
698 width=$pname_width
699 cnt="${#ARGS[@]}"
700 tmp=0
701 test "$pname_width" = 0 && pname_width=$(( ${term_width:-80} / 2 ))
703 for (( i=1; i<$cnt ; i++ )) do
704 test "${ARGS[$i]:0:1}" = '#' && continue
706 # str_dispatch_method2 "${ARGS[$i]}"
707 str_dispatch "${ARGS[$i]}"
709 if [[ ${param[1]} == "prog" ]]; then
710 p_prog=${param[2]}
711 p_progdesc=${param[3]//\'/} #'
712 elif [[ ${param[1]} =~ "blank" ]]; then
713 content+="\n"
714 test -n "${param[2]}" && p_desc[$idx]="${param[2]}"
715 idx=$(( idx + 1 ))
716 elif [[ ${param[1]} =~ "param" ]]; then
718 # use string match to implement this code.
720 desc_param_setting
721 # desc_param_setting_method2
723 content+="\n"
724 idx=$(( idx + 1 ))
725 else
726 tmp="${ARGS[$i]//[ |\t]/}"
727 if test -z "${tmp}"; then
729 elif [[ "${ARGS[$i]}" =~ [^\ ] ]]; then
730 # if there is no header with 'param' or 'prog'
731 # and it's not a blank line, it's maybe a descript string line
732 # continued from last config data.
733 tmp=${ARGS[$i]}
734 tmp=${tmp%%\'*} #'
736 if [[ -z $tmp || ! $tmp =~ "-" && ! $tmp =~ "=" && ! $tmp =~ "%" && ! $tmp =~ "!" ]]; then
737 tmp="${ARGS[$i]#*\'}"
738 tmp="${tmp%\'*}"
739 p_desc[$(( $idx - 1 ))]+="$tmp"
740 unset p_desc[$idx]
744 i=$((i++))
745 done
747 p_pcnt=$idx
749 # re-generate if pname_width equal to 0.
750 if test "$width" = 0 ; then
751 IFS_OLD="$IFS"
752 IFS=$'\n'
753 content=( $content )
754 IFS="$IFS_OLD"
755 tmp=0
756 for (( i=$(( ${#content[@]} - 1 )); i>=0; i-- )); do
757 cnt=${#content[$i]}
758 test "$tmp" -lt "$cnt" && tmp="$cnt"
759 done
761 # update value of pname_width
762 pname_width=$(( $tmp + 1 ))
767 # fdesc: for '-<c> <param>', it processed as "-<c> '' -- <param>".
768 # this func are used to fix this problem.
770 arg_proc ()
772 local i
773 local j=0
774 local cnt
775 local param="$@"
777 param=( ${param##*--} )
778 cnt=$(($# - ${#param[@]}))
779 for (( i=1; $i < $# ; i++ )); do
780 eval test "\"\${$i}\"" = '--' && break;
782 # NULL param means there are opt param maybe after '--'
783 if eval test -z "\"\${$i}\"" || eval test "\"\${$i}\"" = "''" ; then
784 if test -z "${param[$j]}" ; then
785 echo -ne "${!i} "
786 else
787 echo -ne "${param[$j]} "
789 unset param[$j]
790 j=$((j+1))
791 continue
794 # output normal
795 echo -ne "${!i} "
796 done
798 echo "-- ${param[@]}"
799 # echo -ne "-- ${param[@]}\n" >&2
803 # fsyntax: prog_opt_proc <arg-list>
804 # fdesc: process options in cmd arg, save the coressponding flag variable to 'enable', and
805 # append process function to actionlist.
806 # save paramters in cmd arg to actionlist with process function.
807 # at last, invoke functions in actionlist.
809 prog_opt_proc ()
811 local ARGS
812 local short_opt=
813 local long_opt=
814 local cmd_opt=
816 local index=
817 local param=
818 local name=
819 local value=
820 local proc=
822 local i
823 local cnt
825 local general_param=
827 dbgoutdd "=================================================\n"
829 # short option
830 short_opt=${p_shortparam:1}
831 short_opt=${short_opt//|/}
832 dbgoutdd "short_opt = $short_opt\n"
834 # long option
835 long_opt=${p_longparam:1}
836 long_opt=${long_opt//|/,}
837 dbgoutdd "long_opt = $long_opt\n"
839 # for ((i=1; i<=$#; i++)); do eval echo "\$i=\${$i}"; done
842 # paramter format translating.
843 # --long=a => --long a
844 # paramter maybe is a string with blanks in quote,
845 # if $@ directly, quoted string will be treated as several words.
846 # put "$@" to cmd string,
847 # ARGS="`eval $ARGS "$@"`"
848 # do not use eval.
850 # NOTE:
851 # there is an error when -o option with no parameter, eg: $short_opt is NULL.
852 # add quote here to generate a parameter even if $short_opt is NULL string.
854 ARGS="getopt -o \"$short_opt\" -l ${long_opt},insert-helper,inc-path,param-desc-str-alian --"
855 dbgoutdd "getopt cmd: ARGS = $ARGS $@\n"
856 ARGS=( `$ARGS "$@"` )
859 # exit if failed
861 if test "$?" != 0 ; then
862 err "Fail to get args.\n"
863 exit 1
866 dbgoutdd "get opt resualt: ARGS = $ARGS\n"
868 ARGS="$(arg_proc ${ARGS[@]})"
870 # trimp arguments
871 eval set -- "${ARGS}"
873 dbgoutdd "ARGS = $@\n"
874 # generate actionlist[] by argument
875 argflag=0
876 for ((i=0; i < 1000 ; i++)) do
877 value=""
878 index=""
879 dbgoutdd "\$1 = $1\n"
880 test -z "$1" && break
881 case "$1" in
882 -- )
883 dbgoutdd "param --\n"
884 general_param="$@"
885 test "$argflag" = 1 && break
886 argflag=1
887 shift
888 continue
892 # inner func param
894 --insert-helper )
895 dbgout "param_insert_helper()\n"
896 param_insert_helper
897 exit
900 --inc-path )
901 dbgout "param_inc_path()\n"
902 param_inc_path
903 exit
906 --param-desc-str-alian )
907 dbgout "param_param_desc_str_alian()\n"
908 param_param_desc_str_alian
909 exit
912 --* )
913 #param=$1
914 param=${1:2}
915 index=${p_longopt2idx[$param]}
916 test -z $index && break
919 -* )
920 # dbgoutdd "$1,$2,$3\n"
921 #param=$1
922 param=${1:1}
923 index=${p_char2idx[$param]}
925 dbgoutdd "=================================================\n"
926 dbgoutdd "option -$param processing ...\n"
927 dbgoutdd "\${!p_char2idx[@]} = ${!p_char2idx[@]}\n"
928 dbgoutdd "\${p_char2idx[@]} = ${p_char2idx[@]}\n"
929 dbgoutdd "\${p_char2idx[\"$param\"]} = ${p_char2idx[$param]}\n"
930 dbgoutdd "param = $param\n"
931 dbgoutdd "index = $index\n"
933 test -z "$index" && break
937 param=${1}
938 index=${p_cmd2idx[$param]}
939 # p_param="$@"
940 test -z "$index" && break
941 # shift
942 # continue
944 esac
946 test -z "$index" && dbgout "err: param ($param) exist, but it's index value not found.\n" && continue
948 # paramter output for debugging
949 dbgoutdd "\$1 = $1\n"
950 dbgoutdd "\$2 = $2\n"
951 dbgoutdd "index = $index\n"
952 dbgoutdd "\${p_value[$index]} = ${p_value[$index]}\n"
954 if test -n "${p_value[$index]}" ; then
955 declare -g ${p_value[$index]}="$2"
956 value=$(eval echo \$${p_value[$index]})
957 else
958 declare -g ${p_name[$index]}="enable"
959 value=""
961 proc=${p_proc[$index]}
963 if test ! -z "$value"; then
964 shift 2
965 else
966 shift
967 value="enable"
970 # option action function list generating.
971 dbgoutdd "value = $value\n"
972 dbgoutdd "proc = $proc()\n"
973 test -n "$proc" && actionlist="$actionlist
974 $proc $value "
975 done
976 # execute paramter proc function.
977 IFS_OLD="$IFS"
978 IFS="
980 actionlist=( $actionlist )
981 IFS="$IFS_OLD"
983 unset argflag
986 action_list_exec ()
988 local ret=
989 local cnt=
991 cnt=${#actionlist[@]}
992 for (( i=0; i < cnt; i++ )); do
993 dbgoutdd "==============================================\n"
994 dbgoutdd "invoke: ${actionlist[$i]}\n"
995 ${actionlist[$i]} $general_param
996 ret=$?
997 done
998 test "$i" = "${#actionlist[@]}" && dbgoutdd "==============================================\n"
999 return $ret
1003 # TBD:
1005 # the code below is reserved, because it's used for optimizing cpu cost in some system
1006 # environment.
1009 #tmp_usage_desc=
1010 # tmp_usage_desc=`echo -ne "${usage_desc_str}" | iconv -f utf-8 -t GBK 2> /dev/null | cut -c 1-$len | iconv -f GBK -t utf-8 2> /dev/null`
1011 # [[ -z $tmp_usage_desc ]] && return
1014 # fsyntax: prog_opt_dispatch <prog-arg-list>
1015 # fdesc: dispatch usage_desc_str to vars, and resolve program args by those
1016 # vars.
1017 # normally, it is not used when developer want to let the desc-str to be a re-usable
1018 # desc-str in another program, which 'source' the program, to use the desc-str.
1020 prog_opt_dispatch ()
1023 # TBD: translate utf8 to gbk at a time, to avoid iconv invoking every line data.
1025 # tmp_usage_desc=`echo -ne "${usage_desc_str}" | iconv -f utf-8 -t GBK 2> /dev/null`
1026 # [[ -z $tmp_usage_desc ]] && return
1028 opt_desc_str_dispatch usage_desc_str;
1030 # do not generate helper string if it is not -h paramter.
1031 # it takes more cpu resource.
1032 # helper="`helper_gen`"
1034 prog_opt_proc "$@"
1039 ##############################
1040 # section: file tail
1041 ##############################
1048 main_args ()
1050 dbgout "usage_desc_str = $usage_desc_str\n"
1052 # prog_opt_testing
1054 dbgout "#######################################\n"
1056 # 这里的-i参数为可选,但参数是以-ixxx的形式将参数xxx传递到程序的。pp0
1057 # -i xxx的方式不会识别参数。像是gcc中-m参数的使用。
1058 # 也可以-i'xxx'的形式使用。
1059 prog_opt_testing -a --test params aaa -iabc -x 123
1061 dbgout "#######################################\n"
1063 opt_helper
1065 exit
1067 prog_opt_testing -a
1069 dbgout "#######################################\n"
1071 prog_opt_testing --help
1073 opt_helper
1077 #main "@"
1082 # wc方式以字节长度计数,但运行费时,使用字符长度,在英文字母为字符串的参数信息中,等同于字节数。
1083 # if [[ $(( $len + "`echo \"${p_cmd[$i]}\" | wc -L`" + 2 )) -gt $pname_width ]]; then
1092 # parmater list. used for enum paramter var.
1093 param_list=
1098 # foo () { ARGS="$(getopt -o lS:y::n:LFraAt:b:e:x:RBscuvqfj:M:d:O:pmgVh -l list,save:,sync::,num:,failed-begin,failed,ignor-err,all,test:,begin:,end:,exclude:,rollup,rollback,set,clean,update,verbose,quiet,force,multi-task:,matching:,dir:,output-dir:,print-vars,mono,logfile,version,help,debug,insert-helper,inc-path,param-desc-str-alian -- "$@")"; echo "ARGS=\"$ARGS\""; eval set -- "$ARGS"; echo param="$@"; echo "\$1='$1'"; echo "\$2='$2'"; for ((i=1; i<=$#; i++)); do eval echo "\\\$$i=\"\${$i}\""; done; }
1101 # foo ()
1103 # ARGS="$(getopt -o lS:y::n:LFraAt:b:e:x:RBscuvqfj:M:d:O:pmgVh -l list,save:,sync::,num:,failed-begin,failed,ignor-err,all,test:,begin:,end:,exclude:,rollup,rollback,set,clean,update,verbose,quiet,force,multi-task:,matching:,dir:,output-dir:,print-vars,mono,logfile,version,help,debug,insert-helper,inc-path,param-desc-str-alian -- "$@")";
1104 # echo "ARGS=\"$ARGS\"";
1105 # eval set -- "$ARGS";
1106 # echo param="$@";
1107 # echo "\$1='$1'";
1108 # echo "\$2='$2'";
1109 # for ((i=1; i<=$#; i++)); do
1110 # eval echo "\\\$$i=\"\${$i}\"";
1111 # done;
1113 # $ foo -M '<861874> [citem] aqstk "stack and queue data structure with array feature"'
1114 # ARGS=" -M '<861874> [citem] aqstk "stack and queue data structure with array feature"' --"
1115 # param=-M <861874> [citem] aqstk "stack and queue data structure with array feature" --
1116 # $1='-M'
1117 # $2='<861874> [citem] aqstk "stack and queue data structure with array feature"'
1118 # $1=-M
1119 # $2=<861874> [citem] aqstk "stack and queue data structure with array feature"
1120 # $3=--
1126 blank_param_setting ()
1128 p_char[$idx]=""; p_char2idx["${param[2]:0:1}"]=""
1129 p_longopt[$idx]=""; p_longopt2idx["${param[3]}"]=""
1130 p_cmd[$idx]=""; p_cmd2idx["${param[4]}"]=""
1132 p_char[$idx]=""
1133 p_char[$idx]=""
1134 p_char[$idx]=""
1153 # fsyntax: __oneline_desc_output <param-str-len> <width> <idx>
1154 # fdesc: output single line option description string.
1156 __oneline_desc_output_bak ()
1158 local len=
1159 local cnt=
1160 local tmp=
1161 local size=
1163 offset=${offset:-0}
1165 test "$offset" -ge "${#p_desc[$3]}" && return 0
1167 # it's the length of string byte, but string length is the string char count.
1168 len=$(( term_width - pname_width - desc_blanks ))
1170 printf "%*s" $(( pname_width - $1 + desc_blanks )) " "
1173 # todo: this cmd coast more cpu resource.
1175 tmp=`echo -ne "${p_desc[$3]:$offset:$len}" | iconv -f utf-8 -t GBK 2> /dev/null | cut -c 1-$len | iconv -f GBK -t utf-8 2> /dev/null`
1176 # tmp=`echo -ne "${p_desc[$3]:$offset:$len}" | cut -c 1-$len`
1177 test -z "$tmp" && return 0
1179 cnt=`echo -ne "$tmp" | wc -c`
1180 size=${#tmp}
1181 tmp=`echo -ne "$tmp" | fold -b -s -w $cnt - 2> /dev/null`
1183 echo -ne "$tmp\n"
1184 offset=$(( offset + size ))
1186 return 1