Skip to content

Commit

Permalink
Merge pull request #45 from DanCardin/dc/multiple-as-args
Browse files Browse the repository at this point in the history
  • Loading branch information
DanCardin authored Jun 14, 2023
2 parents c1e3f1d + 47a1281 commit e21a28b
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 149 deletions.
344 changes: 256 additions & 88 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sauce"
version = "0.8.0"
version = "0.9.0"
authors = ["Dan Cardin <ddcardin@gmail.com>"]
edition = "2021"
description = "A tool for managing directory-specific state."
Expand Down Expand Up @@ -37,11 +37,15 @@ toml_edit = "0.2.0"
once_cell = "1.8.0"

[dependencies.clap]
version = "3.1.18"
version = "4.3.3"
features = [
"suggestions",
"color",
"derive",
"error-context",
"help",
"suggestions",
"usage",
"wrap_help",
]

[dependencies.indexmap]
Expand Down
11 changes: 10 additions & 1 deletion doc/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Any key-value pair can be tagged with, you might call “namespaces”.
Consider an env var definition

```toml
AWS_PROFILE = {default = "projectname-dev", uat = "projectname-uat", prod = "projectname-prod"}
AWS_PROFILE = {default = "dev", uat = "uat", prod = "prod"}
```

Given `sauce`, you will get the “default” namespace
Expand All @@ -26,6 +26,15 @@ Given `sauce --as prod`, you will get the “prod” namespace
(i.e. AWS_PROFILE=projectname-prod) for this value, as well as all
other unnamespaced values.

As of v0.9.0, you can supply multiple `--as` arguments, and they will be used to
choose the first matching value (falling back to "default" if none match).

Per the above example, `sauce --as foo` would produce "dev" and
`sauce --as uat --as prod` would produce "uat".

If there was no "default" option specified above, then any "namespaced" keys without
a matching value would be unchanged relative to the current environment.

## `sauce --glob glob` and `sauce --filter filter`

