feat: bash v1
This commit is contained in:
parent
11ea01a88f
commit
4c821ae145
1
barg.js
1
barg.js
@ -306,6 +306,7 @@ const INPUTS = [
|
|||||||
"dev stop mycontainer --kill -- not specified one",
|
"dev stop mycontainer --kill -- not specified one",
|
||||||
"dev build --from alpine --image node:20 mybox -- echo hi",
|
"dev build --from alpine --image node:20 mybox -- echo hi",
|
||||||
"dev build --aaaaa --from alpine --image node:20 --bbbbb orb mybox ccccc -- echo hi",
|
"dev build --aaaaa --from alpine --image node:20 --bbbbb orb mybox ccccc -- echo hi",
|
||||||
|
"dev build mybox --global -qvi=debian",
|
||||||
];
|
];
|
||||||
|
|
||||||
INPUTS.forEach((input) => {
|
INPUTS.forEach((input) => {
|
||||||
|
|||||||
379
barg.sh
Normal file
379
barg.sh
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Bash 5+
|
||||||
|
|
||||||
|
# TODO (NOT IMPLEMENTED):
|
||||||
|
# - repeatable attribute
|
||||||
|
|
||||||
|
# ---------- helpers ----------
|
||||||
|
join() {
|
||||||
|
local out="" sep="" x
|
||||||
|
for x in "$@"; do
|
||||||
|
[[ -n "$x" ]] || continue
|
||||||
|
out+="${sep}${x}"
|
||||||
|
sep="."
|
||||||
|
done
|
||||||
|
printf '%s' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- tokenizer ----------
|
||||||
|
tokenize() {
|
||||||
|
local input=$1
|
||||||
|
TOKENS=()
|
||||||
|
read -r -a args <<<"$input"
|
||||||
|
local root=${args[0]}
|
||||||
|
TOKENS+=("root::${root}")
|
||||||
|
args=("${args[@]:1}")
|
||||||
|
|
||||||
|
while ((${#args[@]})); do
|
||||||
|
local token=${args[0]}
|
||||||
|
args=("${args[@]:1}")
|
||||||
|
|
||||||
|
if [[ "$token" == "--" ]]; then
|
||||||
|
TOKENS+=("rest::${args[*]}")
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$token" == --* ]]; then
|
||||||
|
local kv=${token#--}
|
||||||
|
local key=${kv%%=*}
|
||||||
|
TOKENS+=("narg::${key}")
|
||||||
|
if [[ "$kv" == *"="* ]]; then
|
||||||
|
local value=${kv#*=}
|
||||||
|
TOKENS+=("value::${value}")
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$token" == -* ]]; then
|
||||||
|
local sv=${token#-}
|
||||||
|
local flags=${sv%%=*}
|
||||||
|
local value=
|
||||||
|
if [[ "$sv" == *"="* ]]; then value=${sv#*=}; fi
|
||||||
|
local i ch
|
||||||
|
for ((i = 0; i < ${#flags}; i++)); do
|
||||||
|
ch=${flags:i:1}
|
||||||
|
TOKENS+=("narg::${ch}")
|
||||||
|
done
|
||||||
|
[[ -n "$value" ]] && TOKENS+=("value::${value}")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
TOKENS+=("unk::${token}")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- parser ----------
|
||||||
|
declare -A S A
|
||||||
|
|
||||||
|
generate_schema() {
|
||||||
|
S=()
|
||||||
|
A=()
|
||||||
|
local spec_name=$1
|
||||||
|
local -n SPEC_REF="$spec_name"
|
||||||
|
|
||||||
|
local _id=100
|
||||||
|
local level=-1
|
||||||
|
local -a pos=()
|
||||||
|
local -a cmds=()
|
||||||
|
|
||||||
|
for entry in "${SPEC_REF[@]}"; do
|
||||||
|
IFS=';' read -r -a parts <<<"$entry"
|
||||||
|
local type=${parts[0]}
|
||||||
|
case "$type" in
|
||||||
|
command)
|
||||||
|
local id=$_id
|
||||||
|
_id=$((_id + 1))
|
||||||
|
IFS=',' read -r -a aliases <<<"${parts[1]}"
|
||||||
|
local help="${parts[2]}"
|
||||||
|
S["$id.entryType"]="command"
|
||||||
|
S["$id.name"]="${aliases[0]}"
|
||||||
|
S["$id.help"]="$help"
|
||||||
|
S["$id.args"]=""
|
||||||
|
|
||||||
|
if ((${#cmds[@]} == 0)); then
|
||||||
|
A["cmd::root"]=$id
|
||||||
|
else
|
||||||
|
local last=$((${#cmds[@]} - 1))
|
||||||
|
for alias in "${aliases[@]}"; do
|
||||||
|
A["$(join "${cmds[$last]}" "cmd::${alias}")"]=$id
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
level=$((level + 1))
|
||||||
|
cmds+=("$id")
|
||||||
|
pos+=(0)
|
||||||
|
;;
|
||||||
|
end)
|
||||||
|
level=$((level - 1))
|
||||||
|
if ((${#cmds[@]} > 0)); then cmds=("${cmds[@]:0:${#cmds[@]}-1}"); fi
|
||||||
|
if ((${#pos[@]} > 0)); then pos=("${pos[@]:0:${#pos[@]}-1}"); fi
|
||||||
|
;;
|
||||||
|
argument)
|
||||||
|
local id=$_id
|
||||||
|
_id=$((_id + 1))
|
||||||
|
IFS=',' read -r -a aliases <<<"${parts[1]}"
|
||||||
|
local name="${aliases[0]}"
|
||||||
|
|
||||||
|
local dest="$name"
|
||||||
|
local required="false"
|
||||||
|
local atype="positional"
|
||||||
|
local value=""
|
||||||
|
local help=""
|
||||||
|
local i kv k v
|
||||||
|
for ((i = 2; i < ${#parts[@]}; i++)); do
|
||||||
|
kv=${parts[i]}
|
||||||
|
if [[ "$kv" == *":"* ]]; then
|
||||||
|
k=${kv%%:*}
|
||||||
|
v=${kv#*:}
|
||||||
|
else
|
||||||
|
k=$kv
|
||||||
|
v="true"
|
||||||
|
fi
|
||||||
|
case "$k" in
|
||||||
|
dest) dest="$v" ;;
|
||||||
|
required) required="$v" ;;
|
||||||
|
type) atype="$v" ;;
|
||||||
|
help) help="$v" ;;
|
||||||
|
default) value="$v" ;;
|
||||||
|
repeatable) S["$id.repeatable"]="$v" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
S["$id.entryType"]="argument"
|
||||||
|
S["$id.name"]="$name"
|
||||||
|
S["$id.dest"]="$dest"
|
||||||
|
S["$id.required"]="$required"
|
||||||
|
S["$id.type"]="$atype"
|
||||||
|
[[ -n "$help" ]] && S["$id.help"]="$help"
|
||||||
|
[[ -n "$value" ]] && S["$id.value"]="$value"
|
||||||
|
|
||||||
|
local last=$((${#cmds[@]} - 1))
|
||||||
|
local cmdArgsKey
|
||||||
|
cmdArgsKey="$(join "${cmds[$last]}" "args")"
|
||||||
|
if [[ -z "${S[$cmdArgsKey]}" ]]; then
|
||||||
|
S["$cmdArgsKey"]="$id"
|
||||||
|
else
|
||||||
|
S["$cmdArgsKey"]+=",${id}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$atype" in
|
||||||
|
positional)
|
||||||
|
A["$(join "${cmds[$last]}" "pos::${pos[$last]}")"]=$id
|
||||||
|
pos[$last]=$((pos[$last] + 1))
|
||||||
|
;;
|
||||||
|
rest)
|
||||||
|
A["$(join "${cmds[$last]}" "rest")"]=$id
|
||||||
|
;;
|
||||||
|
option | flag)
|
||||||
|
for alias in "${aliases[@]}"; do
|
||||||
|
A["$(join "${cmds[$last]}" "narg::${alias}")"]=$id
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Error: Invalid entry type: "%s"\n' "$type" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
print_result() {
|
||||||
|
local -a COMMANDS=("${!1}")
|
||||||
|
declare -n VALUES_REF="$2"
|
||||||
|
|
||||||
|
printf 'commands:'
|
||||||
|
local c
|
||||||
|
for c in "${COMMANDS[@]}"; do printf ' %s' "$c"; done
|
||||||
|
printf '\n'
|
||||||
|
printf 'values:\n'
|
||||||
|
local k v
|
||||||
|
for k in "${!VALUES_REF[@]}"; do
|
||||||
|
v=${VALUES_REF[$k]}
|
||||||
|
if [[ "$v" == *" "* ]]; then
|
||||||
|
printf ' %s="%s"\n' "$k" "$v"
|
||||||
|
else
|
||||||
|
printf ' %s=%s\n' "$k" "$v"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_input() {
|
||||||
|
local spec_name=$1
|
||||||
|
local input=$2
|
||||||
|
tokenize "$input"
|
||||||
|
generate_schema "$spec_name" || return 1
|
||||||
|
|
||||||
|
local -a cmds=()
|
||||||
|
local -a pos=()
|
||||||
|
local tagged token tokenTag
|
||||||
|
|
||||||
|
while ((${#TOKENS[@]})); do
|
||||||
|
tagged=${TOKENS[0]}
|
||||||
|
TOKENS=("${TOKENS[@]:1}")
|
||||||
|
tokenTag=${tagged%%::*}
|
||||||
|
token=${tagged#*::}
|
||||||
|
|
||||||
|
local found=0
|
||||||
|
local level start_level entryId id
|
||||||
|
start_level=$((${#cmds[@]} ? ${#cmds[@]} - 1 : 0))
|
||||||
|
|
||||||
|
for ((level = start_level; level >= 0; level--)); do
|
||||||
|
entryId=""
|
||||||
|
case "$tokenTag" in
|
||||||
|
root)
|
||||||
|
entryId="${A["cmd::root"]}"
|
||||||
|
;;
|
||||||
|
rest)
|
||||||
|
((${#cmds[@]})) || {
|
||||||
|
entryId=""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
entryId="${A["$(join "${cmds[$level]}" "rest")"]}"
|
||||||
|
;;
|
||||||
|
narg)
|
||||||
|
((${#cmds[@]})) || {
|
||||||
|
entryId=""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
entryId="${A["$(join "${cmds[$level]}" "$tagged")"]}"
|
||||||
|
;;
|
||||||
|
unk)
|
||||||
|
((${#cmds[@]})) || { entryId=""; } # cannot be positional before a command
|
||||||
|
if [[ -z "$entryId" && ${#cmds[@]} -gt 0 ]]; then
|
||||||
|
id="${A["$(join "${cmds[$level]}" "cmd::${token}")"]}"
|
||||||
|
if [[ -z "$id" && $level -eq $((${#pos[@]} - 1)) ]]; then
|
||||||
|
id="${A["$(join "${cmds[$level]}" "pos::${pos[$level]}")"]}"
|
||||||
|
[[ -n "$id" ]] && pos[$level]=$((pos[$level] + 1))
|
||||||
|
fi
|
||||||
|
entryId="$id"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[[ -z "$entryId" ]] && continue
|
||||||
|
|
||||||
|
local entryType=${S["$entryId.entryType"]}
|
||||||
|
case "$entryType" in
|
||||||
|
command)
|
||||||
|
cmds+=("$entryId")
|
||||||
|
pos+=(0)
|
||||||
|
if [[ "$tokenTag" == "root" ]]; then
|
||||||
|
S["$entryId.name"]="$token"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
argument)
|
||||||
|
local atype=${S["$entryId.type"]}
|
||||||
|
local val=""
|
||||||
|
case "$atype" in
|
||||||
|
rest | positional) val="$token" ;;
|
||||||
|
flag) val="true" ;;
|
||||||
|
option)
|
||||||
|
if ((${#TOKENS[@]} == 0)); then
|
||||||
|
printf 'Error: No value provided for option argument: "%s"\n' "$token" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
local nTagged=${TOKENS[0]}
|
||||||
|
local nTag=${nTagged%%::*}
|
||||||
|
local nTok=${nTagged#*::}
|
||||||
|
if [[ "$nTag" != "value" && "$nTag" != "unk" ]]; then
|
||||||
|
printf 'Error: Expected option argument, but got: "%s" (%s). Skipping...\n' "$nTok" "$nTag" >&2
|
||||||
|
else
|
||||||
|
TOKENS=("${TOKENS[@]:1}")
|
||||||
|
val="$nTok"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
S["$entryId.value"]="$val"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
found=1
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((found == 0)); then
|
||||||
|
printf 'Invalid argument: "%s" (%s). Skipping...\n' "$token" "$tokenTag"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# finalize
|
||||||
|
local -a COMMANDS=()
|
||||||
|
declare -A VALUES=()
|
||||||
|
|
||||||
|
local cmdId cmdName cmdArgs argId name dest value required
|
||||||
|
for cmdId in "${cmds[@]}"; do
|
||||||
|
cmdName=${S["$cmdId.name"]}
|
||||||
|
COMMANDS+=("$cmdName")
|
||||||
|
cmdArgs=${S["$cmdId.args"]}
|
||||||
|
[[ -z "$cmdArgs" ]] && continue
|
||||||
|
IFS=',' read -r -a argIds <<<"$cmdArgs"
|
||||||
|
for argId in "${argIds[@]}"; do
|
||||||
|
name=${S["$argId.name"]}
|
||||||
|
dest=${S["$argId.dest"]}
|
||||||
|
value=${S["$argId.value"]}
|
||||||
|
required=${S["$argId.required"]}
|
||||||
|
|
||||||
|
if [[ "$required" == "true" && -z "$value" ]]; then
|
||||||
|
printf 'Error: Argument "%s" is required in command "%s"\n' "$name" "$cmdName" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [[ -n "${VALUES[$dest]+x}" ]]; then
|
||||||
|
VALUES["${cmdName}_${dest}"]="$value"
|
||||||
|
else
|
||||||
|
VALUES["$dest"]="$value"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
print_result COMMANDS[@] VALUES
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_input_wrap() {
|
||||||
|
local spec_name=$1
|
||||||
|
local input=$2
|
||||||
|
parse_input "$spec_name" "$input" || printf 'Error: Failed to parse input\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------- spec and tests ----------
|
||||||
|
SPEC=(
|
||||||
|
"command;barg;Barg - Bash Argument Parser"
|
||||||
|
"argument;global;type:flag"
|
||||||
|
"command;build;Build a dev container"
|
||||||
|
"argument;from;type:option;dest:fromName;help:Name of the base container"
|
||||||
|
"argument;name;type:positional;required;help:Name of the container"
|
||||||
|
"argument;nametwo;help:Name of the container"
|
||||||
|
"argument;image,i;type:option;required;dest:imageName;help:Base image"
|
||||||
|
"argument;include,I;type:option;repeatable;dest:includePaths;help:Include paths"
|
||||||
|
"argument;verbose,v;type:flag;repeatable;default:0;help:Increase verbosity (-v, -vv, ...)"
|
||||||
|
"argument;quiet,q;type:flag;default:false;help:Suppress all output"
|
||||||
|
"argument;cmd;type:rest;help:Command and args to run inside the container"
|
||||||
|
"argument;name;dest:nameOpt;type:option;help:Building container name"
|
||||||
|
"command;container;Container building"
|
||||||
|
"argument;dev;type:flag;help:Is dev or not"
|
||||||
|
"argument;name;required;help:Building container name"
|
||||||
|
"argument;cmd;type:rest;dest:cmdRest;help:Command and args to run inside the container"
|
||||||
|
"end"
|
||||||
|
"end"
|
||||||
|
"command;stop;Stop a dev container"
|
||||||
|
"argument;name;type:positional;required;help:Name of the container"
|
||||||
|
"argument;kill,k;type:flag;default:false;help:Force kill the container"
|
||||||
|
"end"
|
||||||
|
"end"
|
||||||
|
)
|
||||||
|
|
||||||
|
INPUTS=(
|
||||||
|
"dev build -i myimage buildtwo --from base-dev --image node:18 --include src --include test -v -v -q mycontainer -- npm run start"
|
||||||
|
"dev build buildname container -i myimage --dev --name webapp externalContainer"
|
||||||
|
"dev --global build --image debian mybox container --dev --name webapp externalContainer -- echo hi"
|
||||||
|
"dev stop mycontainer --kill"
|
||||||
|
"dev stop mycontainer --kill -- not specified one"
|
||||||
|
"dev build --from alpine --image node:20 mybox -- echo hi"
|
||||||
|
"dev build --aaaaa --from alpine --image node:20 --bbbbb orb mybox ccccc -- echo hi"
|
||||||
|
)
|
||||||
|
|
||||||
|
for input in "${INPUTS[@]}"; do
|
||||||
|
printf "\n\n------------------------\n\n> %s\n" "$input"
|
||||||
|
parse_input_wrap SPEC "$input"
|
||||||
|
done
|
||||||
Loading…
Reference in New Issue
Block a user