cli/config: Use clap
This commit is contained in:
parent
8d90699c30
commit
8604d3bcc7
|
|
@ -1,152 +1,30 @@
|
||||||
#![allow(clippy::or_fun_call)]
|
mod args;
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::path::Path;
|
pub use args::Args;
|
||||||
use std::str::FromStr;
|
use args::Command;
|
||||||
|
pub(crate) use args::ABOUT;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use radicle::node::Alias;
|
|
||||||
use radicle::profile::{config, Config, ConfigPath, RawConfig};
|
use radicle::profile::{config, Config, ConfigPath, RawConfig};
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
|
||||||
use crate::terminal::Element as _;
|
use crate::terminal::Element as _;
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
name: "config",
|
|
||||||
description: "Manage your local Radicle configuration",
|
|
||||||
version: env!("RADICLE_VERSION"),
|
|
||||||
usage: r#"
|
|
||||||
Usage
|
|
||||||
|
|
||||||
rad config [<option>...]
|
|
||||||
rad config show [<option>...]
|
|
||||||
rad config init --alias <alias> [<option>...]
|
|
||||||
rad config edit [<option>...]
|
|
||||||
rad config get <key> [<option>...]
|
|
||||||
rad config schema [<option>...]
|
|
||||||
rad config set <key> <value> [<option>...]
|
|
||||||
rad config unset <key> [<option>...]
|
|
||||||
rad config push <key> <value> [<option>...]
|
|
||||||
rad config remove <key> <value> [<option>...]
|
|
||||||
|
|
||||||
If no argument is specified, prints the current radicle configuration as JSON.
|
|
||||||
To initialize a new configuration file, use `rad config init`.
|
|
||||||
|
|
||||||
Options
|
|
||||||
|
|
||||||
--help Print help
|
|
||||||
|
|
||||||
"#,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
enum Operation {
|
|
||||||
#[default]
|
|
||||||
Show,
|
|
||||||
Get(String),
|
|
||||||
Schema,
|
|
||||||
Set(String, String),
|
|
||||||
Push(String, String),
|
|
||||||
Remove(String, String),
|
|
||||||
Unset(String),
|
|
||||||
Init,
|
|
||||||
Edit,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Options {
|
|
||||||
op: Operation,
|
|
||||||
alias: Option<Alias>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args for Options {
|
|
||||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
|
||||||
use lexopt::prelude::*;
|
|
||||||
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
|
||||||
let mut op: Option<Operation> = None;
|
|
||||||
let mut alias = None;
|
|
||||||
|
|
||||||
#[allow(clippy::never_loop)]
|
|
||||||
while let Some(arg) = parser.next()? {
|
|
||||||
match arg {
|
|
||||||
Long("help") | Short('h') => {
|
|
||||||
return Err(Error::Help.into());
|
|
||||||
}
|
|
||||||
Long("alias") => {
|
|
||||||
let value = parser.value()?;
|
|
||||||
let input = value.to_string_lossy();
|
|
||||||
let input = Alias::from_str(&input)?;
|
|
||||||
|
|
||||||
alias = Some(input);
|
|
||||||
}
|
|
||||||
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
|
|
||||||
"show" => op = Some(Operation::Show),
|
|
||||||
"schema" => op = Some(Operation::Schema),
|
|
||||||
"edit" => op = Some(Operation::Edit),
|
|
||||||
"init" => op = Some(Operation::Init),
|
|
||||||
"get" => {
|
|
||||||
let key = parser.value()?;
|
|
||||||
let key = key.to_string_lossy();
|
|
||||||
op = Some(Operation::Get(key.to_string()));
|
|
||||||
}
|
|
||||||
"set" => {
|
|
||||||
let key = parser.value()?;
|
|
||||||
let key = key.to_string_lossy();
|
|
||||||
let value = parser.value()?;
|
|
||||||
let value = value.to_string_lossy();
|
|
||||||
|
|
||||||
op = Some(Operation::Set(key.to_string(), value.to_string()));
|
|
||||||
}
|
|
||||||
"push" => {
|
|
||||||
let key = parser.value()?;
|
|
||||||
let key = key.to_string_lossy();
|
|
||||||
let value = parser.value()?;
|
|
||||||
let value = value.to_string_lossy();
|
|
||||||
|
|
||||||
op = Some(Operation::Push(key.to_string(), value.to_string()));
|
|
||||||
}
|
|
||||||
"remove" => {
|
|
||||||
let key = parser.value()?;
|
|
||||||
let key = key.to_string_lossy();
|
|
||||||
let value = parser.value()?;
|
|
||||||
let value = value.to_string_lossy();
|
|
||||||
|
|
||||||
op = Some(Operation::Remove(key.to_string(), value.to_string()));
|
|
||||||
}
|
|
||||||
"unset" => {
|
|
||||||
let key = parser.value()?;
|
|
||||||
let key = key.to_string_lossy();
|
|
||||||
op = Some(Operation::Unset(key.to_string()));
|
|
||||||
}
|
|
||||||
unknown => anyhow::bail!("unknown operation '{unknown}'"),
|
|
||||||
},
|
|
||||||
_ => return Err(anyhow!(arg.unexpected())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
Options {
|
|
||||||
op: op.unwrap_or_default(),
|
|
||||||
alias,
|
|
||||||
},
|
|
||||||
vec![],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|
||||||
let home = ctx.home()?;
|
let home = ctx.home()?;
|
||||||
let path = home.config();
|
let path = home.config();
|
||||||
|
let command = args.command.unwrap_or(Command::Show);
|
||||||
|
|
||||||
match options.op {
|
match command {
|
||||||
Operation::Show => {
|
Command::Show => {
|
||||||
let profile = ctx.profile()?;
|
let profile = ctx.profile()?;
|
||||||
term::json::to_pretty(&profile.config, path.as_path())?.print();
|
term::json::to_pretty(&profile.config, path.as_path())?.print();
|
||||||
}
|
}
|
||||||
Operation::Schema => {
|
Command::Schema => {
|
||||||
term::json::to_pretty(&schemars::schema_for!(Config), path.as_path())?.print();
|
term::json::to_pretty(&schemars::schema_for!(Config), path.as_path())?.print()
|
||||||
}
|
}
|
||||||
Operation::Get(key) => {
|
Command::Get { key } => {
|
||||||
let mut temp_config = RawConfig::from_file(&path)?;
|
let mut temp_config = RawConfig::from_file(&path)?;
|
||||||
let key: ConfigPath = key.into();
|
let key: ConfigPath = key.into();
|
||||||
let value = temp_config.get_mut(&key).ok_or_else(|| {
|
let value = temp_config.get_mut(&key).ok_or_else(|| {
|
||||||
|
|
@ -154,38 +32,33 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
})?;
|
})?;
|
||||||
print_value(value)?;
|
print_value(value)?;
|
||||||
}
|
}
|
||||||
Operation::Set(key, value) => {
|
Command::Set { key, value } => {
|
||||||
let value = modify(path, |tmp| tmp.set(&key.into(), value.into()))?;
|
let value = modify(path, |tmp| tmp.set(&key.into(), value.into()))?;
|
||||||
print_value(&value)?;
|
print_value(&value)?;
|
||||||
}
|
}
|
||||||
Operation::Push(key, value) => {
|
Command::Push { key, value } => {
|
||||||
let value = modify(path, |tmp| tmp.push(&key.into(), value.into()))?;
|
let value = modify(path, |tmp| tmp.push(&key.into(), value.into()))?;
|
||||||
print_value(&value)?;
|
print_value(&value)?;
|
||||||
}
|
}
|
||||||
Operation::Remove(key, value) => {
|
Command::Remove { key, value } => {
|
||||||
let value = modify(path, |tmp| tmp.remove(&key.into(), value.into()))?;
|
let value = modify(path, |tmp| tmp.remove(&key.into(), value.into()))?;
|
||||||
print_value(&value)?;
|
print_value(&value)?;
|
||||||
}
|
}
|
||||||
Operation::Unset(key) => {
|
Command::Unset { key } => {
|
||||||
let value = modify(path, |tmp| tmp.unset(&key.into()))?;
|
let value = modify(path, |tmp| tmp.unset(&key.into()))?;
|
||||||
print_value(&value)?;
|
print_value(&value)?;
|
||||||
}
|
}
|
||||||
Operation::Init => {
|
Command::Init { alias } => {
|
||||||
if path.try_exists()? {
|
if path.try_exists()? {
|
||||||
anyhow::bail!("configuration file already exists at `{}`", path.display());
|
anyhow::bail!("configuration file already exists at `{}`", path.display());
|
||||||
}
|
}
|
||||||
Config::init(
|
Config::init(alias, &path)?;
|
||||||
options.alias.ok_or(anyhow!(
|
|
||||||
"an alias must be provided to initialize a new configuration"
|
|
||||||
))?,
|
|
||||||
&path,
|
|
||||||
)?;
|
|
||||||
term::success!(
|
term::success!(
|
||||||
"Initialized new Radicle configuration at {}",
|
"Initialized new Radicle configuration at {}",
|
||||||
path.display()
|
path.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Operation::Edit => match term::editor::Editor::new(&path)?.extension("json").edit()? {
|
Command::Edit => match term::editor::Editor::new(&path)?.extension("json").edit()? {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
term::success!("Successfully made changes to the configuration at {path:?}")
|
term::success!("Successfully made changes to the configuration at {path:?}")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use radicle::node::Alias;
|
||||||
|
|
||||||
|
pub(crate) const ABOUT: &str = "Manage your local Radicle configuration";
|
||||||
|
|
||||||
|
const LONG_ABOUT: &str = r#"
|
||||||
|
If no argument is specified, prints the current radicle configuration as JSON.
|
||||||
|
To initialize a new configuration file, use `rad config init`.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub(crate) command: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
#[group(multiple = false)]
|
||||||
|
pub(crate) enum Command {
|
||||||
|
/// Show the current radicle configuration as JSON (default)
|
||||||
|
Show,
|
||||||
|
/// Initialize a new config file
|
||||||
|
Init {
|
||||||
|
/// Alias to use for the new configuration
|
||||||
|
#[arg(long)]
|
||||||
|
alias: Alias,
|
||||||
|
},
|
||||||
|
/// Open the config in your editor
|
||||||
|
Edit,
|
||||||
|
/// Get a value from the current configuration
|
||||||
|
Get {
|
||||||
|
/// The JSON key path to the value you want to get
|
||||||
|
key: String,
|
||||||
|
},
|
||||||
|
/// Prints the JSON Schema of the Radicle configuration
|
||||||
|
Schema,
|
||||||
|
/// Set a key to a value in the current configuration
|
||||||
|
Set {
|
||||||
|
/// The JSON key path to the value you want to set
|
||||||
|
key: String,
|
||||||
|
/// The JSON value used to set the field
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
/// Set a key in the current configuration to `null`
|
||||||
|
Unset {
|
||||||
|
/// The JSON key path to the value you want to unset
|
||||||
|
key: String,
|
||||||
|
},
|
||||||
|
/// Push a value onto an array, which is identified by the key, in the
|
||||||
|
/// current configuration
|
||||||
|
Push {
|
||||||
|
/// The JSON key path to the array you want to push to
|
||||||
|
key: String,
|
||||||
|
/// The JSON value being pushed onto the array
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
/// Remove a value from an array, which is identified by the key, in the
|
||||||
|
/// current configuration
|
||||||
|
///
|
||||||
|
/// All instances of the value in the array will be removed
|
||||||
|
Remove {
|
||||||
|
/// The JSON key path to the array you want to push to
|
||||||
|
key: String,
|
||||||
|
/// The JSON value being pushed onto the array
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,10 @@ const COMMANDS: &[CommandItem] = &[
|
||||||
name: "clone",
|
name: "clone",
|
||||||
about: crate::commands::clone::ABOUT,
|
about: crate::commands::clone::ABOUT,
|
||||||
},
|
},
|
||||||
CommandItem::Lexopt(crate::commands::config::HELP),
|
CommandItem::Clap {
|
||||||
|
name: "config",
|
||||||
|
about: crate::commands::config::ABOUT,
|
||||||
|
},
|
||||||
CommandItem::Clap {
|
CommandItem::Clap {
|
||||||
name: "debug",
|
name: "debug",
|
||||||
about: crate::commands::debug::ABOUT,
|
about: crate::commands::debug::ABOUT,
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ enum Commands {
|
||||||
Clone(clone::Args),
|
Clone(clone::Args),
|
||||||
#[command(hide = true)]
|
#[command(hide = true)]
|
||||||
Cob(cob::Args),
|
Cob(cob::Args),
|
||||||
|
Config(config::Args),
|
||||||
Debug(debug::Args),
|
Debug(debug::Args),
|
||||||
|
|
||||||
/// This command is deprecated and delegates to `git diff`.
|
/// This command is deprecated and delegates to `git diff`.
|
||||||
|
|
@ -230,7 +231,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"config" => {
|
"config" => {
|
||||||
term::run_command_args::<config::Options, _>(config::HELP, config::run, args.to_vec());
|
if let Some(Commands::Config(args)) = CliArgs::parse().command {
|
||||||
|
term::run_command_fn(config::run, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"diff" => {
|
"diff" => {
|
||||||
if let Some(Commands::Diff(mut args)) = CliArgs::parse().command {
|
if let Some(Commands::Diff(mut args)) = CliArgs::parse().command {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue