Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
cli/config: Use clap
Merged did:key:z6MkkfM3...sVz5 opened 6 months ago
4 files changed +86 -151 8d90699c 8604d3bc
modified crates/radicle-cli/src/commands/config.rs
@@ -1,152 +1,29 @@
-
#![allow(clippy::or_fun_call)]
-
use std::ffi::OsString;
+
mod args;
+

+
pub use args::Args;
+
use args::Command;
+
pub(crate) use args::ABOUT;
+

use std::path::Path;
-
use std::str::FromStr;

-
use anyhow::anyhow;
-
use radicle::node::Alias;
use radicle::profile::{config, Config, ConfigPath, RawConfig};

use crate::terminal as term;
-
use crate::terminal::args::{Args, Error, Help};
use crate::terminal::Element as _;

-
pub const HELP: Help = Help {
-
    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<()> {
+
pub fn run(options: Args, ctx: impl term::Context) -> anyhow::Result<()> {
    let home = ctx.home()?;
    let path = home.config();

-
    match options.op {
-
        Operation::Show => {
-
            let profile = ctx.profile()?;
-
            term::json::to_pretty(&profile.config, path.as_path())?.print();
-
        }
-
        Operation::Schema => {
+
    match options {
+
        Args {
+
            command: Some(Command::Schema),
+
        } => {
            term::json::to_pretty(&schemars::schema_for!(Config), path.as_path())?.print();
        }
-
        Operation::Get(key) => {
+
        Args {
+
            command: Some(Command::Get { key }),
+
        } => {
            let mut temp_config = RawConfig::from_file(&path)?;
            let key: ConfigPath = key.into();
            let value = temp_config.get_mut(&key).ok_or_else(|| {
@@ -154,43 +31,57 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
            })?;
            print_value(value)?;
        }
-
        Operation::Set(key, value) => {
+
        Args {
+
            command: Some(Command::Set { key, value }),
+
        } => {
            let value = modify(path, |tmp| tmp.set(&key.into(), value.into()))?;
            print_value(&value)?;
        }
-
        Operation::Push(key, value) => {
+
        Args {
+
            command: Some(Command::Push { key, value }),
+
        } => {
            let value = modify(path, |tmp| tmp.push(&key.into(), value.into()))?;
            print_value(&value)?;
        }
-
        Operation::Remove(key, value) => {
+
        Args {
+
            command: Some(Command::Remove { key, value }),
+
        } => {
            let value = modify(path, |tmp| tmp.remove(&key.into(), value.into()))?;
            print_value(&value)?;
        }
-
        Operation::Unset(key) => {
+
        Args {
+
            command: Some(Command::Unset { key }),
+
        } => {
            let value = modify(path, |tmp| tmp.unset(&key.into()))?;
            print_value(&value)?;
        }
-
        Operation::Init => {
+
        Args {
+
            command: Some(Command::Init { alias }),
+
        } => {
            if path.try_exists()? {
                anyhow::bail!("configuration file already exists at `{}`", path.display());
            }
-
            Config::init(
-
                options.alias.ok_or(anyhow!(
-
                    "an alias must be provided to initialize a new configuration"
-
                ))?,
-
                &path,
-
            )?;
+
            Config::init(alias, &path)?;
            term::success!(
                "Initialized new Radicle configuration at {}",
                path.display()
            );
        }
-
        Operation::Edit => match term::editor::Editor::new(&path)?.extension("json").edit()? {
+
        Args {
+
            command: Some(Command::Edit),
+
        } => match term::editor::Editor::new(&path)?.extension("json").edit()? {
            Some(_) => {
                term::success!("Successfully made changes to the configuration at {path:?}")
            }
            None => term::info!("No changes were made to the configuration at {path:?}"),
        },
+
        Args { command: None }
+
        | Args {
+
            command: Some(Command::Show),
+
        } => {
+
            let profile = ctx.profile()?;
+
            term::json::to_pretty(&profile.config, path.as_path())?.print();
+
        }
    }

    Ok(())
added crates/radicle-cli/src/commands/config/args.rs
@@ -0,0 +1,38 @@
+
use clap::{Parser, Subcommand};
+
use radicle::node::Alias;
+

+
pub(crate) const ABOUT: &str = "Manage your local Radicle configuration";
+

+
#[derive(Debug, Parser)]
+
#[command(about = 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 config
+
        #[arg(long)]
+
        alias: Alias,
+
    },
+
    /// Open the config in your editor
+
    Edit,
+
    /// Get a value from the current config
+
    Get { key: String },
+
    /// Show the config schema
+
    Schema,
+
    /// Set a key to a value in the current config
+
    Set { key: String, value: String },
+
    /// Unset a key in the current config
+
    Unset { key: String },
+
    /// Push a value to a key set in the current config
+
    Push { key: String, value: String },
+
    /// Remove a value from a key set in the current config
+
    Remove { key: String, value: String },
+
}
modified crates/radicle-cli/src/commands/help.rs
@@ -53,7 +53,10 @@ const COMMANDS: &[CommandItem] = &[
        name: "clone",
        about: crate::commands::clone::ABOUT,
    },
-
    CommandItem::Lexopt(crate::commands::config::HELP),
+
    CommandItem::Clap {
+
        name: "config",
+
        about: crate::commands::config::ABOUT,
+
    },
    CommandItem::Clap {
        name: "debug",
        about: crate::commands::debug::ABOUT,
modified crates/radicle-cli/src/main.rs
@@ -50,6 +50,7 @@ enum Commands {
    Checkout(checkout::Args),
    Clean(clean::Args),
    Clone(clone::Args),
+
    Config(config::Args),
    Debug(debug::Args),

    /// This command is deprecated and delegates to `git diff`.
@@ -224,7 +225,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
            term::run_command_args::<cob::Options, _>(cob::HELP, cob::run, args.to_vec());
        }
        "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" => {
            if let Some(Commands::Diff(mut args)) = CliArgs::parse().command {