18. Shell Option Evaluation Loop

A generalized option evaluation loop is implemented in snippet sh_b.opt-loop. This chapter describes the algorithm employed.

18.1. Shell Commmand Grammar

BNF for simple scripts:

command: command-name
       | command-name options
       | command-name arguments
       | command-name options arguments

options : option
        | option options

option: option-symbol
      | option-symbol argument

BNF for script implementing a command shell:

command-shell: command-shell-name
             | command-shell-name options
             | command-shell-name command
             | command-shell-name options command

18.2. Option Loop Activity Diagram

@startuml /' a98 '/
skinparam padding 1
partition "multiple option loop" {
start
:setup options with default values
* set //opt_debug// false
* set //opt_arg// to //default arg//
* initialize pass_opts;
while (forever) is (do)
if (first shell argument is **not** set?) then (yes)
    break
endif
if (first shell argument\nmatches -d, ~--d*\n~--debug) then (yes)
    :set //opt_debug// true
    and add to //pass_opts//;
elseif (first shell argument\nmatches -a, ~--a*\n~--arg) then (yes)
    :set //opt_arg//
    to option argument;
    :remove option from
    argument list so that
    next //shift// removes
    the option argument;
elseif (first shell argument\nmatches -?, -h, ~--he*\n~--help) then (yes)
    :display usage;
    stop
    elseif (first shell argument\nmatches ~--u|--um*\n~--umlx) then (yes)
        :extract UML diagram
        matching RX and
        convert to TYP;
        stop
elseif (first shell argument\nmatches //--//) then (yes)
    ://--// marks the end of
    options, remove it
    and break;
    (A)
    -[#transparent]->
    break
elseif (first shell argument\nmatches //-//) then (yes)
    ://-// signifies standard
    input (or standard
    output), keep it
    and break;
    (A)
    -[#transparent]->
    break
elseif (first shell argument\nmatches //-*//) then (yes)
    :error message
    unknown option;
    end
else
    :first non-option
    argument, keep
    it and break;
    (A)
    -[#transparent]->
    break
endif
:shift shell arguments left
(2nd argument becomes 1st argument,
3rd argument becomes 2nd argument, ...;
endwhile
(A)
note right
argument list consists of first non-option
argument and all following arguments
end note
stop
} /' move before //stop//, if there are subsections '/
@enduml

18.3. Option Loop Template

# ::here::
opt_debug=0
opt_arg='default arg'
pass_opts=
while :
do
    test x${1+set} = xset || break
    case "${1}" in
    ## (progn (forward-line 1) (snip-insert "sh_b.opt-option" t t "sh" " --key skip_for_update --key opt-minimalx") (insert "\n"))
    # Defining opt_short as `o`, opt_long as `option`, opt_arg as `ARG` => creates option: -o, --option ARG; the value `::fillme::` is considered blank, it does not have to be removed
    # (let ((opt_short "::fillme::") (opt_long "::fillme::") (opt_arg "::fillme::")) (forward-line 2) (snip-insert "sh_b.opt-option" t t "sh" (concat " --key opt-minimalx --key skip_for_update --key opt_short --value " opt_short " --key opt_long --value " opt_long " --key opt_arg --value " opt_arg)))
    # ::here::
    -d|--d|--de|--deb|--debu|--debug) # --debug
        opt_debug=1
        pass_opts="${pass_opts}"' --debug'
        ;;
    -a|--a|--ar|--arg) # --arg OPT-ARG
        opt_arg="${2-}"
        test x"${2+set}" = xset && shift
        pass_opts="${pass_opts}"' --arg '"${opt_arg}"
        #pass_opts="${pass_opts}"' --arg '"$( single_quote_enclose "${opt_arg}" )"
        ;;
    -\?|-h|--h|--he|--hel|--help) # --help
        usage; exit 0
        pass_opts="${pass_opts}"' --help'
        ;;
    --u|--um|--uml|--umlx) # --umlx RX[:TYP]
        opt_umlx="${2-}"
        test x"${2+set}" = xset && shift
        opt_umlx_match="$( printf "%s\n" "${opt_umlx}" | ${SED__PROG-sed} 's,^\([^:]*\)\(:\([^:]*\)\)*.*,\1,' )"
        opt_umlx_type="$( printf "%s\n" "${opt_umlx}" | ${SED__PROG-sed} 's,^\([^:]*\)\(:\([^:]*\)\)*.*,\3,' )"
        out_filt_conv ()
        {
            _type="${1}"
            case "${_type}" in
            ## (progn (forward-line 1) (snip-insert "sh_b.case_patterns_for_actions" t t "sh" " --key actions --value '::fillme::'") (insert ""))
            v|vi|vie|view) _type='svg';;
            esac
            if test -z "${_type}"
            then
                cat
            else
                plantuml -t"${_type}" -pipe
            fi
        }
        out_filt_disp ()
        {
            _type="${1}"
            case "${_type}" in
            ## (progn (forward-line 1) (snip-insert "sh_b.case_patterns_for_actions" t t "sh" " --key actions --value '::fillme::'") (insert ""))
            v|vi|vie|view) _type='view';;
            esac
            if test "${_type}" != view
            then
                cat
            else
                display
            fi
        }
        (
        if test -n "${opt_umlx_match}"
        then
            line_diversion.py --match "${opt_umlx_match}" "${prog_path-${0}}"
        else
            line_diversion.py "${prog_path-${0}}"
        fi
        ) \
        | out_filt_conv "${opt_umlx_type}" \
        | out_filt_disp "${opt_umlx_type}"
        exit 0
        #pass_opts="${pass_opts}"' --umlx '"${opt_umlx}"
        #pass_opts="${pass_opts}"' --umlx '"$( single_quote_enclose "${opt_umlx}" )
        ;;
    --) # end of options
        shift; break;;
    -) # stdin
        break;;
    -*) # unknown option
        echo >&2 "${prog_name-$( basename "${0}" )}: error: unknown option \`${1}'"
        exit 1
        break
        ;;
    *)
        break;;
    esac
    shift
