#!/bin/sh
# GPL_3+
cat << 'EEE' > /dev/null
/* ckopt ... option parse helper. posix-shell script.
 * Copyright (C) 2018 Momi-g
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
EEE

# --- recommend info 
# http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
# http://catb.org/%7Eesr/writings/taoup/html/ch10s05.html
# https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr12/index.html
# https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr1n/index.html

# --- refs.
# https://www.mm2d.net/main/prog/c/getopt-03.html
# https://wp.mikeforce.net/gnome/category/gtk
# https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/html/gtk-General.html#gtk-init-with-args
# https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/glib/glib-Commandline-option-parser.html#GOptionEntry
# http://catb.org/%7Eesr/writings/taoup/html/ch10s05.html

# make namespace
set -ue
lname=${0##*/}
optpre="opt_"
pbuf="${lname}_buf"
popt="${lname}_opt"
pfunc="${lname}_func"

if [ "${1-''}" = "-h" ] || [ $# -ne 1 ] ; then
cat << 'EEE'
HowTo (ckopt, option parser & checker. posix-shell script)
opt: -h, -H(detail help)
------
eg.)	...save as 'yoursh.sh'
 #!/bin/sh			
 cmd=$(cat << 'END'   # 'END'.. not expand $foo etc. 
    # opt dfl  type	( +add command you want to test input opt)	
      -a  100	inT
      -b  vvv	STr	'[ "$opt_b" != "bob" ]'
      -c  "0"	'bool'	'echo "detect c"'	'#E myflg="$opt_c"'
 END
 )
 buf=`ckopt ":$cmd"`  #..or.. "$cmd"  (colon, quiet err mode as getopts )
 eval "$buf"
 if [ "$?" = "1" ] ;then echo "err. $OPTARG" ;exit 1 ;fi
 echo "$opt_a $opt_b $myflg $@"   #check working

#---
 ~$ yoursh.sh zzz -b ttt yyy -- -a 10
	#>>	100 ttt 0 (a b myflg)
		zzz yyy -a 10 (parse until detect '--' or end. remove options)
EEE
exit 0
fi

if [ "${1:-null}" = "-H" ] ; then
cat << 'EEE'
*** samples are written at the end ***
--- optlist format:
# ignore line head '#(+space/tab)'.  (-v 0 "bool"   # comment	...NG)
# column1  clm2   clm3  clm4,5,6...
# (must)  (must) (must)  (optional)
     -v      0   "bool"
     -n     111	  iNt	'[ $opt_n -gt 0 ]' '#E linenum=$opt_n'
     '-j'  "abc"  str	'test -e "$opt_j"'
     -b    '`printf "a\nbc\n"`'  STR	echo\ aaa;:

- splited by blank. (space/tab)
- line parsing. you cant use newline char directly. use `printf 'a\nbc\n'` etc.
- lines are used/parsed in 'set' and 'eval'.

- clm1: option char (posix)
   short option only. allows [a-zA-Z0-9] (NG: -@, -_).

- clm2: defalut value
   the dfl value skips int/bool/str test. (allow '-v' 'bob' 'bool')

- clm3: types you expect. 3 types.
   bool: "0" or "1". switch type option.(-v, -h etc.)
   int: 9, 011(octal), 0x09(hex) etc. converted to decimal.(opt_n=9)
        checked/conved by 'printf "%d" "$opt_n"' suc or not.
        'printf' + 012/0x11/123 (shell command) is posix. portable.
   str: others. use as string. (hello -> opt_j='hello', 0x11 -> opt_j='0x11' )

- clm4,5,6... : optional cmd
   not must. write cmd you want to exec.
      -a 0 bool		>>> myscript -a ... opt_a=1	
      -a 0 bool 'echo "abcde"'	 >>> myscript -a ... opt_a=1 and disp 'abcde'	

   stop parsing if cmd rtns $?=1 (exit status != 0)
      -a 0 bool '[ 1 = 0 ]' "echo 'aaa'" >> myscript -a ..opt_a=1 only

   cmd with prefix '#E ' runs at script end. the bewlow is almost the same.
 
	aaa=`ckopt "-a int 0"`         aaa=`ckopt "-a int 0 '#E flg=$opt_a'"`
	eval "$aaa"               <->  eval "$aaa"
	flg=$opt_a                     ( --delete-- )
   
   -a int 100 '#E zzz=$opt_a'  <-> -a int 100 
   -b bool 1                       -b bool 1 '#E zzz=$opt_a'

