Add shell completion generation
This commit is contained in:
parent
d9728a326d
commit
5b2c30f817
7 changed files with 98 additions and 3 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -591,6 +591,15 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete"
|
||||||
|
version = "4.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.6.1"
|
version = "4.6.1"
|
||||||
|
|
@ -1512,6 +1521,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
"geth-cas",
|
"geth-cas",
|
||||||
"geth-config",
|
"geth-config",
|
||||||
"geth-control",
|
"geth-control",
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ base64 = "0.22"
|
||||||
blake3 = "1"
|
blake3 = "1"
|
||||||
bytes = "1"
|
bytes = "1"
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
clap_complete = "4"
|
||||||
directories = "5"
|
directories = "5"
|
||||||
# Compatibility pins for iroh 0.95's ed25519-dalek prerelease dependency.
|
# Compatibility pins for iroh 0.95's ed25519-dalek prerelease dependency.
|
||||||
ed25519 = "=3.0.0-rc.1"
|
ed25519 = "=3.0.0-rc.1"
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,11 @@ metadata from an authorized peer over Iroh.
|
||||||
|
|
||||||
The bootstrap implementation provides:
|
The bootstrap implementation provides:
|
||||||
|
|
||||||
- `geth guide [init|owner-setup|enrollment|keys|overlay|service|smoke-test]` for
|
- `geth guide [init|owner-setup|enrollment|keys|overlay|service|completions|smoke-test]` for
|
||||||
embedded workflow help, including `--admin-key` / `--signing-key` setup
|
embedded workflow help, including `--admin-key` / `--signing-key` setup
|
||||||
examples
|
examples
|
||||||
|
- `geth completions <bash|zsh|fish|powershell|elvish>` for shell completion
|
||||||
|
scripts generated from the live CLI command tree
|
||||||
- `geth init`
|
- `geth init`
|
||||||
- `geth init --admin-key <public-key> --signing-key <private-key> --node-name <name>`
|
- `geth init --admin-key <public-key> --signing-key <private-key> --node-name <name>`
|
||||||
- `geth daemon run`
|
- `geth daemon run`
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ license.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
|
clap_complete.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
geth-config = { path = "../geth-config" }
|
geth-config = { path = "../geth-config" }
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
|
||||||
|
use clap_complete::{Shell, generate};
|
||||||
use geth_config::GethPaths;
|
use geth_config::GethPaths;
|
||||||
use geth_control::{ControlRequest, ControlResponse};
|
use geth_control::{ControlRequest, ControlResponse};
|
||||||
use geth_node::service::{ServiceInstallOptions, ServiceManager, ServiceReport};
|
use geth_node::service::{ServiceInstallOptions, ServiceManager, ServiceReport};
|
||||||
use std::io::Read;
|
use std::io::{Read, stdout};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const TOP_LEVEL_AFTER_HELP: &str = r#"Common starts:
|
const TOP_LEVEL_AFTER_HELP: &str = r#"Common starts:
|
||||||
geth guide init
|
geth guide init
|
||||||
geth guide owner-setup
|
geth guide owner-setup
|
||||||
geth guide overlay
|
geth guide overlay
|
||||||
|
geth guide completions
|
||||||
geth init
|
geth init
|
||||||
geth init --admin-key ~/.ssh/id_ed25519_sk.pub --signing-key ~/.ssh/id_ed25519_sk --node-name laptop
|
geth init --admin-key ~/.ssh/id_ed25519_sk.pub --signing-key ~/.ssh/id_ed25519_sk --node-name laptop
|
||||||
geth daemon run
|
geth daemon run
|
||||||
|
|
@ -73,6 +75,7 @@ Topics:
|
||||||
keys Meaning of --admin-key and --signing-key.
|
keys Meaning of --admin-key and --signing-key.
|
||||||
overlay Optional Iroh overlay network planning.
|
overlay Optional Iroh overlay network planning.
|
||||||
service Install and manage geth as a user service.
|
service Install and manage geth as a user service.
|
||||||
|
completions Shell completion installation examples.
|
||||||
smoke-test Minimal commands to verify a node and daemon."#;
|
smoke-test Minimal commands to verify a node and daemon."#;
|
||||||
|
|
||||||
const GUIDE_INIT: &str = r#"geth init has two modes.
|
const GUIDE_INIT: &str = r#"geth init has two modes.
|
||||||
|
|
@ -178,6 +181,34 @@ Preview definitions without installing:
|
||||||
geth daemon service print
|
geth daemon service print
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
const GUIDE_COMPLETIONS: &str = r#"Shell completions:
|
||||||
|
|
||||||
|
geth can print completions for bash, zsh, fish, PowerShell, and elvish. The
|
||||||
|
generated scripts are produced from the same Clap command tree as `geth --help`,
|
||||||
|
so subcommands and flags stay in sync with the executable.
|
||||||
|
|
||||||
|
Bash:
|
||||||
|
mkdir -p ~/.local/share/bash-completion/completions
|
||||||
|
geth completions bash > ~/.local/share/bash-completion/completions/geth
|
||||||
|
|
||||||
|
Zsh:
|
||||||
|
mkdir -p ~/.zfunc
|
||||||
|
geth completions zsh > ~/.zfunc/_geth
|
||||||
|
# Ensure ~/.zfunc is in fpath, then run: compinit
|
||||||
|
|
||||||
|
Fish:
|
||||||
|
mkdir -p ~/.config/fish/completions
|
||||||
|
geth completions fish > ~/.config/fish/completions/geth.fish
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
geth completions powershell > geth.ps1
|
||||||
|
# Source geth.ps1 from your PowerShell profile.
|
||||||
|
|
||||||
|
Elvish:
|
||||||
|
mkdir -p ~/.elvish/lib
|
||||||
|
geth completions elvish > ~/.elvish/lib/geth.elv
|
||||||
|
"#;
|
||||||
|
|
||||||
const GUIDE_OVERLAY: &str = r#"Optional overlay network:
|
const GUIDE_OVERLAY: &str = r#"Optional overlay network:
|
||||||
|
|
||||||
geth has an experimental overlay-network design inspired by iroh-lan. The
|
geth has an experimental overlay-network design inspired by iroh-lan. The
|
||||||
|
|
@ -256,6 +287,10 @@ pub enum Command {
|
||||||
#[arg(value_enum)]
|
#[arg(value_enum)]
|
||||||
topic: Option<GuideTopic>,
|
topic: Option<GuideTopic>,
|
||||||
},
|
},
|
||||||
|
Completions {
|
||||||
|
#[arg(value_enum)]
|
||||||
|
shell: Shell,
|
||||||
|
},
|
||||||
#[command(long_about = INIT_LONG_ABOUT, after_long_help = INIT_AFTER_HELP)]
|
#[command(long_about = INIT_LONG_ABOUT, after_long_help = INIT_AFTER_HELP)]
|
||||||
Init {
|
Init {
|
||||||
#[arg(
|
#[arg(
|
||||||
|
|
@ -366,6 +401,7 @@ pub enum GuideTopic {
|
||||||
Keys,
|
Keys,
|
||||||
Overlay,
|
Overlay,
|
||||||
Service,
|
Service,
|
||||||
|
Completions,
|
||||||
SmokeTest,
|
SmokeTest,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1063,6 +1099,10 @@ pub struct EmptyArgs {}
|
||||||
|
|
||||||
pub async fn run() -> Result<()> {
|
pub async fn run() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
if let Command::Completions { shell } = &cli.command {
|
||||||
|
print_completions(*shell);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let paths = GethPaths::resolve().context("resolve geth paths")?;
|
let paths = GethPaths::resolve().context("resolve geth paths")?;
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Guide { topic } => {
|
Command::Guide { topic } => {
|
||||||
|
|
@ -1169,6 +1209,7 @@ fn print_guide(topic: Option<GuideTopic>, json: bool) -> Result<()> {
|
||||||
Some(GuideTopic::Keys) => ("keys", GUIDE_KEYS),
|
Some(GuideTopic::Keys) => ("keys", GUIDE_KEYS),
|
||||||
Some(GuideTopic::Overlay) => ("overlay", GUIDE_OVERLAY),
|
Some(GuideTopic::Overlay) => ("overlay", GUIDE_OVERLAY),
|
||||||
Some(GuideTopic::Service) => ("service", GUIDE_SERVICE),
|
Some(GuideTopic::Service) => ("service", GUIDE_SERVICE),
|
||||||
|
Some(GuideTopic::Completions) => ("completions", GUIDE_COMPLETIONS),
|
||||||
Some(GuideTopic::SmokeTest) => ("smoke-test", GUIDE_SMOKE_TEST),
|
Some(GuideTopic::SmokeTest) => ("smoke-test", GUIDE_SMOKE_TEST),
|
||||||
};
|
};
|
||||||
if json {
|
if json {
|
||||||
|
|
@ -1185,8 +1226,14 @@ fn print_guide(topic: Option<GuideTopic>, json: bool) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_completions(shell: Shell) {
|
||||||
|
let mut command = Cli::command();
|
||||||
|
generate(shell, &mut command, "geth", &mut stdout());
|
||||||
|
}
|
||||||
|
|
||||||
fn request_for_command(command: Command) -> Result<ControlRequest> {
|
fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
Ok(match command {
|
Ok(match command {
|
||||||
|
Command::Completions { .. } => bail!("completion generation does not use the daemon"),
|
||||||
Command::Status => ControlRequest::Status,
|
Command::Status => ControlRequest::Status,
|
||||||
Command::Sync {
|
Command::Sync {
|
||||||
command: SyncCommand::Status,
|
command: SyncCommand::Status,
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,36 @@ fn guide_command_explains_overlay_boundaries() {
|
||||||
assert!(stdout.contains("all overlay packets must be carried over Iroh"));
|
assert!(stdout.contains("all overlay packets must be carried over Iroh"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completions_are_generated_without_daemon() {
|
||||||
|
let home = tempfile::tempdir().expect("tempdir");
|
||||||
|
let output = run_geth(home.path(), &["completions", "bash"]);
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"stderr: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
assert!(stdout.contains("_geth"));
|
||||||
|
assert!(stdout.contains("overlay"));
|
||||||
|
assert!(stdout.contains("completions"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn guide_command_explains_completion_installation() {
|
||||||
|
let home = tempfile::tempdir().expect("tempdir");
|
||||||
|
let output = run_geth(home.path(), &["guide", "completions"]);
|
||||||
|
assert!(
|
||||||
|
output.status.success(),
|
||||||
|
"stderr: {}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
assert!(stdout.contains("geth completions bash"));
|
||||||
|
assert!(stdout.contains("PowerShell"));
|
||||||
|
assert!(stdout.contains("fish"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn owner_init_requires_admin_key_and_signing_key() {
|
fn owner_init_requires_admin_key_and_signing_key() {
|
||||||
let home = tempfile::tempdir().expect("tempdir");
|
let home = tempfile::tempdir().expect("tempdir");
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ Implementation order:
|
||||||
- `[x]` README has a two-machine walkthrough for the main smoke tests.
|
- `[x]` README has a two-machine walkthrough for the main smoke tests.
|
||||||
- `[x]` CLI recovery errors tell operators the next command to run.
|
- `[x]` CLI recovery errors tell operators the next command to run.
|
||||||
- `[x]` JSON sync status is script-friendly for stale/failed peer detection.
|
- `[x]` JSON sync status is script-friendly for stale/failed peer detection.
|
||||||
|
- `[x]` Shell completions are generated from the Clap command tree for
|
||||||
|
bash, zsh, fish, PowerShell, and elvish.
|
||||||
|
- `[x]` Completion installation examples are available through
|
||||||
|
`geth guide completions`.
|
||||||
|
|
||||||
- `[x]` Two-node operator-flow test coverage.
|
- `[x]` Two-node operator-flow test coverage.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue