diff options
Diffstat (limited to 'development/tools/cargo-workspace2/src/query')
-rw-r--r-- | development/tools/cargo-workspace2/src/query/executor.rs | 83 | ||||
-rw-r--r-- | development/tools/cargo-workspace2/src/query/mod.rs | 280 |
2 files changed, 363 insertions, 0 deletions
diff --git a/development/tools/cargo-workspace2/src/query/executor.rs b/development/tools/cargo-workspace2/src/query/executor.rs new file mode 100644 index 000000000000..da67324de4c0 --- /dev/null +++ b/development/tools/cargo-workspace2/src/query/executor.rs @@ -0,0 +1,83 @@ +use super::{Constraint, DepConstraint}; +use crate::models::{CrateId, DepGraph}; +use std::collections::BTreeSet; + +/// Execute a simple set query +pub(crate) fn set(crates: &Vec<String>, g: &DepGraph) -> Vec<CrateId> { + crates + .iter() + .filter_map(|name| match g.find_crate(name) { + None => { + eprintln!("[ERROR]: Unable to find crate: `{}`", name); + None + } + some => some, + }) + .collect() +} + +/// Execute a search query on the dependency graph +pub(crate) fn deps(mut deps: Vec<DepConstraint>, g: &DepGraph) -> Vec<CrateId> { + // Parse the anchor point (first crate) + let DepConstraint { + ref _crate, + ref constraint, + } = deps.remove(0); + let init_id = get_crate_error(_crate, g); + + // Get it's dependents + let mut dependents: BTreeSet<_> = match constraint { + Constraint::Initial(true) => g.get_dependents(init_id).into_iter().collect(), + Constraint::Initial(false) => g + .get_all() + .iter() + .filter(|c| c.has_dependency(init_id)) + .map(|c| c.id) + .collect(), + _ => { + eprintln!("Invalid initial constraint! Only `<` and `!<` are allowed!"); + std::process::exit(2); + } + }; + + // Then loop over all other constraints and subtract crates from + // the dependents set until all constraints are met. + deps.reverse(); + while let Some(dc) = deps.pop() { + let DepConstraint { + ref _crate, + ref constraint, + } = dc; + + let id = get_crate_error(_crate, g); + let ldeps = g.get_dependents(id); + dependents = apply_constraint(dependents, ldeps, constraint); + } + + dependents.into_iter().collect() +} + +fn get_crate_error(_crate: &String, g: &DepGraph) -> CrateId { + match g.find_crate(&_crate.trim().to_string()) { + Some(id) => id, + None => { + eprintln!("[ERROR]: Crate `{}` not found in workspace!", _crate); + std::process::exit(2); + } + } +} + +fn apply_constraint( + init: BTreeSet<CrateId>, + cmp: Vec<CrateId>, + cnd: &Constraint, +) -> BTreeSet<CrateId> { + let cmp: BTreeSet<CrateId> = cmp.into_iter().collect(); + let init = init.into_iter(); + match cnd { + Constraint::And(true) => init.filter(|id| cmp.contains(id)).collect(), + Constraint::And(false) => init.filter(|id| !cmp.contains(id)).collect(), + Constraint::Or => init.chain(cmp.into_iter()).collect(), + _ => todo!(), + } +} diff --git a/development/tools/cargo-workspace2/src/query/mod.rs b/development/tools/cargo-workspace2/src/query/mod.rs new file mode 100644 index 000000000000..a888f6571663 --- /dev/null +++ b/development/tools/cargo-workspace2/src/query/mod.rs @@ -0,0 +1,280 @@ +//! Parse the query language for the CLI and other tools +//! +//! The `cargo-ws2` query language (`ws2ql`) allows users to specify a +//! set of inputs, and an operation to execute on it. +//! +//! ## Basic rules +//! +//! * Inside `[]` are sets (meaning items de-dup) that don't require +//! reparations +//! * IF `[]` contains a `/` anywhere _but_ the beginning AND end, +//! query becomes a path glob +//! * IF `[]` contains `/` at start and end, query becomes a regex +//! * An operation is parsed in the order of the fields in it's struct +//! (so publish order is `type mod devel`, etc) +//! * Inside `{}` you can create dependency maps with arrows. +//! * `{ foo < }` represents all crates that depend on `foo` +//! * `{ foo < bar < }` represents all crates that depend on `foo` AND `bar` +//! * `{ foo < bar |< }` represents all crates that depend on `foo` but NOT on `bar` +//! +//! ... etc. + +use crate::models::{CrateId, DepGraph}; + +mod executor; + +/// A query on the dependency graph +#[derive(Debug)] +pub enum Query { + /// Simple set of names `[ a b c ]` + Set(Vec<String>), + /// Simple path glob query `./foo/*` + Path(String), + /// Regular expression done on paths in tree `/foo\$/` + Regex(String), + /// A dependency graph query (see documentation for rules) + DepGraph(Vec<DepConstraint>), +} + +impl Query { + /// Parse an argument iterator (provided by `std::env::args`) + pub fn parse<'a>(line: impl Iterator<Item = String>) -> (Self, Vec<String>) { + let parser = QueryParser::new(line.skip(1).collect()); + match parser.run() { + Some((q, rest)) => (q, rest), + None => { + eprintln!("Failed to parse query!"); + std::process::exit(2); + } + } + } + + /// Execute a query with a dependency graph + pub fn execute(self, g: &DepGraph) -> Vec<CrateId> { + match self { + Self::Set(ref crates) => executor::set(crates, g), + Self::DepGraph(deps) => executor::deps(deps, g), + _ => todo!(), + } + } +} + +/// Express a dependency constraint +#[derive(Debug)] +pub struct DepConstraint { + pub _crate: String, + pub constraint: Constraint, +} + +/// All constraints can be negated +#[derive(Debug)] +pub enum Constraint { + Initial(bool), + And(bool), + Or, +} + +struct QueryParser { + line: Vec<String>, +} + +impl QueryParser { + fn new(line: Vec<String>) -> Self { + Self { line } + } + + /// Run the parser until it yields an error or finished query + fn run(mut self) -> Option<(Query, Vec<String>)> { + let line: Vec<String> = + std::mem::replace(&mut self.line, vec![]) + .into_iter() + .fold(vec![], |mut vec, line| { + line.split(" ").for_each(|seg| { + vec.push(seg.into()); + }); + vec + }); + + // This is here to get around infinately sized enums + #[derive(Debug)] + enum Brace { + Block, + Curly, + } + + // Track the state of the query braces + #[derive(Debug)] + enum BraceState { + Missing, + BlockOpen, + CurlyOpen, + Done(Brace), + } + use {Brace::*, BraceState::*}; + let mut bs = Missing; + let mut buf = vec![]; + let mut cbuf = String::new(); // Store a single crate name as a buffer + let mut skip = 1; + + // Parse the crate set + for elem in &line { + match (&bs, elem.as_str()) { + // The segment starts with a [ + (Missing, e) if e.starts_with("[") => { + bs = BlockOpen; + // Handle the case where we need to grab the crate from this segment + if let Some(_crate) = e.strip_prefix("[") { + if _crate != "" { + buf.push(_crate.to_string()); + } + } + } + // The segment starts with a { + (Missing, e) if e.starts_with("{") => { + bs = CurlyOpen; + + if let Some(_crate) = e.strip_prefix("{") { + if _crate != "" { + cbuf = _crate.into(); + } + } + } + (BlockOpen, e) if e.ends_with("]") => { + if let Some(_crate) = e.strip_suffix("]") { + if _crate != "" { + buf.push(_crate.to_string()); + } + } + + bs = Done(Block); + break; + } + (BlockOpen, _crate) => buf.push(_crate.to_string()), + (CurlyOpen, e) if e.ends_with("}") && cbuf == "" => { + bs = Done(Curly); + break; + } + (CurlyOpen, e) if e.ends_with("}") && cbuf != "" => { + eprintln!("[ERROR]: Out of place `}}`, expected operand!"); + std::process::exit(2); + } + (CurlyOpen, op) if cbuf != "" => { + buf.push(format!("{} $ {}", cbuf, op)); + cbuf = "".into(); + } + (CurlyOpen, _crate) => { + cbuf = _crate.into(); + } + (_, _) => {} + } + skip += 1; + } + + let rest = line.into_iter().skip(skip).collect(); + match bs { + Done(Block) => Some((Query::Set(buf), rest)), + Done(Curly) => { + let mut init = true; + + let c: Vec<_> = buf + .into_iter() + .map(|val| { + let mut s: Vec<_> = val.split("$").collect(); + let _crate = s.remove(0).trim().to_string(); + let c = s.remove(0).trim().to_string(); + + DepConstraint { + _crate, + constraint: match c.as_str() { + "<" if init => { + init = false; + Constraint::Initial(true) + } + "!<" if init => { + init = false; + Constraint::Initial(false) + } + "&<" => Constraint::And(true), + "!&<" => Constraint::And(false), + "|<" => Constraint::Or, + c => { + eprintln!("[ERROR]: Invalid constraint: `{}`", c); + std::process::exit(2); + } + }, + } + }) + .collect(); + + if c.len() < 1 { + eprintln!("[ERROR]: Provided an empty graph set: {{ }}. At least one dependency required!"); + std::process::exit(2); + } + + Some((Query::DepGraph(c), rest)) + } + _ if rest.len() < 1 => crate::cli::render_help(2), + _line => { + eprintln!("[ERROR]: You reached some unimplemented code in cargo-ws2! \ + This might be a bug, or it might be a missing feature. Contact me with your query, \ + and we can see which one it is :)"); + std::process::exit(2); + } + } + } +} + +#[test] +fn block_parser_spaced() { + let _ = QueryParser::new( + vec!["", "[", "foo", "bar", "baz", "]", "publish", "minor"] + .into_iter() + .map(Into::into) + .collect(), + ) + .run(); +} + +#[test] +fn block_parser_offset_front() { + let _ = QueryParser::new( + vec!["my-program", "[foo", "bar", "baz", "]", "publish", "minor"] + .into_iter() + .map(Into::into) + .collect(), + ) + .run(); +} + +#[test] +fn block_parser_offset_back() { + let _ = QueryParser::new( + vec!["my-program", "[", "foo", "bar", "baz]", "publish", "minor"] + .into_iter() + .map(Into::into) + .collect(), + ) + .run(); +} + +#[test] +fn block_parser_offset_both() { + let _ = QueryParser::new( + vec!["my-program", "[foo", "bar", "baz]", "publish", "minor"] + .into_iter() + .map(Into::into) + .collect(), + ) + .run(); +} + +#[test] +fn curly_parser_simple() { + let _ = QueryParser::new( + vec!["my-program", "{ foo < bar &< }", "print"] + .into_iter() + .map(Into::into) + .collect(), + ) + .run(); +} |