Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support separate files for subcommand implementations #254

Closed
lexun opened this issue Oct 4, 2023 · 8 comments
Closed

Support separate files for subcommand implementations #254

lexun opened this issue Oct 4, 2023 · 8 comments

Comments

@lexun
Copy link

lexun commented Oct 4, 2023

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!

@sigoden
Copy link
Owner

sigoden commented Oct 5, 2023

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

@lexun
Copy link
Author

lexun commented Oct 5, 2023

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?

@sigoden
Copy link
Owner

sigoden commented Oct 5, 2023

cf2cb38 solved the problem.

Build argc from main branch or wait for the next realease. Then try agin.

@lexun
Copy link
Author

lexun commented Oct 5, 2023

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 🙏

@lexun lexun closed this as completed Oct 5, 2023
@davide-ganito
Copy link

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!

@sigoden
Copy link
Owner

sigoden commented May 20, 2024

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.

@davide-ganito
Copy link

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!

@davide-ganito
Copy link

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"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants