Giter Site home page Giter Site logo

adaszko / complgen Goto Github PK

View Code? Open in Web Editor NEW
213.0 4.0 7.0 822 KB

Declarative bash/fish/zsh completions without writing shell scripts

License: Apache License 2.0

Rust 80.27% Python 18.67% Shell 1.06%
bash-shell fish-shell shell-completion zsh-shell bash completion completions fish zsh

complgen's Introduction

Value Proposition

complgen allows you to generate completion scripts for all major shells from a single, concise EBNF-like grammar. It compiles the grammar down to a standalone bash/fish/zsh shell script that can be distributed on its own. As a separate use case, it can also produce completions directly on stdout, which is meant to be used in interactive shells (see below).

complgen takes flags to complete from grammar files. Ideally, the grammar files are meant to be developed and versioned along with the completed command line tool to avoid version mismatches. There's nothing stopping you however from writing the grammar file yourself, optionally tailoring it for your most-frequent use cases, sort of like shell aliases on steroids.

Demo

asciicast

Usage

There are two ways to use complgen:

1. Generate standalone completion scripts for bash/fish/zsh:

This mode is most useful if you're a CLI tool author and want to ship shell completions in your installation package.

$ complgen aot --bash-script grep.bash usage/small.usage
$ bash
$$ source grep.bash
$$ grep --color <TAB>
always auto never

Note: ZSH also supports automatic loading of completion scripts. It is enough to place the generated script at one of directories listed in the $fpath variable.

2. Generate completions on stdout by compiling the grammar "just-in-time":

This mode is useful if you're command line user and want to improve the CLI experience on your machine by either implementing a missing autocompletion for a specific CLI tool, or override the default one with a one better tailored for your needs and usage patterns. Or you simply want to iterate quickly on a .usage file before you compile it to a shell script.

$ complgen jit usage/small.usage bash -- --color
always
auto
never

The just-in-time mode is intended to be further integrated with shells so that it provides completions directly from grammars, bypassing compilation and sourceing completion shell script files.

Note that it is assummed the .usage file stem is the same as the completed command name, so to complete grep command, its grammar needs to land in grep.usage.

Bash Integration

Note: This assumes you have bash-completion OS-level package installed and it's been sourced! It often boils down to apt install bash-completion; source /etc/bash_completion or brew install bash-completion; source /opt/homebrew/etc/profile.d/bash_completion.sh, depending on your OS. Without this package, scripts generated by complgen are not able to correctly process command lines containing characters like =, :, @, or any other from $COMP_WORDBREAKS.

See also https://github.com/git/git/commit/da48616f1df51ff43acc64cdf8966f7b72142a11

Assumming your .usage files are stored in the ~/.config/complgen directory, add this to your ~/.bashrc:

for path in ~/.config/complgen/*.usage; do
    stem=$(basename "$path" .usage)
    eval "
_complgen_jit_$stem () {
    local words cword
    _get_comp_words_by_ref -n \"\$COMP_WORDBREAKS\" words cword
    local prefix="\${words[\$cword]}"
    local bash_code=\"\$(complgen jit \"{usage_files_dir}/${stem}.usage\" bash --comp-wordbreaks=\"\$COMP_WORDBREAKS\" --prefix=\"\$prefix\" -- \"\${words[@]:1:\$cword-1}\")\"
    eval \"\$bash_code\"
    return 0
}
"
    complete -o nospace -F _complgen_jit_$stem "$stem"
    unset stem
done

Fish Integration

Assumming your .usage files are stored in the ~/.config/complgen directory, add this to your ~/.config/fish/config.fish:

function _complgen_jit
    set --local COMP_LINE (commandline --cut-at-cursor)
    set --local COMP_WORDS
    echo $COMP_LINE | read --tokenize --array COMP_WORDS
    if string match --quiet --regex '.*\s$' $COMP_LINE
        set COMP_CWORD (math (count $COMP_WORDS) + 1)
    else
        set COMP_CWORD (count $COMP_WORDS)
    end
    set --local usage_file_path $argv[1]
    set --local prefix $COMP_WORDS[$COMP_CWORD]
    set --local last (math $COMP_CWORD - 1)
    if test $last -lt 2
        set words
    else
        set words $COMP_WORDS[2..$last]
    end
    complgen jit $usage_file_path fish --prefix="$prefix" -- $words | source -
    __complgen_jit "$prefix"
end

for path in ~/.config/complgen/*.usage
    set --local stem (basename $path .usage)
    complete --command $stem --no-files --arguments "(_complgen_jit ~/.config/complgen/$basename.usage)"
end

Zsh Integration

Assumming your .usage files are stored in the ~/.config/complgen directory, add this to your ~/.zshrc:

_complgen_jit () {
    local stem=$1
    local -a w=("${(@)words[2,$CURRENT-1]}")
    local zsh_code=$(complgen jit ~/.config/complgen/${stem}.usage zsh --prefix="$PREFIX" -- "${w[@]}")
    eval $zsh_code
    return 0
}

for f in $HOME/.config/complgen/*.usage(N); do
    local stem=$f:t:r
    compdef "_complgen_jit $stem" $stem
done

Installation

cargo install --git https://github.com/adaszko/complgen complgen

Syntax

See the examples subdirectory for simple examples and usage subdirectory for more involved ones.

Try piping through the scrape subcommand to quickly generate grammar skeleton that can be tweaked further, e.g.:

$ grep --help | complgen scrape
 | (-E | --extended-regexp) "PATTERNS are extended regular expressions"
 | (-F | --fixed-strings) "PATTERNS are strings"
 | (-G | --basic-regexp) "PATTERNS are basic regular expressions"
[...]

The grammar is based on compleat's one.

A grammar is a series of lines terminated by a semicolon (;). Each line either represents a single variant of invoking the completed command or is a nonterminal definition.

  • a b matches a followed by b.
  • a b | c matches either a b or c (IOW: sequence binds stronger than alternative).
  • [a] matches zero or one occurrences of a.
  • a... matches one or more occurrences of a
  • [a]... matches zero or more occurrences of a.

Use parentheses to group patterns:

  • a (b | c) matches a followed by either b or c.
  • (a | b) ... matches a or b followed by any number of additional a or b.

Filename completion

There's a couple of predefined nonterminals that are handled specially by complgen:

Name bash fish zsh Description
<PATH> file or directory path
<DIRECTORY> directory path
<PID> process id
<USER> user name
<GROUP> group name
<HOST> hostname
<INTERFACE> network interface name
<PACKAGE> OS package name

The reason there's no predefined <FILE> nonterminal is that it would work only for files from the current directory which is too specific to be generally useful.

These nonterminals can still be defined in the grammar in the usual way (<PATH> ::= ...), in which case their predefined meaning gets overriden.

Completion descriptions (fish/zsh only)

If a literal is immediately followed with a quoted string, it's going to appear as a hint to the user at completion time. E.g. the grammar:

grep --extended-regexp "PATTERNS are extended regular expressions" | --exclude  (skip files that match GLOB)

results in something like this under fish (and zsh):

fish> grep --ex<TAB>
--exclude  (skip files that match GLOB)  --extended-regexp  (PATTERNS are extended regular expressions)

Note that bash does not support showing descriptions.

Sourcing completions from external commands output

It is possible to use entire shell commands as a source of completions:

cargo {{{ rustup toolchain list | cut -d' ' -f1 | sed 's/^/+/' }}};

The stdout of the pipeline above will be automatically filtered by the shell based on the prefix entered so far.

The prefix entered so far

Sometimes, it's more efficient to take into account the entered prefix in the shell command itself. For all three shells (bash, fish, zsh), it's available in the $1 variable:

cargo {{{ rustup toolchain list | cut -d' ' -f1 | grep "^$1" | sed 's/^/+/' }}};

Note that in general, it's best to leave the filtering up to the executing shell since it may be configured to perform some non-standard filtering. zsh for example is capable of expanding /u/l/b to /usr/local/bin.

Completion descriptions

Externals commands are also assumed to produce descriptions similar to those described in the section above. Their expected stdout format is a sequence of lines of the form

COMPLETION\tDESCRIPTION

For fish and zsh, the DESCRIPTION part will be presented to the user. Under bash, only the COMPLETION part will be visible. All external commands nonetheless need to take care as to not produce superfluous \t characters that may confuse the resulting shell scripts.

Target shell-specific behavior

In order to make use of shell-specific completion functions, complgen supports a mechanism that allows for picking a specific nonterminal expansion based on the target shell. To use an example: all shells are able to complete a user on the system, although each has a different function for it. We unify their interface under the nonterminal <USER> using few nonterminal@shell definitions:

cmd <USER>;
<USER@bash> ::= {{{ compgen -A user "$1" | sort | uniq }}}; # bash produces duplicates for some reason
<USER@fish> ::= {{{ __fish_complete_users "$1" }}};
<USER@zsh> ::= {{{ _users }}};

Completing option arguments

It's possible to match not only entire words, but also within words themselves, using the same grammar syntax as for matching entire words. In that sense, it all fractally works on subwords too (there are limitations on {{{ ... }}} usage though). The most common application of that general mechanism is to handle equal sign arguments (--option=ARGUMENT):

grep --color=(always | never | auto);

Note however that equal sign arguments aren't some special case within complgen — the same mechanism works for more complicated things, e.g.:

strace -e <EXPR>;
<EXPR> ::= [<qualifier>=][!]<value>[,<value>]...;
<qualifier> ::= trace | read | write | fault;
<value> ::= %file | file | all;

The above grammar was pulled straight out of strace man page.

Fallback Completions

This feature is currently work-in-progress, here's where it's supported already:

Shell jit aot
bash
fish
zsh

If you do git <TAB> in most shells you're presented with a list of git subcommands. Even though git accepts a bunch of global options (--help, --version, etc.), they don't show up there (sic!). That's a special mechanism intended for reducing clutter. Under complgen, the same effect is achieved via a construction called fallbacks, which are represented in the grammar as the double bar operator (||):

mygit (<SUBCOMMAND> || <OPTION>);
<SUBCOMMAND> ::= fetch | add | commit | push;
<OPTION> ::= --help | --version;

With the grammar above, git <TAB> will offer to complete only subcommands. For git --<TAB> OTOH, complgen will offer to complete options.

|| has the lowest priority of all operators, so the grammar above might have been written without any use of <NONTERMINALS>. They're there only for readability sake.

Trailing spaces handling

There are few general rules governing whether to append a space to a completion:

  • A space is appended if the completion corresponds to an entire literal from the .usage file, e.g. for the grammar cmd --help; and the command line cmd <TAB>, it completes to cmd --help<SPACE>.

  • A trailing space isn't appended if the literal is a part of a subword and the entire subword hasn't been completed yet, e.g. for the grammar cmd --color=(auto|always); and the command line cmd --col<TAB>, it completes to cmd --color= (no trailing space).

There are exceptions:

Caveats

  • {{{ ... }}} is only allowed at tail positions, where it doesn't lead to matching against an arbitrary output of an external command:

    • OK: cmd {{{ echo foo }}} {{{ echo bar }}};

    • ERROR: cmd ({{{ echo foo }}} | {{{ echo bar }}});

      We'd have to match against the output of {{{ echo foo }}} at compilation time to determine which branch to take, which is impossible to do in general as echo foo might as well have been an arbitrary shell command.

    • OK: cmd (foo | {{{ echo bar }}});

    • ERROR: cmd ({{{ echo foo }}} || {{{ echo bar }}});

      Reason: Same as for | above.

    • OK: cmd (foo || {{{ echo bar }}});

    • OK: cmd [{{{ echo foo }}}] foo;

      The entire commands always gets tokenized into shell words as a first step, so it's possible to tell where the output of {{{ echo foo }}} ends. Note that two foo completions are produced here, then they're deduplicated, and finally, only one foo is offered to the user.

    • OK: cmd {{{ echo foo }}}... foo baz;

  • Within subwords, {{{ ... }}} is only allowed at tail position, where it doesn't lead to matching against an arbitrary output of an external command:

    • ERROR: {{{ git tag }}}..{{{ git tag }}}

      Impossible to guess at compilation time where the output of the first {{{ git tag }} ends and our .. begins.

    • OK: --option={{{ echo foo }}}

    • ERROR: {{{ echo foo }}}{{{ echo bar }}}

      Impossible to guess at compilation time where the output of the first {{{ echo foo }} ends the second {{{ echo bar }}} begins.

  • The limitations above also apply to predefined nonterminals (<PATH>, <DIRECTORY>, etc.) since they're internally implemented as external commands.

  • Bash requires bash-completion OS package to be installed because completion scripts produced by complgen, call shell functions from that package at completion time. This is necessary to work around Bash's default behavior of breaking shell words on any character present in the $COMP_WORDBREAKS environment variable.

  • Non-regular grammars aren't completed 100% precisely. For instance, in case of find(1), complgen will still suggest ) even in cases when all ( have already been properly closed before the cursor.

Keeping abreast

Best way is to watch GitHub releases.

complgen's People

Contributors

adaszko avatar meator avatar ordoviz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

complgen's Issues

Representing multiple exclusive words separated by ,

First of all. Thank you for creating this tool, it really makes it simple to write completion scripts!

I am trying to write a completion for duf. Several of its flags support giving multiple values separated with comma e.g:

duf --hide-fs tmpfs,vfat

So far I got it working with a single value with the <FS> nonterminal. But when i specify the alternative -hide-fs <FS>[,<FS>]... and press tab i get all of them at once with no comma separation:

image

Is it possible to express this pattern, and if so what is the correct syntax for it?

System

I use fish version 3.7.1


Here is my duf.usage file:

// https://github.com/muesli/duf
duf [<OPTS>]...;

<OPTS> ::=
	-all "include pseudo, duplicate, inaccessible file systems"
	| -hide-fs <FS>[,<FS>]... "hide specific filesystems, separated with commas"
	;

<FS@fish> ::= {{{ string split ' ' --fields 3 </proc/mounts | sort --unique }}};

Parsing of statement-level shell code in grammars (`@{bash,fish,zsh} {{{ ... }}}`)

The parser is to be extended to support a new Statement enum variant: Statement::ShellCode { code: ustr; }. Those blocks of code will be copied verbatim into the output shell scripts so that complgen supports executing pieces of shell scripts larger than what fits inside inline ones (e.g. { ls }), but this issue is only about parsing. It’s going to need several tests to check the parsing works as intended.

File completion

complgen should support completing file name arguments. It should be possible to specify in the grammar which positional arguments are to be completed as a file name.

Each shell seems be doing it differently and we need to be frugal in weeding through the file system as we may easily freeze the shell for seconds.

  • Bash
    compgen -f -- <PREFIX> (or compgen -A file) prints results on stdout so it’s easily integrated into complgen
  • Fish
    __fish_complete_path <PREFIX> <DESCRIPTION> prints results on stdout so it’s easily integrated into complgen
  • Zsh
    There are _files and _path_files bulitins that implicitly invoke compadd (AFAIU) so we can’t treat it as another command invoked via { … }. This case needs special handling.
  • Interpreter mode (JIT)
    In order to respect shell completion settings (like whether to expand completions like * for instance), it should actually shell out to list files

Emit zsh completion script based on a DFA

Similarly to the existing Bash and Fish logic, we need a way to encode a DFA walking as a zsh completion script. A completion script consists of several parts:

  1. It splits the currently-entered input by user into shell words
  2. It matches the words entered so far by walking a DFA
  3. Once it reaches the final word, it generates possible completions by looking at the current state in the DFA and seeing what possible transitions can we take.

There are additional concerns like strings duplication and output script size but the first step is to get it working.
Familiarity with how zsh completion system works will be required, so see the ZSH section in CONTRIBUTING.md.
The code should be analogous to the existing bash.rs and fish.rs since it’s just another backend. Completion based on executing external commands isn’t necessary in the first version and can be left out.

End-to-end tests for completion scripts

Completion scripts are currently tested largely by hand which is brittle and it’s all too easy to accidentally introduce a regression. Ideally, tests should be automatic and as close to what the end user does as possible. This means, given some .usage file, the Rust code could emit a completion script for a particular shell, source the script within that shell and ensure the shell generated expected completions. Such tests could be orchestrated by either cargo test or pytest (pyexpect?), whatever is deemed to produce more readable and reliable tests.

Inline commands completion bug

In bash, for the grammar

cargo [<toolchain>] (--version | --help);
<toolchain> ::= { rustup toolchain list | cut -d' ' -f1 | sed 's/^/+/' };

after completing the <toolchain> word correctly, completion doesn’t suggest either —version or —help whereas it should.

`complgen scrape` should output grammars using the "distributive descriptions" feature

i.e., instead of

$ grep --help | complgen scrape
grep [<OPTION>]... <PATTERNS> [<FILE>]...;
 | -E "are extended regular expressions" | --extended-regexp "are extended regular expressions" <PATTERNS>
[...]

it should produce

$ grep --help | complgen scrape
grep [<OPTION>]... <PATTERNS> [<FILE>]...;
 | (-E <PATTERNS> | --extended-regexp <PATTERNS>) "are extended regular expressions"
[...]

for brevity.

[FR] <PATH> behaviour inconsistent with non-complgen path completion

completes paths in a basic way that isn't identical to path completion for commands where completion isn't made with complgen.
I'm using zsh.
It ignores zstyle settings related to e.g. ignoring case and adding a space after filename completion.

As a side note, I see that _path_files is being called in the code so I tried redefining , e.g. for ignoring case:

<PATH@zsh> ::= {{{ _path_files -M 'm:{[:lower:][:upper:]}={[:upper:][:lower:]}' }}}

The -M flag seems to be ignored. I'm able to have other flags work, e.g. -F for ignoring patterns. This might be an issue with my setup somehow and not related to complgen.

Thanks for the great project btw, I'm playing around with a (very heuristic) help text parser that goes well with this.

Bash: Respect `completion-ignore-case` readline setting

In JIT mode, the current bash completion matching logic simply does completion.starts_with(...) which is wrong when completion-ignore-case is set.

Similarly in AOT mode:

Fix: Retrieve the settings value with something like bind -V | grep completion-ignore-case and behave conditionally based on its value.

  • TODO:
    • Write e2e tests for JIT mode
    • Wrte e2e tests for AOT mode
    • Fix JIT mode
    • Fix AOT mode

[FR] Add support for option arguments using =

I've seen this in the description of the project:

Passing option arguments using = is not currently supported. E.g. --foo=bar doesn't work, but --foo bar does.

Is there a way to workaround this limitation?
Unfortunately, psql's option service works only that way and there are no synonymous options that don't require the =.

p.s.: if the limitation is too hard to work around and you don't plan to add it - it'd be sad, of course, but feel free to close this ticket then.

JIT: Execute external command lazily based on the fallbacks order

For a grammar cmd (--foo | {{{ echo bar }}});, the {{{ echo bar }}} command is executed even when we're matching the first word (i.e. cmd <TAB>), which is premature. {{{ echo bar }}} should be executed lazily only after we know --foo hasn't matched.

The output currently produced:

__complgen_jit () {
    local -a matches=()
    local -a completions=("bar" "foo")
    compadd -Q -a completions
    compadd -O matches -a completions
    [[ ${#matches} -gt 0 ]] && return
}
__complgen_jit

whereas it should look more like (in this case for ZSH)

__complgen_jit () {
    local -a matches=()
    local -a completions=("foo")
    compadd -Q -a completions
    compadd -O matches -a completions
    [[ ${#matches} -gt 0 ]] && return

    completions=("${(@f)$(echo bar)}")
    compadd -Q -a completions
    compadd -O matches -a completions
    [[ ${#matches} -gt 0 ]] && return
}
__complgen_jit

This piece of code is responsible for generating those completions:

complgen/src/jit.rs

Lines 395 to 400 in 5e29dcb

// Complete `prefix` based on `state`.
let mut output: Vec<Completion> = Default::default();
for (input, _) in dfa.iter_transitions_from(state) {
get_completions_for_input(&input, prefix, shell, &mut output)?;
}
output.sort_unstable_by(|left, right| left.get_completion().cmp(&right.get_completion()));

[bash] Words containing a colon not completed correctly

Consider this simple grammar:

colontest b:c;

If you source the Bash script generated with complgen compile --bash-script, and type colontest <TAB> you get colontest b:c. So far so good. However, pressing TAB again yields colontest b:b:c.

Adding __ltrim_colon_completions "$prefix" or COMP_WORDBREAKS=${COMP_WORDBREAKS//:} to the generated Bash script fixes it, but the former requires bash-completion to be installed and the latter could affect the behavior of other completion scripts.

Interestingly, the JIT Bash integration works fine even without __ltrim_colon_completions, but it fails with the more complex grammar:

colontest (b:c | b:d);

It offer completions up to colontest b: but pressing TAB again does nothing.

There seems to be a discrepancy between the docs and the runtime

Hey there. I noticed that the last git commit was roughly 14 hours ago, so this could be related to that, for all I know.

Anyways, I am trying out complgen for the first time. I am updating someone else's completion usage for the hyprctl command. However, it does not build. I read the examples and usage, and I am fairly certain that my syntax is correct.

# https://github.com/adaszko/complgen#syntax
hyprctl [<OPTION>]... <COMMAND>;

<OPTION> ::= -j                "JSON output"
            | (-i | --instance) "use specified Hyprland instance" <INSTANCE>
            ;

<COMMAND> ::= animations          "list animations and beziers (not in --help)"
            | activewindow        "print active window name"
            | activeworkspace     "show info about active workspace"

#<et cetera>

            | workspaces          "list all workspaces with their properties"
            | --batch           "execute multiple commands, separated by ';'"
            ;

# see KeybindManager.cpp
<DISPATCHER> ::= alterzorder (top | bottom)
            | bringactivetotop

#<similar content>

            | workspaceopt
            ;

<ICON> ::= -1 "no icon"
            | 0 "warning"
            | 1 "info"
            | 2 "hint"
            | 3 "error"
            | 4 "confused"
            | 5 "ok"
            ;

<PAPERCOMMAND> ::= preload <PATH>
            | unload (<PATH> | all)
            | wallpaper
            ;

# See parseKeyword in ConfigManager.cpp
# not updated
<KEYWORD> ::= monitor
            | exec

#<similar content>

            | plugin
            ;

<DEVICE> ::= {{{ hyprctl devices -j | awk '/^"keyboards"/,/^\],$/' | sed -n 's/.*"name": "\(.*\)".*/\1/p' }}};

<CURSORTHEME> ::= {{{ printf '%s\n' /usr/share/icons/*/cursors | cut -d/ -f5 }}};

<MONITOR> ::= {{{ hyprctl monitors | sed -n 's/^Monitor \(.*\) (ID .*/\1/p' }}};

<INSTANCE> ::= {{{ hyprctl instances -j | sed -n 's/.*"instance": "\(.*\)".*/\1/p' }}};

When I run this through complgen check ./hyprctl.usage, I get the following error:

137:13:error: Commands are only allowed at a tail position to avoid ambiguities in matching
    |
137 |   # https://github.com/adaszko/complgen#syntax
    |  ______________^
138 | | hyprctl [<OPTION>]... <COMMAND>;
139 | |
140 | | <OPTION> ::= -j                "JSON output"
    | |____________________________^
141 |               | (-i | --instance) "use specified Hyprland instance" <INSTANCE>
142 |               ;
143 |
144 |   <COMMAND> ::= animations          "list animations and beziers (not in --help)"

#<file continues>

274 |   <DEVICE> ::= {{{ hyprctl devices -j | awk '/^"keyboards"/,/^\],$/' | sed -n 's/.*"name": "\(.*\)".*/\1/p' }}};
275 |
276 |   <CURSORTHEME> ::= {{{ printf '%s\n' /usr/share/icons/*/cursors | cut -d/ -f5 }}};
277 |
278 |   <MONITOR> ::= {{{ hyprctl monitors | sed -n 's/^Monitor \(.*\) (ID .*/\1/p' }}};
279 |
280 |   <INSTANCE> ::= {{{ hyprctl instances -j | sed -n 's/.*"instance": "\(.*\)".*/\1/p' }}};
    |
    = help: try moving the command to the tail position

Am I doing this correctly? As far as I can tell from external-commands and specialization, my command syntax looks identical.

Unable to use subword completion

Hello 👋 ,

firstly, thank you very much for this project. I love it. Unfortunately, I'm working on an auto-completion for a more complicated build process to make more sense of it. For this, I though using complgen might be a great idea. My usage file looks like this:

build [<OPTION>] <TARGET>;

<OPTION> ::= [--container-image=(localhost/builder|<IMAGES>)]                               "The default container image that is used."
           | [--container-engine=(docker| podman)]                                          "The software solution to implement a kernel namespace for the process." 
           | [--resolve-cname]                                                              "The platfrom, element or flag that should resolved for"
           | [--print-container-image]                                                      "Print the usaged container image."
           | [--kms]                                                                        "Set AWS environment variables to allow access to KMS." 
           | [--privileged]                                                                 "Set the container to have higher privileges." 
           | [--target <PATH> ]                                                             "The folder in which the artifices will be safed to." 
           ;

<TARGET> ::= <PLATFORM>[-(amd64|arm64)];

<ARCHITECTUR> ::= (amd64|arm64);

<PLATFORM@fish> ::= {{{ for feature in (ls -1 features);grep -E 'platform' features/$feature/info.yaml > /dev/null 2> /dev/null   && echo $feature;end  }}};
<PLATFORM@bash> ::= {{{ for feature in $(ls -1 features);do grep -qE 'platform' features/$feature/info.yaml 2> /dev/null  && echo $feature;done }}};
<IMAGES> ::= {{{ podman images -q }}};

#<PLATFORM>[-<ELEMENT>...] [-<FLAGS>..]-[<ARCHITECTUR>][-<TIME>];

The key problem I'm having is the following:

./build kvm- TAB

I want to mimic the following string, as an example:
./build kvm-python_readonly_dev-amd64-today

Currently, I autocomplete the platform, AWS or KVM here the options is working likewise, but I can't add anything that follows. I know that the dash is already breaking the fish completion, but I though building for bash first. I'm not sure what I'm doing wrong at the time, or does the dash break here more?

Not only that, but I think it should look like this later on:

<TARGET> ::= <PLATFORM>[-<ELEMENT>...] [_<FLAGS>...]-[<ARCHITECTUR>][-<TIME>];

I'm using Archlinux with the version 0.1.8-1 of comlgen from pacman. bash has Version 5.2.26(1)-release and fish has version 3.7.1.

Any help is appreciated!

And once more thank you for the the great work here.

Bash integration not working for `--color=(always|never)`

Consider this grammar from the e2e tests:

mygrep --color "use markers to highlight the matching strings"=<WHEN>;
<WHEN> ::= always | never | auto;

If I save this to /tmp/mygrep.usage and source the Bash integration from the README

for path in /tmp/*.usage; do
    stem=$(basename "$path" .usage)
    eval "
_complgen_jit_$stem () {
    local words cword
    _get_comp_words_by_ref -n = words cword
    local prefix="\${COMP_WORDS[\$COMP_CWORD]}"
    local -a completions=(\$(complgen complete \"/tmp/${stem}.usage\" bash --prefix="\$prefix" -- \${COMP_WORDS[@]:1:\$COMP_CWORD-1}))
    for item in "\${completions[@]}"; do
        if [[ \$item = "\${prefix}"* ]]; then
            COMPREPLY+=("\$item")
        fi
    done
    __ltrim_colon_completions "\$prefix"
    return 0
}
"
    complete -o nospace -F _complgen_jit_$stem "$stem"
    unset stem
done

it will autocomplete up to mygrep --color= but doesn't suggest always or never.

I believe the output from (or the invocation of) complgen complete needs to be adjusted to fix this, but I don't actually know. The PR #33 does NOT fix this.

Compiling the grammar into a Bash script and sourcing that works fine.

zsh completion doesn't add space after choose a word

prctl [<GLOBAL-OPTION>]...;

prctl [<GLOBAL-OPTION>]... cg "cgroups related commands" list "list prctl creates cgroups";
prctl [<GLOBAL-OPTION>]... cg "cgroups related commands" create "create a cgroup by name" [(-h | --help) "Print help"] <PATTERNS>...;
prctl [<GLOBAL-OPTION>]... cg "cgroups related commands" delete "delete a cgroup by name" [(-h | --help) "Print help"] <CGROUPS>...;
prctl [<GLOBAL-OPTION>]... cg "cgroups related commands" show "show cgroup control processes" [(-h | --help) "Print help"] <CGROUPS>...;
prctl [<GLOBAL-OPTION>]... process "process related commands" move "move pids to specify cgroup" [(-h | --help) "Print help"] <CGROUPS> <PIDS>...;

<GLOBAL-OPTION> ::= (-t <CGROUP_TYPE>) "[default: v2] [possible values: systemd, v2]"
                    | (--pause-path <PATH>) "pause path"
                    | (-h | --help) "Print help";

<CGROUPS> ::= {{{ prctl cg list }}};
<CGROUP_TYPE> ::= (systemd | v2);

that's my usage file, when I compile and source it in zsh, input prctl c then press , it show complete the cg` and add a space after the word, but it doesn't

[FR] Provide a way to not order suggested autocompletion variants alphabetically

Currently such such .usage's:

fake <I>;
<I> ::= { echo '3\n2\n1' };
fake <I>;
<I> ::= c | b | a ;

produce shell autocompletion rules where the items are suggested in alphabetical order (1 2 3 in the first case and a b c in the second) instead of the way they were ordered in the .usage files (I'd expect 3 2 1 in the first case and c b a in the second).

Completion for ZSH doesn't work in default path

I have converted the usage/complgen.usage to ZSH completion script and put it into the standard ZSH completions directory, /usr/share/zsh/site-functions/_complgen. It doesn't work. It doesn't generate any warnings nor errors, but by looking around, I came across this warning:

/usr/share/zsh/site-functions/_complgen:377: command not found: compdef

All of the other completions in /usr/share/zsh/site-functions begin with #compdef ..., but not complgen. Are complgen's ZSH completion scripts incompatible with this type of completion script distribution?

I should note that I am not familiar with the completion systems of the major shells. I have hoped that this program would spare me from this.

[FR] Add support of 'Descriptions (a.k.a. completion hints) to 'External commands'

AFAIU complgen currently doesn't support descriptions (aka completion hints) with External commands: it looks like external commands are currently meant to be used only for getting a list of possible values and those values will be suggested to the user as autocompletion variants.

But in some cases it would be nice to have descriptions for those variants.
Here's a minimal synthetic illustration of the feature request:

fake [--type <TYPE>];
<TYPE> ::= { echo 'bool "a boolean (true/false)"\nstr "a string"\nint "an integer"' };

I would like this .usage rule to produce an autocompletion that would suggest bool/str/int with a description next to them.
Instead, currently it produces bool\ \"a\ boolean\ \(true/false\)\"/int\ \"an\ integer\"/str\ \"a\ string\" as values (so the supposed description is currently treated as a part of the value).

Provide instructions on how to integrate JIT completions with {bash,fish,zsh}

  • zsh
_complgen_jit () {
    local command=$1
    local -a w=("${(@)words[2,$#words]}")
    local -a completions=($(complgen complete ~/.config/complgen/${command}.usage $((CURRENT - 2)) -- "${w[@]}"))
    compadd -a completions
    return 0
}

for f in $HOME/.config/complgen/*.usage; do
    local stem=$f:t:r
    compdef "_complgen_jit $stem" $stem
done

Release 0.2.0 introduces breaking changes when git is not present

Hey, I was updating my complgen package to the newest version and I've noticed that the build now fails for me. The error message is unfortunately pretty cryptic, but through some debugging, I have figured out the cause:

https://github.com/adaszko/complgen/blob/master/build.rs#L5

This line is accompanied by the following comment:

// note: add error checking yourself.

I am not sure whether I understand it correctly. Are you saying that it is the user's responsibility to add error checking?

I do not have experience with Rust, but I have already come across this issue (making the program aware of this version) in other languages. The "standard" way to handle this is to use revision control to retrieve program's version, but provide a sensible fallback when revision control fails for whatever reason. A good choice for the fallback would be the project's version set in the Cargo.toml file, but that is unfortunately out of date: #51

git may not be always available. The code could have been downloaded through a release archive (which many package builders do) instead of cloning.

I also use git describe in a program I maintain. I have it set up so that it prints the same version (for example r3.0) as the fallback version when the repo is checked out to a tag. If the HEAD of the repository isn't tied to a tag, git-describe prints the usual commit hash info. This means that it doesn't matter whether someone downloads a release of my program through git clone or through the release archive, printed version will be the same.

I also believe that this breaking change should be mentioned in the release notes for v0.2.0 it it's intentional.

Allow specifying regex that external command output must match

If we had a way to specify that <FS@fish> ::= {{{ string split ' ' --fields 3 </proc/mounts | sort --unique }}} always matches \w+, it would be possible to complete <FS>[,<FS>]....

Working version syntax:

duf -hide-fs <FS>[,<FS>]...;
<FS@fish> ::= {{{ string split ' ' --fields 3 </proc/mounts | sort --unique }}}(fish="\w+");

Such mechanism would relax the biggest restriction of current grammars -- that an external command has to occur only at a tail position.

There is no common syntax for regexes across all shells, so shell-specific ones are used instead.

Inspired by #53.

Add an “scrape” mode that guesses grammar based on `cmd --help` output

It should read the text on stdin and output the grammar on stdout so that it can be captured by the user and tweaked until it’s satisfactory. The idea behind this is to avoid necessary manual editing as much as possible.

It’s best to implement it in Rust so that no additional dependencies are necessary. Most likely using the nom crate, just like the grammar parser already does.

Example:

$ cargo test --help
Execute all unit and integration tests and build examples of a local package

Usage: cargo test [OPTIONS] [TESTNAME] [-- [args]...]

Arguments:
  [TESTNAME]  If specified, only run tests containing this string in their names
  [args]...   Arguments for the test binary

Options:
  -q, --quiet                   Display one character per test instead of one line
      --lib                     Test only this package's library unit tests
      --bin [<NAME>]            Test only the specified binary
      --bins                    Test all binaries
  -v, --verbose...              Use verbose output (-vv very verbose/build.rs output)
      --example [<NAME>]        Test only the specified example
      --color <WHEN>            Coloring: auto, always, never
      --examples                Test all examples
      --frozen                  Require Cargo.lock and cache are up to date
      --test [<NAME>]           Test only the specified test target
      --locked                  Require Cargo.lock is up to date
      --tests                   Test all tests
      --bench [<NAME>]          Test only the specified bench target
      --offline                 Run without accessing the network
      --benches                 Test all benches
      --config <KEY=VALUE>      Override a configuration value
      --all-targets             Test all targets
  -Z <FLAG>                     Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
      --doc                     Test only this library's documentation
      --no-run                  Compile, but don't run tests
      --no-fail-fast            Run all tests regardless of failure
  -p, --package [<SPEC>]        Package to run tests for
      --workspace               Test all packages in the workspace
      --exclude <SPEC>          Exclude packages from the test
      --all                     Alias for --workspace (deprecated)
  -j, --jobs <N>                Number of parallel jobs, defaults to # of CPUs
      --keep-going              Do not abort the build as soon as there is an error (unstable)
  -r, --release                 Build artifacts in release mode, with optimizations
      --profile <PROFILE-NAME>  Build artifacts with the specified profile
  -F, --features <FEATURES>     Space or comma separated list of features to activate
      --all-features            Activate all available features
      --no-default-features     Do not activate the `default` feature
      --target <TRIPLE>         Build for the target triple
      --target-dir <DIRECTORY>  Directory for all generated artifacts
      --manifest-path <PATH>    Path to Cargo.toml
      --ignore-rust-version     Ignore `rust-version` specification in packages
      --message-format <FMT>    Error format
      --unit-graph              Output build graph in JSON (unstable)
      --future-incompat-report  Outputs a future incompatibility report at the end of the build
      --timings[=<FMTS>]        Timing output formats (unstable) (comma separated): html, json
  -h, --help                    Print help

Run `cargo help test` for more detailed information.
Run `cargo test -- --help` for test binary options.

should result in something along the lines of

cargo test [OPTIONS] [TESTNAME] [-- [args]...];

<OPTIONS> ::= -q "Display one character per test instead of one line"
	    | --quiet "Display one character per test instead of one line"
            | --lib "Test only this package's library unit tests"
            | --bin "Test only the specified binary" [<NAME>]
	    [...]
	    ;

Parsing error on defining autocompletion of .sh scripts

Description

I get an error when I try to generate a completion script for a script which name ends as .sh.

For example, I can create a bash style autocompletion script for `foo`, but not for `foo.sh`
> echo -n "foo [-h] ;" | complgen compile - --bash-script -
_foo () {
    declare -A literals
    literals=([-h]=0)
... # this works
> echo -n "foo.sh [-h] ;" | complgen compile - --bash-script -
Error: Parsing error: "foo.sh [-h] ;"

Additional comment

Awesome tool btw 😄

Warning: Final DFA contains ambiguous transitions

For the grammar cmd ({{{ echo foo }}} | {{{ echo bar }}});, the following warning gets produced:

Warning: Final DFA contains ambiguous transitions; inputs: [Command(u!("echo foo"), 0), Command(u!("echo bar"), 0)]

Diagnosis: In order to proceed with matching command line words, complgen needs to be able to tell what {{{ echo foo }}} returned but that information gets lost between invocations of the completion functions and therefore it cannot work (it would be too complicated even if it memoized it).

Fix: Allow elements like {{{ ... }}} and <NONTERM>, where <NONTERM> expands to some external command, to only occur at the final position in any alternative (|), fallback (||), repetition (...) or optional ([...]) expression. Also allow the above elements to only occur at the final position in subword expressions. Examples:

  • OK: cmd {{{ echo foo }}} {{{ echo bar }}};
  • ERROR: cmd ({{{ echo foo }}} | {{{ echo bar }}});
  • OK: cmd (foo | {{{ echo bar }}});
  • ERROR: cmd ({{{ echo foo }}} || {{{ echo bar }}});
  • OK: cmd (foo || {{{ echo bar }}});
  • ERROR: cmd [{{{ echo foo }}}] foo;
  • ERROR: cmd {{{ echo foo }}}... foo baz;

usage/complgen.usage is producing warnings

When I run

complgen aot --bash-script /dev/null usage/complgen.usage

I get

Warning: Undefined nonterminal(s): PREFIX COMP_WORDBREAKS

I get the same error with --fish-script and --zsh-script. I am running complgen v0.1.8.

I have freshly installed complgen and I'm getting familiar with it. Am I missing any dependencies?

Friendlier parsing error messages

The current parsing error messages only indicate where (roughly) the parser stopped and aren't very readable. In order to understand what went wrong and why the parser couldn't proceed, it would be informative to list all failed nom's alt() branches. This is what ErrorTree offers. This issue is about plugging ErrorTree into the existing parser.

Just as an aside (not in the scope), it would be nice to produce error messages in the nice form supported by chic.

Complete `<DIRECTORY>` (like `<FILE>`)

Sample use case: cargo test --target-dir <DIRECTORY>

Enable the e2e test case:

@pytest.mark.skip(reason="not implemented yet")
def test_completes_directories(complgen_binary_path: Path):
GRAMMAR = '''cmd <DIRECTORY> [--help];'''
with completion_script_path(complgen_binary_path, GRAMMAR) as completions_file_path:
with tempfile.TemporaryDirectory() as dir:
with set_working_dir(Path(dir)):
os.mkdir('foo')
os.mkdir('bar')
completions = get_sorted_completions(completions_file_path, '''COMP_WORDS=(cmd); COMP_CWORD=1; _cmd; printf '%s\n' "${COMPREPLY[@]}"''')
assert completions == ['bar', 'foo']

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.