use {
crate::{compiler::Compiler, config::Config, error::Error, loader::Loader},
std::{collections::BTreeMap, io, path::Path},
};
mod full {
pub(crate) use crate::{
assignment::Assignment, condition::Condition, conditional_operator::ConditionalOperator,
dependency::Dependency, expression::Expression, fragment::Fragment, justfile::Justfile,
line::Line, parameter::Parameter, parameter_kind::ParameterKind, recipe::Recipe,
};
}
pub fn summary(path: &Path) -> io::Result<Result<Summary, String>> {
let loader = Loader::new();
match Compiler::compile(&Config::new().unwrap(), &loader, path) {
Ok(compilation) => Ok(Ok(Summary::new(&compilation.justfile))),
Err(error) => Ok(Err(if let Error::Compile { compile_error } = error {
compile_error.to_string()
} else {
format!("{error:?}")
})),
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub struct Summary {
pub assignments: BTreeMap<String, Assignment>,
pub recipes: BTreeMap<String, Recipe>,
}
impl Summary {
fn new(justfile: &full::Justfile) -> Self {
let mut aliases = BTreeMap::new();
for alias in justfile.aliases.values() {
aliases
.entry(alias.target.name())
.or_insert_with(Vec::new)
.push(alias.name.to_string());
}
Self {
recipes: justfile
.recipes
.iter()
.map(|(name, recipe)| {
(
(*name).to_string(),
Recipe::new(recipe, aliases.remove(name).unwrap_or_default()),
)
})
.collect(),
assignments: justfile
.assignments
.iter()
.map(|(name, assignment)| ((*name).to_owned(), Assignment::new(assignment)))
.collect(),
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub struct Recipe {
pub aliases: Vec<String>,
pub dependencies: Vec<Dependency>,
pub lines: Vec<Line>,
pub parameters: Vec<Parameter>,
pub private: bool,
pub quiet: bool,
pub shebang: bool,
}
impl Recipe {
fn new(recipe: &full::Recipe, aliases: Vec<String>) -> Self {
Self {
aliases,
dependencies: recipe.dependencies.iter().map(Dependency::new).collect(),
lines: recipe.body.iter().map(Line::new).collect(),
parameters: recipe.parameters.iter().map(Parameter::new).collect(),
private: recipe.private,
quiet: recipe.quiet,
shebang: recipe.shebang,
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub struct Parameter {
pub default: Option<Expression>,
pub kind: ParameterKind,
pub name: String,
}
impl Parameter {
fn new(parameter: &full::Parameter) -> Self {
Self {
kind: ParameterKind::new(parameter.kind),
name: parameter.name.lexeme().to_owned(),
default: parameter.default.as_ref().map(Expression::new),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum ParameterKind {
Plus,
Singular,
Star,
}
impl ParameterKind {
fn new(parameter_kind: full::ParameterKind) -> Self {
match parameter_kind {
full::ParameterKind::Singular => Self::Singular,
full::ParameterKind::Plus => Self::Plus,
full::ParameterKind::Star => Self::Star,
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub struct Line {
pub fragments: Vec<Fragment>,
}
impl Line {
fn new(line: &full::Line) -> Self {
Self {
fragments: line.fragments.iter().map(Fragment::new).collect(),
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub enum Fragment {
Expression { expression: Expression },
Text { text: String },
}
impl Fragment {
fn new(fragment: &full::Fragment) -> Self {
match fragment {
full::Fragment::Text { token } => Self::Text {
text: token.lexeme().to_owned(),
},
full::Fragment::Interpolation { expression } => Self::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(assignment: &full::Assignment) -> Self {
Self {
exported: assignment.export,
expression: Expression::new(&assignment.value),
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub enum Expression {
And {
lhs: Box<Self>,
rhs: Box<Self>,
},
Assert {
condition: Condition,
error: Box<Self>,
},
Backtick {
command: String,
},
Call {
name: String,
arguments: Vec<Self>,
},
Concatenation {
lhs: Box<Self>,
rhs: Box<Self>,
},
Conditional {
lhs: Box<Self>,
rhs: Box<Self>,
then: Box<Self>,
otherwise: Box<Self>,
operator: ConditionalOperator,
},
FormatString {
start: String,
expressions: Vec<(Self, String)>,
},
Join {
lhs: Option<Box<Self>>,
rhs: Box<Self>,
},
Or {
lhs: Box<Self>,
rhs: Box<Self>,
},
String {
text: String,
},
Variable {
name: String,
},
}
impl Expression {
fn new(expression: &full::Expression) -> Self {
use full::Expression::*;
match expression {
And { lhs, rhs } => Self::And {
lhs: Self::new(lhs).into(),
rhs: Self::new(rhs).into(),
},
Assert {
condition: full::Condition { lhs, rhs, operator },
error,
..
} => Self::Assert {
condition: Condition {
lhs: Box::new(Self::new(lhs)),
rhs: Box::new(Self::new(rhs)),
operator: ConditionalOperator::new(*operator),
},
error: Box::new(Self::new(error)),
},
Backtick { contents, .. } => Self::Backtick {
command: (*contents).clone(),
},
Call { name, arguments } => Self::Call {
name: name.lexeme().to_owned(),
arguments: arguments.iter().map(Self::new).collect(),
},
Concatenation { lhs, rhs } => Self::Concatenation {
lhs: Self::new(lhs).into(),
rhs: Self::new(rhs).into(),
},
Conditional {
condition: full::Condition { lhs, rhs, operator },
otherwise,
then,
} => Self::Conditional {
lhs: Self::new(lhs).into(),
operator: ConditionalOperator::new(*operator),
otherwise: Self::new(otherwise).into(),
rhs: Self::new(rhs).into(),
then: Self::new(then).into(),
},
FormatString { start, expressions } => Self::FormatString {
start: start.cooked.clone(),
expressions: expressions
.iter()
.map(|(expression, string)| (Self::new(expression), string.cooked.clone()))
.collect(),
},
Group { contents } => Self::new(contents),
Join { lhs, rhs } => Self::Join {
lhs: lhs.as_ref().map(|lhs| Self::new(lhs).into()),
rhs: Self::new(rhs).into(),
},
Or { lhs, rhs } => Self::Or {
lhs: Self::new(lhs).into(),
rhs: Self::new(rhs).into(),
},
StringLiteral { string_literal } => Self::String {
text: string_literal.cooked.clone(),
},
Variable { name, .. } => Self::Variable {
name: name.lexeme().to_owned(),
},
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub struct Condition {
lhs: Box<Expression>,
operator: ConditionalOperator,
rhs: Box<Expression>,
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub enum ConditionalOperator {
Equality,
Inequality,
RegexMatch,
RegexMismatch,
}
impl ConditionalOperator {
fn new(operator: full::ConditionalOperator) -> Self {
match operator {
full::ConditionalOperator::Equality => Self::Equality,
full::ConditionalOperator::Inequality => Self::Inequality,
full::ConditionalOperator::RegexMatch => Self::RegexMatch,
full::ConditionalOperator::RegexMismatch => Self::RegexMismatch,
}
}
}
#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Debug, Clone)]
pub struct Dependency {
pub arguments: Vec<Expression>,
pub recipe: String,
}
impl Dependency {
fn new(dependency: &full::Dependency) -> Self {
let mut arguments = Vec::new();
for group in &dependency.arguments {
for argument in group {
arguments.push(Expression::new(argument));
}
}
Self {
recipe: dependency.recipe.name().to_owned(),
arguments,
}
}
}