Skip to content

Commit

Permalink
fix(config): apply config options from subcommands, too
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Apr 18, 2023
1 parent f8e8e27 commit 3c31caa
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 29 deletions.
5 changes: 3 additions & 2 deletions book/src/guide/configuration.md
Expand Up @@ -16,8 +16,9 @@ Options are read from three possible `oro.kdl` file locations:
All files files use [KDL](https://kdl.dev) as their configuration language.
Key/value options are typically specified using `node-name "value"` format.
For example, `foo "bar"` would be the equivalent of `--foo bar`. For boolean
operations, use `foo true` and `foo false`. Negations (`no-foo`) are not
supported.
operations, use `foo true` and `foo false`. If an empty node name is found,
that will be treated as if it had a value of `true`. Negations (`no-foo`) are
not supported.

Some configurations, such a [Options](#options-from-orokdl), exist in nested
nodes. Refer to their dedicated sections for more details.
Expand Down
4 changes: 2 additions & 2 deletions crates/oro-config/src/kdl_source.rs
Expand Up @@ -128,7 +128,7 @@ fn node_value(node: &KdlNode) -> Value {
)
}
} else {
// Probably unreachable, but just in case...
Value::new(None, ValueKind::Nil)
// Nodes without args or children act the same as "true"
Value::new(None, ValueKind::Boolean(true))
}
}
32 changes: 16 additions & 16 deletions crates/oro-config/src/lib.rs
@@ -1,10 +1,6 @@
//! Configuration loader for Orogene config files.

use std::{
collections::{HashMap, HashSet},
ffi::OsString,
path::PathBuf,
};
use std::{collections::HashSet, ffi::OsString, path::PathBuf};

pub use clap::{ArgMatches, Command};
pub use config::Config as OroConfig;
Expand All @@ -19,7 +15,7 @@ mod kdl_source;

pub trait OroConfigLayerExt {
fn with_negations(self) -> Self;
fn layered_matches(self, config: &OroConfig) -> Result<ArgMatches>;
fn layered_args(&self, args: &mut Vec<OsString>, config: &OroConfig) -> Result<()>;
}

impl OroConfigLayerExt for Command {
Expand Down Expand Up @@ -55,24 +51,28 @@ impl OroConfigLayerExt for Command {
self.args(negations)
}

fn layered_matches(self, config: &OroConfig) -> Result<ArgMatches> {
let mut short_opts = HashMap::new();
fn layered_args(&self, args: &mut Vec<OsString>, config: &OroConfig) -> Result<()> {
let mut long_opts = HashSet::new();
for opt in self.get_arguments() {
if let Some(short) = opt.get_short() {
short_opts.insert(short, (*opt).clone());
}
if opt.get_long().is_some() {
long_opts.insert(opt.get_id().to_string());
}
}
let mut args = std::env::args_os().collect::<Vec<_>>();
let matches = self.clone().get_matches_from(&args);
let matches = self
.clone()
.ignore_errors(true)
.get_matches_from(&args.clone());
for opt in long_opts {
if matches.value_source(&opt) != Some(clap::parser::ValueSource::CommandLine) {
let opt = opt.replace('_', "-");
if !args.contains(&OsString::from(format!("--no-{}", opt))) {
if let Ok(value) = config.get_string(&opt) {
if !args.contains(&OsString::from(format!("--no-{opt}"))) {
if let Ok(bool) = config.get_bool(&opt) {
if bool {
args.push(OsString::from(format!("--{}", opt)));
} else {
args.push(OsString::from(format!("--no-{}", opt)));
}
} else if let Ok(value) = config.get_string(&opt) {
args.push(OsString::from(format!("--{}", opt)));
args.push(OsString::from(value));
} else if let Ok(value) = config.get_table(&opt) {
Expand All @@ -91,7 +91,7 @@ impl OroConfigLayerExt for Command {
}
}
}
Ok(self.get_matches_from(args))
Ok(())
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/commands/remove.rs
Expand Up @@ -2,8 +2,8 @@ use async_trait::async_trait;
use clap::Args;
use miette::{Diagnostic, IntoDiagnostic, Result};
use nassun::PackageSpec;
use thiserror::Error;
use oro_pretty_json::Formatted;
use thiserror::Error;

use crate::apply_args::ApplyArgs;
use crate::commands::OroCommand;
Expand All @@ -14,11 +14,8 @@ enum RemoveCmdError {
/// remove`, but you passed either a package specifier or an invalid
/// package name.
#[error("{0} is not a valid package name. Only package names should be passed to `oro remove`, but you passed either a non-NPM package specifier or an invalid package name.")]
#[diagnostic(
code(oro::remove::invalid_package_name),
url(docsrs)
)]
InvalidPackageName(String)
#[diagnostic(code(oro::remove::invalid_package_name), url(docsrs))]
InvalidPackageName(String),
}

/// Removes one or more dependencies from the target package.
Expand All @@ -45,7 +42,10 @@ impl OroCommand for RemoveCmd {
.into_diagnostic()?;
let mut count = 0;
for name in &self.names {
if let Ok(PackageSpec::Npm { name: spec_name, .. }) = name.parse() {
if let Ok(PackageSpec::Npm {
name: spec_name, ..
}) = name.parse()
{
if &spec_name != name {
tracing::warn!("Ignoring version specifier in {name}. Arguments to `oro remove` should only be package names. Proceeding with {spec_name} instead.");
}
Expand Down
36 changes: 34 additions & 2 deletions src/lib.rs
Expand Up @@ -37,6 +37,7 @@

use std::{
collections::VecDeque,
ffi::OsString,
path::{Path, PathBuf},
};

Expand Down Expand Up @@ -331,6 +332,36 @@ impl Orogene {
command
}

fn layer_command_args(
command: &Command,
args: &mut Vec<OsString>,
config: &OroConfig,
) -> Result<()> {
// First, we do a fake parse. All we really want is to get the subcommand, here.
let matches = command.clone().ignore_errors(true).get_matches();
let mut matches_ref = &matches;

// Next, we "recursively" follow the subcommand chain until we bottom
// out, then keep that path.
let mut subcmd_path = VecDeque::new();
while let Some((sub_cmd, new_m)) = matches_ref.subcommand() {
subcmd_path.push_back(sub_cmd);
matches_ref = new_m;
}

// Then, we find the subcommand starting from our toplevel Command.
let mut command = command.clone();
let mut subcmd = &mut command;
subcmd.layered_args(args, config)?;
while let Some(name) = subcmd_path.pop_front() {
subcmd = subcmd
.find_subcommand_mut(name)
.expect("This should definitely exist?");
subcmd.layered_args(args, config)?;
}
Ok(())
}

pub async fn load() -> Result<()> {
let start = std::time::Instant::now();
// We have to instantiate Orogene twice: once to pick up "base" config
Expand All @@ -343,8 +374,9 @@ impl Orogene {
let matches = command.clone().get_matches();
let oro = Orogene::from_arg_matches(&matches).into_diagnostic()?;
let config = oro.build_config()?;
let oro =
Orogene::from_arg_matches(&command.layered_matches(&config)?).into_diagnostic()?;
let mut args = std::env::args_os().collect::<Vec<_>>();
Self::layer_command_args(&command, &mut args, &config)?;
let oro = Orogene::from_arg_matches(&command.get_matches_from(&args)).into_diagnostic()?;
let log_file = oro
.cache
.clone()
Expand Down

0 comments on commit 3c31caa

Please sign in to comment.