diff --git a/Cargo.lock b/Cargo.lock index 014a5dc..c91b16d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "env_logger" -version = "0.5.13" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -182,7 +182,7 @@ dependencies = [ [[package]] name = "itertools" -version = "0.7.11" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -200,9 +200,9 @@ dependencies = [ "ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "executable-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -504,14 +504,14 @@ dependencies = [ "checksum dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d0a1279c96732bc6800ce6337b6a614697b0e74ae058dc03c62ebeb78b4d86" "checksum edit-distance 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" -"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" +"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" "checksum executable-path 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ebc5a6d89e3c90b84e8f33c8737933dda8f1c106b5415900b38b9d433841478" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" -"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" diff --git a/Cargo.toml b/Cargo.toml index 8032905..d1a572a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,15 +9,15 @@ readme = "crates-io-readme.md" edition = "2018" [dependencies] -ansi_term = "0.11" +ansi_term = "0.11.0" assert_matches = "1.1.0" atty = "0.2.1" brev = "0.1.6" clap = "2.0.0" dotenv = "0.13.0" edit-distance = "2.0.0" -env_logger = "0.5.12" -itertools = "0.7" +env_logger = "0.6.1" +itertools = "0.8.0" lazy_static = "1.0.0" libc = "0.2.21" log = "0.4.4" diff --git a/src/lib.rs b/src/lib.rs index 2742f4b..4348141 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,3 +42,5 @@ mod token; mod verbosity; pub use crate::run::run; + +pub mod summary; diff --git a/src/summary.rs b/src/summary.rs new file mode 100644 index 0000000..263d0a4 --- /dev/null +++ b/src/summary.rs @@ -0,0 +1,194 @@ +//! Justfile summary creation, for testing purposes only. +//! +//! The contents of this module are not bound by any stability guarantees. +//! Breaking changes may be introduced at any time. +//! +//! The main entry point into this module is the `summary` function, which +//! parses a justfile at a given path and produces a `Summary` object, +//! which broadly captures the functionality of the parsed justfile, or +//! an error message. +//! +//! This functionality is intended to be used with `janus`, a tool for +//! ensuring that changes to just do not inadvertently break or +//! change the interpretation of existing justfiles. + +use std::{ + collections::{BTreeMap, BTreeSet}, + fs, io, + path::Path, +}; + +use crate::{expression, fragment, justfile::Justfile, parser::Parser, recipe}; + +pub fn summary(path: impl AsRef) -> Result, io::Error> { + let path = path.as_ref(); + + let text = fs::read_to_string(path)?; + + match Parser::parse(&text) { + Ok(justfile) => Ok(Ok(Summary::new(justfile))), + Err(compilation_error) => Ok(Err(compilation_error.to_string())), + } +} + +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)] +pub struct Summary { + pub assignments: BTreeMap, + pub recipes: BTreeMap, +} + +impl Summary { + fn new(justfile: Justfile) -> Summary { + let exports = justfile.exports; + + let mut aliases = BTreeMap::new(); + + for alias in justfile.aliases.values() { + aliases + .entry(alias.target) + .or_insert(Vec::new()) + .push(alias.name.to_string()); + } + + Summary { + recipes: justfile + .recipes + .into_iter() + .map(|(name, recipe)| { + ( + name.to_string(), + Recipe::new(recipe, aliases.remove(name).unwrap_or(Vec::new())), + ) + }) + .collect(), + assignments: justfile + .assignments + .into_iter() + .map(|(name, expression)| { + ( + name.to_string(), + Assignment::new(name, expression, &exports), + ) + }) + .collect(), + } + } +} + +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)] +pub struct Recipe { + pub aliases: Vec, + pub dependencies: BTreeSet, + pub lines: Vec, + pub private: bool, + pub quiet: bool, + pub shebang: bool, +} + +impl Recipe { + fn new(recipe: recipe::Recipe, aliases: Vec) -> Recipe { + Recipe { + private: recipe.private, + shebang: recipe.shebang, + quiet: recipe.quiet, + dependencies: recipe.dependencies.into_iter().map(str::to_owned).collect(), + lines: recipe.lines.into_iter().map(Line::new).collect(), + aliases, + } + } +} + +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)] +pub struct Line { + pub fragments: Vec, +} + +impl Line { + fn new(fragments: Vec) -> Line { + Line { + fragments: fragments.into_iter().map(Fragment::new).collect(), + } + } +} + +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)] +pub enum Fragment { + Text { text: String }, + Expression { expression: Expression }, +} + +impl Fragment { + fn new(fragment: fragment::Fragment) -> Fragment { + match fragment { + fragment::Fragment::Text { text } => Fragment::Text { + text: text.lexeme.to_owned(), + }, + fragment::Fragment::Expression { expression } => Fragment::Expression { + expression: Expression::new(expression), + }, + } + } +} + +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)] +pub struct Assignment { + pub exported: bool, + pub expression: Expression, +} + +impl Assignment { + fn new(name: &str, expression: expression::Expression, exports: &BTreeSet<&str>) -> Assignment { + Assignment { + exported: exports.contains(name), + expression: Expression::new(expression), + } + } +} + +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)] +pub enum Expression { + Backtick { + command: String, + }, + Call { + name: String, + arguments: Vec, + }, + Concatination { + lhs: Box, + rhs: Box, + }, + String { + text: String, + }, + Variable { + name: String, + }, +} + +impl Expression { + fn new(expression: expression::Expression) -> Expression { + use expression::Expression::*; + match expression { + Backtick { raw, .. } => Expression::Backtick { + command: raw.to_owned(), + }, + Call { + name, arguments, .. + } => Expression::Call { + name: name.to_owned(), + arguments: arguments.into_iter().map(Expression::new).collect(), + }, + Concatination { lhs, rhs } => Expression::Concatination { + lhs: Box::new(Expression::new(*lhs)), + rhs: Box::new(Expression::new(*rhs)), + }, + String { cooked_string } => Expression::String { + text: cooked_string.cooked, + }, + Variable { name, .. } => Expression::Variable { + name: name.to_owned(), + }, + } + } +}