Skip to content

Commit

Permalink
Merge pull request #17 from juanibiapina/new-completions
Browse files Browse the repository at this point in the history
New completions system
  • Loading branch information
juanibiapina authored Jun 20, 2024
2 parents cdab419 + f92468d commit fcb9d11
Show file tree
Hide file tree
Showing 23 changed files with 381 additions and 28 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v2.2.0 - 20.06.2024

- Add new completions system. The old system is still supported so this isn't a
breaking change. The new system has priority over the old system when both
are present.

## v2.1.0 - 16.06.2024

- Rework `--validation` flag. It now needs to come after `--` and validates any
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sub"
version = "2.1.0"
version = "2.2.0"
description = "Dynamically generate rich CLIs from scripts."
authors = ["Juan Ibiapina <[email protected]>"]
edition = "2021"
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ file. The special comments are:
Usage comment, when present, has specific syntactic rules and is used to
parse command line arguments. See [Validating arguments](#validating-arguments)
and [Parsing arguments](#parsing-arguments) for more information.
- `Options:` A description of the options the script accepts. This is used to
display help information and generate completions. See
[Completions](#completions) for more details.
- Extended documentation: Any other comment lines in this initial block will be
considered part of the extended documentation.

Expand Down Expand Up @@ -255,6 +258,42 @@ if [[ "${args[long]}" == "true" ]]; then
fi
```

## Completions

`sub` automatically provides completions for subcommand names.

To enable completions for positional arguments in the `Usage` comment, add an
`Options:` comment with a list of arguments. An option must have the format:
`name (completion_type): description`. Completion type is optional. Currently,
the only supported completion type is `script`, which allows for dynamic
completions like the following example:

```sh
# Usage: {cmd} <name>
# Options:
# name (script): A name

# check if we're being requested completions
if [[ "$_HAT_COMPLETE" == "true" ]]; then
if [[ "$_HAT_COMPLETE_ARG" == "name" ]]; then
echo "Alice"
echo "Bob"
echo "Charlie"
# note that you can run any command here to generate completions
fi

# make sure to exit when generating completions to prevent the script from running
exit 0
fi

# read arguments
declare -A args="($_HAT_ARGS)"

# say hello
echo "Hello, ${args[name]}!"
```


## Nested subcommands

`sub` supports nested directories for hierarchical command structures. For
Expand Down
2 changes: 1 addition & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
with pkgs;
rustPlatform.buildRustPackage rec {
name = "sub-${version}";
version = "2.1.0";
version = "2.2.0";
src = ./.;

cargoLock = {
Expand Down
68 changes: 68 additions & 0 deletions integration/completions-old.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bats

load test_helper

@test "completions: without arguments, lists commands" {
fixture "completions-old"

run main --completions

assert_success
assert_output "$(main --commands)"
}

@test "completions: fails gracefully when command is not found" {
fixture "completions-old"

run main --completions not-found

assert_failure
assert_output ""
}

@test "completions: invokes command completions" {
fixture "completions-old"

run main --completions with-completions

assert_success
assert_output "comp1
comp2"
}

@test "completions: lists nothing if command provides no completions" {
fixture "completions-old"

run main --completions no-completions

assert_success
assert_output ""
}

@test "completions: displays for directory commands" {
fixture "completions-old"

run main --completions directory

assert_success
assert_output "$(main --commands directory)"
}

@test "completions: displays double nested directory commands" {
fixture "completions-old"

run main --completions directory double

assert_success
assert_output "$(main --commands directory double)"
}

@test "completions: displays double nested subcommands" {
fixture "completions-old"

run main --completions directory double with-completions

assert_success
assert_output "comp11
comp21"
}
12 changes: 11 additions & 1 deletion integration/completions.bats
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ load test_helper
assert_output ""
}

@test "completions: invokes command completions" {
@test "completions: script: invokes command completions for first argument" {
fixture "completions"

run main --completions with-completions
Expand All @@ -30,6 +30,16 @@ load test_helper
comp2"
}

@test "completions: script: invokes command completions for second argument" {
fixture "completions"

run main --completions with-completions value1

assert_success
assert_output "comp3
comp4"
}

@test "completions: lists nothing if command provides no completions" {
fixture "completions"

Expand Down
5 changes: 5 additions & 0 deletions integration/fixtures/completions-old/bin/main
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

set -e

$SUB_BIN --color never --name main --executable "${BASH_SOURCE[0]}" --relative ".." -- "$@"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

# Provide completions
if [ "$1" = "--complete" ]; then
echo comp11
echo comp21
exit
fi
Empty file.
8 changes: 8 additions & 0 deletions integration/fixtures/completions-old/libexec/with-completions
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash

# Provide completions
if [ "$1" = "--complete" ]; then
echo comp1
echo comp2
exit
fi
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/usr/bin/env bash
#
# Usage: {cmd} <option>
# Options:
# option(script): Description of the option

# Provide completions
if [ "$1" = "--complete" ]; then
echo comp11
echo comp21
exit
fi
set -e

echo "comp11"
echo "comp21"
5 changes: 5 additions & 0 deletions integration/fixtures/completions/libexec/no-completions
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
#
# Usage: {cmd}

exit 1
31 changes: 26 additions & 5 deletions integration/fixtures/completions/libexec/with-completions
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
#!/usr/bin/env bash
#
# Usage: {cmd} <option1> <option2>
# Options:
# option1 (script): Description of option 1
# option2 (script): Description of option 2

# Provide completions
if [ "$1" = "--complete" ]; then
echo comp1
echo comp2
exit
set -e

if [[ "$_MAIN_COMPLETE" == "true" ]]; then
if [[ "$_MAIN_COMPLETE_ARG" == "option1" ]]; then
echo "comp1"
echo "comp2"

exit 0
fi

if [[ "$_MAIN_COMPLETE_ARG" == "option2" ]]; then
echo "comp3"
echo "comp4"

exit 0
fi

exit 201
fi

exit 202

5 changes: 4 additions & 1 deletion integration/fixtures/project/libexec/with-help
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#
# Summary: Command with complete help
#
# Usage: {cmd} [args]...
# Usage: {cmd} <positional> [args]...
#
# Options:
# positional: A positional argument
#
# This is a complete test script with documentation.
#
Expand Down
7 changes: 4 additions & 3 deletions integration/help.bats
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Available subcommands:
assert_output "Usage: main no-doc [args]...
Arguments:
[args]...
[args]... other arguments
Options:
-h, --help Print help"
Expand All @@ -67,10 +67,11 @@ Options:
assert_success
assert_output "Command with complete help
Usage: main with-help [args]...
Usage: main with-help <positional> [args]...
Arguments:
[args]...
<positional> A positional argument
[args]...
Options:
-h, --help Print help
Expand Down
5 changes: 3 additions & 2 deletions src/commands/directory.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fs;
use std::path::PathBuf;
use std::collections::HashMap;

use clap::Arg;

Expand Down Expand Up @@ -35,7 +36,7 @@ impl<'a> DirectoryCommand<'a> {
}
}

let usage = Usage::new(command, None);
let usage = Usage::new(command, HashMap::new(), None);

return Self {
names,
Expand Down Expand Up @@ -63,7 +64,7 @@ impl<'a> DirectoryCommand<'a> {
}
}

let usage = Usage::new(command, None);
let usage = Usage::new(command, HashMap::new(), None);

return Self {
names,
Expand Down
34 changes: 34 additions & 0 deletions src/commands/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,39 @@ impl<'a> Command for FileCommand<'a> {
}

fn completions(&self) -> Result<i32> {
// new completion system
if self.usage.provides_completions() {
let name = self.usage.get_next_option_name_for_completions(&self.args);

let completion_type = match name {
Some(ref name) => self.usage.get_completion_type(name),
None => None,
};

match completion_type {
Some(usage::CompletionType::Script) => {
let mut command = process::Command::new(&self.path);

command.env(format!("_{}_ROOT", self.config.name.to_uppercase()), &self.config.root);
command.env(format!("_{}_COMPLETE", self.config.name.to_uppercase()), "true");
command.env(format!("_{}_COMPLETE_ARG", self.config.name.to_uppercase()), name.unwrap());

let status = command.status().unwrap();

return match status.code() {
Some(code) => Ok(code),
None => Err(Error::SubCommandInterrupted),
};
},
None => {
// do nothing
},
};

return Ok(0);
}

// old completion system
if parser::provides_completions(&self.path) {
let mut command = process::Command::new(&self.path);

Expand All @@ -73,6 +106,7 @@ impl<'a> Command for FileCommand<'a> {
None => Err(Error::SubCommandInterrupted),
};
}

Ok(0)
}

Expand Down
1 change: 1 addition & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ pub enum Error {
SubCommandInterrupted,
UnknownSubCommand(String),
InvalidUsageString(Vec<Simple<char>>),
InvalidOptionString(Vec<Simple<char>>),
InvalidUTF8,
}
Loading

0 comments on commit fcb9d11

Please sign in to comment.