done

18.4. Snippet for defining an option

sh_b.opt-loop contains shell comments with emacs lisp code for inserting an option definiton:

;; opt_short: o | opt_long: option | opt_arg: ARG => description: -o, --option  ARG || ::fillme:: is considered blank, it does not have to be removed
(let ((opt_short "::fillme::")
      (opt_long "::fillme::")
      (opt_arg "::fillme::"))
  (forward-line 1)
  (snip-insert "sh_b.opt-option" t t "sh"
               (concat " --key opt_short --value " opt_short
                       " --key opt_long --value " opt_long
                       " --key opt_arg --value " opt_arg)))

The ::fillme:: tag is interpreted as if the parameter was blank. I.e., the ::fillme:: tags do not have to be deleted, only replaced as necessary.

18.4.1. Example options

18.4.1.1. No parameters

The following Emacs Lisp expression, which does not define any of opt_short, opt_long and opt_arg,

(progn
  (forward-line 1)
  (snip-insert "sh_b.opt-option" t t "sh"))

results in the following snippet shell command and output:

>>>snc --mode 'sh' --key 'filename' --value 'sh_b.opt-option' --process --replace '/home/ws/snippets/sh_b.opt-option'
#
opt_=0
    ) #
        opt_=1;;

18.4.1.2. All parameters = ::fillme::

The following Emacs Lisp expression, which defines opt_short as ::fillme::, opt_long as ::fillme:: and opt_arg as ::fillme::,

(let ((opt_short "::fillme::")
      (opt_long "::fillme::")
      (opt_arg "::fillme::"))
  ;; ...
  )

results in the following snippet shell command and output:

>>>snc --mode 'sh' --key 'filename' --value 'sh_b.opt-option' --key opt_short --value ::fillme:: --key opt_long --value ::fillme:: --key opt_arg --value ::fillme:: --process --replace '/home/ws/snippets/sh_b.opt-option'
#
opt_=0
    ) #
        opt_=1;;

18.4.1.3. opt_short = q, opt_long = quiet-day

The following Emacs Lisp expression, which defines opt_short as q, opt_long as quiet-day and opt_arg as ::fillme::,

