Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ jobs:
run: cargo check --locked
- name: Test
run: make test
# Shell completions are generated from CLI help and should be platform-independent
- name: Check completions are up to date
run: ./scripts/check-completions.sh

lint:
runs-on: ubuntu-latest
Expand Down
51 changes: 47 additions & 4 deletions .github/workflows/release-binary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:

- name: Prepare release directory
run: |
mkdir out out/docs
mkdir out out/docs out/complete
cp $BIN out
cp README.md docs/*.md out/docs

Expand All @@ -142,10 +142,48 @@ jobs:
"${{ matrix.qemu }}" "/$BIN" \
--generate man > "out/docs/lychee.1"

- name: Generate shell completions without emulation
if: matrix.qemu == ''
run: |
$BIN --generate complete-bash > out/complete/lychee.bash
$BIN --generate complete-elvish > out/complete/lychee.elv
$BIN --generate complete-fish > out/complete/lychee.fish
$BIN --generate complete-powershell > out/complete/_lychee.ps1
$BIN --generate complete-zsh > out/complete/_lychee

- name: Generate shell completions with emulation
if: matrix.qemu != ''
run: |
docker run --rm -v \
"$PWD/target:/target:Z" \
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
"${{ matrix.qemu }}" "/$BIN" \
--generate complete-bash > "out/complete/lychee.bash"
docker run --rm -v \
"$PWD/target:/target:Z" \
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
"${{ matrix.qemu }}" "/$BIN" \
--generate complete-elvish > "out/complete/lychee.elv"
docker run --rm -v \
"$PWD/target:/target:Z" \
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
"${{ matrix.qemu }}" "/$BIN" \
--generate complete-fish > "out/complete/lychee.fish"
docker run --rm -v \
"$PWD/target:/target:Z" \
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
"${{ matrix.qemu }}" "/$BIN" \
--generate complete-powershell > "out/complete/_lychee.ps1"
docker run --rm -v \
"$PWD/target:/target:Z" \
"ghcr.io/cross-rs/${{ matrix.target }}:main" \
"${{ matrix.qemu }}" "/$BIN" \
--generate complete-zsh > "out/complete/_lychee"

- name: Package release
run: |
cd out
tar -czf lychee.tar.gz lychee docs/
tar -czf lychee.tar.gz lychee docs/ complete/

- name: Check if possible to release
if: ${{ needs.prepare.outputs.upload_url == '' }}
Expand Down Expand Up @@ -185,10 +223,15 @@ jobs:
cd target/release
strip lychee
chmod +x lychee
mkdir docs
mkdir docs complete
cp ../../README.md ../../docs/TROUBLESHOOTING.md docs
./lychee --generate man > docs/lychee.1
tar -czf lychee.tar.gz lychee docs/
./lychee --generate complete-bash > complete/lychee.bash
./lychee --generate complete-elvish > complete/lychee.elv
./lychee --generate complete-fish > complete/lychee.fish
./lychee --generate complete-powershell > complete/_lychee.ps1
./lychee --generate complete-zsh > complete/_lychee
tar -czf lychee.tar.gz lychee docs/ complete/

mkdir dmg
mv lychee dmg/
Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

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

12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ clean: ## Clean up build artifacts
build: ## Build Rust code locally
cargo build

.PHONY: completions
completions: ## Update shell completions
@echo "Building release binary..."
@cargo build --release
@echo "Generating completions..."
@target/release/lychee --generate complete-bash > lychee-bin/complete/lychee.bash
@target/release/lychee --generate complete-elvish > lychee-bin/complete/lychee.elv
@target/release/lychee --generate complete-fish > lychee-bin/complete/lychee.fish
@target/release/lychee --generate complete-powershell > lychee-bin/complete/_lychee.ps1
@target/release/lychee --generate complete-zsh > lychee-bin/complete/_lychee
@echo "All completions updated in lychee-bin/complete/"

.PHONY: install
install: ## Install project locally
cargo install --path lychee-bin --locked
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ Options:
--generate <GENERATE>
Generate special output (e.g. the man page) instead of performing link checking

[possible values: man]
[possible values: man, complete-bash, complete-elvish, complete-fish, complete-powershell, complete-zsh]

--github-token <GITHUB_TOKEN>
GitHub API token to use when checking github.com links, to avoid rate limiting
Expand Down
1 change: 1 addition & 0 deletions clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
doc-valid-idents = ["PowerShell", ".."]
1 change: 1 addition & 0 deletions lychee-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ lychee-lib = { path = "../lychee-lib", version = "0.22.0", default-features = fa
anyhow = "1.0.100"
assert-json-diff = "2.0.2"
clap = { version = "4.5.53", features = ["env", "derive", "cargo", "string"] }
clap_complete = "4.5.38"
clap_mangen = "0.2.31"
console = "0.16.2"
const_format = "0.2.35"
Expand Down
138 changes: 138 additions & 0 deletions lychee-bin/complete/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Shell Completions for lychee

lychee comes with built-in support for generating shell completions.
This unlocks tab-completion for commands, options, and arguments in your shell!

## Installation

### Bash

**User installation:**
```bash
dir="${XDG_CONFIG_HOME:-$HOME/.config}/bash_completion"
mkdir -p "$dir"
lychee --generate complete-bash > "$dir/lychee.bash"
```

Then source it in your `~/.bashrc` or `~/.bash_profile`:
```bash
source "$dir/lychee.bash"
```

**System-wide installation:**
```bash
# On Linux
sudo lychee --generate complete-bash > /usr/share/bash-completion/completions/lychee

# On macOS with Homebrew
lychee --generate complete-bash > $(brew --prefix)/etc/bash_completion.d/lychee
```

### Zsh

**Recommended approach:**

```zsh
dir="$HOME/.zsh-completions"
mkdir -p "$dir"
lychee --generate complete-zsh > "$dir/_lychee"
```

Add to your `~/.zshrc`:
```zsh
fpath=($HOME/.zsh-completions $fpath)
autoload -Uz compinit && compinit
```

**System-wide installation:**
```bash
# On Linux
sudo lychee --generate complete-zsh > /usr/local/share/zsh/site-functions/_lychee

# On macOS with Homebrew
lychee --generate complete-zsh > $(brew --prefix)/share/zsh/site-functions/_lychee
```

**Alternative (slower, not recommended for daily use):**

You can generate completions on-the-fly by adding this to `~/.zshrc`:
```zsh
source <(lychee --generate complete-zsh)
```

Note: This is easier to set up but slower, adding startup time to your shell.

### Fish

**User installation:**
```bash
dir="${XDG_CONFIG_HOME:-$HOME/.config}/fish/completions"
mkdir -p "$dir"
lychee --generate complete-fish > "$dir/lychee.fish"
```

Fish will automatically load the completions on next shell start.

**System-wide installation:**
```bash
# On Linux
sudo lychee --generate complete-fish > /usr/share/fish/vendor_completions.d/lychee.fish

# On macOS with Homebrew
lychee --generate complete-fish > $(brew --prefix)/share/fish/vendor_completions.d/lychee.fish
```

### Elvish

```bash
dir="${XDG_CONFIG_HOME:-$HOME/.config}/elvish/lib"
mkdir -p "$dir"
lychee --generate complete-elvish > "$dir/lychee.elv"
```

Then add the following to your `~/.elvish/rc.elv`:
```elvish
use lychee
```

**PowerShell:** Verify your execution policy allows running scripts:
```powershell
Get-ExecutionPolicy
# If restricted, run:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```

### Updating completions

After upgrading lychee, regenerate completions to get the latest options:
```bash
lychee --generate complete-<your-shell> > <completion-file>
```

### PowerShell

**Windows:**

Generate the completion file:
```powershell
lychee --generate complete-powershell | Out-File -Encoding UTF8 _lychee.ps1
```

Then add to your PowerShell profile:
```powershell
# Find your profile location
echo $PROFILE

# Add this line to your profile
. C:\Path\To\_lychee.ps1
```

**Linux/macOS with PowerShell:**
```bash
lychee --generate complete-powershell > ~/.config/powershell/_lychee.ps1
```

Add to your profile (`~/.config/powershell/Microsoft.PowerShell_profile.ps1`):
```powershell
. ~/.config/powershell/_lychee.ps1
```
30 changes: 27 additions & 3 deletions lychee-bin/src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use anyhow::Result;
use clap::{CommandFactory, crate_authors};
use clap_complete::{Shell, generate as generate_completion};
use clap_mangen::{
Man,
roff::{Roff, roman},
Expand Down Expand Up @@ -72,20 +73,43 @@ const EXIT_CODE_SECTION: &str = "
/// What to generate when providing the --generate flag
#[derive(Debug, Deserialize, Clone, Display, EnumIter, EnumString, VariantNames, PartialEq)]
#[non_exhaustive]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
#[strum(serialize_all = "kebab-case")]
#[serde(rename_all = "kebab-case")]
pub(crate) enum GenerateMode {
/// Generate roff used for the man page
Man,
/// Generate shell completion for Bash
CompleteBash,
/// Generate shell completion for Elvish
CompleteElvish,
/// Generate shell completion for Fish
CompleteFish,
/// Generate shell completion for PowerShell
CompletePowershell,
/// Generate shell completion for Zsh
CompleteZsh,
}

/// Generate special output according to the [`GenerateMode`]
pub(crate) fn generate(mode: &GenerateMode) -> Result<String> {
match mode {
GenerateMode::Man => man_page(),
GenerateMode::CompleteBash => shell_completion(Shell::Bash),
GenerateMode::CompleteElvish => shell_completion(Shell::Elvish),
GenerateMode::CompleteFish => shell_completion(Shell::Fish),
GenerateMode::CompletePowershell => shell_completion(Shell::PowerShell),
GenerateMode::CompleteZsh => shell_completion(Shell::Zsh),
}
}

/// Generate shell completion for the given shell
fn shell_completion(shell: Shell) -> Result<String> {
let mut cmd = LycheeOptions::command();
let mut buffer = Vec::new();
generate_completion(shell, &mut cmd, "lychee", &mut buffer);
Ok(String::from_utf8(buffer)?)
}

/// Generate the lychee man page in roff format using [`clap_mangen`]
fn man_page() -> Result<String> {
let authors = crate_authors!("\n\n").to_owned() + CONTRIBUTOR_THANK_NOTE;
Expand All @@ -105,7 +129,7 @@ fn man_page() -> Result<String> {
man.render_version_section(buffer)?;
man.render_authors_section(buffer)?;

Ok(std::str::from_utf8(buffer)?.to_owned())
Ok(String::from_utf8(buffer.clone())?)
}

fn render_exit_codes(buffer: &mut Vec<u8>) -> Result<()> {
Expand Down
Loading
Loading