--- err handling:
   if check rtns err, disp msg & exit 1 if colon isnt set.("$optlist")
   if colon is set (":$optlist"), run as quiet mode. exit status returns 1
   and set emsg to 'OPTARG'. (uses OPTARG as msgbuf)

 -- stop mode
   aaa=`ckopt "$optlist"`
   eval "$aaa"		# errmsg & stop

 -- quiet mode
   aaa=`ckopt ":$optlist"`
   eval "$aaa"
   if [ "$?" = "1" ] ;then
     echo "err.. $OPTARG"   # >>>errmsg will be printed.
     exit 1
   fi
...

--- copy & paste (portable use + low overhead):
if you execute directly, code will be printed.

~$ optlist='-h "bool" 0'
~$ ckopt "$optlist"	>>> output code

you can use this code directly.

   #!/bin/sh
   
   buf='                  --->  ---
   # help                 ---> 
   -h "bool" 0            --->  copy & paste output code
   '                      --->	    (100 - 150 lines)
                          ---> 
   aaa=`ckopt ":$buf"`    ---> 
   eval "$aaa"            --->  ---
   							
   if [ "$?" = "1" ] ;then     if [ "$?" = "1" ] ;then
								
time: 40-150 ms (normal use) 
      4-30 ms (copy & paste style)
EEE
cat << EEE0 ; cat << 'EEE'

using vars: $pbuf
            $popt
            $pfunc
            OPTARG
            OPTIND
            opt_a,b,c...
EEE0

--------------------------
******** samples *********
--------------------------

#!/bin/sh
buf='
# help
-h 0 "bool" '

aaa=`ckopt "$buf"`
eval "$aaa"

---
#!/bin/sh

func_usage() {
	echo "hello,world"
	exit 0
}
	
buf=`cat << 'END'
-h	0	"bool"	func_usage
END
`

bbb=`ckopt ":$buf"`
eval "$bbb"
if [ "$?" = "1" ] ;then
	echo "invalid opt. $OPTARG" >/dev/stderr
	exit 1
fi
echo "good-bye world"

