Add cbd-cli crate: shared clap CLI definitions + executor stub
Stage 2 of the CLI dev-flow (api-design). cbd-cli holds the clap Parser/Subcommand types for all three binaries (ServerCli/TuiCli/CbdCli, library/queue/global, guard/scan/auth, Role, RemoteArgs), asset generation (clap_complete + clap_mangen), and a feature-gated gRPC executor (run_remote) whose per-command dispatch is stubbed for the implement stage. Compiles with and without the client feature. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
56098f7c26
commit
e215dd8b87
|
|
@ -664,6 +664,19 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cbd-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
|
"clap_mangen",
|
||||||
|
"crabidy-core",
|
||||||
|
"tokio",
|
||||||
|
"tonic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cbd-tui"
|
name = "cbd-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -797,6 +810,15 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete"
|
||||||
|
version = "4.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.6.1"
|
version = "4.6.1"
|
||||||
|
|
@ -815,6 +837,16 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_mangen"
|
||||||
|
version = "0.2.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e30ffc187e2e3aeafcd1c6e2aa416e29739454c0ccaa419226d5ecd181f2d78"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"roff",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.58"
|
version = "0.1.58"
|
||||||
|
|
@ -4119,6 +4151,12 @@ dependencies = [
|
||||||
"thiserror 2.0.19",
|
"thiserror 2.0.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roff"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "323c417e1d9665a65b263ec744ba09030cfb277e9daa0b018a4ab62e57bc8189"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rquickjs"
|
name = "rquickjs"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"audio-player",
|
"audio-player",
|
||||||
"cbd",
|
"cbd",
|
||||||
|
"cbd-cli",
|
||||||
"cbd-tui",
|
"cbd-tui",
|
||||||
"cbd-web",
|
"cbd-web",
|
||||||
"crabidy-core",
|
"crabidy-core",
|
||||||
|
|
@ -27,6 +28,8 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||||
axum = "0.8"
|
axum = "0.8"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
clap-serde-derive = "0.2"
|
clap-serde-derive = "0.2"
|
||||||
|
clap_complete = "4"
|
||||||
|
clap_mangen = "0.2"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
|
|
@ -91,6 +94,7 @@ url = "2"
|
||||||
|
|
||||||
# Local crates
|
# Local crates
|
||||||
audio-player = { path = "audio-player" }
|
audio-player = { path = "audio-player" }
|
||||||
|
cbd-cli = { path = "cbd-cli" }
|
||||||
cbd-tui = { path = "cbd-tui" }
|
cbd-tui = { path = "cbd-tui" }
|
||||||
crabidy-core = { path = "crabidy-core" }
|
crabidy-core = { path = "crabidy-core" }
|
||||||
crabidy-server = { path = "crabidy-server" }
|
crabidy-server = { path = "crabidy-server" }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "cbd-cli"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap.workspace = true
|
||||||
|
clap_complete.workspace = true
|
||||||
|
clap_mangen.workspace = true
|
||||||
|
|
||||||
|
# The `client` feature adds the gRPC executor for the remote
|
||||||
|
# library/queue/global commands. Kept optional so binaries' build.rs can
|
||||||
|
# depend on this crate (default features = clap only) without dragging in
|
||||||
|
# tonic on every build (architecture/cli.md D1/D7).
|
||||||
|
crabidy-core = { workspace = true, optional = true }
|
||||||
|
tonic = { workspace = true, optional = true, features = [
|
||||||
|
"transport",
|
||||||
|
"codegen",
|
||||||
|
] }
|
||||||
|
tokio = { workspace = true, optional = true, features = [
|
||||||
|
"rt-multi-thread",
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
|
base64 = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
client = ["dep:crabidy-core", "dep:tonic", "dep:tokio", "dep:base64"]
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
//! The gRPC executor for the remote `library`/`queue`/`global` commands
|
||||||
|
//! (`client` feature). Connects a generated `crabidy-core` client with a
|
||||||
|
//! basic-auth interceptor and runs one command against a server.
|
||||||
|
//!
|
||||||
|
//! Stage-2 (api-design) note: the connection builder is real; `run_remote`'s
|
||||||
|
//! per-command dispatch is finalized by the implement stage.
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
|
use tonic::metadata::MetadataValue;
|
||||||
|
use tonic::service::{interceptor::InterceptedService, Interceptor};
|
||||||
|
use tonic::transport::{Channel, Endpoint};
|
||||||
|
use tonic::{Request, Status};
|
||||||
|
|
||||||
|
use crabidy_core::proto::crabidy::crabidy_service_client::CrabidyServiceClient;
|
||||||
|
|
||||||
|
use crate::{Connection, RemoteCmd};
|
||||||
|
|
||||||
|
/// Attaches `authorization: Basic …` when a user is configured; an empty user
|
||||||
|
/// means an open server. The header value is a secret and never logged.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AuthInterceptor {
|
||||||
|
header: Option<MetadataValue<tonic::metadata::Ascii>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthInterceptor {
|
||||||
|
fn new(user: &str, password: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
if user.is_empty() {
|
||||||
|
return Ok(Self { header: None });
|
||||||
|
}
|
||||||
|
let encoded =
|
||||||
|
base64::engine::general_purpose::STANDARD.encode(format!("{user}:{password}"));
|
||||||
|
let header = format!("Basic {encoded}")
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| "cannot encode credentials header")?;
|
||||||
|
Ok(Self {
|
||||||
|
header: Some(header),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interceptor for AuthInterceptor {
|
||||||
|
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
|
||||||
|
if let Some(header) = &self.header {
|
||||||
|
request
|
||||||
|
.metadata_mut()
|
||||||
|
.insert("authorization", header.clone());
|
||||||
|
}
|
||||||
|
Ok(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client = CrabidyServiceClient<InterceptedService<Channel, AuthInterceptor>>;
|
||||||
|
|
||||||
|
/// Connects (lazily) to the server described by `conn`.
|
||||||
|
async fn connect(conn: &Connection) -> Result<Client, Box<dyn std::error::Error>> {
|
||||||
|
let endpoint = Endpoint::from_shared(conn.address.clone())?.connect_lazy();
|
||||||
|
let interceptor = AuthInterceptor::new(&conn.user, &conn.password)?;
|
||||||
|
Ok(CrabidyServiceClient::with_interceptor(
|
||||||
|
endpoint,
|
||||||
|
interceptor,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs one remote command against the server and prints the result.
|
||||||
|
pub async fn run_remote(
|
||||||
|
conn: &Connection,
|
||||||
|
cmd: RemoteCmd,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _client = connect(conn).await?;
|
||||||
|
let _ = cmd;
|
||||||
|
todo!("dispatch each Library/Queue/Global variant to its RPC and print")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
//! Shared command-line definitions for the crabidy binaries
|
||||||
|
//! (`crabidy-server`, `cbd-tui`, `cbd`), plus a feature-gated gRPC executor
|
||||||
|
//! for the remote `library`/`queue`/`global` commands.
|
||||||
|
//!
|
||||||
|
//! See `architecture/cli.md`. The clap types live here so every binary shares
|
||||||
|
//! one command surface and each binary's `build.rs` can generate shell
|
||||||
|
//! completions and man pages from its own top-level [`clap::Command`] with the
|
||||||
|
//! default (clap-only) feature set. The `client` feature adds [`run_remote`],
|
||||||
|
//! which the binaries call to execute a remote command against a running
|
||||||
|
//! server.
|
||||||
|
|
||||||
|
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||||
|
|
||||||
|
/// A server auth role. The `ValueEnum` names double as the basic-auth user
|
||||||
|
/// names the server expects.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
||||||
|
pub enum Role {
|
||||||
|
/// Full control (the normal user).
|
||||||
|
Owner,
|
||||||
|
/// Anything on the queue and playback, no library writes.
|
||||||
|
QueueOwner,
|
||||||
|
/// May browse and append to the queue; nothing else.
|
||||||
|
Appender,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Role {
|
||||||
|
/// The basic-auth user name for this role.
|
||||||
|
pub fn user_name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Role::Owner => "owner",
|
||||||
|
Role::QueueOwner => "queue-owner",
|
||||||
|
Role::Appender => "queue-appender",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connection flags shared by every remote command. Defaults match the client
|
||||||
|
/// config; a flag overrides the config-file value.
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct RemoteArgs {
|
||||||
|
/// Server address (default: the client config's, else localhost).
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub address: Option<String>,
|
||||||
|
/// Basic-auth user / role name (empty against an open server).
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub user: Option<String>,
|
||||||
|
/// Basic-auth password.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Library operations against a running server.
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum LibraryCmd {
|
||||||
|
/// List a node's child nodes and tracks (default path `/`).
|
||||||
|
List {
|
||||||
|
#[arg(default_value = "/")]
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
/// Create a child node under a creatable parent (e.g. a search term).
|
||||||
|
Create { parent: String, title: String },
|
||||||
|
/// Rename an editable node.
|
||||||
|
Rename { path: String, title: String },
|
||||||
|
/// Delete a deletable node or track (never touches the content store).
|
||||||
|
Delete { path: String },
|
||||||
|
/// Save a subtree as a link bookmark under `/crabidy/<name>`.
|
||||||
|
Save { path: String, name: String },
|
||||||
|
/// Capture a subtree under `/crabidy/<name>` (downloads audio).
|
||||||
|
Capture { path: String, name: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Queue operations against a running server.
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum QueueCmd {
|
||||||
|
/// Print the current queue.
|
||||||
|
Show,
|
||||||
|
/// Append tracks/subtrees (by library path) to the end of the queue.
|
||||||
|
Append { paths: Vec<String> },
|
||||||
|
/// Insert tracks/subtrees at a position.
|
||||||
|
Insert { position: u32, paths: Vec<String> },
|
||||||
|
/// Replace the whole queue with the given tracks/subtrees.
|
||||||
|
Replace { paths: Vec<String> },
|
||||||
|
/// Remove queue entries by position.
|
||||||
|
Remove { positions: Vec<u32> },
|
||||||
|
/// Clear the queue (optionally keeping the current track).
|
||||||
|
Clear {
|
||||||
|
#[arg(long)]
|
||||||
|
keep_current: bool,
|
||||||
|
},
|
||||||
|
/// Jump to a queue position.
|
||||||
|
SetCurrent { position: u32 },
|
||||||
|
/// Link-save the current queue as `/crabidy/<name>`.
|
||||||
|
Save { name: String },
|
||||||
|
/// Capture the current queue into `/crabidy/<name>` (downloads audio).
|
||||||
|
Capture { name: String },
|
||||||
|
/// Toggle shuffle.
|
||||||
|
Shuffle,
|
||||||
|
/// Toggle repeat.
|
||||||
|
Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Playback and global operations against a running server.
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum GlobalCmd {
|
||||||
|
/// Toggle play/pause.
|
||||||
|
Play,
|
||||||
|
/// Stop playback.
|
||||||
|
Stop,
|
||||||
|
/// Skip to the next track.
|
||||||
|
Next,
|
||||||
|
/// Go to the previous track.
|
||||||
|
Prev,
|
||||||
|
/// Restart the current track.
|
||||||
|
Restart,
|
||||||
|
/// Toggle mute.
|
||||||
|
Mute,
|
||||||
|
/// Change the volume by a delta in [-1.0, 1.0] (e.g. `0.05`, `-0.1`).
|
||||||
|
Volume { delta: f32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The remote command groups an executor can run (`client` feature).
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum RemoteCmd {
|
||||||
|
/// Library operations.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Library(LibraryCmd),
|
||||||
|
/// Queue operations.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Queue(QueueCmd),
|
||||||
|
/// Playback / global operations.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Global(GlobalCmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `guard <role> [password]`: hash a role password and (unless `--no-config`)
|
||||||
|
/// write it into `crabidy-server.toml`'s `[auth]`.
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct GuardArgs {
|
||||||
|
/// Which role's password to set.
|
||||||
|
pub role: Role,
|
||||||
|
/// The password. If omitted, it is read from stdin (pipe-friendly, and
|
||||||
|
/// keeps it out of the process list).
|
||||||
|
pub password: Option<String>,
|
||||||
|
/// Only print the PHC hash; do not modify `crabidy-server.toml`.
|
||||||
|
#[arg(long)]
|
||||||
|
pub no_config: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `scan <path> [--capture|--move]`: index a music folder.
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct ScanArgs {
|
||||||
|
/// Directory to walk for playable files.
|
||||||
|
pub path: std::path::PathBuf,
|
||||||
|
/// Copy each file into the content store; the toml points there.
|
||||||
|
#[arg(long)]
|
||||||
|
pub capture: bool,
|
||||||
|
/// Move each file into the content store instead of copying.
|
||||||
|
#[arg(long, name = "move")]
|
||||||
|
pub move_: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `auth <role> [password]`: write a role + cleartext password into the client
|
||||||
|
/// config.
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct AuthArgs {
|
||||||
|
/// Which role to authenticate as.
|
||||||
|
pub role: Role,
|
||||||
|
/// The cleartext password (read from stdin if omitted).
|
||||||
|
pub password: Option<String>,
|
||||||
|
/// Also set the server address in the client config.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub address: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `completions <shell>`: print a completion script to stdout.
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct CompletionsArgs {
|
||||||
|
/// Target shell.
|
||||||
|
pub shell: clap_complete::Shell,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subcommands of `crabidy-server`.
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum ServerCommand {
|
||||||
|
/// Set a role password (hash + write config).
|
||||||
|
Guard(GuardArgs),
|
||||||
|
/// Index a music folder with `.cbd-track.toml` files.
|
||||||
|
Scan(ScanArgs),
|
||||||
|
/// Library operations against a running server.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Library(LibraryCmd),
|
||||||
|
/// Queue operations against a running server.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Queue(QueueCmd),
|
||||||
|
/// Playback / global operations against a running server.
|
||||||
|
#[command(subcommand)]
|
||||||
|
Global(GlobalCmd),
|
||||||
|
/// Print a shell completion script.
|
||||||
|
Completions(CompletionsArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `crabidy-server`: no subcommand runs the server.
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(name = "crabidy-server", author, version, about)]
|
||||||
|
pub struct ServerCli {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub remote: RemoteArgs,
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Option<ServerCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subcommands of `cbd-tui`.
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum TuiCommand {
|
||||||
|
/// Write a role + password into the client config.
|
||||||
|
Auth(AuthArgs),
|
||||||
|
#[command(subcommand)]
|
||||||
|
Library(LibraryCmd),
|
||||||
|
#[command(subcommand)]
|
||||||
|
Queue(QueueCmd),
|
||||||
|
#[command(subcommand)]
|
||||||
|
Global(GlobalCmd),
|
||||||
|
/// Print a shell completion script.
|
||||||
|
Completions(CompletionsArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `cbd-tui`: no subcommand runs the TUI.
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(name = "cbd-tui", author, version, about)]
|
||||||
|
pub struct TuiCli {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub remote: RemoteArgs,
|
||||||
|
/// Show the frequency-spectrum bars under the track progress.
|
||||||
|
#[arg(long)]
|
||||||
|
pub spectrum: Option<bool>,
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Option<TuiCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subcommands of `cbd` — the union of the server and client commands.
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum CbdCommand {
|
||||||
|
Guard(GuardArgs),
|
||||||
|
Scan(ScanArgs),
|
||||||
|
Auth(AuthArgs),
|
||||||
|
#[command(subcommand)]
|
||||||
|
Library(LibraryCmd),
|
||||||
|
#[command(subcommand)]
|
||||||
|
Queue(QueueCmd),
|
||||||
|
#[command(subcommand)]
|
||||||
|
Global(GlobalCmd),
|
||||||
|
/// Print a shell completion script.
|
||||||
|
Completions(CompletionsArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `cbd`: no subcommand runs the in-process server + TUI.
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(name = "cbd", author, version, about)]
|
||||||
|
pub struct CbdCli {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub remote: RemoteArgs,
|
||||||
|
#[arg(long)]
|
||||||
|
pub spectrum: Option<bool>,
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Option<CbdCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolved connection settings for the executor (owned, unlike the TUI's
|
||||||
|
/// `&'static ServerConfig`).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Connection {
|
||||||
|
pub address: String,
|
||||||
|
pub user: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes bash/zsh/fish completions and a man page for `cmd` into `dir`.
|
||||||
|
/// Used by each binary's `build.rs` (architecture/cli.md D7).
|
||||||
|
pub fn generate_assets(
|
||||||
|
mut cmd: clap::Command,
|
||||||
|
bin_name: &str,
|
||||||
|
dir: &std::path::Path,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
use clap_complete::Shell;
|
||||||
|
std::fs::create_dir_all(dir.join("completions"))?;
|
||||||
|
std::fs::create_dir_all(dir.join("man"))?;
|
||||||
|
for shell in [Shell::Bash, Shell::Zsh, Shell::Fish] {
|
||||||
|
clap_complete::generate_to(shell, &mut cmd, bin_name, dir.join("completions"))?;
|
||||||
|
}
|
||||||
|
let man = clap_mangen::Man::new(cmd);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
man.render(&mut out)?;
|
||||||
|
std::fs::write(dir.join("man").join(format!("{bin_name}.1")), out)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a completion script for `shell` to stdout (the `completions`
|
||||||
|
/// subcommand).
|
||||||
|
pub fn print_completions(shell: clap_complete::Shell, cmd: &mut clap::Command, bin_name: &str) {
|
||||||
|
clap_complete::generate(shell, cmd, bin_name, &mut std::io::stdout());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
mod client;
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub use client::run_remote;
|
||||||
Loading…
Reference in New Issue