(let ((opt_short "q")
      (opt_long "quiet-day")
      (opt_arg "::fillme::"))
  ;; ...
  )

results in the following snippet shell command and output:

>>>snc --mode 'sh' --key 'filename' --value 'sh_b.opt-option' --key opt_short --value q --key opt_long --value quiet-day --key opt_arg --value ::fillme:: --process --replace '/home/ws/snippets/sh_b.opt-option'
#   -q, --quiet-day
opt_quiet_day=0
    -q|--q*) # --quiet-day
        opt_quiet_day=1;;

18.4.1.4. opt_short = q, opt_arg = INT

The following Emacs Lisp expression, which defines opt_short as q, opt_long as ::fillme:: and opt_arg as INT,

(let ((opt_short "q")
      (opt_long "::fillme::")
      (opt_arg "INT"))
  ;; ...
  )

results in the following snippet shell command and output:

>>>snc --mode 'sh' --key 'filename' --value 'sh_b.opt-option' --key opt_short --value q --key opt_long --value ::fillme:: --key opt_arg --value INT --process --replace '/home/ws/snippets/sh_b.opt-option'
#   -q  INT
opt_q=''
    -q) #  INT
        opt_q="${2-}"
        test x"${2+set}" = xset && shift;;

18.4.1.5. opt_short = q, opt_long = quiet-day, opt_arg = INT

The following Emacs Lisp expression, which defines opt_short as q, opt_long as quiet-day and opt_arg as INT,

(let ((opt_short "q")
      (opt_long "quiet-day")
      (opt_arg "INT"))
  ;; ...
  )

results in the following snippet shell command and output:

>>>snc --mode 'sh' --key 'filename' --value 'sh_b.opt-option' --key opt_short --value q --key opt_long --value quiet-day --key opt_arg --value INT --process --replace '/home/ws/snippets/sh_b.opt-option'
#   -q, --quiet-day  INT
opt_quiet_day=''
    -q|--q*) # --quiet-day INT
        opt_quiet_day="${2-}"
        test x"${2+set}" = xset && shift;;

18.4.1.6. opt_long = quiet-day, opt_arg = INT

The following Emacs Lisp expression, which defines opt_short as ::fillme::, opt_long as quiet-day and opt_arg as INT,

(let ((opt_short "::fillme::")
      (opt_long "quiet-day")
      (opt_arg "INT"))

  ;; ...
  )

results in the following snippet shell command and output:

>>>snc --mode 'sh' --key 'filename' --value 'sh_b.opt-option' --key opt_short --value ::fillme:: --key opt_long --value quiet-day --key opt_arg --value INT --process --replace '/home/ws/snippets/sh_b.opt-option'
#   --quiet-day  INT
opt_quiet_day=''
    --q*) # --quiet-day INT
        opt_quiet_day="${2-}"
        test x"${2+set}" = xset && shift;;

18.4.2. Option generator snippet

# ||<-snap->|| if defined opt-minimal
# ||<-snap->|| alias opt-minimal rem
# ||<-snap->|| alias opt-not-minimal skip
# ||<-snap->|| subst opt-minimal-x
# ||<-snap->|| subst comm_activity @|empty@ #@empty@a99
# ||<-snap->|| subst case_short_end ;;
# ||<-snap->|| subst case_long_end
# ||<-snap->|| else
# ||<-snap->|| alias opt-minimal skip
# ||<-snap->|| alias opt-not-minimal rem
# ||<-snap->|| subst opt-minimal-x x
# ||<-snap->|| subst comm_activity @|empty@ #@empty@a0
# ||<-snap->|| subst case_short_end
# ||<-snap->|| subst case_long_end ;;
# ||<-snap->|| fi !defined opt-minimal
# ||<-snap->|| alias sh_b.opt-option rem
# ||<-snap->|| if !defined sh_b.opt-option
# ||<-snap->|| if defined skip_for_new
# ||<-snap->|| alias sh_b.opt-option skip
# ||<-snap->|| fi defined skip_for_new
# ||<-snap->|| fi !defined sh_b.opt-option

