Support separate files for subcommand implementations
lexun opened this issue · 8 comments
Hello, love the project! What a clean API and implementation!
Just curious what you'd think about supporting subcommands being implemented in other files. I'm not currently seeing a way to break large CLI projects down into smaller files for organization purposes.
I was thinking something like this could be useful:
# main.sh
# @describe A demo cli
# @cmd Upload a file
# @alias u
# @arg target! File to upload
upload() {
echo "cmd upload"
echo "arg: target $argc_target"
}
# @cmd Download a file
# @cmd-file ./subcommands/download.sh
eval "$(argc --argc-eval "$0" "$@")"
# subcommands/download.sh
# @alias d
# @flag -f --force Override existing file
# @option -t --tries <NUM> Set number of retries to NUM
# @arg source! Url to download from
# @arg target Save file to
download() {
echo "cmd: download"
echo "flag: --force $argc_force"
echo "option: --tries $argc_tries"
echo "arg: source $argc_source"
echo "arg: target $argc_target"
}
This might just be opening a big can of worms, but I'd love to hear your thoughts!
Including files are prone to errors.
The recommended approach is to use the external subcommand.
main.sh
# @cmd Download a file
# @arg args~
download() {
./subcommands/download.sh "$@"
}
subcommands/download.sh
# @flag -f --force Override existing file
# @option -t --tries <NUM> Set number of retries to NUM
# @arg source! Url to download from
# @arg target Save file to
Interesting, I like that better in theory. I can't quite get the documentation delegated properly though.
For instance, if I run ./main.sh download -h
, I'll get the following:
Download a file
USAGE: main download [ARGS]...
ARGS:
[ARGS]...
If I pass another arg before -h
then it works, for example with ./main.sh download my-source -h
USAGE: download [OPTIONS] <SOURCE> [TARGET]
ARGS:
<SOURCE> Url to download from
[TARGET] Save file to
OPTIONS:
-f, --force Override existing file
-t, --tries <NUM> Set number of retries to NUM
-h, --help Print help
These are the full implementations I'm testing with:
# main.sh
# @describe A demo cli
# @cmd Download a file
# @arg args~
download() {
./subcommands/download.sh "$@"
}
eval "$(argc --argc-eval "$0" "$@")"
# subcommands/download.sh
# @alias d
# @flag -f --force Override existing file
# @option -t --tries <NUM> Set number of retries to NUM
# @arg source! Url to download from
# @arg target Save file to
main() {
echo "cmd: download"
echo "flag: --force $argc_force"
echo "option: --tries $argc_tries"
echo "arg: source $argc_source"
echo "arg: target $argc_target"
}
eval "$(argc --argc-eval "$0" "$@")"
It seems like skipping the special behavior around known args like -h
and -v
when encountering @arg args~
would solve this. Is there currently a way to override that behavior?
cf2cb38 solved the problem.
Build argc from main branch or wait for the next realease. Then try agin.
That works! 🎉
And wow, only 12 minutes from the time I posted you already had a fix on main.
You're an open source legend, thank you 🙏
Hello and thank you very much for this wonderful project!
Can you help me understand how to generate completion from all subcommands, recursively, if they are defined in separate files? Thx!
The core for generate completion from seperate file is:
argc --argc-compgen generic <argc-file> <arg>s...
Example
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
# @cmd Download a file
# @arg args~[?`_choice_args`]
download() {
"$SCRIPT_DIR/subcommands/download.sh" "$@"
}
# @cmd Upload a file
# @arg args~[?`_choice_args`]
upload() {
"$SCRIPT_DIR/subcommands/upload.sh" "$@"
}
_choice_args() {
args=( "${argc__positionals[@]}" )
args[-1]="$ARGC_LAST_ARG"
argc --argc-compgen generic "$SCRIPT_DIR/subcommands/$argc__cmd_fn.sh" "$argc__cmd_fn" "${args[@]}"
}
# See more details at https://github.com/sigoden/argc
eval "$(argc --argc-eval "$0" "$@")"
Explain
Why need args[-1]="$ARGC_LAST_ARG"
?
Argc has specially processed the last argument, such as removing quote or something, which needs to be restored here.
That's awesome! Thanks, also for the explanation!
If I understand well, we need to be sure that the primary command name (the bash function name) is equal to the subcommand filename in order to use $argc__cmd_fn
as lookup key. This method than works perfectly with command alias!
I noticed that also help
is always listed as possible completion.
It's not a big deal, I solved with grep, but I think could be useful to let you know.
This is my solution:
argc --argc-compgen generic "$commands/${argc__cmd_fn:?}.sh" "$argc__cmd_fn" "${args[@]}" | grep -v "help"