Skip to content

Commit

Permalink
Add --install-autocomplete command for bash completion only
Browse files Browse the repository at this point in the history
  • Loading branch information
igrek51 committed Jun 21, 2020
1 parent 892de75 commit f5df7bb
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 85 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ __pycache__
*.egg-info
build/
dist/
/venv

.idea
*.iml
32 changes: 16 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,15 +411,15 @@ CliBuilder(
Defaults options are:
-h, --help: displaying help,
--version: displaying version,
--bash-install APP-NAME: installing application in bash with autocompleting,
--bash-autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash
--install-bash APP-NAME: installing application in bash with autocompleting,
--autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash

`usage_onerror` - wheter usage output should be displayed on syntax error

`reraise_error` - wheter syntax error should not be caught but reraised instead.
Enabling this causes stack trace to be flooded to the user.

`hide_internal` - wheter internal options (`--bash-install`, `--bash-autocomplete`) should be hidden on help output.
`hide_internal` - wheter internal options (`--install-bash`, `--autocomplete`) should be hidden on help output.

### Step 2. Declaring CLI rules
The next step is to declare CLI rules for `CliBuilder` using `.has()` method
Expand Down Expand Up @@ -1005,11 +1005,11 @@ CliBuilder('completers-demo').has(

In order to enable auto-completion, you need to install some extension to bash. Fortunately `cliglue` has built-in tools to do that:
```console
foo@bar:~$ sudo ./completers.py --bash-install completers-demo
foo@bar:~$ sudo ./completers.py --install-bash completers-demo
[info] creating link: /usr/bin/completers-demo -> ~/cliglue/docs/example/completers.py
#!/bin/bash
_autocomplete_98246661() {
COMPREPLY=( $(completers-demo --bash-autocomplete "${COMP_LINE}") )
COMPREPLY=( $(completers-demo --autocomplete "${COMP_LINE}") )
}
complete -F _autocomplete_98246661 completers-demo
[info] Autocompleter has been installed in /etc/bash_completion.d/autocomplete_completers-demo.sh. Please restart your shell.
Expand All @@ -1021,8 +1021,8 @@ foo@bar:~$ completers-d[Tab]
foo@bar:~$ completers-demo

foo@bar:~$ completers-demo [Tab][Tab]
--bash-autocomplete -h --mode --output
--bash-install --help --mode= --output=
--autocomplete -h --mode --output
--install-bash --help --mode= --output=

foo@bar:~$ completers-demo --mo[Tab]
foo@bar:~$ completers-demo --mode
Expand Down Expand Up @@ -1061,7 +1061,7 @@ CliBuilder().has(
In order to enable the autocompletion, there must be a specific script in `/etc/bash_completion.d/`.
With `cliglue` you just need to run:
```console
# sudo ./sample-app.py --bash-install sample-app
# sudo ./sample-app.py --install-bash sample-app
```
It will install autocompletion script and add a symbolic link in `/usr/bin/`,
so as you can run your app with `sample-app` command instead of `./sample_app.py`.
Expand All @@ -1074,17 +1074,17 @@ Sometimes, you need to make some modifications in your code,
but after these modifications you will NOT need to reinstall autocompletion again.
You had to do it only once, because autocompletion script only redirects its query and run `sample_app.py`:
```console
sample-app --bash-autocomplete "sample-app --he"
sample-app --autocomplete "sample-app --he"
```

### How does auto-completion work?
1. While typing a command in `bash`, you hit `Tab` key. (`your-app.py cmd[TAB]`)
2. `bash` looks for an autocompletion script in `/etc/bash_completion.d/`.
There should be a script installed for your command after running `--bash-install` on your application.
There should be a script installed for your command after running `--install-bash` on your application.
So when it's found, this script is called by bash.
3. The autocompletion script redirects to your application, running it with `--bash-autocomplete` option, namely script runs `your-app.py --bash-autocomplete "cmd"`, asking it for returning the most relevant command proposals.
3. The autocompletion script redirects to your application, running it with `--autocomplete` option, namely script runs `your-app.py --autocomplete "cmd"`, asking it for returning the most relevant command proposals.
Notice that in that manner, the autocompletion algorithm is being run always in up-to-date version.
4. `your-app.py` has `--bash-autocomplete` option enabled by default so it starts to analyze which keyword from your CLI definition is the most relevant to the currently typed word (`cmd`).
4. `your-app.py` has `--autocomplete` option enabled by default so it starts to analyze which keyword from your CLI definition is the most relevant to the currently typed word (`cmd`).
5. If you defined custom completers functions, they will be invoked right now (if needed) in order to get up-to-date proposals and analyze them as well.
6. `your-app.py` returns a list of proposals to the `bash`.
7. `bash` shows you these results.
Expand Down Expand Up @@ -1147,8 +1147,8 @@ Usage:
Options:
--version - Print version information and exit
-h, --help [SUBCOMMANDS...] - Display this help and exit
--bash-install APP-NAME - Install script as a bash binary and add autocompletion links
--bash-autocomplete [CMDLINE...] - Return matching autocompletion proposals
--install-bash APP-NAME - Install script as a bash binary and add autocompletion links
--autocomplete [CMDLINE...] - Return matching autocompletion proposals

Commands:
git
Expand Down Expand Up @@ -1573,8 +1573,8 @@ Usage:
Options:
--version - Print version information and exit
-h, --help [SUCOMMANDS...] - Display this help and exit
--bash-install APP-NAME - Install script as a bash binary and add autocompletion links
--bash-autocomplete [CMDLINE...] - Return matching autocompletion proposals
--install-bash APP-NAME - Install script as a bash binary and add autocompletion links
--autocomplete [CMDLINE...] - Return matching autocompletion proposals

Commands:
git
Expand Down
14 changes: 12 additions & 2 deletions cliglue/autocomplete/bash_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cliglue.utils.shell import shell


def bash_install(app_name: str):
def install_bash(app_name: str):
"""
Install script link in /usr/bin/{app_name}
and create bash autocompletion script
Expand All @@ -31,6 +31,16 @@ def bash_install(app_name: str):
info(f'creating link: {usr_bin_executable} -> {app_path}')
shell(f'ln -s {app_path} {usr_bin_executable}')

install_autocomplete(app_name)


def install_autocomplete(app_name: str):
"""
Create bash autocompletion script
"""
if os.geteuid() != 0:
warn("you may need to have root privileges in order to install autocompletion script")

# bash autocompletion install
completion_script_path: str = f'/etc/bash_completion.d/cliglue_{app_name}.sh'
app_hash: int = hash(app_name) % (10 ** 8)
Expand All @@ -39,7 +49,7 @@ def bash_install(app_name: str):
shell(f"""cat << 'EOF' | tee {completion_script_path}
#!/bin/bash
{function_name}() {{
COMPREPLY=( $({app_name} --bash-autocomplete "${{COMP_LINE}}" ${{COMP_CWORD}}) )
COMPREPLY=( $({app_name} --autocomplete "${{COMP_LINE}}" ${{COMP_CWORD}}) )
}}
complete -F {function_name} {app_name}
EOF
Expand Down
23 changes: 15 additions & 8 deletions cliglue/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Callable, List, Optional

from cliglue.autocomplete.autocomplete import bash_autocomplete
from cliglue.autocomplete.bash_install import bash_install
from cliglue.autocomplete.bash_install import install_bash, install_autocomplete
from cliglue.help.help import print_version, print_help, print_usage
from cliglue.parser.error import CliSyntaxError, CliDefinitionError
from cliglue.parser.parser import Parser
Expand Down Expand Up @@ -33,12 +33,12 @@ def __init__(self,
Defaults options are:
-h, --help: displaying help,
--version: displaying version,
--bash-install APP-NAME: installing application in bash with autocompleting,
--bash-autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash
--install-bash APP-NAME: installing application in bash with autocompleting,
--autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash
:param usage_onerror: wheter usage output should be displayed on syntax error
:param reraise_error: wheter syntax error should not be caught but reraised instead.
Enabling this causes stack trace to be flooded to the user.
:param hide_internal: wheter internal options (--bash-install, --bash-autocomplete)
:param hide_internal: wheter internal options (--install-bash, --autocomplete)
should be hidden on help output.
"""
self.__name: str = name
Expand Down Expand Up @@ -116,8 +116,11 @@ def __print_subcommand_help(subcommands: List[str]):
def __print_version():
print_version(self.__name, self.__version)

def __bash_install(app_name: str):
bash_install(app_name)
def __install_bash(app_name: str):
install_bash(app_name)

def __install_autocomplete(app_name: str):
install_autocomplete(app_name)

def __bash_autocomplete(cmdline: str, word_idx: Optional[int]):
self.__bash_autocomplete(cmdline, word_idx)
Expand All @@ -131,12 +134,16 @@ def __bash_autocomplete(cmdline: str, word_idx: Optional[int]):
primary_option('-h', '--help', run=__print_subcommand_help, help='Display this help and exit').has(
arguments('subcommands'),
),
primary_option('--bash-install', run=__bash_install,
primary_option('--install-bash', run=__install_bash,
help='Install this program in bash to be executable from anywhere, '
'add autocompletion links').has(
argument('app-name', help='binary name'),
),
primary_option('--bash-autocomplete', run=__bash_autocomplete,
primary_option('--install-autocomplete', run=__install_autocomplete,
help='Install autocompletion links').has(
argument('app-name', help='binary name'),
),
primary_option('--autocomplete', run=__bash_autocomplete,
help='Return matching autocompletion proposals').has(
argument('cmdline', help='current command line'),
argument('word-idx', help='current word index', type=int, required=False),
Expand Down
5 changes: 2 additions & 3 deletions cliglue/help/help.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import sys
from typing import List, Set, Optional

from dataclasses import dataclass, field
from typing import List, Set, Optional

from cliglue.builder.rule import PrimaryOptionRule, ParameterRule, FlagRule, CliRule, SubcommandRule, \
PositionalArgumentRule, ManyArgumentsRule, DictionaryRule, ValueRule
Expand All @@ -22,7 +21,7 @@ class _OptionHelp(object):
subrules: List[CliRule] = field(default_factory=lambda: [])


internal_options = {'--bash-autocomplete', '--bash-install'}
internal_options = {'--autocomplete', '--install-bash', '--install-autocomplete'}


def print_help(rules: List[CliRule], app_name: str, version: str, help: str, subargs: List[str], hide_internal: bool):
Expand Down
18 changes: 9 additions & 9 deletions docs/autocompletion.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ CliBuilder('completers-demo').has(

In order to enable auto-completion, you need to install some extension to bash. Fortunately `cliglue` has built-in tools to do that:
```console
foo@bar:~$ sudo ./completers.py --bash-install completers-demo
foo@bar:~$ sudo ./completers.py --install-bash completers-demo
[info] creating link: /usr/bin/completers-demo -> ~/cliglue/docs/example/completers.py
#!/bin/bash
_autocomplete_98246661() {
COMPREPLY=( $(completers-demo --bash-autocomplete "${COMP_LINE}") )
COMPREPLY=( $(completers-demo --autocomplete "${COMP_LINE}") )
}
complete -F _autocomplete_98246661 completers-demo
[info] Autocompleter has been installed in /etc/bash_completion.d/autocomplete_completers-demo.sh. Please restart your shell.
Expand All @@ -53,8 +53,8 @@ foo@bar:~$ completers-d[Tab]
foo@bar:~$ completers-demo

foo@bar:~$ completers-demo [Tab][Tab]
--bash-autocomplete -h --mode --output
--bash-install --help --mode= --output=
--autocomplete -h --mode --output
--install-bash --help --mode= --output=

foo@bar:~$ completers-demo --mo[Tab]
foo@bar:~$ completers-demo --mode
Expand Down Expand Up @@ -93,7 +93,7 @@ CliBuilder().has(
In order to enable the autocompletion, there must be a specific script in `/etc/bash_completion.d/`.
With `cliglue` you just need to run:
```console
# sudo ./sample-app.py --bash-install sample-app
# sudo ./sample-app.py --install-bash sample-app
```
It will install autocompletion script and add a symbolic link in `/usr/bin/`,
so as you can run your app with `sample-app` command instead of `./sample_app.py`.
Expand All @@ -106,17 +106,17 @@ Sometimes, you need to make some modifications in your code,
but after these modifications you will NOT need to reinstall autocompletion again.
You had to do it only once, because autocompletion script only redirects its query and run `sample_app.py`:
```console
sample-app --bash-autocomplete "sample-app --he"
sample-app --autocomplete "sample-app --he"
```

### How does auto-completion work?
1. While typing a command in `bash`, you hit `Tab` key. (`your-app.py cmd[TAB]`)
2. `bash` looks for an autocompletion script in `/etc/bash_completion.d/`.
There should be a script installed for your command after running `--bash-install` on your application.
There should be a script installed for your command after running `--install-bash` on your application.
So when it's found, this script is called by bash.
3. The autocompletion script redirects to your application, running it with `--bash-autocomplete` option, namely script runs `your-app.py --bash-autocomplete "cmd"`, asking it for returning the most relevant command proposals.
3. The autocompletion script redirects to your application, running it with `--autocomplete` option, namely script runs `your-app.py --autocomplete "cmd"`, asking it for returning the most relevant command proposals.
Notice that in that manner, the autocompletion algorithm is being run always in up-to-date version.
4. `your-app.py` has `--bash-autocomplete` option enabled by default so it starts to analyze which keyword from your CLI definition is the most relevant to the currently typed word (`cmd`).
4. `your-app.py` has `--autocomplete` option enabled by default so it starts to analyze which keyword from your CLI definition is the most relevant to the currently typed word (`cmd`).
5. If you defined custom completers functions, they will be invoked right now (if needed) in order to get up-to-date proposals and analyze them as well.
6. `your-app.py` returns a list of proposals to the `bash`.
7. `bash` shows you these results.
Expand Down
6 changes: 3 additions & 3 deletions docs/builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ CliBuilder(
Defaults options are:
-h, --help: displaying help,
--version: displaying version,
--bash-install APP-NAME: installing application in bash with autocompleting,
--bash-autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash
--install-bash APP-NAME: installing application in bash with autocompleting,
--autocomplete [CMDLINE...]: internal action for generating autocompleted proposals to be handled by bash

`usage_onerror` - wheter usage output should be displayed on syntax error

`reraise_error` - wheter syntax error should not be caught but reraised instead.
Enabling this causes stack trace to be flooded to the user.

`hide_internal` - wheter internal options (`--bash-install`, `--bash-autocomplete`) should be hidden on help output.
`hide_internal` - wheter internal options (`--install-bash`, `--autocomplete`) should be hidden on help output.

### Step 2. Declaring CLI rules
The next step is to declare CLI rules for `CliBuilder` using `.has()` method
Expand Down
4 changes: 2 additions & 2 deletions docs/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ Usage:
Options:
--version - Print version information and exit
-h, --help [SUCOMMANDS...] - Display this help and exit
--bash-install APP-NAME - Install script as a bash binary and add autocompletion links
--bash-autocomplete [CMDLINE...] - Return matching autocompletion proposals
--install-bash APP-NAME - Install script as a bash binary and add autocompletion links
--autocomplete [CMDLINE...] - Return matching autocompletion proposals

Commands:
git
Expand Down
2 changes: 1 addition & 1 deletion docs/example/peek-demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ if __name__ == '__main__':
main()
```

sudo ./autocomplete-demo.py --bash-install autocomplete-demo
sudo ./autocomplete-demo.py --install-bash autocomplete-demo

bash

Expand Down
4 changes: 2 additions & 2 deletions docs/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ Usage:
Options:
--version - Print version information and exit
-h, --help [SUBCOMMANDS...] - Display this help and exit
--bash-install APP-NAME - Install script as a bash binary and add autocompletion links
--bash-autocomplete [CMDLINE...] - Return matching autocompletion proposals
--install-bash APP-NAME - Install script as a bash binary and add autocompletion links
--autocomplete [CMDLINE...] - Return matching autocompletion proposals

Commands:
git
Expand Down
Loading

0 comments on commit f5df7bb

Please sign in to comment.