# ||<-snap->|| rem setup defaults
# ||<-snap->|| subst have_opt no
# ||<-snap->|| default opt_short
# ||<-snap->|| subst dash_opt_short

# ||<-snap->|| subst opt_short_long_comma
# ||<-snap->|| subst opt_short_long_bar

# ||<-snap->|| default opt_long
# ||<-snap->|| subst opt_name
# ||<-snap->|| subst dash_opt_long
# ||<-snap->|| subst opt_long_first
# ||<-snap->|| subst opt_long_cases
# ||<-snap->|| subst star_opt_long
# ||<-snap->|| subst comm_opt

# ||<-snap->|| default opt_arg
# ||<-snap->|| subst have_arg no
# ||<-snap->|| subst opt_arg_sep

# ||<-snap->|| rem ignore ::fillme:: tags
# ||<-snap->|| if eq opt_init ::fillme::
# ||<-snap->|| undef opt_init
# ||<-snap->|| fi
# ||<-snap->|| if eq opt_short ::fillme::
# ||<-snap->|| subst opt_short
# ||<-snap->|| fi
# ||<-snap->|| if eq opt_long ::fillme::
# ||<-snap->|| subst opt_long
# ||<-snap->|| fi
# ||<-snap->|| if eq opt_arg ::fillme::
# ||<-snap->|| subst opt_arg
# ||<-snap->|| fi

# ||<-snap->|| rem setup opt_short
# ||<-snap->|| if !eq opt_short @|empty@
# ||<-snap->|| subst have_opt yes
# ||<-snap->|| subst dash_opt_short -
# ||<-snap->|| if !eq opt_long @|empty@
# ||<-snap->|| subst opt_short_long_bar |
# ||<-snap->|| subst opt_short_long_comma ,@|space@
# ||<-snap->|| else
# ||<-snap->|| subst comm_opt @|empty@ # @||dash_opt_short@@||opt_short@
# ||<-snap->|| fi
# ||<-snap->|| fi

# ||<-snap->|| rem setup opt_long
# ||<-snap->|| if !eq opt_long @|empty@
# ||<-snap->|| subst have_opt yes
# ||<-snap->|| subst dash_opt_long --
# ||<-snap->|| subst star_opt_long *
# ||<-snap->|| subst comm_opt @|empty@ # @|dash_opt_long@@|opt_long@
# ||<-snap->|| define opt_name process
# ||<-snap->|| exec dump !process replace
printf "%s\n" '@opt_long@' | ${SED__PROG-sed} 's,[^0-9A-Za-z],_,g'
# ||<-snap->|| exec
# ||<-snap->|| define opt_name
# ||<-snap->|| define opt_long_first process
# ||<-snap->|| exec dump !process replace
printf "%s\n" '@opt_long@' | ${CUT__PROG-cut} -c 1
# ||<-snap->|| exec
# ||<-snap->|| define opt_long_first
# ||<-snap->|| subst pass_opt @dash_opt_long@@opt_long@
# ||<-snap->|| else
# ||<-snap->|| subst opt_name @opt_short@
# ||<-snap->|| subst pass_opt @dash_opt_short@@opt_short@
# ||<-snap->|| fi

# ||<-snap->|| trim right
# ||<-snap->|| define opt_option_templates
    # Defining opt_short as `o`, opt_long as `option`, opt_arg as `ARG` => creates option: -o, --option ARG; the value `::fillme::` is considered blank, it does not have to be removed
    # (let ((opt_short "::fillme::") (opt_long "::fillme::") (opt_arg "::fillme::")) (forward-line 2) (snip-insert "sh_b.opt-option" t t "sh" (concat " --key opt-minimal@opt-minimal-x@ --key skip_for_update --key opt_short --value " opt_short " --key opt_long --value " opt_long " --key opt_arg --value " opt_arg)))
