Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ jobs:
with:
toit-version: ${{ matrix.toit-version }}

- name: Install shell completion test dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y fish

- name: Install shell completion test dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install tmux fish

- name: Test
run: |
make test
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ It provides:
* Composable subcommands: `myapp subcommand`
* Type options/flags that parse arguments: `myapp --int-flag=49 enum_rest_arg`
* Automatic help generation
* Shell completion for bash, zsh, and fish
* Command aliases
* Functionality to cache data between runs
* Functionality to store configurations
Expand Down Expand Up @@ -316,7 +317,7 @@ main args:

run invocation/Invocation:
ui := invocation.cli.ui
ui.emit
ui.emit --result
// Block that is invoked if structured data is needed.
--structured=: {
"result": "Computed result"
Expand All @@ -338,6 +339,51 @@ The shorthands `ui.info`, `ui.debug`, also dispatch to these methods if they rec

See the documentation of the `ui` library for more details.

## Shell Completion

Programs built with this package automatically get a `completion` subcommand that
generates shell completion scripts for bash, zsh, and fish. Users enable completions
by sourcing the output:

``` sh
# Bash (~/.bashrc):
source <(myapp completion bash)

# Zsh (~/.zshrc):
source <(myapp completion zsh)

# Fish (~/.config/fish/config.fish):
myapp completion fish | source
```

Enum options and subcommands are completed automatically. For custom completions
(e.g., completing device IDs from a database), pass a `--completion` callback when
creating an option:

``` toit
import cli show *

create-command -> Command:
return Command "deploy"
--options=[
Option "device"
--help="The device to deploy to."
--completion=:: | context/CompletionContext |
[
CompletionCandidate "device-001" --description="Living Room Sensor",
CompletionCandidate "device-002" --description="Garden Monitor",
]
]
--run=:: | invocation/Invocation |
print "Deploying to $(invocation["device"])"
```

The callback receives a `CompletionContext` with the current prefix, option,
command, and already-provided options. It returns a list of `CompletionCandidate`
objects, which can include descriptions shown by shells that support them (zsh, fish).

See `examples/completion.toit` for a complete example.

## Features and bugs

Please file feature requests and bugs at the [issue tracker][tracker].
Expand Down
110 changes: 110 additions & 0 deletions examples/completion.toit
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (C) 2026 Toit contributors.
// Use of this source code is governed by an MIT-style license that can be
// found in the package's LICENSE file.

import cli show *

/**
Demonstrates shell completion support.

To try it out, compile to a binary and source the completion script:

```
# Compile:
toit compile -o /tmp/fleet examples/completion.toit

# Enable completions (bash):
source <(/tmp/fleet completion bash)

# Enable completions (zsh):
source <(/tmp/fleet completion zsh)

# Enable completions (fish):
/tmp/fleet completion fish | source
```

Then type `/tmp/fleet ` and press Tab to see suggestions.
*/

main arguments:
root := Command "fleet"
--help="""
An imaginary fleet manager for Toit devices.

Manages a fleet of devices, allowing you to deploy firmware,
monitor status, and configure devices.
"""

root.add create-deploy-command
root.add create-status-command

root.run arguments

KNOWN-DEVICES ::= {
"d3b07384-d113-4ec6-a7d2-8c6b2ab3e8f5": "Living Room Sensor",
"6f1ed002-ab5d-42e0-868f-9e0c30e5a295": "Garden Monitor",
"1f3870be-2748-4c9a-81e4-1b3b5e5a5c7f": "Front Door Lock",
}

/**
Completion callback that returns device UUIDs with human-readable descriptions.
*/
complete-device context/CompletionContext -> List:
result := []
KNOWN-DEVICES.do: | uuid/string name/string |
if uuid.starts-with context.prefix:
result.add (CompletionCandidate uuid --description=name)
return result

create-deploy-command -> Command:
return Command "deploy"
--help="""
Deploys firmware to a device.

Uploads and installs the specified firmware file on the target device.
"""
--options=[
Option "device" --short-name="d"
--help="The device to deploy to."
--completion=:: complete-device it
--required,
OptionEnum "channel" ["stable", "beta", "dev"]
--help="The release channel."
--default="stable",
]
--rest=[
Option "firmware"
--type="file"
--help="Path to the firmware file."
--required,
]
--run=:: run-deploy it

create-status-command -> Command:
return Command "status"
--help="Shows live status of devices."
--options=[
Option "device" --short-name="d"
--help="The device to show. Shows all if omitted."
--completion=:: complete-device it,
OptionEnum "format" ["table", "json", "sparkline"]
--help="Output format."
--default="table",
]
--run=:: run-status it

run-deploy invocation/Invocation:
device := invocation["device"]
channel := invocation["channel"]
firmware := invocation["firmware"]
name := KNOWN-DEVICES.get device --if-absent=: "unknown"
print "Deploying '$firmware' to '$name' on $channel channel."

run-status invocation/Invocation:
device := invocation["device"]
format := invocation["format"]
if device:
name := KNOWN-DEVICES.get device --if-absent=: "unknown"
print "Status of '$name' (format: $format)."
else:
print "Status of all devices (format: $format)."
Loading