Coder Social home page Coder Social logo

Bash completions do not work about bashly HOT 19 CLOSED

dannyben avatar dannyben commented on May 25, 2024
Bash completions do not work

from bashly.

Comments (19)

iamjackg avatar iamjackg commented on May 25, 2024 1

Just to chime in, I also ran into this a while ago and ended up using option 1, but in my case the script is supposed to always be called from its own directory, so using a different relative path is not a case I needed to cover.

That being said, what about switching from checking that $COMP_LINE starts with <script_name> <command> to checking that ${COMP_WORDS[1]} (i.e. the second word in $COMP_LINE) matches <command>?

Using complete -F _cli_completions cli is already telling bash that this function should be used for cli, even when referring to the script from a relative/absolute path, so by the time the function is called we're sure we're being called for cli: there's no need to check for that again.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024 1

How about this? Will this work in all cases to your knowledge?

This is trying Jack's idea:

  1. Use COMP_LINE without its first word when matching
  2. Match patterns changed to not include the command name
#!/usr/bin/env bash

# This bash completions script was generated by
# completely (https://github.com/dannyben/completely)
# Modifying it manually is not recommended
# shellcheck disable=SC2207
_mygit_completions() {
  local cur=${COMP_WORDS[COMP_CWORD]}
  local comp_line="${COMP_WORDS[*]:1}"

  case "$comp_line" in
    'status'*) COMPREPLY=($(compgen -W "forSTATUS1 forSTATUS2" -- "$cur")) ;;
    'commit'*) COMPREPLY=($(compgen -W "forCOMMIT1 forCOMMIT2" -- "$cur")) ;;
    *) COMPREPLY=($(compgen -W "status commit" -- "$cur")) ;;
  esac
}

complete -F _mygit_completions mygit

This can be tested by saving and sourcing it, and then:

$ mygit s<tab>        #=> mygit status
$ mygit c<tab>        #=> mygit commit
$ mygit commit <tab>  #=> mygit commit forCOMMIT1
$ ...

From what I can tell, this works with or without a leading path string (either ./ or even /longer/path/mygit)

from bashly.

iamjackg avatar iamjackg commented on May 25, 2024 1

Well - the new implementation doesn't change anything in that regard does it?

Yeah exactly, it was just an FYI because I noticed it while researching. Thanks for being so quick, as usual!

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

Am I doing something wrong or is this a bug?

It seems like a little bit of both:

  1. You are doing it wrong
  2. Since the documentation or example did not help you enough, its a documentation bug

Steps I took...

The command bashly init creates an initial src/bashly.yml for you.
So the order of things you have done is incorrect, you should either run bashly init, and then paste the example YAML in it, or, create the file src/bashly.yml manually.

Better than both these options, you can take a look (and clone) the files in the same example folder on GitHub. The reason these examples are on GitHub, is that they will include the files needed.

Note that, unlike the example, I do not have a completions command

This probably means that you did not copy the YAML as is. It looks like you are showing the output of the commands example.

Notice that the first command in the YAML is completions:

commands:
- name: completions
help: |-
Generate bash completions
Usage: eval "\$(cli completions)"

Now, that the bashly add comp function just adds a function that you can call from anywhere in your script. In this example, it is from a completions command. So take a look in the src/completions_command.sh file - since it calls this function:

The reason the completions feature is implemented like this, where it requires a little more involvement from you, is that I suspect that different users would want to use it differently:

  1. Some would want it in their-cli completions command
  2. Others would want it in their-cli --completions flag
  3. Others would want it as a standalone script, that they install in their user-level completions directory
  4. etc.

You can also take a look at the completions docs, hopefully they are helpful. If not, let me know what would help to clarify these instructions or example.

from bashly.

ndepal avatar ndepal commented on May 25, 2024

Thanks for the fast response!

Doing bashly init first and then editing the yml file it generates works as expected.
I would have also overlooked that I needed to manually call send_completions in the compeltions_command.sh. But it makes sense that I have to do that.

Now the bash completions work ... sort of.
I'm not familiar with bash completion scripts, but it looks to me like it only registers completions for commands that start with cli. I.e., if I call the script as ./cli, it won't work. I have to install it / put it on my $PATH in order to be able to use the completion feature.

