My personal project and infrastructure archive
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nomicon/development/tools/cargo-workspace2/src/query/mod.rs

280 lines
8.7 KiB

//! 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();
}