cli: Fix `rad config` bugs
The command was unable to deal with an invalid config. We fix this by introducing a way to load the user's home without loading the config.
This commit is contained in:
parent
b0fbbeed6b
commit
d86d99e002
|
|
@ -2,8 +2,11 @@
|
|||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use radicle::node::Alias;
|
||||
use radicle::profile::Config;
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
|
|
@ -18,11 +21,10 @@ Usage
|
|||
|
||||
rad config [<option>...]
|
||||
rad config show [<option>...]
|
||||
rad config init [<option>...]
|
||||
rad config init --alias <alias> [<option>...]
|
||||
rad config edit [<option>...]
|
||||
rad config get <key> [<option>...]
|
||||
|
||||
|
||||
If no argument is specified, prints the current radicle configuration as JSON.
|
||||
To initialize a new configuration file, use `rad config init`.
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ enum Operation {
|
|||
|
||||
pub struct Options {
|
||||
op: Operation,
|
||||
alias: Option<Alias>,
|
||||
}
|
||||
|
||||
impl Args for Options {
|
||||
|
|
@ -52,6 +55,7 @@ impl Args for Options {
|
|||
|
||||
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()? {
|
||||
|
|
@ -59,6 +63,13 @@ impl Args for Options {
|
|||
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),
|
||||
"edit" => op = Some(Operation::Edit),
|
||||
|
|
@ -78,6 +89,7 @@ impl Args for Options {
|
|||
Ok((
|
||||
Options {
|
||||
op: op.unwrap_or_default(),
|
||||
alias,
|
||||
},
|
||||
vec![],
|
||||
))
|
||||
|
|
@ -85,14 +97,16 @@ impl Args for Options {
|
|||
}
|
||||
|
||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
let profile = ctx.profile()?;
|
||||
let path = profile.home.config();
|
||||
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::Get(key) => {
|
||||
let profile = ctx.profile()?;
|
||||
let data = serde_json::to_value(profile.config)?;
|
||||
if let Some(value) = get_value(&data, &key) {
|
||||
print_value(value)?;
|
||||
|
|
@ -102,7 +116,16 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
if path.try_exists()? {
|
||||
anyhow::bail!("configuration file already exists at `{}`", path.display());
|
||||
}
|
||||
profile.config.write(&path)?;
|
||||
Config::init(
|
||||
options.alias.ok_or(anyhow!(
|
||||
"an alias must be provided to initialize a new configuration"
|
||||
))?,
|
||||
&path,
|
||||
)?;
|
||||
term::success!(
|
||||
"Initialized new Radicle configuration at {}",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
Operation::Edit => {
|
||||
let Some(cmd) = term::editor::default_editor() else {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ fn print_help() -> anyhow::Result<()> {
|
|||
println!("{DESCRIPTION}");
|
||||
println!();
|
||||
|
||||
rad_help::run(Default::default(), term::profile)
|
||||
rad_help::run(Default::default(), term::DefaultContext)
|
||||
}
|
||||
|
||||
fn run(command: Command) -> Result<(), Option<anyhow::Error>> {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use std::process;
|
|||
|
||||
pub use radicle_term::*;
|
||||
|
||||
use radicle::profile::Profile;
|
||||
use radicle::profile::{Home, Profile};
|
||||
|
||||
use crate::terminal;
|
||||
|
||||
|
|
@ -22,20 +22,17 @@ use crate::terminal;
|
|||
pub trait Context {
|
||||
/// Return the currently active profile, or an error if no profile is active.
|
||||
fn profile(&self) -> Result<Profile, anyhow::Error>;
|
||||
/// Return the Radicle home.
|
||||
fn home(&self) -> Result<Home, std::io::Error>;
|
||||
}
|
||||
|
||||
impl Context for Profile {
|
||||
fn profile(&self) -> Result<Profile, anyhow::Error> {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Context for F
|
||||
where
|
||||
F: Fn() -> Result<Profile, anyhow::Error>,
|
||||
{
|
||||
fn profile(&self) -> Result<Profile, anyhow::Error> {
|
||||
self()
|
||||
fn home(&self) -> Result<Home, std::io::Error> {
|
||||
Ok(self.home.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +54,7 @@ where
|
|||
pub fn run_command<A, C>(help: Help, cmd: C) -> !
|
||||
where
|
||||
A: Args,
|
||||
C: Command<A, fn() -> anyhow::Result<Profile>>,
|
||||
C: Command<A, DefaultContext>,
|
||||
{
|
||||
let args = std::env::args_os().skip(1).collect();
|
||||
|
||||
|
|
@ -67,7 +64,7 @@ where
|
|||
pub fn run_command_args<A, C>(help: Help, cmd: C, args: Vec<OsString>) -> !
|
||||
where
|
||||
A: Args,
|
||||
C: Command<A, fn() -> anyhow::Result<Profile>>,
|
||||
C: Command<A, DefaultContext>,
|
||||
{
|
||||
use io as term;
|
||||
|
||||
|
|
@ -108,7 +105,7 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
match cmd.run(options, self::profile) {
|
||||
match cmd.run(options, DefaultContext) {
|
||||
Ok(()) => process::exit(0),
|
||||
Err(err) => {
|
||||
terminal::fail(help.name, &err);
|
||||
|
|
@ -117,17 +114,25 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the default profile. Fails if there is no profile.
|
||||
pub fn profile() -> Result<Profile, anyhow::Error> {
|
||||
match Profile::load() {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::WithHint {
|
||||
err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
|
||||
hint: "To setup your radicle profile, run `rad auth`.",
|
||||
/// Gets the default profile. Fails if there is no profile.
|
||||
pub struct DefaultContext;
|
||||
|
||||
impl Context for DefaultContext {
|
||||
fn home(&self) -> Result<Home, std::io::Error> {
|
||||
radicle::profile::home()
|
||||
}
|
||||
|
||||
fn profile(&self) -> Result<Profile, anyhow::Error> {
|
||||
match Profile::load() {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::WithHint {
|
||||
err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
|
||||
hint: "To setup your radicle profile, run `rad auth`.",
|
||||
}
|
||||
.into()),
|
||||
Err(radicle::profile::Error::Config(e)) => Err(e.into()),
|
||||
Err(e) => Err(anyhow::anyhow!("Could not load radicle profile: {e}")),
|
||||
}
|
||||
.into()),
|
||||
Err(radicle::profile::Error::Config(e)) => Err(e.into()),
|
||||
Err(e) => Err(anyhow::anyhow!("Could not load radicle profile: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue