diff options
author | Mx Kookie <kookie@spacekookie.de> | 2020-10-31 19:06:35 +0100 |
---|---|---|
committer | Mx Kookie <kookie@spacekookie.de> | 2020-12-21 05:10:28 +0100 |
commit | 90a45a314f99c7b08f25a02efcee2a847685562b (patch) | |
tree | a3c15278adbda7d0a79dcc170340b501da01d847 /development/tools/cargo-workspace2/src/query/mod.rs | |
parent | f56286e4c490ea69163988742a90d4376a4db08e (diff) |
Add 'development/tools/cargo-workspace2/' from commit 'c3e1e0679bae7aa6bd668125cb997812a590f875'
git-subtree-dir: development/tools/cargo-workspace2
git-subtree-mainline: ddeaea72333d8bb6911ac45fcfe40ee1caa00b04
git-subtree-split: c3e1e0679bae7aa6bd668125cb997812a590f875
Diffstat (limited to 'development/tools/cargo-workspace2/src/query/mod.rs')
-rw-r--r-- | development/tools/cargo-workspace2/src/query/mod.rs | 280 |
1 files changed, 280 insertions, 0 deletions
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(); +} |