#!/bin/sh
# platform ... devuan dash
# GPL_3+
cat << 'EEE' > /dev/null
/* ckopt ... option parse helper. bourne-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
# 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
lname=${0##*/}
optpre="opt_"
pbuf="${lname}_buf"
popt="${lname}_opt"
pfunc="$lname""_func"

if [ "${1:-null}" = "-h" ] || [ $# -ne 1 ] ; then
cat << 'EEE'
HowTo (ckopt, option parser & checker. bourne-shell script)
opt: -h, -H(detail help)
------
eg.)	...save as 'yoursh.sh'
 #!/bin/sh			
 cmd=$(cat << 'END'
    # 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" >/dev/stderr'	'#E myflg="$opt_c"'
 END
 )
 buf=`ckopt ":$cmd"`  #..or.. "$cmd"  (colon, quiet err mode like 'getopts' )
 eval "$buf"
 if [ "$?" = "1" ] ; then echo "err. $OPTARG" ; exit 1 ; fi 
 echo "$opt_a $opt_b $myflg"	# >>> check working. opt_* is fixed prefix.
 echo "$@"

...  run 'yoursh.sh zzz -b ttt yyy -- -a 10'
>>> 100 ttt 0 (a b myflg)	+  show msg if input -c
    zzz yyy -a 10	   >>> parse until detect '--' or end. remove options.
eg.) ~$ ckopt "$cmd"	# >>> output code. copy&paste to your script directly.
EEE
exit 0
fi

if [ "${1:-null}" = "-H" ] ; then
cat << 'EEE'
*** samples are written at the end ***
--- check sequence samle
ini: '-a'  111  int  "cmd1"  "cmd2"
cmd: aaa.sh -a 123 (or -a zzz etc)   
>>>
opt_a="111"	(always run) 

 -a get? -y->  int?         -y-> opt_a=123 -> cmd1 -suc-> cmd2 -suc-> ...
          +-n-> (opt_a=111)  +-n-> rtn err           +err-> rtn err

--- optlist format:
# ignore line head '#(+space/tab)'.  (-v "bool" 0   # comment	...NG)
# column1  clm2   clm3  clm4,5,6...
# (must)  (must) (must)  (optional)
     -v      0   "bool"
     -n     111	  iNt	'#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.
- grammar conforms to shell style. quote, backslash, grouping etc.

- clm1: option char (posix)
   short option only. allows a-z,A-Z,0-9. (-@, -_ ... NG).

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

- clm3: option type you expect. 3 types.
   bool: "0" or "1". switch type option.(-v, -h etc.)
   int: integer. 9, 011(octal), 0x09(hex) etc. converted to decimal.(opt_n=9)
        checked 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... : additional command
   not must. write commands you want to execute.
      -a 0 bool		>>> myscript -a ... opt_a=1	
      -a 0 bool 'echo "abcde"'	 >>> myscript -a ... opt_a=1 and disp 'abcde'	

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

   cmd with prefix '#E' runs at script end. the following descriptions are
   almost equivalent. this feature is intended to summarize the option handling.
	
	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 returns err, disp msg & stop process if no colon is set.("$optlist")
   if colon is set.(":$optlist"), run as quiet err mode. exit status returns 1
   and set errmsg (errmsg -a 'cmd' etc) 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 "mmm... 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-100 ms (normal use) 
      4-8 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"
#	return 0
	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
`

abc=`ckopt "$buf"`
eval "$abc"
echo "ckname=@ $uname @"


# --- recommend info 
# http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
# 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
EEE
exit 0
fi


# posixでawkはEREのスーパーセットと決められている。
ck_posixopt() {
	printf '%s\n' "$*" | tr ' ' '\n' | awk '
	$1 != "" {
		buf=match($1, /^-[a-zA-Z0-9]/)
		if(length($1) != 2 || buf == 0) {
			printf "%s ", $1
			err=1
		}
	}
	END{
		if(err == 1) {
			exit 1
		}
	}'
}

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

# output...
#casemaker "$fmt" b  '[ "$opt_b" != "bob" ]' '#E myflg="$opt_b"'
#opt_a=1
#:a:bc

# fnc "$rawlist"
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
		
		#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")
	} '
}

# 追加実行用関数
# 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#[#]?}"
	if [ "$?" != "0" ] ; then
EEE
printf '		OPTARG="errmsg $%s $1"\n' "$popt"
cat << 'EEE'
		return 1
	fi
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' "$@"
	fi
shift
while [ $# -gt 0 ]
do
	eval "$1"
	if [ "$?" != "0" ] ; then
EEE
printf '		OPTARG="errmsg $%s $1"\n' "$popt"
cat << 'EEE'
		return 1
	fi
shift
done
return 0
fi

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




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

# ...ck quite ':'
qerr=0
printf '%s\n' "$rawlist" | awk 'NR == 1 && $1 ~ /^:/ {exit 1}'
if [ "$?" = "1" ] ; then
	qerr=1
	rawlist=`printf '%s\n' "$rawlist" | awk 'NR == 1 {sub(/:/, "", $1)} {print $0}'`
fi

# ...ck_posixopt -a -f -? ..etc. rtn err opt
buf=`printf '%s\n' "$rawlist" | awk '$1 != "" && $1 !~ /^#/ {print $1} ' `
set -- $buf
rtn=`eval "ck_posixopt $@" `	# rtn ... erropt -s etc
if [ "$?" != "0" ] ; then
	printf 'OPTARG="errmsg %s invalid opt is defined"\n' "$rtn"
		test "$qerr" = "0" || printf 'echo "$OPTARG" >/dev/stderr ; while : ; do sleep 1000 ; done\n'
	printf 'test 1 = 0\n'
	exit 1
fi

# 13ms

fmt=`func_getfmt "$rawlist"`
# どっかのリストで要素が足りない。要素は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"
		test "$qerr" = "0" || printf 'echo "$OPTARG" >/dev/stderr ; while : ; do sleep 1000 ; done\n'
	printf 'test 1 = 0\n'
	exit 1
fi
# 16ms

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

# info out
dfldata="
OPTIND=1	# fixed value. posix rule.
OPTERR=0
${pbuf}=''
${popt}=''

$buf"

# output dflsetdata (opt_b="0" etc)
printf "## ---this code is generated by $lname\n"
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
if [ "$1" = "--" ] ; then
	shift ; break
fi
EEE

printf 'getopts ":%s" %s "$@"		# ":a:bc:f:" etc...\n' "$fmt" "$popt"	# popt:getopts :a ckopt_opt "$@"
cat << 'EEE'
if [ "$?" = "1" ] ; then	# detect end/normal args. save general args.
	shift $((OPTIND - 1))
	if [ "$#" -eq "0" ] ; then
		break
	fi
EEE
# skip系
#	ckopt_buf="ckopt_ar$ckopt_delcnt"	# ar1, 2...
#	eval "$ckopt_buf="'"$1"'	# ar3="$1"
#	ckopt_delskip="$ckopt_delskip ""$ckopt_buf""='' ;"
#	ckopt_buf='"$'"$ckopt_buf"'"'	# ar1 -> "$ar1"
#	ckopt_skip="$ckopt_skip $ckopt_buf"
#	ckopt_delcnt=$((ckopt_delcnt + 1 ))
#	shift
#	OPTIND=1
#	continue

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. 

OPTARG="errmsg -$OPTARG invalid option / misses subargs"
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 $? ... xn\n'
printf '%s\n' "$cmdlist" | awk '$1 ~ /^#/ {$1=""; $3="-e" ; print $0}'

cat << 'EEE'
done

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



exit
