Organize the public CLI with clap

This commit is contained in:
ms2k 2026-07-18 23:25:40 +09:00
commit d08d931b5f

View file

@ -1,8 +1,6 @@
use std::ffi::OsString;
use std::fmt;
use std::fs::File;
use std::io::{self, Write};
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use std::sync::Mutex;
@ -22,6 +20,7 @@ use sha2::{Digest, Sha256};
use crate::terminal::{HostTerminal, InteractiveSessionTransport, run_interactive_session};
pub mod cli;
pub mod coordinator_backend;
pub mod state;
pub mod terminal;
@ -34,82 +33,6 @@ pub struct ClientCommand {
pub argv: Vec<Vec<u8>>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ArgumentError(&'static str);
impl fmt::Display for ArgumentError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.0)
}
}
impl std::error::Error for ArgumentError {}
/// Parses `--profile`, optional host paths, and the exact command following `--`.
///
/// # Errors
/// Returns a concise usage error for missing values, unknown options, or a missing command.
pub fn parse_command_line(
arguments: impl IntoIterator<Item = OsString>,
current_workspace: PathBuf,
) -> Result<ClientCommand, ArgumentError> {
let mut arguments = arguments.into_iter();
let mut profile = None;
let mut workspace = None;
let mut configuration = None;
let mut argv = Vec::new();
while let Some(argument) = arguments.next() {
match argument.to_str() {
Some("--profile") => profile = Some(required_utf8(&mut arguments, "--profile")?),
Some("--workspace") => {
workspace = Some(PathBuf::from(required_value(
&mut arguments,
"--workspace",
)?));
}
Some("--config") => {
configuration = Some(PathBuf::from(required_value(&mut arguments, "--config")?));
}
Some("--") => {
argv.extend(arguments.map(OsString::into_vec));
break;
}
Some(_) | None => return Err(ArgumentError("unknown option before `--`")),
}
}
let profile = profile.ok_or(ArgumentError("`--profile <name>` is required"))?;
if argv.is_empty() {
return Err(ArgumentError("a command is required after `--`"));
}
Ok(ClientCommand {
profile,
workspace: workspace.unwrap_or(current_workspace),
configuration,
argv,
})
}
fn required_value(
arguments: &mut impl Iterator<Item = OsString>,
option: &'static str,
) -> Result<OsString, ArgumentError> {
arguments.next().ok_or(match option {
"--profile" => ArgumentError("`--profile` requires a value"),
"--workspace" => ArgumentError("`--workspace` requires a path"),
"--config" => ArgumentError("`--config` requires a path"),
_ => ArgumentError("option requires a value"),
})
}
fn required_utf8(
arguments: &mut impl Iterator<Item = OsString>,
option: &'static str,
) -> Result<String, ArgumentError> {
required_value(arguments, option)?
.into_string()
.map_err(|_| ArgumentError("profile name must be UTF-8"))
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DomainStatus<E> {
Absent,