$ bash <(curl -Ls git.io/nodecliac) && source ~/.bashrc
More installation methods
curl Install (explicit defaults):
$ bash <(curl -Ls git.io/nodecliac) --installer= --branch=master --rcfile=~/.bashrc && source ~/.bashrc
wget Install (defaults):
$ bash <(wget -qO- git.io/nodecliac) && source ~/.bashrc
Manual Install: One can also install manually.
- First download the GitHub nodecliac repository.
- Next unzip the folder via
$ unzip nodecliac-*.zip
or by right-clicking and using the OS provided extractor utility. cd
into the repository and install:$ sudo chmod +x install.sh && ./install.sh --manual && source ~/.bashrc
- Delete the downloaded
zip
folder, its extracted folder, and start using.
Checksum Install: If desired, the install script file's integrity can be verified before running.
install.sh sha256sum
checksum: 2b4f37dae3cbe5cc81a0ce52f55a5d300445e3e3a68d9d7e97040504e1aec9da
Create an executable shell file called install.sh
, add the following, and run it.
#!/bin/bash
# The script downloads the install script, generates its checksum, and checks
# it against the valid sha256 sum value. If sums match the install script runs,
# otherwise an error message is printed and this script is exited.
install() {
url="git.io/nodecliac"
is="$([[ "$(command -v curl)" ]] && sudo curl -Ls "$url" || sudo wget -qO- "$url")"
x=($([[ "$OSTYPE" == "darwin"* ]] && shasum -a 256 <<< "$is" || sha256sum <<< "$is"))
c="2b4f37dae3cbe5cc81a0ce52f55a5d300445e3e3a68d9d7e97040504e1aec9da"
err="\033[1;31mError\033[0m: Verification failed: checksums don't match."
[[ "$c" == "$x" ]] && bash <(echo "$is") \
--installer= \
--branch=master \
--rcfile=~/.bashrc \
&& source ~/.bashrc || echo -e "$err" && exit 1
} && install
Installation options
--installer
: The installer to use. (default:yarn
>npm
>binary
)--branch
: An existing nodecliac branch name to install. (default:master
)--rcfile
:bashrc
file to install nodecliac to. (default:~/.bashrc
)--yes
: Automate install by saying yes to any prompt(s).--packages
: Install collection of pre-made completion packages.--manual
: Let's install script to take manual install route.--update
: Let's install script to take update router over fresh install route.
Requirements
Uninstall
$ nodecliac uninstall
If a custom rcfile path was used during install provide it again during uninstall.
$ nodecliac uninstall --rcfile=path/to/.bashrc
bash-completion
is awesome. It enhances the user experience by completing paths, file names, commands, flags, etc. Ironically enough, having to use Bash
to add it to one's program puts some off from using it.
nodecliac's approach is different. Rather than directly using Bash, nodecliac provides a layer of abstraction. It lets one easily map a program's commands with their flags in an auto-completion map (.acmap
) file. Merely write the program's .acmap
, compile to .acdef
, and let nodecliac handle the rest. That's it.
If Bash is needed, .acmap
files are flexible enough to run shell code to generate matches. Better yet, write the necessary completion logic in a familiar language like Perl
, Python
, Ruby
, etc., and use Bash as glue code to tie it all together.
In all, this project aims to 1
minimize the effort needed to add bash-completion so more programs support it, 2
provide a uniform bash-completion experience, and 3
to ultimately build a collection of community made completion packages for all to enjoy.
Why the name nodecliac? Originally it was made in mind for Node.js programs. However, as development continued it proved useful for non Node.js programs as well.
The idea is simple. nodecliac uses two file types: auto-completion map (.acmap
) and auto-completion definition (.acdef
). Those familiar with CSS preprocessors will quickly understand. Similar to how .sass
and .less
files are compiled to .css
— .acmap
files must be compiled to .acdef
.
Therefore, an .acmap
is a user generated file that uses a simple syntax to map a program's commands with their flags. While an .acdef
is a nodecliac generated definition file. It's this file nodecliac uses to provide completions.
Ok, 1
but where do these files go? 2
How does nodecliac use them? Good questions. 1
Files will end up in the command's completion package; a folder containing the necessary files needed to provide completions for a command. 2
Completion packages will end up in nodecliac's registry (nodecliac's collection of completion packages).
With that, nodecliac can provide Bash completions for programs by using their respective completion package stored in the registry.
Expand section
With the program's completion package created and stored in the registry the following is possible:
-
Tab key pressed: Bash completion invokes nodecliac's completion function for the program.
-
CLI input analysis: Input is parsed for commands, flags, positional arguments, etc.
-
.acdef
lookup: The program's.acdef
is compared against the CLI input to return possible completions.
Complete details/events are oversimplified and condensed to get the main points across.
CLI commands/flags
- Main:
- Helper:
- Package:
make
Compile
.acdef
.
--source=
: (required): Path to.acmap
file.--print
: Log output to console.
$ nodecliac make --source path/to/program.acmap # Compile .acmap file to .acdef.
Test/debugging flags (internal)
--trace
: Trace parsers (for debugging).--test
: Log output without file headers (for tests).
format
Format (prettify)
.acmap
file.
--source=
: (required): Path to.acmap
file.--strip-comments
: Remove comments when formatting.--indent="(s|t):Number"
: Formatting indentation string:s
for spaces ort
for tabs followed by amount-per-indentation level.t:1
: Use 1 tab per indentation level (default).s:2
: Use 2 spaces per indentation level.
--print
: Log output to console.
# Prettify using 2 spaces per indentation level and print output.
$ nodecliac format --source path/to/program.acmap --print --indent "s:2"
Test/debugging flags (internal)
--trace
: Trace parsers (for debugging).--test
: Log output without file headers (for tests).
init
Starts nodecliac's completion package generator to easily scaffold a completion package.
--force
: Overwrites existing folder of the same name.
$ nodecliac init
bin
Prints nodecliac's bin location.
- No arguments
$ nodecliac bin # Binary location.
cache
Interact with nodecliac's cache system.
--clear
: Clears cache.--level=<level>
:- Without argument it prints the current cache level.
- With argument it sets cache level to provide level.
- Levels:
0
,1
,2
- Levels:
$ nodecliac cache --clear # Clear cache.
$ nodecliac cache --level # Print cache level.
$ nodecliac cache --level 1 # Set cache level to 1.
setup
Setup nodecliac.
--force
: (required if nodecliac is already setup): Overwrites old nodecliac setup and installs anew.--yes
: Automate install by saying yes to any prompt(s).--rcfile
: By default~/.bashrc
is used. If another rcfile should be used provide its path.- Note: Setup appends
ncliac=~/.nodecliac/src/main/init.sh; [ -f "$ncliac" ] && . "$ncliac";
to rcfile.
$ nodecliac setup # Setup nodecliac.
$ nodecliac setup --force # Force nodecliac setup.
$ nodecliac setup --force --yes # Force nodecliac setup and assume yes to any prompt(s).
status
Returns status of nodecliac (enabled or disabled).
--enable
: Enables nodecliac.--disable
: Disables nodecliac.
$ nodecliac status # Get nodecliac's status.
$ nodecliac status --enable # Enable nodecliac.
$ nodecliac status --disable # Disable nodecliac.
uninstall
Uninstalls nodecliac.
--rcfile
: Path of rcfile used in setup to remove changes from.
$ nodecliac uninstall # Remove nodecliac.
Print acmap/def file contents for files in registry.
--command=
: Name of command (uses available packages in registry).- Note: Command is rather pointless and is primarily used to showcase
command-string
s.
$ nodecliac print --command=<command> # Print .acdef for given command.
registry
Lists packages in registry.
- No arguments
$ nodecliac registry # Print packages in registry.
add
Adds package to registry.
--path
: Path to completion package.--repo
: Repo to install completion package from.- Github: Repo only (
master
):<username>/<repo_name>
- Github: Repo branch (default:
master
):<username>/<repo_name><#branch_name>
- Github: Repo sub-directory:
<username>/<repo_name>/trunk/<sub_directory_path>
- Github: Repo branch + sub-directory:
<username>/<repo_name><#branch_name>/trunk/<sub_directory_path>
- Or GitHub, GitLab, or BitBucket URL to completion package:
<repo_url>
- URL must start with
git@
orhtttp://
and end with.git
.
- URL must start with
- Github: Repo only (
--allow-size
: Disables local repo package10MB
size check, letting for any size.- Meant as a safeguard to prevent accidentally copying large folders.
--allow-structure
: Disables valid base completion package structure checks.--allow-overwrite
: Disables overwrite warning of same name package in registry.--force
: Skip all guards/checks (size, structure, overwrite).--size
: Size of local repo is no longer checked when copying to registry.--structure
: Basic completion package structure checks are disabled.--overwrite
: Same name completion package overwriting is allowed.
$ nodecliac add # Copies cwd folder (completion package) to registry.
$ nodecliac add --path ~/Desktop/subl # Installs completion package at specified path.
$ nodecliac add --repo cgabriel5/nodecliac # Install completion package from a GitHub repo.
# Install completion package from a specific branch (defaults to master branch).
$ nodecliac add --repo cgabriel5/nodecliac#master
# Install completion package from a specific directory in a GitHub repo.
$ nodecliac add --repo cgabriel5/nodecliac/trunk/resources/packages/yarn
# Install completion package from a specific directory + branch (defaults to master branch).
$ nodecliac add --repo cgabriel5/nodecliac#dev/trunk/resources/packages/yarn
# Install completion package via GitHub, GitLab, BitBucket URL.
$ nodecliac add --repo <repo_url>
remove
Removes package(s) from registry.
- Takes n-amount of package names as arguments.
--all
: Removes all packages in registry.
$ nodecliac remove # Removes cwd folder (completion package) from registry.
$ nodecliac remove --all # Removes all packages from registry.
link
Creates soft symbolic link of package in registry.
--path
: Path to completion package.
$ nodecliac link # Symlinks cwd folder (completion package) to registry.
$ nodecliac link --path ~/Desktop/subl # Symlinks completion package at specified path.
unlink
Alias to
remove
command.
- See
remove
command.
$ nodecliac unlink # Removes cwd folder (completion package) from registry.
$ nodecliac unlink --all # Removes all packages from registry.
enable
Enables completions for package(s).
- Takes n-amount of package names as arguments.
--all
: Enables all packages in registry.
$ nodecliac enable # Enables disabled package(s).
$ nodecliac enable --all # Enables all disabled packages.
disable
Disables completions for package(s).
- Takes n-amount of package names as arguments.
--all
: Disables all packages in registry.
$ nodecliac disable # Disables enabled package(s).
$ nodecliac disable --all # Disables all enabled packages.
refresh
Fetches and updates the list of nodecliac completion packages.
- No arguments
$ nodecliac refresh
CLI quick usage
$ nodecliac make --source path/to/program.acmap
# Prettify using 2 spaces per indentation level and print output.
$ nodecliac format --source path/to/program.acmap --print --indent "s:2"
CLI anatomy breakdown
nodecliac assumes following CLI program design pathway:
program-name
→subcommands
→short-flags
/long-flags
→positional-parameters
$ program [subcommand ...] [-a | -b] [--a-opt <Number> | --b-opt <String>] [file ...]
^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
| \ \ | /
CLI program's Program Program Program long Program's (flag-less)
command. subcommands. short flags. flags. positional parameters.
acmap
(auto-completion map) is a simple domain-specific language which maps a program's commands to their flags in .acmap
files.
- Comments begin with a number-sign (
#
) and continue to the end of the line. - Whitespace indentation can precede a comment.
- Trailing comments are allowed.
- Multi-line comments are not supported.
# This is a comment.
# Whitespace can precede comment.
program.command = --flag # A trailing comment.
- Settings begin with an at-sign (
@
) followed by the setting name. - Setting values are assigned with
=
followed by the setting value. - Any amount of whitespace before and after
=
is allowed. - Whitespace indentation can precede a setting declaration.
- Note: Settings can be declared anywhere within your
.acmap
file.- However, it's best if declared at the start of file to quickly spot them.
# Available settings.
@compopt = "default"
@filedir = ""
@disable = false
@placehold = true
@compopt
:comp-option
(-o
) value to Bash's builtincomplete
function.- Values:
false
(no value),true
(default:false
)
- Values:
@filedir
: Pattern to provide bash-completion's_filedir
function.- Values: A string value (i.e.
"@(acmap)
,"-d"
) (default:""
)
- Values: A string value (i.e.
@disable
: Disables bash-completion for command.- Values:
false
,true
(default:false
)
- Values:
@placehold
: Placehold long.acdef
rows to provide faster file lookups.- Values:
false
,true
(default:false
) - Note: Used only when compiling
.acdef
files.
- Values:
- Variables begin with a dollar-sign (
$
) followed by the variable name. - Variable name must start with an underscore (
_
) or a letter (a-zA-Z
). - Variable values are assigned with
=
followed by the variable value. - A variable's value must be enclosed with quotes.
- Any amount of whitespace before and after
=
is allowed. - Whitespace indentation can precede a variable declaration.
- Note: Variables can be declared anywhere within your
.acmap
.
$scriptpath = "~/path/to/script1.sh"
$scriptpath="~/path/to/script2.sh"
$scriptpath = "~/path/to/script3.sh"
# Note: `$scriptpath` gets declared 3 times.
# It's final value is: "~/path/to/script3.sh"
Variable Interpolation
- Variables are intended to be used inside quoted strings.
- Interpolation has the following structure:
- Start with
${
and close with}
. - Any amount of space between opening/closing syntax is allowed.
- The string between the closing/starting syntax is the variable name.
- Start with
$mainscript = "~/.nodecliac/registry/yarn/init.sh"
yarn.remove = default $("${mainscript} remove")
yarn.run = default $("${mainscript} run")
Variable Builtins
acmap
s provide the following builtin variables:
$OS
: The user's platform:linux
,macosx
$HOME
: The user's home directory.$COMMAND
: The command being completed.$PATH
: The command's nodecliac registry path:- For example:
~/.nodecliac/registry/<COMMAND>
- For example:
- Commands/subcommands should be viewed as chains which read from left to right.
- They start with the CLI program's name, are followed by any commands/subcommands, and are dot (
.
) delimited. - If a (sub)command happens to use a dot then simply escape the dot. Non escaped dots will be used as delimiters.
- Whitespace indentation can precede a command chain.
Example: Say the CLI program program
has two commands install
and uninstall
. It's .acmap
will be:
program.install
program.uninstall
Command default documentation
A command chain's default
command-string
(a runable shell command string) can be used to dynamically generate auto-completion items. This command-string
is run when no completion items (commands/flags) are returned. Think of it as a fallback.
- Start by using the keyword
default
followed by a whitespace character. - Follow that with the
command-string
:- A command string is denoted with starting
$(
and closing)
. - The string between the closing/starting syntax is the
command-string
. - Example:
default $("./path/to/script.sh arg1 arg2")
- A command string is denoted with starting
program.command = [
default $("./path/to/script.sh arg1 arg2")
]
Command-string example
For example, say we are implementing an .acmap
file for the dependency manager yarn and would like to return the names of installed packages when removing a package (i.e.$ yarn remove...
). Essentially, we want to extract the package.json
's dependency
and devDependency
entries and supply them to nodecliac. Using a command-string
one can run a script/shell command to do just that.
yarn.remove = [
# The command will run on '$ yarn remove [TAB]'. The script 'script.sh' should contain the
# logic needed to parse package.json to return the installed (dev)dependency package names.
default $("~/.nodecliac/registry/yarn/script.sh")
]
Command-string escaping
- Level 0: Hypothetical
script.sh
with the following contents. No extra escaping when running a script.
for f in ~/.nodecliac/registry/yarn/hooks/*.*; do
[[ "${f##*/}" =~ ^(pre-parse)\.[a-zA-Z]+$ ]] && echo "$f"
done
-
Code Breakdown
- The code will loop over the
~/.nodecliac/registry/yarn/hooks
directory. - File names matching the pattern (
^(pre-parse).[a-zA-Z]+$
) will print to console.
- The code will loop over the
-
Level 1: If
bash
is one's default shell, copy/paste and run this one-liner in a Terminal:
for f in ~/.nodecliac/registry/yarn/hooks/*.*; do [[ "${f##*/}" =~ ^(pre-parse)\.[a-zA-Z]+$ ]] && echo "$f"; done
- Level 2: Now say we want to run the same line of code via
bash -c
. Run the following in a Terminal:
bash -c "for f in ~/.nodecliac/registry/yarn/hooks/*.*; do [[ \"\${f##*/}\" =~ ^(pre-parse)\\.[a-zA-Z]+$ ]] && echo \"\$f\"; done;"
- Level 3: How about using
Perl
to runbash -c
to execute the command?
perl -e 'print `bash -c "for f in ~/.nodecliac/registry/yarn/hooks/*.*; do [[ \\\"\\\${f##*/}\\\" =~ ^(pre-parse)\\.[a-zA-Z]+\$ ]] && echo \"\\\$f\"; done;"`';
As shown, the more programs involved the more escaping required due to the string being passed from program to program. Escaping can get cumbersome. If so, running the code from a file will be the easiest alternative.
Example: Command-string escaping.
Now let's make a command-string
to print all .acdef
file names (without extension) in the nodecliac registry:
$ s="";for f in ~/.nodecliac/registry/*/*.acdef; do s="$s$f\n"; done; echo -e "$s" | LC_ALL=C perl -ne "print \"\$1\n\" while /(?! \/)([^\/]*)\.acdef$/g"
Using the following .acmap
contents the command-string
would be the following:
- Note: Ensure the
|
and\
characters are escaped.
# The escaped command-string.
$cmdstr = 's="";for f in ~/.nodecliac/registry/*/*.acdef; do s="$s$f\\n"; done; echo -e "$s" \| LC_ALL=C perl -ne "print \"\$1\\n\" while /(?! \\/)([^\\/]*)\\.acdef$/g"'
nodecliac.print = --command=$('${cmdstr}')
Compiling to .acdef
, an .acdef
file with the following contents will be generated:
# DON'T EDIT FILE —— GENERATED: Mon Mar 02 2020 14:15:13 (1583187313)
--
.print --command=|--command=$('s="";for f in ~/.nodecliac/registry/*/*.acdef; do s="$s$f\\n"; done; echo -e "$s" \| LC_ALL=C perl -ne "print \"\$1\\n\" while /(?! \\/)([^\\/]*)\\.acdef$/g"')
Letting the completion engine know an option should be ignored (not displayed) is simple. Merely prefix the option with an exclamation-mark (!
). This is meant to be used when an option has already been used and therefore doesn't need to be shown again as a possible completion item.
Note: For more information about command-string
s take a look at acmap Syntax > Flags > Flag Variants > Flags (dynamic values)
. The section contains more details for command-string
s like special character escaping caveats, dynamic/static arguments, and examples with their breakdowns. Keep in mind that the section uses the term command-flag
due it being used for flags but command-flag
and command-string
are effectively the same thing — just a runable shell command string. The naming (command-{string|flag}
) is based on its application (i.e. for command-chains or flags).
Command chain grouping
Command chains can be grouped. It is not necessary but doing may help condense acmaps.
- A command group is denoted with starting
{
and closing}
. - The commands are found in between the closing/starting syntax.
- Commands are comma delimited.
For example, take the following:
program.deploy-keys.add
program.deploy-keys.list
program.deploy-keys.rm
Grouping can reduce it to:
program.deploy-keys.{add,list,rm}
To define flags we need to extend the command chain syntax.
- Flags are wrapped with
= [
and a closing]
. - The
= [
must be on the same line of the command chain. - The closing
]
must be on its own line and man have any amount of indentation.
Building on the command chain section example, say the install
command has the flags: destination/d
and force/f
. Code can be updated to:
program.install = [
--destination
-d
--force
-f
]
program.uninstall
However, it can be cleaned up a bit by using the flag alias
syntax:
program.install = [
--destination::d
--force::f
]
program.uninstall
Flag variants
- If flag requires user input append
=
to the flag.
program.command = [
--flag=
]
- If flag is a switch (boolean) append a
?
to the flag to let the completion engine know the flag doesn't require value completion.
program.command = [
--flag?
]
- Sometimes a flag can be supplied multiple times.
- Let the completion engine know this by using the multi-flag indicator
*
.
program.command = [
# Allow user to provide multiple file paths.
--file=*
# Hard-coded values.
--colors=*(red green yellow)
]
- This method should be used when the flag value list can be kept to a single line.
- Note: Values must be delimited with spaces.
- Note: When a flag has many values a long form list should be used for clarity's sake.
program.command = [
# Supply 1, "2", false, 4 as hard-coded values.
--flag=(1 "2" false 4)
# If multiple values can be supplied to program use the multi-flag indicator '*'.
# This allows --flag to be used multiple times until all values have been used.
--flag=*(1 "2" false 4)
]
- Flag long form lists are wrapped with starting
=(
and a closing)
. - The
=(
must be on the same line as the flag. - The closing
)
must be on its own line and man have any amount of indentation. - A flag value option starts with
-
(a hyphen + a space) followed by the value. - Any amount of whitespace indentation can precede the flag value option
-
sequence.
program.command = [
--flag=(
- 1
- "2"
- false
- 4
)
# Allow flag to be used multiple times.
--flag=*(
- 1
- "2"
- false
- 4
)
]
program.uninstall
Sometimes static values are not enough so a command-flag
can be used. A command-flag
is just a runnable shell command.
command-flag
syntax:
- Begins with starting
$(
, followed by command, and ends with closing)
. - Output: a newline (
\n
) delimited list is expected.- Each completion item should be on its own line.
- Example:
$("cat ~/colors.text")
- Note: Command must be quoted (double or single).
static or dynamic arguments may be provided.
- Example:
$("cat ~/colors.text", "!red", $"cat ~/names.text")
:- This provides the static
!red
and dynamiccat ~/names.text
arguments. !red
will be argument0
and the output ofcat ~/names.text
will be argument1
.
- This provides the static
- Note: dynamic arguments must be dollar-sign prefixed (
$
).
Escaping: $
and |
are used internally so require escaping when used.
--flag=$("echo \$0-\$1", $"echo 'john'", "doe")
:- The
$
s in the command are escaped.
- The
--flag=$("nodecliac registry \| grep -oP \"(?<=─ )([-a-z]*)\"")
:- The
|
gets escaped here. - Note: Inner quotes are also escaped like one would on the command-line.
- The
Example: Showcases dynamic and static values.
program.command = [
# '*' denotes the flag is a multi-flag.
--flag=*
--flag=(
- index.js
- ':task:js'
- "some-thing"
# Dynamic values get combined with hard-coded values.
- $("cat ~/values.text")
)
# Same as above.
--flag=*(
- index.js
- ':task:js'
- "some-thing"
- $("cat ~/values.text")
)
]
program.uninstall
When no completion items are found bash-completion's _filedir
function is used as a fallback. _filedir
performs file/directory completion. By default it returns both file and directory names. However, this can be controlled to only return directory names or files of certain types.
- Start by using the keyword
filedir
followed by a whitespace character. - Follow that with a string:
- To only return directories use
"-d"
. - To filter file type extensions provide a pattern like
"@(acmap)"
. - Example:
filedir "@(acmap)"
- To only return directories use
program.command = [
filedir "@(acmap)"
]
Note: This filedir
usage is per command chain. If this is not needed, a global filedir
value can be provided via the @filedir
setting like so: @filedir = "@(acmap)"
. Both can be used but precedence is as follows:
- If a command uses
filedir
use that. - If not, look for
@filedir
setting. - If neither are provided all files/directories are returned (no filtering).
The context
keyword provides the ability to disable flags and deal with mutual flag exclusivity.
- Start by using the keyword
context
followed by a whitespace character. - Follow that with a string:
- Conditional Example:
context "!help: #fge0"
- Mutual Exclusivity Example:
context "{ json | yaml | csv }
- Conditional Example:
Conditional context strings have their own grammar: "<flag1, flagN> : <condition1, conditionN>"
. If each <condition>
results in true
the <flags>
are enabled/disabled.
- A flag is represented without the hyphens.
- Example: For the flag
--help
it would just behelp
.
- Example: For the flag
- If the flag needs to be disabled, prepend a
!
.- Example:
help
(If conditions aretrue
flag will be enabled) - Example:
!help
(If conditions aretrue
flag will be disabled)
- Example:
- Check against flag/positional arguments:
- Format:
# + (f)lag|(a)rgument + operator + number
- Example (flag check):
#fge0
- Example (argument check):
#age0
- Format:
- Operators:
eq
: Equal tone
: Not equal togt
: Greater thange
: Greater than or equal tolt
: Less thanle
: Less than or equal to
- Number:
- Must be a positive number.
- Inversion: Tests can be inverted by prepending a
!
.
Disable help
and version
flags when used flag count is greater or equal to 0.
program.command = [
--help?
--version?
context "!help, !version: #fge0"
]
Disable help
flag when the used flag count is greater or equal to 0 and version flag is used.
program.command = [
--help?
--version?
context "!help: #fge0, version"
]
Mutual exclusivity is represented like so: "{ flag1 | flagN }"
. Once a grouped flag is used the other(s) are disabled.
For example, say the --json
, --csv
, and --text
flags are allowed but the --json
flag is used. The remaining flags --text
and --csv
won't be shown as completion items.
program.command = [
--json=,
--csv=,
--text=(false true)
context "{ json | csv | text }"
]
In this example, once --follow
or --tail
is used the other flag will be disabled.
program.command = [
--follow=,
--tail=(false true)
context "{follow | tail}"
]
This is equivalent to the previous example.
program.command = [
--follow=,
--tail=(false true)
context "!follow: tail"
context "!tail: follow"
]
Context strings can be combined but for maintainability it's better to separate them.
program.command = [
--help?
--version?
context "!help, !version: #fge0"
--json=,
--csv=,
--text=(false true)
context "{ json | csv | text }"
--follow=,
--tail=(false true)
context "{follow | tail}"
--hours=
--minutes=
--seconds=
--service=
--job-id=
--target=
context "{ job-id | target }"
]
Context strings can be combined by delimiting them with ;
.
program.command = [
--help?
--version?
--json=,
--csv=,
--text=(false true)
--follow=,
--tail=(false true)
--hours=
--minutes=
--seconds=
--service=
--job-id=
--target=
context "!help, !version: #fge0; { json | csv | text }; { follow | tail }; { job-id | target }"
]
Note: Context strings are evaluated on every completion cycle. Therefore, using too many may slow down the 'perceived completion feel' as it takes time to evaluate all provided contexts.
The exclude
keyword is only allowed in a wildcard command block. It serves to easily give all command strings the same (universal/shared) flags. Although this can be done manually, this can help reduce the acmap and make it easier to maintain.
Let's look at an example. All command strings but program.cache
share the --help
flag.
program = [
--help?
--version
]
program.make = [
--help?
--extensions=*(js html css)
]
program.format = [
--help?
--extensions=*(js html css)
--indentation
]
program.cache = [
--clear?
]
Now let's use a wildcard block and exclude the program.cache
command string.
* = [
exclude "program.cache"
--help?
]
program = [
--version
]
program.make = [
--extensions=*(js html css)
]
program.format = [
--extensions=*(js html css)
--indentation
]
program.cache = [
--clear?
]
If desired it can even be condensed to.
* = --help?|exclude "program.cache"
program = --version
program.make,
program.format = --extensions=*(js html css)
program.format = --indentation
program.cache = --clear?
acdef
(auto-completion definition) is a subset of acmap
syntax, is auto-generated, and is used to perform Bash completion lookups.
The following example .acdef
will be used to explain how to read .acdef
files.
# DON'T EDIT FILE —— GENERATED: Mon Mar 02 2020 14:15:13 (1583187313)
--cache-folder|--check-files|--cwd|--disable-pnp
.access --
.add --audit|--dev|--exact|--ignore-workspace-root-check|--optional|--peer|--tilde
.autoclean --force|--init
.bin --
.cache --
.upgrade --caret|--exact|--latest|--pattern|--scope|--tilde
.why --
.workspace --
.workspaces --
.workspaces.info --
.workspaces.run --
.upgrade default $("~/.nodecliac/registry/command/scripts/init.sh upgrade")
.why default $("command list --depth=0 \| perl -wln -e \"/(?! ─ )([-\/_.@(?)a-zA-Z0-9]*)(?=\@)/ and print $&;\"")
.workspace default $("~/.nodecliac/registry/command/scripts/init.sh workspace")
.workspaces.run default $("~/.nodecliac/registry/command/scripts/init.sh run")
- The first line is the file's header.
- It is the only comment in the document.
- It contains a warning to not modify the file and the file's creation information.
# DON'T EDIT FILE —— GENERATED: Mon Mar 02 2020 14:15:13 (1583187313)
...
- The following section contains the command-chains and their respective flags.
- Each line represents a row which starts with the command chain and is followed by a single space.
- Whatever comes after the single space are the command's flags.
- Flags are delimited by pipe (
|
) characters.
- Flags are delimited by pipe (
- Rows that do not have flags will contain two hyphens (
--
) after the single space character.
...
--cache-folder|--check-files|--cwd|--disable-pnp
.access --
.add --audit|--dev|--exact|--ignore-workspace-root-check|--optional|--peer|--tilde
.autoclean --force|--init
.bin --
.cache --
.upgrade --caret|--exact|--latest|--pattern|--scope|--tilde
.why --
.workspace --
.workspaces --
.workspaces.info --
.workspaces.run --
...
Note: Command chain lines, lines starting with a single space or a dot (.
) character, have the program's name removed.
For example, the line .workspaces.run --
can be viewed as command.workspaces.run --
.
- The bottom section of an
.acdef
file will contain any command chain fallbacks.
...
.upgrade default $("~/.nodecliac/registry/command/scripts/init.sh upgrade")
.why default $("command list --depth=0 \| perl -wln -e \"/(?! ─ )([-\/_.@(?)a-zA-Z0-9]*)(?=\@)/ and print $&;\"")
.workspace default $("~/.nodecliac/registry/command/scripts/init.sh workspace")
.workspaces.run default $("~/.nodecliac/registry/command/scripts/init.sh run")
- Depending how complex an
.acmap
is sometimes placeholders are needed. They are used internally to speed up reading, what would otherwise be large,.acdef
files. - Placeholder syntax:
- Begin with
--p#
and are followed by a fixed number of hexadecimal characters. - Example:
--p#d2eef1
- Begin with
The following example .acdef
showcase placeholders.
# DON'T EDIT FILE —— GENERATED: Thu Apr 09 2020 10:4:22 (1586451862)
--help|--version
.buildIndex --p#07d43e
.c --p#07d43e
.cc --p#07d43e
.check --p#07d43e
.compile --p#07d43e
.compileToC --p#07d43e
.compileToCpp --p#07d43e
.compileToOC --p#07d43e
.cpp --p#07d43e
.ctags --p#07d43e
.doc --p#07d43e
.doc2 --p#07d43e
.dump --p#07d43e
.e --p#07d43e
.genDepend --p#07d43e
.js --p#07d43e
.jsondoc --p#07d43e
.objc --p#07d43e
.rst2html --p#07d43e
.rst2tex --p#07d43e
Simply put, the registry (~/.nodecliac/registry
) is where completion packages are stored. Completion packages are stored in the form: ~/.nodecliac/registry/<command>
.
Package documentation is divided into their own sections.
Create |
Add / Link |
Remove / Unlink |
Enable / Disable |
---|
Pre-made completion packages for several programs of varying complexity are available for reference.
Hooks are just regular executable shell scripts that run at specific points along a completion cycle when a completion is attempted. They let one modify internal aspects of nodecliac's completion logic and behavior.
Expand hook section
hooks/pre-parse.sh
: Modifies select initialization variables before running completion script.hooks/post-parse.sh
: Modifies final completions before terminating the completion cycle and printing suggestions.
In the command's completion package create a hooks/
directory. All hook scripts will be stored here.
<command>/
├── <command>.acmap
├── <command>.acdef
├── .<command>.config.acdef
└── hooks/
Hook scripts are provided parsing information via environment variables.
Bash provided variables but exposed by nodecliac
NODECLIAC_COMP_LINE
: Original (unmodified) CLI input.NODECLIAC_COMP_POINT
: Caret index when Tab key was pressed.
nodecliac provided variables
NODECLIAC_MAIN_COMMAND
: The command auto completion is being performed for.NODECLIAC_COMMAND_CHAIN
: The parsed command chain.NODECLIAC_COMP_INDEX
: The index where completion is being attempted.NODECLIAC_LAST
: The last parsed word item.- Note: Last word item could be a partial word item.
- This happens when the Tab key gets pressed within a word item. For example, take the following input:
$ program command
. If theTab key was pressed like so:$ program commTaband
, the last word item iscomm
. Thus a partial word with a remainder string ofand
. Resulting in finding completions forcomm
.
- This happens when the Tab key gets pressed within a word item. For example, take the following input:
- Note: Last word item could be a partial word item.
NODECLIAC_PREV
: The word item preceding the last word item.NODECLIAC_INPUT
: CLI input from start to caret (Tab key press) index.NODECLIAC_INPUT_ORIGINAL
: Original unmodified CLI input.NODECLIAC_INPUT_REMAINDER
: CLI input from start to caret index.NODECLIAC_LAST_CHAR
: Character before caret.NODECLIAC_NEXT_CHAR
: Character after caret.- Note: If char is not
''
(empty) then the last word item (NODECLIAC_LAST
) is a partial word.
- Note: If char is not
NODECLIAC_COMP_LINE_LENGTH
: Original CLI input's length.NODECLIAC_INPUT_LINE_LENGTH
: CLI input length from string beginning to caret position.NODECLIAC_ARG_COUNT
: Amount of arguments parsed inNODECLIAC_INPUT
string.NODECLIAC_ARG_N
: Parsed arguments can be individually accessed with this variable.- First argument is
NODECLIAC_ARG_0
and will always be the program's command. - Because input is variable all other arguments can be retrieved with a loop.
- Use
NODECLIAC_ARG_COUNT
as max loop iteration.
- Use
- Example: Given the CLI input:
$ yarn remove chalk prettier
- Arguments would be:
NODECLIAC_ARG_0
:yarn
NODECLIAC_ARG_1
:remove
NODECLIAC_ARG_2
:chalk
NODECLIAC_ARG_3
:prettier
- Arguments would be:
- First argument is
NODECLIAC_USED_DEFAULT_POSITIONAL_ARGS
: Collected positional arguments.
Take yarn's pre-parse.sh
script as an example:
#!/bin/bash
# Initialization variables:
#
# cline # CLI input.
# cpoint # Index of caret position when [TAB] key was pressed.
# command # Program for which completions are for.
# acdef # The command's .acdef file contents.
output="$("$HOME/.nodecliac/registry/$command/hooks/pre-parse.pl" "$cline")"
# Remaining lines are package.json's script entries.
mapfile -ts1 lines < <(echo -e "$output")
printf -v output '%s\n' "${lines[@]}" && acdef+=$'\n'"$output"
- The Bash script is glue code. It runs the Perl script
pre-parse.pl
to retrieve the cwdpackage.json
scripts
and determine whether yarn is being used in a workspace. - Using the Perl script's output the Bash script overwrites the
cline
variable and appends thepackage.json
scripts
to theacdef
variable. Adding them as their own commands. - nodecliac uses the new values to determine completions.
Take m-cli's post-parse.sh
script as an example:
#!/bin/bash
function completion_logic() {
COMP_CWORD="$NODECLIAC_COMP_INDEX"
prev="$NODECLIAC_PREV"
cmd="$NODECLIAC_ARG_1"
sub="$NODECLIAC_ARG_2"
case "$cmd" in
dir)
case "$prev" in
delete) echo -e "empty\ndsfiles"; return ;;
dsfiles) echo -e "on\noff"; return ;;
esac
;;
disk)
case "$sub" in
# _m_disk
verify|repair) [[ $COMP_CWORD == 3 ]] && echo -e "disk\nvolume"; return ;;
format)
case $COMP_CWORD in
3) echo -e "ExFAT\nJHFS+\nMS-DOS\nvolume" ;;
4) [[ "$NODECLIAC_ARG_3" == "volume" ]] && echo -e "ExFAT\nJHFS+\nMS-DOS" ;;
esac
return
;;
rename) [[ $COMP_CWORD == 3 ]] && \
echo -e "$(grep -oE '(disk[0-9s]+)' <<< "$(diskutil list)")"; return ;;
# _m_dock
autohide) [[ $COMP_CWORD == 3 ]] && echo -e "YES\nNO"; return ;;
magnification) [[ $COMP_CWORD == 3 ]] && echo -e "YES\nNO"; return ;;
position) [[ $COMP_CWORD == 3 ]] && echo -e "BOTTOM\nLEFT\nRIGHT"; return ;;
esac
;;
dock)
case "$sub" in
autohide) [[ $COMP_CWORD == 3 ]] && echo -e "YES\nNO"; return ;;
magnification) [[ $COMP_CWORD == 3 ]] && echo -e "YES\nNO"; return ;;
position) [[ $COMP_CWORD == 3 ]] && echo -e "BOTTOM\nLEFT\nRIGHT"; return ;;
esac
;;
finder) [[ $COMP_CWORD == 3 ]] && echo -e "YES\nNO"; return ;;
screensaver) [[ $sub == "askforpassword" && $COMP_CWORD == 3 ]] && echo -e "YES\nNO"; return ;;
esac
}
completion_logic
- The post hook script is written in Bash but any language may be used. As shown, the script makes use of the provided
NODECLIAC_*
environment variables to determine what completion items to add. Each completion item must be returned on its own line.
To return quicker results completions are cached.
Expand cache section
0
: No caching.1
: Cache all butcommand-string
(dynamic) completions. (default
)2
: Cache everything.
$ nodecliac cache --clear # Clear cache.
$ nodecliac cache --level 0 # Turn cache off.
nodecliac provides a way to test completions for your program.
Expand testing section
Creating tests is done directly from the program's acmap via @test
. Start with @test =
followed by the test string "<completion string> ; <test1 ; testN>"
.
- Test entire completion output (including meta data):
- Does the output contain
format
?:@test = "program --; *format*
- Does the output omit
format
?:@test = "program --; !*format*
- Does the output contain
- Test individual completion items:
- Do any completion items contain
format
?:@test = "program --; *:*format*
- Does the first completion item contain
format
?:@test = "program --; 1:*format*
- Does the first completion item start with
--for
?:@test = "program --; 1:--for*
- Does the first completion item end with
format
?:@test = "program --; 1:*format
- Does the first completion item equal
--format
?:@test = "program --; 1:--format
- Do any completion items contain
- Test completion items count:
- Is there at least 1 completion item?:
@test = "program --; #cgt0
- Are there 3 completion items?:
@test = "program --; #ceq3
- Format:
# + (c)ount + operator + number
- Operators:
eq
: Equal tone
: Not equal togt
: Greater thange
: Greater than or equal tolt
: Less thanle
: Less than or equal to
- Number:
- Must be a positive number.
- Format:
- Is there at least 1 completion item?:
- Inversion: Any test can be inverted by preceding the test with a
!
.
Take the following example acmap. It contains a couple commands and their respective flags.
program.make = --source
program.format = --source
@test = "program make --; *source*"
@test = "program format --for; *format*"
Multiple tests can be provided to test a single completion string. Simply delimit them with ;
.
program.make = --source
program.format = --source
@test = "program make --; *source* ; #ceq1"
@test = "program format --for; *format* ; #ceq1"
Running tests is done by running a built in command: $ nodecliac test <command-name>
. As an example, try running nodecliac's tests. With nodecliac installed, enter nodecliac test nodecliac
into a Terminal and press Enter. Note, for tests to run the program's completion package must exist in the registry to be able to run tests. Running $ nodecliac registry
will list installed completion packages.
Like with testing completion strings, nodecliac also provides a way to debug completions. This is useful when creating a completion package. To start debugging simply enable it. When enabled pressing the Tab key will output debugging information instead of providing bash completions.
Expand debugging section
Run: $ nodecliac debug --enable
Run: $ nodecliac debug --disable
nodecliac's auto-completion script is written in Nim
and Perl
. The Nim version supports Linux/macOS while Perl is used as a fallback. When both versions are installed it's possible to use one over the other to debug. This is done with the --script
flag like so:
- Explicitly use Nim script:
$ nodecliac debug --enable --script nim
- Explicitly use Perl script:
$ nodecliac debug --enable --script perl
To get the debug mode: $ nodecliac debug
0
: Disabled1
: Enabled2
: Enabled + use Perl script3
: Enabled + use Nim script
OS Support
- Made using Node.js
v8.16.0
on a Linux machine runningUbuntu 16.04.5 LTS
. - Tested and working on:
macOS Mojave (v10.14.4)
.Windows 10 - Untested
.
Shell Support
- nodecliac only works with Bash.
- Support for other shells (Zsh, Fish, etc.) may be added with increased usage.
Editor Support
.acmap
/.acdef
grammar packages available for Sublime Text 3, VSCode, and Atom text editors.- Note:
README.md
files are found next to each package explaining how to install it. - Packages are stored under
resources/editors
.
Contributions are welcome! Found a bug, feel like documentation is lacking/confusing and needs an update, have performance/feature suggestions or simply found a typo? Let me know! :)
See how to contribute here.
This project uses the MIT License.