Skip to content

Commit

Permalink
Merge pull request #143 from FyraLabs/w/better-filtering
Browse files Browse the repository at this point in the history
feat!: better `--filters` and new `--excludes`
  • Loading branch information
madonuko authored Jul 28, 2024
2 parents 97eed12 + f9f40a0 commit 3c4878e
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 57 deletions.
36 changes: 19 additions & 17 deletions anda-config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,18 +196,20 @@ pub struct Docker {
pub image: BTreeMap<String, DockerImage>, // tag, file
}

pub fn parse_kv(input: &str) -> impl Iterator<Item = Option<(String, String)>> + '_ {
input
.split(',')
.filter(|item| !item.trim().is_empty())
.map(|item| item.split_once('=').map(|(l, r)| (l.to_owned(), r.to_owned())))
}

pub fn parse_filters(filters: &[String]) -> Option<Vec<Vec<(String, String)>>> {
filters.iter().map(std::ops::Deref::deref).map(crate::parse_kv).map(Iterator::collect).collect()
}

/// Turn a string into a BTreeMap<String, String>
#[must_use]
pub fn parse_map(input: &str) -> Option<BTreeMap<String, String>> {
let mut map = BTreeMap::new();
for item in input.split(',') {
if item.trim().is_empty() {
continue;
}
let (k, v) = item.split_once('=')?;
map.insert(k.to_owned(), v.to_owned());
}
Some(map)
pub fn parse_labels<'a, I: Iterator<Item = &'a str>>(labels: I) -> Option<Vec<(String, String)>> {
labels.flat_map(parse_kv).collect()
}

#[derive(Deserialize, PartialEq, Eq, Serialize, Debug, Clone, Default)]
Expand Down Expand Up @@ -425,17 +427,17 @@ mod test_parser {

#[test]
fn test_map() {
let m: BTreeMap<String, String> = [("foo".to_owned(), "bar".to_owned())].into();
let m = [("foo".to_owned(), "bar".to_owned())].into();

assert_eq!(parse_map("foo=bar"), Some(m));
assert_eq!(parse_labels(std::iter::once("foo=bar".into())), Some(m));

let multieq: BTreeMap<String, String> = [("foo".to_owned(), "bar=baz".to_owned())].into();
let multieq = [("foo".to_owned(), "bar=baz".to_owned())].into();

assert_eq!(parse_map("foo=bar=baz"), Some(multieq));
assert_eq!(parse_labels(std::iter::once("foo=bar=baz".into())), Some(multieq));

let multi: BTreeMap<String, String> =
let multi =
[("foo".to_owned(), "bar".to_owned()), ("baz".to_owned(), "qux".to_owned())].into();

assert_eq!(parse_map("foo=bar,baz=qux"), Some(multi));
assert_eq!(parse_labels(std::iter::once("foo=bar,baz=qux".into())), Some(multi));
}
}
5 changes: 2 additions & 3 deletions andax/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ mod fns;
mod run;

pub use fns::rpm::RPMSpec;
pub use rhai::Map;
pub use run::errhdl;
pub use run::run;
pub use rhai::{self, Map};
pub use run::{errhdl, run};

/// The usual Error type returned by the Rhai engine.
/// Alias for `Box<EvalAltResult>`.
Expand Down
17 changes: 10 additions & 7 deletions andax/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rhai::{
Engine, EvalAltResult as RhaiE, NativeCallContext as Ctx, Scope,
};
use std::fmt::Write;
use std::{collections::BTreeMap, io::BufRead, path::Path};
use std::{io::BufRead, path::Path};
use tracing::{debug, error, instrument, trace, warn};

/// # Errors
Expand Down Expand Up @@ -207,18 +207,21 @@ pub fn errhdl(name: &str, scr: &Path, err: EvalAltResult) {
}

/// Executes an AndaX script.
pub fn run<'a, F: FnOnce(&mut Scope<'a>)>(
pub fn run<
'a,
F: FnOnce(&mut Scope<'a>),
K: Into<rhai::Identifier>,
V: Into<rhai::Dynamic>,
L: Iterator<Item = (K, V)>,
>(
name: &'a str,
scr: &'a Path,
labels: BTreeMap<String, String>,
labels: L,
f: F,
) -> Option<Scope<'a>> {
let (en, mut sc) = gen_en();
f(&mut sc);
let mut lbls = rhai::Map::new();
for (k, v) in labels {
lbls.insert(k.into(), v.into());
}
let lbls: rhai::Map = labels.map(|(k, v)| (k.into(), v.into())).collect();
sc.push("labels", lbls);
exec(name, scr, sc, en)
}
Expand Down
4 changes: 2 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ macro_rules! script {
let sc = andax::run(
$name,
&$scr,
std::collections::BTreeMap::new(),
std::iter::empty::<(String, String)>(),
|_sc| {
$( _sc.push(stringify!($var), $var); )*
},
Expand Down Expand Up @@ -409,7 +409,7 @@ async fn build_all(
.map(|p| p.to_string_lossy().to_string())
.collect::<Vec<String>>()
.as_slice(),
std::mem::take(&mut project.labels),
project.labels.iter().map(|(a, b)| (a.clone(), b.clone())).collect(),
)?;
};
Ok(())
Expand Down
25 changes: 23 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,31 @@ pub enum Command {
Update {
/// Labels to pass to the scripts
#[clap(short, long)]
labels: Option<String>,
labels: Vec<String>,
/// Only run update scripts in project with the specified labels
///
/// This should be a comma-separated list of filters.
/// Each time `--filters=...` is specified, the comma-separated list of key-values will be
/// checked against a project. If missing or different, the project will be ignored.
/// However, specifying `--filters` multiple times will create an "or" effect --- the
/// project will not be ignored if it satisfies one of the list of `--filters`. For
/// example, `-f a=1,b=2 -f c=3` means the project needs to satisfy either "a=1" and "b=2",
/// or only "c=3".
#[clap(short, long)]
filters: Vec<String>,
/// Exclude update scripts in project with the specified labels
///
/// This should be a comma-separated list of excludes.
/// Each time `--exclude=...` is specified, the comma-separated list of key-values will be
/// checked against the labels of a project, and it will be ignored if all the key-values
/// are present. In addition, specifying `--exclude` multiple times will create an "or"
/// effect --- a project will be excluded if it satisfies one of the list of `--filters`.
/// For example, `-e a=1,b=2 -e c=3` means projects with "a=1" and "b=2" at the same time,
/// or "c=3", are excluded. Projects with only "a=1" or "b=2" are not excluded.
///
/// This will always override `--filters`.
#[clap(short, long)]
filters: Option<String>,
excludes: Vec<String>,
},

/// Run .rhai scripts
Expand Down
27 changes: 12 additions & 15 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ mod oci;
mod rpm_spec;
mod update;
mod util;
use anda_config::parse_map;
use anda_config::parse_labels;
use clap::{CommandFactory, Parser};
use clap_complete::generate;
use cli::{Cli, Command};
use color_eyre::{eyre::eyre, Result};
use std::{collections::BTreeMap, io, mem::take};
use std::{io, mem::take};
use tracing::{debug, trace};

#[allow(clippy::unwrap_in_result)]
Expand Down Expand Up @@ -102,24 +102,21 @@ async fn main() -> Result<()> {

println!("build_matrix={}", serde_json::to_string(&entries)?);
}
Command::Update { labels, filters } => {
let labels = parse_map(&labels.unwrap_or_default());
let filters = parse_map(&filters.unwrap_or_default());
update::update(
anda_config::load_from_file(&cli.config)?,
labels.ok_or_else(|| eyre!("Cannot parse --labels"))?,
filters.ok_or_else(|| eyre!("Cannot parse --labels"))?,
)?;
Command::Update { labels, filters, excludes } => {
let labels = parse_labels(labels.iter().map(std::ops::Deref::deref))
.ok_or_else(|| eyre!("Cannot parse --labels"))?;
let filters = anda_config::parse_filters(&filters)
.ok_or_else(|| eyre!("Cannot parse --filters"))?;
let excludes = anda_config::parse_filters(&excludes)
.ok_or_else(|| eyre!("Cannot parse --excludes"))?;
update::update(anda_config::load_from_file(&cli.config)?, labels, filters, excludes)?;
}
Command::Run { scripts, labels } => {
if scripts.is_empty() {
return Err(eyre!("No scripts to run"));
}
let labels = if let Some(lbls) = labels {
parse_map(&lbls).ok_or_else(|| eyre!("Cannot parse --labels"))?
} else {
BTreeMap::new()
};
let labels = parse_labels(labels.iter().map(std::ops::Deref::deref))
.ok_or_else(|| eyre!("Cannot parse --labels"))?;
update::run_scripts(&scripts, labels)?;
}
}
Expand Down
33 changes: 22 additions & 11 deletions src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@ use std::{
use tracing::{debug, error, instrument, trace};

/// Return true only if the project `lbls` does not have the key or the value does not match.
fn filter_project(lbls: &BTreeMap<String, String>) -> impl FnMut((&String, &String)) -> bool + '_ {
fn filter_project(lbls: &BTreeMap<String, String>) -> impl Fn(&(String, String)) -> bool + '_ {
|(k, v)| lbls.get(k).map_or(true, |val| val != v)
}

/// Return true only if `lbls` have the key and the value matches.
fn exclude_project(lbls: &BTreeMap<String, String>) -> impl Fn(&(String, String)) -> bool + '_ {
|(k, v)| lbls.get(k).is_some_and(|val| val == v)
}

#[allow(clippy::arithmetic_side_effects)]
#[instrument(skip(cfg))]
pub fn update(
cfg: Manifest,
global_lbls: BTreeMap<String, String>,
fls: BTreeMap<String, String>,
global_lbls: Vec<(String, String)>,
fls: Vec<Vec<(String, String)>>,
excls: Vec<Vec<(String, String)>>,
) -> Result<()> {
let mut handlers = vec![];
let proj_len = cfg.project.len();
Expand All @@ -29,7 +35,10 @@ pub fn update(
scr_len += 1;
let mut lbls = std::mem::take(&mut proj.labels);
lbls.extend(global_lbls.clone());
if fls.iter().any(filter_project(&lbls)) {
if fls.iter().all(|fls| fls.iter().any(filter_project(&lbls))) {
continue;
}
if excls.iter().any(|excls| excls.iter().all(exclude_project(&lbls))) {
continue;
}
trace!(name, scr = scr.to_str(), "Th start");
Expand All @@ -39,10 +48,12 @@ pub fn update(
let th = thread::current();
let name = th.name().expect("No name for andax thread??");
let start = std::time::Instant::now();
let sc = run(name, &scr, lbls, |sc| {
let sc = run(name, &scr, lbls.iter(), |sc| {
// we have to do it here as `Dynamic` in andax::Map nu Sync impl
let filters =
fls.into_iter().map(|(k, v)| (k.into(), v.into())).collect::<andax::Map>();
let filters = fls
.into_iter()
.flat_map(|fls| fls.into_iter().map(|(k, v)| (k.into(), v.into())))
.collect::<andax::Map>();
sc.push("filters", filters);
if let Some(rpm) = &proj.rpm {
sc.push("rpm", RPMSpec::new(name.to_owned(), &scr, &rpm.spec));
Expand Down Expand Up @@ -113,15 +124,15 @@ pub fn update(
}

#[instrument]
pub fn run_scripts(scripts: &[String], labels: BTreeMap<String, String>) -> Result<()> {
pub fn run_scripts(scripts: &[String], labels: Vec<(String, String)>) -> Result<()> {
let mut handlers = vec![];
for scr in scripts {
trace!(scr, "Th start");
let labels = labels.clone();
handlers.push(Builder::new().name(scr.to_owned()).spawn(move || {
let th = thread::current();
let name = th.name().expect("No name for andax thread??");
run(name, &std::path::PathBuf::from(name), labels, |_| {});
run(name, &std::path::PathBuf::from(name), labels.into_iter(), |_| {});
})?);
}

Expand Down Expand Up @@ -152,10 +163,10 @@ mod tests {
let lbls = std::iter::once(("nightly", "1")).map(|(l, r)| (l.into(), r.into())).collect();
let mut test1 = filter_project(&lbls);
for (k, v) in transform(&[("nightly", "0"), ("hai", "bai"), ("large", "1")]) {
assert!(test1((&k, &v)));
assert!(test1(&(k, v)));
}
for (k, v) in transform(&[("nightly", "1")]) {
assert!(!test1((&k, &v)));
assert!(!test1(&(k, v)));
}
}
}

0 comments on commit 3c4878e

Please sign in to comment.