---
#!/bin/sh
workdir=${0%/*}		# $0 ... /aaa/bb/ccc.sh		>>  /aaa/bb
uname="anon
yno
us"

func_usage() { echo "hello,world" ; exit 0 ; }

buf=`cat << 'END'
-h  0  "bool"  func_usage

# ---debag level (1 - 10)
-d  1  int  '[ 1 -le $opt_d  ] && [ $opt_d -le 10 ]'  'echo "dlevel=$opt_d"'

# ---uniq tmpfile name & make test
# make test runs if '-f' opt detected
-f  'mytmp' str '[ ! -e "$opt_f" ]' 'touch "$opt_f"' 'rm "$opt_f"'

# username. kick length=0
-u  "$uname"  str  '[ ! -z "$opt_u" ]'  '#E uname="$opt_u"'

# working dir
-w  "$workdir"  str  "[ -d \"\$opt_w\" ]"  workdir="$opt_w"

END
`
cmd=`ckopt "$buf"`
eval "$cmd"
echo "ckname=@ $uname @"

# --- recommend info 
# http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
# http://catb.org/%7Eesr/writings/taoup/html/ch10s05.html
# https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr12/index.html
# https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr1n/index.html

# --- refs.
# https://www.mm2d.net/main/prog/c/getopt-03.html
# https://wp.mikeforce.net/gnome/category/gtk
# https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/html/gtk-General.html#gtk-init-with-args
# https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/glib/glib-Commandline-option-parser.html#GOptionEntry
EEE
exit 0
fi

errstop() {
	printf '%s\n' "$1"
	while : ; do sleep 1000 ; done
	exit 1
}


# func "$ini"	>>> a:bc:f etc
func_getfmt() {
	buf=`printf '%s\n' "$*" | awk '
	$1 != "" && $1 !~ /^#/ {$3=tolower($3) ; print $0}' | awk '{print "func_outfmt " $0} '`
	eval "$buf"
}
func_outfmt() {
	buf=':'	#have subargs
	test "${3#*bool*}" = "$3" || buf=""
	if [ $# -lt 3 ] ; then
		buf='::'	#listerr
	fi
	printf '%s%s' "${1#?}" "$buf"
}

# 外部実行リストをまとめて処理しとく。func名と他。
# fmtがopt名順に並んでるはず。拝借できる。
# ckopt_func -d/-c -a 000 int ls ...
func_execlist() {
	printf '%s\n' "$*" | awk '$1 != "" && $1 !~ /^#/ {print $0}' |
	awk -v pfunc="$pfunc" -v fmt="$fmt" -v optpre="$optpre" -v popt="$popt" '
	BEGIN{ pos=1 }
	{
		char=substr(fmt, pos, 1)		# abcv ... の順番にcharを一個ずつ
		subarg=substr(fmt, pos+1, 1)		# subck
		
		print "# " pfunc " -d " $0		# dflout info
		
		#caseout
		printf("if [ \042$%s\042 = \042%s\042 ] ; then\n", popt, char)
		
		rhs=1
		if(subarg == ":"){
			pos=pos+1
			rhs="$" "OPTARG"
		}
		pos=pos+1
		
		printf("	%s%s=\042%s\042\n", optpre, char, rhs)
		printf("	%s -c %s\n", pfunc, $0)	# errはpfuncで処理する。
		printf("	continue\n")
		printf("fi\n")
	} '
}

# 追加実行, test用関数
# funcout "perr" "fname" 
funcout() {
	printf '%s () { \n' "$pfunc"
	printf 'if [ "$1" = "-d" ] ; then\n'
	printf '	eval "%s${2#?}"\047="$3"\047		# opt_?="$3"\n' "$optpre"
	printf '	return 0\nfi\n\n'
	printf '%s="$2"\n' "$popt"
cat << 'EEE'
if [ "$1" = "-e" ] ; then
shift 4
while [ $# -gt 0 ]
do
if [ "$1" != "${1#[#]e}" ] || [ "$1" != "${1#[#]E}" ] ; then
	eval "${1#[#]?}"
fi
shift
done
return 0
fi

if [ "$1" = "-c" ] ; then
shift 3
printf '%s\n' "$1" | tr '[:upper:]' '[:lower:]' | awk '$1 ~ /int/ {exit 1}'
	if [ "$?" = "1" ] ; then
		shift
		set -- "dummy" 'printf "%d" "$OPTARG" >/dev/null 2>&1;test $? = 0' "$@"
	fi
shift
while [ $# -gt 0 ]
do
	echo "$1"|awk '$1~/^#[eE]$/{exit 1}'
	test $? = 1 && shift && continue
	eval "$1"
	if [ "$?" = "1" ] ; then
EEE
printf '		OPTARG="errmsg $%s $1"\n' "$popt"
cat << 'EEE'
		return 1
	fi
shift
done
return 0
fi

echo "$0: fatal bug. sleep" >/dev/stderr
while :
do
	sleep 1000
done
exit 1
}
EEE
}

#---main
# 5ms
rawlist="$1"
shift

# ...ck quite ':'
qerr=0
buf="${rawlist#[:]}"
if [ "$buf" != "$rawlist" ] ; then
	qerr=1
	rawlist="$buf"
fi

# ...ck_posixopt -a -f -? ..etc. rtn err opt
buf=`printf '%s\n' "$rawlist" | awk '$1 != "" && $1 !~ /^#/ {print $1} '|
	tr -d '[a-zA-Z0-9]\n-' `
if [ "${#buf}" != "0" ] ; then
	printf 'OPTARG="errmsg %s unsupported optchar is set"\n' "$buf"
	printf 'echo "$OPTARG" >/dev/stderr\n'
	printf 'test 1 = 0\n'
	exit 1
fi

# 13ms
fmt=`func_getfmt "$rawlist"`
# どっかのリストでclmが足りない。要素はchangelist内部で分解されるが3つ以上欲しい。
# エラー意思表示として異常fmtを出力。
if [ "$fmt" != "${fmt#*::*}" ] ; then
	buf=`echo "$fmt" | sed -e 's/.*\(.\)::.*/-\1/g'`
	printf 'OPTARG="errmsg %s invalid optlist format. (bug)"\n' "$buf"
	printf 'echo "$OPTARG" >/dev/stderr\n'
	printf 'test 1 = 0\n'
	exit 1
fi
# 16ms

cmdlist=`func_execlist "$rawlist" `
buf=`printf '%s\n' "$cmdlist" | awk '$1 ~ /^#/ {$1=""; print $0}'`

#--- src out start
dfldata="
OPTIND=1	# fixed value. posix rule.
OPTERR=0
OPTARG=${OPTARG=""}
${pbuf}=''
${popt}=''
$buf"

# output dflsetdata (opt_b="0" etc)
printf "# GPLv3 or upper, Copyright (C) 2018 Momi-g
# ---this code is generated by $lname
"
printf " ---optsetting
%s" "$rawlist" | awk '{print "#" $0}'
echo
funcout
printf "\n# ---dflset\n"
printf '%s\n' "$dfldata"

#	main out
cat << 'EEE'
#---getopts loop. break if all args read or detect '--'
while :
do
EEE
printf 'if [ 0 -eq "$#" ] || [ "%s" = "errmsg" ] ; then\n' '${OPTARG%% *}'
cat << 'EEE'
	break
fi
# eval展開するので先頭の$と合わせて check_opt=${3-} になる ${OPTIND}ではない
check_opt='check_opt=${'$OPTIND'-}'
eval "$check_opt"
if [ "$check_opt" = "--" ] ; then
	shift $OPTIND; break
fi
OPTARG=''
EEE

printf 'getopts ":%s" %s "$@" || ! :	# ":a:bc:f:" etc...\n' "$fmt" "$popt"	# popt:getopts :a ckopt_opt "$@"
cat << 'EEE'
if [ "$?" = 1 ]&&[ "${OPTARG:=@}" = '@' ]; then	# detect end/normal args. save general args.
	shift $((OPTIND - 1))
	if [ "$#" -eq "0" ] ; then
		break
	fi
EEE
printf '	%s="$%s "' "$pbuf" "$pbuf"
cat << 'EEE'
`printf '%s' "$1" | sed -e "{        
	s#'#'\"'\"'#g
	s/^/'/g
	s/$/'/g
	}"`
	shift
	OPTIND=1
	continue
fi
# --- your setting

EEE
printf '%s\n' "$cmdlist" | awk '$1 !~ /^#/ {print $0}'
printf '# --- your setting end\n'
cat << 'EEE'
# hit err. ckopt chars...
# err detect@silent mode
# $?=1 ... detect optend or '--'
# ':' ... detect option, but dont have subargs (OPTARG="factor").
# '?' ... detect unsupported option char (OPTARG="factor") or args end (OPTARG="blank").
# OPTARG ... "" is optend. "a/b/c..." is invalid option 
# OPTIND ... if err, OPTIND indicates next (new) arg pos. 
EEE
printf 'OPTARG="errmsg -$OPTARG bad option/no subarg: -%s"\n' "$fmt"
cat<<'EEE'
done

# --- post process. set not optional args & clean & #eE last cmd.
for ii in 1	# dummyjump logic
do
	OPTIND=1
	# skip
	test "${OPTARG%% *}" != "errmsg" || break
EEE
printf '	%s="set -- $%s"\047 "$@"\047	# set -- \047cmd\047 .. "$@"\n' "$pbuf" "$pbuf"
printf '	eval "$%s"\n'  "$pbuf"		# %s, no quote. presed
printf '\n	# run #E cmd.  cmd + test $? ... x n\n'
printf '%s\n' "$cmdlist" | awk '$1 ~ /^#/ {$1=""; $3="-e" ; print $0 " ||break"}'
cat << 'EEE'
done

if [ "${OPTARG%% *}" = "errmsg" ] ; then
EEE
if [ "$qerr" = "0" ] ; then
	cat << 'EEE'
	printf "$0: opterr. %s\n" "$OPTARG" >/dev/stderr; exit 1
EEE
fi
printf '	test 1 = 0\nfi\n'
printf "## ---generate by $lname end\n"

exit

cat<<'EEE'>/dev/null
 change log
 --
2021-08-03  Momi-g	<dmy@dmy.dmy>

	* ckopt(inner_func) : add appname to emsg

2021-07-29  Momi-g	<dmy@dmy.dmy>

	* ckopt(main) : v1.0.1
	
	* ckopt(main) : add OPTARG=${OPTARG:="@"} to kill 'set -u' warning

	* ckopt(inner_func) : fix check_opt=$'"{$OPTIND-}, set -u warning
	
	* ckopt(emsg) : disp getopts format if use bad optchars
	* : del sleep 1000 stopper.

2020-12-20  Momi-g	<dmy@dmy.dmy>

	* ckopt(inner exefunc -ce) : change user test suc $?=0 >> $?=1, rewrite code.

EEE