Either `--glob` and/or `--filter` can be applied in order to filter down
Expand Down
4 changes: 2 additions & 2 deletions src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn run() -> Result<()> {
let filter_options = FilterOptions {
globs: &parse_match_option(opts.glob.as_deref()),
filters: &parse_match_option(opts.filter.as_deref()),
as_: opts.r#as.as_deref(),
as_: opts.r#as,
target: opts.target.as_deref(),
filter_exclusions: &[],
};
Expand Down Expand Up @@ -69,7 +69,7 @@ pub fn match_subcommmand(
match cmd.kind {
ShellKinds::Init => context.init_shell(shell_kind, output),
ShellKinds::Exec(command) => {
context.execute_shell_command(shell_kind, &*command.command, output)
context.execute_shell_command(shell_kind, &command.command, output)
}
};
}
Expand Down
48 changes: 24 additions & 24 deletions src/cli/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,65 @@ use std::{io::Write, path::PathBuf};

/// Sauce!
#[derive(Parser, Debug)]
#[clap(version, author)]
#[command(version, author)]
pub struct CliOptions {
/// Determines the shell behavior, this flag should always be set automatically
/// by the shell hook. Valid options are: zsh, fish, bash
#[clap(long)]
#[arg(long)]
pub shell: ShellName,

/// Supplied during autoload sequence. Not generally useful to end-users.
#[clap(long)]
#[arg(long)]
pub autoload: bool,

/// For typical commands such as `sauce` and `sauce clear` this outputs the exact
/// shell output that would have executed. For mutating commands like `sauce config`
/// and `sauce set`, the change is printed but not saved.
#[clap(long)]
#[arg(long)]
pub show: bool,

/// Valid options: always, never, auto. Auto (default) will attempt to autodetect
/// whether it should output color based on the existence of a tty.
#[clap(long, default_value = "auto")]
#[arg(long, default_value = "auto")]
pub color: ColorStrategy,

/// Enables verbose output. This causes all stdout to be mirrored to stderr.
#[clap(short, long)]
#[arg(short, long)]
pub verbose: bool,

/// Disables normal messaging output after a command is executed.
#[clap(short, long)]
#[arg(short, long)]
pub quiet: bool,

/// The path which should be sauce'd. Defaults to the current directory.
#[clap(short, long)]
#[arg(short, long)]
pub path: Option<PathBuf>,

/// Sets a specific saucefile to load, overriding the default lookup mechanisms and
/// cascading behavior
#[clap(long)]
#[arg(long)]
pub file: Option<PathBuf>,

/// Runs the given command "as" the given "as" namespace.
#[clap(short, long)]
pub r#as: Option<String>,
#[arg(short, long)]
pub r#as: Option<Vec<String>>,

/// Filters the set of values to load, allowing globs. By default filters apply to
/// all targets, but also can use the form "<target>:<glob>" to be more specific.
#[clap(short, long)]
#[arg(short, long)]
pub glob: Option<String>,

/// Only use values for a specific target. Essentially this can be thought of
/// as a shortcut for `-g '<target>:*`.
#[clap(short, long)]
#[arg(short, long)]
pub target: Option<String>,

/// Filters the set of values to load, literally. By default filters apply to all
/// targets, but also can use the form "<target>:<filter>" to be more specific.
#[clap(short, long)]
#[arg(short, long)]
pub filter: Option<String>,

#[clap(subcommand)]
#[command(subcommand)]
pub subcmd: Option<SubCommand>,
}

Expand Down Expand Up @@ -109,29 +109,29 @@ pub enum SubCommand {

#[derive(Parser, Debug)]
pub struct ConfigCommand {
#[clap(long, short)]
#[arg(long, short)]
pub global: bool,

#[clap(parse(try_from_str = crate::cli::utilities::parse_key_val))]
#[arg(value_parser = crate::cli::utilities::parse_key_val::<String>)]
pub values: Vec<(String, String)>,
}

#[derive(Parser, Debug)]
pub struct MoveCommand {
/// The destination location to which a `sauce` invocation would point.
/// That is, not the destination saucefile location.
#[clap()]
#[arg()]
pub destination: PathBuf,

/// Instead of removing the files at the source location, leave the original
/// file untouched.
#[clap(short, long)]
#[arg(short, long)]
pub copy: bool,
}

#[derive(Parser, Debug)]
pub struct SetCommand {
#[clap(subcommand)]
#[command(subcommand)]
pub kind: SetKinds,
}

Expand All @@ -146,7 +146,7 @@ pub enum SetKinds {
/// Key-value pairs, delimited by an "=".
#[derive(Parser, Debug)]
pub struct SetVarKind {
#[clap(parse(try_from_str = crate::cli::utilities::parse_key_val))]
#[arg(value_parser = crate::cli::utilities::parse_key_val::<String>)]
pub values: Vec<(String, String)>,
}

Expand All @@ -159,7 +159,7 @@ pub struct KeyValuePair {

#[derive(Parser, Debug)]
pub struct ShellCommand {
#[clap(subcommand)]
#[command(subcommand)]
pub kind: ShellKinds,
}

Expand All @@ -174,13 +174,13 @@ pub enum ShellKinds {

#[derive(Parser, Debug)]
pub struct ExecCommand {
#[clap()]
#[arg()]
pub command: String,
}

#[derive(Parser, Debug)]
pub struct ShowCommand {
#[clap(subcommand)]
#[command(subcommand)]
pub kind: ShowKinds,
}

Expand Down
6 changes: 3 additions & 3 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub type MatchOption<'a> = (Option<&'a str>, &'a str);
#[derive(Debug, Clone, Default)]
pub struct FilterOptions<'a> {
pub target: Option<&'a str>,
pub as_: Option<&'a str>,
pub as_: Option<Vec<String>>,

pub globs: &'a [MatchOption<'a>],

Expand Down Expand Up @@ -42,8 +42,8 @@ impl<'a> FilterOptions<'a> {
}
}

fn check_matches<'a, F>(
globs: &[MatchOption<'a>],
fn check_matches<F>(
globs: &[MatchOption],
kinds: &[&str],
value: &str,
matcher: F,
Expand Down
86 changes: 61 additions & 25 deletions src/saucefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{filter::FilterOptions, output::Output};
use crate::{settings::Settings, toml::unwrap_toml_value};
use indexmap::IndexMap;
use itertools::iproduct;
use std::iter::once;

use crate::toml::get_document;
use std::path::PathBuf;
Expand Down Expand Up @@ -72,7 +73,15 @@ impl Saucefile {
return Vec::new();
}
}
let tag = filter_options.as_.unwrap_or("default");
// let default = ["default".to_string()];

let tags: Vec<String> = filter_options
.as_
.clone()
.unwrap_or(vec![])
.into_iter()
.chain(once("default".to_string()))
.collect();

iproduct!(self.documents(), sections)
.filter_map(|(document, section)| document[section].as_table())
Expand All @@ -82,22 +91,25 @@ impl Saucefile {
&& filter_options.filter_match(sections, key)
&& filter_options.filter_exclude(sections, key)
})
.map(|(key, item)| {
let var = match item {
Item::Value(value) => match value {
Value::InlineTable(table) => match table.get(tag) {
Some(value) => unwrap_toml_value(value),
_ => "".to_string(),
.filter_map(|(key, item)| {
for tag in tags.iter() {
let var = match item {
Item::Value(value) => match value {
Value::InlineTable(table) => match table.get(tag) {
Some(value) => unwrap_toml_value(value),
_ => continue,
},
_ => unwrap_toml_value(value),
},
Item::Table(table) => match &table[tag] {
Item::Value(value) => unwrap_toml_value(value),
_ => continue,
},
_ => unwrap_toml_value(value),
},
Item::Table(table) => match &table[tag] {
Item::Value(value) => unwrap_toml_value(value),
_ => "".to_string(),
},
_ => "".to_string(),
};
(key, var)
_ => continue,
};
return Some((key, var));
}
None
})
.collect::<IndexMap<&str, String>>()
.into_iter()
Expand Down Expand Up @@ -194,11 +206,7 @@ mod tests {
let result = sauce.section(&["foo"], &FilterOptions::default());
assert_eq!(
result,
vec![
("bar", "1".to_string()),
("bees", "2".to_string()),
("boops", "".to_string()),
]
vec![("bar", "1".to_string()), ("bees", "2".to_string()),]
);
}

Expand All @@ -217,16 +225,44 @@ mod tests {
let result = sauce.section(
&["foo"],
&FilterOptions {
as_: Some("wow"),
as_: Some(vec!["wow".to_string()]),
..Default::default()
},
);
assert_eq!(
result,
vec![("bar", "1".to_string()), ("bees", "2".to_string()),]
);
}

#[test]
fn it_selects_multiple_tags() {
let mut sauce = Saucefile::default();

let toml = r#"
[foo]
one = 1
two = {default = 2}
three = {three = 3}
four = {four = 4}
notfive = {ohno = 4}
"#;
sauce.document = toml.parse::<Document>().expect("invalid doc");

let result = sauce.section(
&["foo"],
&FilterOptions {
as_: Some(vec!["three".to_string(), "four".to_string()]),
..Default::default()
},
);
assert_eq!(
result,
vec![
("bar", "1".to_string()),
("bees", "2".to_string()),
("boops", "".to_string()),
("one", "1".to_string()),
("two", "2".to_string()),
("three", "3".to_string()),
("four", "4".to_string()),
]
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/shell/utilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{collections::VecDeque, str::FromStr};

use crate::shell::Shell;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum ShellName {
Zsh,
Fish,
Expand All @@ -26,7 +26,7 @@ impl FromStr for ShellName {
}
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum ColorStrategy {
Always,
Never,
Expand Down
2 changes: 1 addition & 1 deletion src/toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub fn get_document(path: &Path, output: &mut Output) -> Document {
}

pub fn write_document(file: &Path, document: &Document, output: &mut Output) {
let handle = OpenOptions::new().write(true).open(&file);
let handle = OpenOptions::new().write(true).open(file);
write_contents(handle, file, document, output);
}

Expand Down

0 comments on commit e21a28b

Please sign in to comment.