# ||<-snap->|| define opt_option_templates
# ||<-snap->|| if !defined skip_for_update
# ||<-snap->|| default opt_option_update
# ||<-snap->|| if !defined skip_for_new
# ||<-snap->|| if eq have_opt yes
# ||<-snap->|| subst opt_option_update ## **CONDSIDER UPDATING THE OPTION GENERATOR TEMPLATE**
# ||<-snap->|| fi eq have_opt yes
# ||<-snap->|| fi !defined skip_for_new
@opt_option_update@
# ||<-snap->|| trim right
    ## (progn (forward-line 1) (snip-insert "sh_b.opt-option" t t "sh" " --key skip_for_update --key opt-minimal@opt-minimal-x@") (insert "\n"))
@opt_option_templates@
@opt_option_update@
# ||<-snap->|| trim right
# ||<-snap->|| fi !defined skip_for_update
# ||<-snap->|| if defined skip_for_update
# ||<-snap->|| if !defined skip_for_new
# ||<-snap->|| if !eq have_opt yes
@opt_option_templates@
# ||<-snap->|| fi !eq have_opt yes
# ||<-snap->|| fi !defined skip_for_new
# ||<-snap->|| fi defined skip_for_update
# ||<-snap->|| sh_b.opt-option
# ||<-snap->|| undef skip_for_update
# ||<-snap->|| subst skip_for_new

# ||<-snap->|| rem setup opt_long_case_patterns
# ||<-snap->|| define opt_long_case_patterns process
# ||<-snap->|| exec !process replace
python -c '
from __future__ import print_function
import sys;
for arg in sys.argv[1:]:
    output = []
    _indx = 3
    while True:
        output.append(arg[:_indx])
        if not arg[_indx:]:
            print("|".join(output))
            break
        _indx += 1
' '@dash_opt_long@@opt_long@'
# ||<-snap->|| exec
# ||<-snap->|| define opt_long_case_patterns

# ||<-snap->|| rem setup opt_arg
# ||<-snap->|| if eq opt_short @|empty@
# ||<-snap->|| if eq opt_long @|empty@
# ||<-snap->|| subst opt_arg
# ||<-snap->|| fi
# ||<-snap->|| fi
# ||<-snap->|| if defined opt_arg
# ||<-snap->|| if !eq opt_arg @|empty@
# ||<-snap->|| subst have_arg yes
# ||<-snap->|| subst opt_arg_sep @|space@
# ||<-snap->|| fi
# ||<-snap->|| fi

# ||<-snap->|| rem setup initial option value
# ||<-snap->|| if !defined opt_init
# ||<-snap->|| if eq have_arg yes
# ||<-snap->|| subst opt_init ''
# ||<-snap->|| else
# ||<-snap->|| subst opt_init @|empty@0
# ||<-snap->|| fi
# ||<-snap->|| fi

# ||<-snap->|| trim right
# ||<-snap->|| if eq have_opt yes
# ||<-snap->|| rem insert description, initialization, parser
# ||<-snap->|| rem #   @dash_opt_short@@opt_short@@opt_short_long_comma@@dash_opt_long@@opt_long@@opt_arg_sep@@opt_arg_sep@@opt_arg@
#   | @dash_opt_short@@opt_short@@opt_short_long_comma@@dash_opt_long@@opt_long@ |@opt_arg_sep@@opt_arg@ | ::fillme:: |@comm_activity@
opt_@opt_name@=@opt_init@
    @dash_opt_short@@opt_short@@opt_short_long_bar@@opt_long_case_patterns@)@comm_opt@@opt_arg_sep@@opt_arg@
# ||<-snap->|| if eq have_arg yes
        opt_@opt_name@="${2-}"
        test x"${2+set}" != xset || shift@case_short_end@
# ||<-snap->|| opt-not-minimal
        #pass_opts="${pass_opts}"' @pass_opt@ '"${opt_@opt_name@}"
        #pass_opts="${pass_opts}"' @pass_opt@ '"$( single_quote_enclose "${opt_@opt_name@}" )"
        @case_long_end@
# ||<-snap->|| opt-not-minimal
# ||<-snap->|| else
        opt_@opt_name@=1@case_short_end@
# ||<-snap->|| opt-not-minimal
        pass_opts="${pass_opts}"' @pass_opt@'
        @case_long_end@
# ||<-snap->|| opt-not-minimal
# ||<-snap->|| fi
# ||<-snap->|| fi
# ||<-snap->|| sh_b.opt-option