Is there a way of making it work without installing the bash script? (I guess this might be more of a https://github.com/DannyBen/completely issue)
Most of my use cases will be with scripts that live inside of some project repo, and I'd call them like ./tools/myscript. I wouldn't want to have to put all of these tools/ directories on the path.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

I would have also overlooked that I needed to manually call send_completions in the compeltions_command.sh

I will work on the examples a little, to clarify these tricky points.

it only registers completions for commands that start with cli. I.e., if I call the script as ./cli, it won't work.

Well, yes, this is by design.
Bashly is intended to design scripts that look and feel like any other command line tool.

Except for the obvious option to copy this file to your path, you have at least a couple of options now:

Option 1: Change name in bashly.yml

You can try to name the tool (in bashly.yml) as name: ./cli. Although I did not design it for such a use case, it might work without a problem. After doing this, run bashly add comp function && bashly g again to regenerate the completions.

Option 2: Add current directory to PATH globally

Add the current directory to the PATH. This is normally present in your ~/.bashrc or other init script:

$ export PATH=".:$PATH"
$ cli --version
0.1.0

Option 3: Add current directory to PATH locally

If having the current directory in the path is not something you want configured globally, you can use a tool like direnv (recommended not only for this problem) to temporarily and automatically add/update environment variables when you cd into a directory.

Then, you just need to create a .envrc file with this content:

export PATH=".:$PATH"

and it will only be in effect when you are inside this folder.

I guess this might be more of a completely issue

Not necessarily. Bashly sends the name of the application to the completely library, so as we have seen, changing the name, changes the generated script.

If none of the above seem like good options for you, I might be convinced to continue thinking about a solution.

from bashly.

ndepal avatar ndepal commented on May 25, 2024

Maybe it's not common to use bash completion on non-installed scripts, in which case perhaps it should not work the way I expected it to.

That being said, I can achieve the behavior I would like by modifying the send_completions() function as follows:

send_completions() {
  echo $'#!/usr/bin/env bash'
  echo $''
  echo $'# This bash completions script was generated by'
  echo $'# completely (https://github.com/dannyben/completely)'
  echo $'# Modifying it manually is not recommended'
  echo $'_cli_completions() {'
  echo $'  local CMD_NAME=$(readlink -f "$1")'
  echo $'  local SCRIPT_PATH="'$(realpath $0)'"'
  echo $'  if [[ "$CMD_NAME" != "$SCRIPT_PATH" ]] ; then'
  echo $'    # not for us'
  echo $'    return'
  echo $'  fi'
  echo $''
  echo $'  local cur=${COMP_WORDS[COMP_CWORD]}'
  echo $'  COMPREPLY=($(compgen -W "$CMD_NAME" -- "$cur"))'
  echo $'  case "$COMP_LINE" in'
  echo $'    *\'cli completions\'*) COMPREPLY=($(compgen -W "--help -h" -- "$cur")) ;;'
  echo $'    *\'cli download\'*) COMPREPLY=($(compgen -A file -W "--force --help -f -h" -- "$cur")) ;;'
  echo $'    *\'cli upload\'*) COMPREPLY=($(compgen -A directory -A user -W "--help --password --user -h -p -u" -- "$cur")) ;;'
  echo $'    *\'cli\'*) COMPREPLY=($(compgen -W "--help --version -h -v completions download upload" -- "$cur")) ;;'
  echo $'  esac'
  echo $'}'
  echo $''
  echo $'complete -F _cli_completions cli'
}

The most important change is in the case statement: Each case option is prefixed with *, s.t. any invocation of cli, whether it's relative or not, works (./cli, cli, /tmp/bashlytest/cli all match).

complete -F _cli_completions cli will cause our function to be called regardless of the command used, as described here.

At the top also added a check that the executable being completed is actually the one that we're defining the completion function for, just in case there are name collisions.

If you don't see any downsides, consider modifying completely to generate the completion function like this.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

No...

First of all, modifying the completion script manually is totally not recommended.
It should be regenerated whenever you update bashly.yml.

Did you try any of my proposed solutions?

from bashly.

ndepal avatar ndepal commented on May 25, 2024

The options you proposed do not solve my problem (or not as well as my solution, in my opinion).

  • Option 1: I am still only able to call the function from one location. If I'm in the parent directory, completions stop working
  • Option 2: This is a bit better, but still not as nice as what I'm proposing. It requires the user to do one additional step to get this working
  • Option 3: I did not know about direnv, that is interesting. But now I have to ask people to install/enable an additional tool, which is not ideal. But this is probably the best of the three options.

My proposal allows calling the script from anywhere without needing extra steps/tools.

First of all, modifying the completion script manually is totally not recommended.

Yes I know. I just did this to test whether what I want to do actually works. The solution I'm proposing is to modify the code generation in completely to produce this output.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

Oh well.

I am not sure that prefixing the completion pattern with a wildcard is a good idea. I don't know what can be the side effects.

I feel that completions should be a part of scripts with fixed names that exist in the path.

Bashly is perfectly capable of generating scripts for "local consumption", but expecting these to also have bash completions (which is a global feature by nature), sits a bit awkwardly with me. What if you have more than one repo with the same name of the CLI, just in different folders?

At this point I am leaning towards not modifying anything. I might be convinced otherwise as time passes, or more information becomes available though.

I did not know about direnv, that is interesting. But now I have to ask people to install/enable an additional tool, which is not ideal.

Since we are dealing with completions, you will have to instruct users to add something to their ~/.bashrc... just for having completions to a script that only works in this folder, they need to add something to their boot sequence - how is this better?

This is my advice.

  1. If the users of your repo that contains the generated script are co workers, your README should include whatever "setup" instructions they need to start developing
  2. Having a local "helper" script is a great option for developers. It does not need autocomplete.
  3. If it is complex, and you feel that autocomplete is mandatory, then either use direnv (I believe you can also run eval $(your-cli completions) in it), or have your users add the script to the path, or the current directory to the path. Either way, there is no way to add completions without the end user running something, or you providing a setup script that does that for them.

I feel there are several solutions, and some ways to avoid the problem altogether.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

Thanks @iamjackg,

Do you have an example completions script that operates as you mention, but still maintains the same principles of the completion script that bashly generates? (i.e. still matching patterns by longest first, and without a prefix *).

Or even better, what changes would you propose in this template from the completely gem?

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

In the meantime, I have updated all the examples, including the completions example, for improved clarity.

from bashly.

iamjackg avatar iamjackg commented on May 25, 2024

Haha, you beat me to it! I was about to post an almost identical snippet! Looks good to me (obviously 😜)

from bashly.

iamjackg avatar iamjackg commented on May 25, 2024

This has the additional advantage of working even if people add extra spaces between the commands. E.g. this

script     command   subcommand

will still autocomplete.

It does not work with flags specified in the --flag=value format because, by default, = is part of $COMP_WORDBREAKS, but Bashly doesn't support that format anyway, so we're good. In case you ever need it (or you're just curious), this is how git's autocomplete solves that issue!

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

Haha, you beat me to it! I was about to post an almost identical snippet!

Sorry - it bugged me, had to try something... :)

It does not work with flags specified in the --flag=value

Well - the new implementation doesn't change anything in that regard does it?
I mean, it didn't work before, and wont work in the new one.

Although bashly does not support the --flag=value notation, if there is an easy way to at least have it work in the completely gem, it wouldn't hurt.

In any case, I will open a separate ticket for the new feature, and implement it in completely and then in bashly.

I will CC this ticket, so we have links.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

Thank you both for this ticket.

You can now enjoy the fruits of your labor with version 0.6.5, which uses completely 0.2.0, which generates completion scripts that will work even when the script is prefixed by any path.

from bashly.

ndepal avatar ndepal commented on May 25, 2024

Thanks a lot to both of you! This is great.

Just to note, since this does not have the check I proposed, it is possible to get wrong autocomplete suggestions if e.g. you have a script called cli on your $PATH, but have eval-ed the completions for another ./cli.

local CMD_NAME=$(readlink -f "$1")
local SCRIPT_PATH="'$(realpath $0)'"
if [[ "$CMD_NAME" != "$SCRIPT_PATH" ]] ; then
  # not for us
  return
fi

But you're probably right in feeling like this:

Bashly is perfectly capable of generating scripts for "local consumption", but expecting these to also have bash completions (which is a global feature by nature), sits a bit awkwardly with me.

The change I proposed does address this concern though:

What if you have more than one repo with the same name of the CLI, just in different folders?

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

Autocomplete relies on the basename of the script.

If you have two scripts with the same name, you should avoid enabling autocomplete globally for them.

from bashly.

DannyBen avatar DannyBen commented on May 25, 2024

BTW @ndepal - as I understand your use case is to create little scripts for each of your projects, you might want to take a look at opcode. This is what I use for a per-project shortcuts.

This is of course not a full blown "script generator" like bashly, but if your scripts mainly run other commands, then opcode is perfect for this.

I then have op.conf files committed to my git repos, and everybody can easily run the shortcuts.

from bashly.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.