Don't conflate recipes with the same name in different modules (#1825)
This commit is contained in:
parent
0dbd5bf0b6
commit
1ea5e6ac31
@ -47,7 +47,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
(*original, name)
|
(*original, name)
|
||||||
};
|
};
|
||||||
|
|
||||||
return Err(redefinition.token().error(Redefinition {
|
return Err(redefinition.token.error(Redefinition {
|
||||||
first_type,
|
first_type,
|
||||||
second_type,
|
second_type,
|
||||||
name: name.lexeme(),
|
name: name.lexeme(),
|
||||||
@ -83,7 +83,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
if let Some(absolute) = absolute {
|
if let Some(absolute) = absolute {
|
||||||
define(*name, "module", false)?;
|
define(*name, "module", false)?;
|
||||||
modules.insert(
|
modules.insert(
|
||||||
name.to_string(),
|
name.lexeme().into(),
|
||||||
(*name, Self::analyze(loaded, paths, asts, absolute)?),
|
(*name, Self::analyze(loaded, paths, asts, absolute)?),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -160,7 +160,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
|
|
||||||
for parameter in &recipe.parameters {
|
for parameter in &recipe.parameters {
|
||||||
if parameters.contains(parameter.name.lexeme()) {
|
if parameters.contains(parameter.name.lexeme()) {
|
||||||
return Err(parameter.name.token().error(DuplicateParameter {
|
return Err(parameter.name.token.error(DuplicateParameter {
|
||||||
recipe: recipe.name.lexeme(),
|
recipe: recipe.name.lexeme(),
|
||||||
parameter: parameter.name.lexeme(),
|
parameter: parameter.name.lexeme(),
|
||||||
}));
|
}));
|
||||||
@ -173,7 +173,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
return Err(
|
return Err(
|
||||||
parameter
|
parameter
|
||||||
.name
|
.name
|
||||||
.token()
|
.token
|
||||||
.error(RequiredParameterFollowsDefaultParameter {
|
.error(RequiredParameterFollowsDefaultParameter {
|
||||||
parameter: parameter.name.lexeme(),
|
parameter: parameter.name.lexeme(),
|
||||||
}),
|
}),
|
||||||
@ -201,7 +201,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
|
|
||||||
fn analyze_assignment(&self, assignment: &Assignment<'src>) -> CompileResult<'src> {
|
fn analyze_assignment(&self, assignment: &Assignment<'src>) -> CompileResult<'src> {
|
||||||
if self.assignments.contains_key(assignment.name.lexeme()) {
|
if self.assignments.contains_key(assignment.name.lexeme()) {
|
||||||
return Err(assignment.name.token().error(DuplicateVariable {
|
return Err(assignment.name.token.error(DuplicateVariable {
|
||||||
variable: assignment.name.lexeme(),
|
variable: assignment.name.lexeme(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
|
|
||||||
for attr in &alias.attributes {
|
for attr in &alias.attributes {
|
||||||
if *attr != Attribute::Private {
|
if *attr != Attribute::Private {
|
||||||
return Err(alias.name.token().error(AliasInvalidAttribute {
|
return Err(alias.name.token.error(AliasInvalidAttribute {
|
||||||
alias: name,
|
alias: name,
|
||||||
attr: *attr,
|
attr: *attr,
|
||||||
}));
|
}));
|
||||||
@ -238,10 +238,9 @@ impl<'src> Analyzer<'src> {
|
|||||||
recipes: &Table<'src, Rc<Recipe<'src>>>,
|
recipes: &Table<'src, Rc<Recipe<'src>>>,
|
||||||
alias: Alias<'src, Name<'src>>,
|
alias: Alias<'src, Name<'src>>,
|
||||||
) -> CompileResult<'src, Alias<'src>> {
|
) -> CompileResult<'src, Alias<'src>> {
|
||||||
let token = alias.name.token();
|
|
||||||
// Make sure the alias doesn't conflict with any recipe
|
// Make sure the alias doesn't conflict with any recipe
|
||||||
if let Some(recipe) = recipes.get(alias.name.lexeme()) {
|
if let Some(recipe) = recipes.get(alias.name.lexeme()) {
|
||||||
return Err(token.error(AliasShadowsRecipe {
|
return Err(alias.name.token.error(AliasShadowsRecipe {
|
||||||
alias: alias.name.lexeme(),
|
alias: alias.name.lexeme(),
|
||||||
recipe_line: recipe.line_number(),
|
recipe_line: recipe.line_number(),
|
||||||
}));
|
}));
|
||||||
@ -250,7 +249,7 @@ impl<'src> Analyzer<'src> {
|
|||||||
// Make sure the target recipe exists
|
// Make sure the target recipe exists
|
||||||
match recipes.get(alias.target.lexeme()) {
|
match recipes.get(alias.target.lexeme()) {
|
||||||
Some(target) => Ok(alias.resolve(Rc::clone(target))),
|
Some(target) => Ok(alias.resolve(Rc::clone(target))),
|
||||||
None => Err(token.error(UnknownAliasTarget {
|
None => Err(alias.name.token.error(UnknownAliasTarget {
|
||||||
alias: alias.name.lexeme(),
|
alias: alias.name.lexeme(),
|
||||||
target: alias.target.lexeme(),
|
target: alias.target.lexeme(),
|
||||||
})),
|
})),
|
||||||
|
@ -59,16 +59,19 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
|
|||||||
if self.evaluated.contains(variable) {
|
if self.evaluated.contains(variable) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if self.stack.contains(&variable) {
|
} else if self.stack.contains(&variable) {
|
||||||
let token = self.assignments[variable].name.token();
|
|
||||||
self.stack.push(variable);
|
self.stack.push(variable);
|
||||||
Err(token.error(CircularVariableDependency {
|
Err(
|
||||||
|
self.assignments[variable]
|
||||||
|
.name
|
||||||
|
.error(CircularVariableDependency {
|
||||||
variable,
|
variable,
|
||||||
circle: self.stack.clone(),
|
circle: self.stack.clone(),
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
} else if self.assignments.contains_key(variable) {
|
} else if self.assignments.contains_key(variable) {
|
||||||
self.resolve_assignment(variable)
|
self.resolve_assignment(variable)
|
||||||
} else {
|
} else {
|
||||||
Err(name.token().error(UndefinedVariable { variable }))
|
Err(name.token.error(UndefinedVariable { variable }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Call { thunk } => match thunk {
|
Expression::Call { thunk } => match thunk {
|
||||||
|
@ -13,17 +13,17 @@ impl Compiler {
|
|||||||
let mut srcs: HashMap<PathBuf, &str> = HashMap::new();
|
let mut srcs: HashMap<PathBuf, &str> = HashMap::new();
|
||||||
let mut loaded = Vec::new();
|
let mut loaded = Vec::new();
|
||||||
|
|
||||||
let mut stack: Vec<(PathBuf, u32)> = Vec::new();
|
let mut stack = Vec::new();
|
||||||
stack.push((root.into(), 0));
|
stack.push(Source::root(root));
|
||||||
|
|
||||||
while let Some((current, depth)) = stack.pop() {
|
while let Some(current) = stack.pop() {
|
||||||
let (relative, src) = loader.load(root, ¤t)?;
|
let (relative, src) = loader.load(root, ¤t.path)?;
|
||||||
loaded.push(relative.into());
|
loaded.push(relative.into());
|
||||||
let tokens = Lexer::lex(relative, src)?;
|
let tokens = Lexer::lex(relative, src)?;
|
||||||
let mut ast = Parser::parse(depth, ¤t, &tokens)?;
|
let mut ast = Parser::parse(¤t.path, ¤t.namepath, current.depth, &tokens)?;
|
||||||
|
|
||||||
paths.insert(current.clone(), relative.into());
|
paths.insert(current.path.clone(), relative.into());
|
||||||
srcs.insert(current.clone(), src);
|
srcs.insert(current.path.clone(), src);
|
||||||
|
|
||||||
for item in &mut ast.items {
|
for item in &mut ast.items {
|
||||||
match item {
|
match item {
|
||||||
@ -39,7 +39,7 @@ impl Compiler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let parent = current.parent().unwrap();
|
let parent = current.path.parent().unwrap();
|
||||||
|
|
||||||
let import = if let Some(relative) = relative {
|
let import = if let Some(relative) = relative {
|
||||||
let path = parent.join(Self::expand_tilde(&relative.cooked)?);
|
let path = parent.join(Self::expand_tilde(&relative.cooked)?);
|
||||||
@ -55,10 +55,13 @@ impl Compiler {
|
|||||||
|
|
||||||
if let Some(import) = import {
|
if let Some(import) = import {
|
||||||
if srcs.contains_key(&import) {
|
if srcs.contains_key(&import) {
|
||||||
return Err(Error::CircularImport { current, import });
|
return Err(Error::CircularImport {
|
||||||
|
current: current.path,
|
||||||
|
import,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
*absolute = Some(import.clone());
|
*absolute = Some(import.clone());
|
||||||
stack.push((import, depth + 1));
|
stack.push(current.module(*name, import));
|
||||||
} else if !*optional {
|
} else if !*optional {
|
||||||
return Err(Error::MissingModuleFile { module: *name });
|
return Err(Error::MissingModuleFile { module: *name });
|
||||||
}
|
}
|
||||||
@ -70,6 +73,7 @@ impl Compiler {
|
|||||||
path,
|
path,
|
||||||
} => {
|
} => {
|
||||||
let import = current
|
let import = current
|
||||||
|
.path
|
||||||
.parent()
|
.parent()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join(Self::expand_tilde(&relative.cooked)?)
|
.join(Self::expand_tilde(&relative.cooked)?)
|
||||||
@ -77,10 +81,13 @@ impl Compiler {
|
|||||||
|
|
||||||
if import.is_file() {
|
if import.is_file() {
|
||||||
if srcs.contains_key(&import) {
|
if srcs.contains_key(&import) {
|
||||||
return Err(Error::CircularImport { current, import });
|
return Err(Error::CircularImport {
|
||||||
|
current: current.path,
|
||||||
|
import,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
*absolute = Some(import.clone());
|
*absolute = Some(import.clone());
|
||||||
stack.push((import, depth + 1));
|
stack.push(current.import(import));
|
||||||
} else if !*optional {
|
} else if !*optional {
|
||||||
return Err(Error::MissingImportFile { path: *path });
|
return Err(Error::MissingImportFile { path: *path });
|
||||||
}
|
}
|
||||||
@ -89,7 +96,7 @@ impl Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
asts.insert(current.clone(), ast.clone());
|
asts.insert(current.path, ast.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let justfile = Analyzer::analyze(&loaded, &paths, &asts, root)?;
|
let justfile = Analyzer::analyze(&loaded, &paths, &asts, root)?;
|
||||||
@ -155,7 +162,7 @@ impl Compiler {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
|
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
|
||||||
let tokens = Lexer::test_lex(src)?;
|
let tokens = Lexer::test_lex(src)?;
|
||||||
let ast = Parser::parse(0, &PathBuf::new(), &tokens)?;
|
let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)?;
|
||||||
let root = PathBuf::from("justfile");
|
let root = PathBuf::from("justfile");
|
||||||
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
||||||
asts.insert(root.clone(), ast);
|
asts.insert(root.clone(), ast);
|
||||||
|
@ -179,11 +179,11 @@ impl<'src> Error<'src> {
|
|||||||
fn context(&self) -> Option<Token<'src>> {
|
fn context(&self) -> Option<Token<'src>> {
|
||||||
match self {
|
match self {
|
||||||
Self::AmbiguousModuleFile { module, .. } | Self::MissingModuleFile { module, .. } => {
|
Self::AmbiguousModuleFile { module, .. } | Self::MissingModuleFile { module, .. } => {
|
||||||
Some(module.token())
|
Some(module.token)
|
||||||
}
|
}
|
||||||
Self::Backtick { token, .. } => Some(*token),
|
Self::Backtick { token, .. } => Some(*token),
|
||||||
Self::Compile { compile_error } => Some(compile_error.context()),
|
Self::Compile { compile_error } => Some(compile_error.context()),
|
||||||
Self::FunctionCall { function, .. } => Some(function.token()),
|
Self::FunctionCall { function, .. } => Some(function.token),
|
||||||
Self::MissingImportFile { path } => Some(*path),
|
Self::MissingImportFile { path } => Some(*path),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
|||||||
config: &'run Config,
|
config: &'run Config,
|
||||||
dotenv: &'run BTreeMap<String, String>,
|
dotenv: &'run BTreeMap<String, String>,
|
||||||
parameters: &[Parameter<'src>],
|
parameters: &[Parameter<'src>],
|
||||||
arguments: &[&str],
|
arguments: &[String],
|
||||||
scope: &'run Scope<'src, 'run>,
|
scope: &'run Scope<'src, 'run>,
|
||||||
settings: &'run Settings,
|
settings: &'run Settings,
|
||||||
search: &'run Search,
|
search: &'run Search,
|
||||||
@ -289,13 +289,13 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
|||||||
}
|
}
|
||||||
} else if parameter.kind.is_variadic() {
|
} else if parameter.kind.is_variadic() {
|
||||||
for value in rest {
|
for value in rest {
|
||||||
positional.push((*value).to_owned());
|
positional.push(value.clone());
|
||||||
}
|
}
|
||||||
let value = rest.to_vec().join(" ");
|
let value = rest.to_vec().join(" ");
|
||||||
rest = &[];
|
rest = &[];
|
||||||
value
|
value
|
||||||
} else {
|
} else {
|
||||||
let value = rest[0].to_owned();
|
let value = rest[0].clone();
|
||||||
positional.push(value.clone());
|
positional.push(value.clone());
|
||||||
rest = &rest[1..];
|
rest = &rest[1..];
|
||||||
value
|
value
|
||||||
|
@ -271,7 +271,7 @@ impl<'src> Justfile<'src> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ran = BTreeSet::new();
|
let mut ran = Ran::default();
|
||||||
for invocation in invocations {
|
for invocation in invocations {
|
||||||
let context = RecipeContext {
|
let context = RecipeContext {
|
||||||
settings: invocation.settings,
|
settings: invocation.settings,
|
||||||
@ -283,7 +283,12 @@ impl<'src> Justfile<'src> {
|
|||||||
Self::run_recipe(
|
Self::run_recipe(
|
||||||
&context,
|
&context,
|
||||||
invocation.recipe,
|
invocation.recipe,
|
||||||
&invocation.arguments,
|
&invocation
|
||||||
|
.arguments
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
&dotenv,
|
&dotenv,
|
||||||
search,
|
search,
|
||||||
&mut ran,
|
&mut ran,
|
||||||
@ -399,17 +404,12 @@ impl<'src> Justfile<'src> {
|
|||||||
fn run_recipe(
|
fn run_recipe(
|
||||||
context: &RecipeContext<'src, '_>,
|
context: &RecipeContext<'src, '_>,
|
||||||
recipe: &Recipe<'src>,
|
recipe: &Recipe<'src>,
|
||||||
arguments: &[&str],
|
arguments: &[String],
|
||||||
dotenv: &BTreeMap<String, String>,
|
dotenv: &BTreeMap<String, String>,
|
||||||
search: &Search,
|
search: &Search,
|
||||||
ran: &mut BTreeSet<Vec<String>>,
|
ran: &mut Ran<'src>,
|
||||||
) -> RunResult<'src> {
|
) -> RunResult<'src> {
|
||||||
let mut invocation = vec![recipe.name().to_owned()];
|
if ran.has_run(&recipe.namepath, arguments) {
|
||||||
for argument in arguments {
|
|
||||||
invocation.push((*argument).to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ran.contains(&invocation) {
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,20 +440,13 @@ impl<'src> Justfile<'src> {
|
|||||||
.map(|argument| evaluator.evaluate_expression(argument))
|
.map(|argument| evaluator.evaluate_expression(argument))
|
||||||
.collect::<RunResult<Vec<String>>>()?;
|
.collect::<RunResult<Vec<String>>>()?;
|
||||||
|
|
||||||
Self::run_recipe(
|
Self::run_recipe(context, recipe, &arguments, dotenv, search, ran)?;
|
||||||
context,
|
|
||||||
recipe,
|
|
||||||
&arguments.iter().map(String::as_ref).collect::<Vec<&str>>(),
|
|
||||||
dotenv,
|
|
||||||
search,
|
|
||||||
ran,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recipe.run(context, dotenv, scope.child(), search, &positional)?;
|
recipe.run(context, dotenv, scope.child(), search, &positional)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut ran = BTreeSet::new();
|
let mut ran = Ran::default();
|
||||||
|
|
||||||
for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) {
|
for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) {
|
||||||
let mut evaluated = Vec::new();
|
let mut evaluated = Vec::new();
|
||||||
@ -462,18 +455,11 @@ impl<'src> Justfile<'src> {
|
|||||||
evaluated.push(evaluator.evaluate_expression(argument)?);
|
evaluated.push(evaluator.evaluate_expression(argument)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::run_recipe(
|
Self::run_recipe(context, recipe, &evaluated, dotenv, search, &mut ran)?;
|
||||||
context,
|
|
||||||
recipe,
|
|
||||||
&evaluated.iter().map(String::as_ref).collect::<Vec<&str>>(),
|
|
||||||
dotenv,
|
|
||||||
search,
|
|
||||||
&mut ran,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ran.insert(invocation);
|
ran.ran(&recipe.namepath, arguments.to_vec());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
src/lib.rs
26
src/lib.rs
@ -25,17 +25,17 @@ pub(crate) use {
|
|||||||
fragment::Fragment, function::Function, function_context::FunctionContext,
|
fragment::Fragment, function::Function, function_context::FunctionContext,
|
||||||
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
|
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
|
||||||
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
|
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
|
||||||
load_dotenv::load_dotenv, loader::Loader, name::Name, ordinal::Ordinal, output::output,
|
load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal,
|
||||||
output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, parser::Parser,
|
output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
|
||||||
platform::Platform, platform_interface::PlatformInterface, position::Position,
|
parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
|
||||||
positional::Positional, range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext,
|
positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe,
|
||||||
recipe_resolver::RecipeResolver, scope::Scope, search::Search, search_config::SearchConfig,
|
recipe_context::RecipeContext, recipe_resolver::RecipeResolver, scope::Scope, search::Search,
|
||||||
search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang,
|
search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting,
|
||||||
shell::Shell, show_whitespace::ShowWhitespace, string_kind::StringKind,
|
settings::Settings, shebang::Shebang, shell::Shell, show_whitespace::ShowWhitespace,
|
||||||
string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
|
source::Source, string_kind::StringKind, string_literal::StringLiteral, subcommand::Subcommand,
|
||||||
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
|
suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind,
|
||||||
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
|
unresolved_dependency::UnresolvedDependency, unresolved_recipe::UnresolvedRecipe,
|
||||||
verbosity::Verbosity, warning::Warning,
|
use_color::UseColor, variables::Variables, verbosity::Verbosity, warning::Warning,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
cmp,
|
cmp,
|
||||||
@ -47,6 +47,7 @@ pub(crate) use {
|
|||||||
io::{self, Cursor, Write},
|
io::{self, Cursor, Write},
|
||||||
iter::{self, FromIterator},
|
iter::{self, FromIterator},
|
||||||
mem,
|
mem,
|
||||||
|
ops::Deref,
|
||||||
ops::{Index, Range, RangeInclusive},
|
ops::{Index, Range, RangeInclusive},
|
||||||
path::{self, Path, PathBuf},
|
path::{self, Path, PathBuf},
|
||||||
process::{self, Command, ExitStatus, Stdio},
|
process::{self, Command, ExitStatus, Stdio},
|
||||||
@ -149,6 +150,7 @@ mod list;
|
|||||||
mod load_dotenv;
|
mod load_dotenv;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod name;
|
mod name;
|
||||||
|
mod namepath;
|
||||||
mod ordinal;
|
mod ordinal;
|
||||||
mod output;
|
mod output;
|
||||||
mod output_error;
|
mod output_error;
|
||||||
@ -159,6 +161,7 @@ mod platform;
|
|||||||
mod platform_interface;
|
mod platform_interface;
|
||||||
mod position;
|
mod position;
|
||||||
mod positional;
|
mod positional;
|
||||||
|
mod ran;
|
||||||
mod range_ext;
|
mod range_ext;
|
||||||
mod recipe;
|
mod recipe;
|
||||||
mod recipe_context;
|
mod recipe_context;
|
||||||
@ -174,6 +177,7 @@ mod settings;
|
|||||||
mod shebang;
|
mod shebang;
|
||||||
mod shell;
|
mod shell;
|
||||||
mod show_whitespace;
|
mod show_whitespace;
|
||||||
|
mod source;
|
||||||
mod string_kind;
|
mod string_kind;
|
||||||
mod string_literal;
|
mod string_literal;
|
||||||
mod subcommand;
|
mod subcommand;
|
||||||
|
46
src/name.rs
46
src/name.rs
@ -1,50 +1,24 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// A name. This is effectively just a `Token` of kind `Identifier`, but we give
|
/// A name. This is just a `Token` of kind `Identifier`, but we give it its own
|
||||||
/// it its own type for clarity.
|
/// type for clarity.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
pub(crate) struct Name<'src> {
|
pub(crate) struct Name<'src> {
|
||||||
pub(crate) column: usize,
|
pub(crate) token: Token<'src>,
|
||||||
pub(crate) length: usize,
|
|
||||||
pub(crate) line: usize,
|
|
||||||
pub(crate) offset: usize,
|
|
||||||
pub(crate) path: &'src Path,
|
|
||||||
pub(crate) src: &'src str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src> Name<'src> {
|
impl<'src> Name<'src> {
|
||||||
/// The name's text contents
|
pub(crate) fn from_identifier(token: Token<'src>) -> Self {
|
||||||
pub(crate) fn lexeme(&self) -> &'src str {
|
|
||||||
&self.src[self.offset..self.offset + self.length]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turn this name back into a token
|
|
||||||
pub(crate) fn token(&self) -> Token<'src> {
|
|
||||||
Token {
|
|
||||||
column: self.column,
|
|
||||||
kind: TokenKind::Identifier,
|
|
||||||
length: self.length,
|
|
||||||
line: self.line,
|
|
||||||
offset: self.offset,
|
|
||||||
path: self.path,
|
|
||||||
src: self.src,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_identifier(token: Token<'src>) -> Name {
|
|
||||||
assert_eq!(token.kind, TokenKind::Identifier);
|
assert_eq!(token.kind, TokenKind::Identifier);
|
||||||
Name {
|
Self { token }
|
||||||
column: token.column,
|
|
||||||
length: token.length,
|
|
||||||
line: token.line,
|
|
||||||
offset: token.offset,
|
|
||||||
path: token.path,
|
|
||||||
src: token.src,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn error(&self, kind: CompileErrorKind<'src>) -> CompileError<'src> {
|
impl<'src> Deref for Name<'src> {
|
||||||
self.token().error(kind)
|
type Target = Token<'src>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/namepath.rs
Normal file
28
src/namepath.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub(crate) struct Namepath<'src>(Vec<Name<'src>>);
|
||||||
|
|
||||||
|
impl<'src> Namepath<'src> {
|
||||||
|
pub(crate) fn join(&self, name: Name<'src>) -> Self {
|
||||||
|
Self(self.0.iter().copied().chain(iter::once(name)).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'str> Serialize for Namepath<'str> {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut path = String::new();
|
||||||
|
|
||||||
|
for (i, name) in self.0.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
path.push_str("::");
|
||||||
|
}
|
||||||
|
path.push_str(name.lexeme());
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer.serialize_str(&path)
|
||||||
|
}
|
||||||
|
}
|
@ -23,34 +23,31 @@ use {super::*, TokenKind::*};
|
|||||||
/// find it, it adds that token to the set. When the parser accepts a token, the
|
/// find it, it adds that token to the set. When the parser accepts a token, the
|
||||||
/// set is cleared. If the parser finds a token which is unexpected, the
|
/// set is cleared. If the parser finds a token which is unexpected, the
|
||||||
/// contents of the set is printed in the resultant error message.
|
/// contents of the set is printed in the resultant error message.
|
||||||
pub(crate) struct Parser<'tokens, 'src> {
|
pub(crate) struct Parser<'run, 'src> {
|
||||||
/// Source tokens
|
expected_tokens: BTreeSet<TokenKind>,
|
||||||
tokens: &'tokens [Token<'src>],
|
file_path: &'run Path,
|
||||||
/// Index of the next un-parsed token
|
module_namepath: &'run Namepath<'src>,
|
||||||
next: usize,
|
next_token: usize,
|
||||||
/// Current expected tokens
|
recursion_depth: usize,
|
||||||
expected: BTreeSet<TokenKind>,
|
submodule_depth: u32,
|
||||||
/// Current recursion depth
|
tokens: &'run [Token<'src>],
|
||||||
depth: usize,
|
|
||||||
/// Path to the file being parsed
|
|
||||||
path: PathBuf,
|
|
||||||
/// Depth of submodule being parsed
|
|
||||||
submodule: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tokens, 'src> Parser<'tokens, 'src> {
|
impl<'run, 'src> Parser<'run, 'src> {
|
||||||
/// Parse `tokens` into an `Ast`
|
/// Parse `tokens` into an `Ast`
|
||||||
pub(crate) fn parse(
|
pub(crate) fn parse(
|
||||||
submodule: u32,
|
file_path: &'run Path,
|
||||||
path: &Path,
|
module_namepath: &'run Namepath<'src>,
|
||||||
tokens: &'tokens [Token<'src>],
|
submodule_depth: u32,
|
||||||
|
tokens: &'run [Token<'src>],
|
||||||
) -> CompileResult<'src, Ast<'src>> {
|
) -> CompileResult<'src, Ast<'src>> {
|
||||||
Parser {
|
Self {
|
||||||
depth: 0,
|
expected_tokens: BTreeSet::new(),
|
||||||
expected: BTreeSet::new(),
|
file_path,
|
||||||
next: 0,
|
module_namepath,
|
||||||
path: path.into(),
|
next_token: 0,
|
||||||
submodule,
|
recursion_depth: 0,
|
||||||
|
submodule_depth,
|
||||||
tokens,
|
tokens,
|
||||||
}
|
}
|
||||||
.parse_ast()
|
.parse_ast()
|
||||||
@ -65,7 +62,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
fn unexpected_token(&self) -> CompileResult<'src, CompileError<'src>> {
|
fn unexpected_token(&self) -> CompileResult<'src, CompileError<'src>> {
|
||||||
self.error(CompileErrorKind::UnexpectedToken {
|
self.error(CompileErrorKind::UnexpectedToken {
|
||||||
expected: self
|
expected: self
|
||||||
.expected
|
.expected_tokens
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|kind| *kind != ByteOrderMark)
|
.filter(|kind| *kind != ByteOrderMark)
|
||||||
@ -81,8 +78,8 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the remaining significant tokens
|
/// An iterator over the remaining significant tokens
|
||||||
fn rest(&self) -> impl Iterator<Item = Token<'src>> + 'tokens {
|
fn rest(&self) -> impl Iterator<Item = Token<'src>> + 'run {
|
||||||
self.tokens[self.next..]
|
self.tokens[self.next_token..]
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|token| token.kind != Whitespace)
|
.filter(|token| token.kind != Whitespace)
|
||||||
@ -107,7 +104,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
/// The first token in `kinds` will be added to the expected token set.
|
/// The first token in `kinds` will be added to the expected token set.
|
||||||
fn next_are(&mut self, kinds: &[TokenKind]) -> bool {
|
fn next_are(&mut self, kinds: &[TokenKind]) -> bool {
|
||||||
if let Some(&kind) = kinds.first() {
|
if let Some(&kind) = kinds.first() {
|
||||||
self.expected.insert(kind);
|
self.expected_tokens.insert(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut rest = self.rest();
|
let mut rest = self.rest();
|
||||||
@ -126,10 +123,10 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
|
|
||||||
/// Advance past one significant token, clearing the expected token set.
|
/// Advance past one significant token, clearing the expected token set.
|
||||||
fn advance(&mut self) -> CompileResult<'src, Token<'src>> {
|
fn advance(&mut self) -> CompileResult<'src, Token<'src>> {
|
||||||
self.expected.clear();
|
self.expected_tokens.clear();
|
||||||
|
|
||||||
for skipped in &self.tokens[self.next..] {
|
for skipped in &self.tokens[self.next_token..] {
|
||||||
self.next += 1;
|
self.next_token += 1;
|
||||||
|
|
||||||
if skipped.kind != Whitespace {
|
if skipped.kind != Whitespace {
|
||||||
return Ok(*skipped);
|
return Ok(*skipped);
|
||||||
@ -419,7 +416,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.next == self.tokens.len() {
|
if self.next_token == self.tokens.len() {
|
||||||
Ok(Ast {
|
Ok(Ast {
|
||||||
warnings: Vec::new(),
|
warnings: Vec::new(),
|
||||||
items,
|
items,
|
||||||
@ -427,7 +424,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
} else {
|
} else {
|
||||||
Err(self.internal_error(format!(
|
Err(self.internal_error(format!(
|
||||||
"Parse completed with {} unparsed tokens",
|
"Parse completed with {} unparsed tokens",
|
||||||
self.tokens.len() - self.next,
|
self.tokens.len() - self.next_token,
|
||||||
))?)
|
))?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -464,7 +461,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
|
|
||||||
/// Parse an expression, e.g. `1 + 2`
|
/// Parse an expression, e.g. `1 + 2`
|
||||||
fn parse_expression(&mut self) -> CompileResult<'src, Expression<'src>> {
|
fn parse_expression(&mut self) -> CompileResult<'src, Expression<'src>> {
|
||||||
if self.depth == if cfg!(windows) { 48 } else { 256 } {
|
if self.recursion_depth == if cfg!(windows) { 48 } else { 256 } {
|
||||||
let token = self.next()?;
|
let token = self.next()?;
|
||||||
return Err(CompileError::new(
|
return Err(CompileError::new(
|
||||||
token,
|
token,
|
||||||
@ -472,7 +469,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.depth += 1;
|
self.recursion_depth += 1;
|
||||||
|
|
||||||
let expression = if self.accepted_keyword(Keyword::If)? {
|
let expression = if self.accepted_keyword(Keyword::If)? {
|
||||||
self.parse_conditional()?
|
self.parse_conditional()?
|
||||||
@ -496,7 +493,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.depth -= 1;
|
self.recursion_depth -= 1;
|
||||||
|
|
||||||
Ok(expression)
|
Ok(expression)
|
||||||
}
|
}
|
||||||
@ -740,11 +737,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
doc,
|
doc,
|
||||||
name,
|
name,
|
||||||
parameters: positional.into_iter().chain(variadic).collect(),
|
parameters: positional.into_iter().chain(variadic).collect(),
|
||||||
path: self.path.clone(),
|
file_path: self.file_path.into(),
|
||||||
priors,
|
priors,
|
||||||
private: name.lexeme().starts_with('_'),
|
private: name.lexeme().starts_with('_'),
|
||||||
quiet,
|
quiet,
|
||||||
depth: self.submodule,
|
depth: self.submodule_depth,
|
||||||
|
namepath: self.module_namepath.join(name),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -962,7 +960,8 @@ mod tests {
|
|||||||
fn test(text: &str, want: Tree) {
|
fn test(text: &str, want: Tree) {
|
||||||
let unindented = unindent(text);
|
let unindented = unindent(text);
|
||||||
let tokens = Lexer::test_lex(&unindented).expect("lexing failed");
|
let tokens = Lexer::test_lex(&unindented).expect("lexing failed");
|
||||||
let justfile = Parser::parse(0, &PathBuf::new(), &tokens).expect("parsing failed");
|
let justfile =
|
||||||
|
Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens).expect("parsing failed");
|
||||||
let have = justfile.tree();
|
let have = justfile.tree();
|
||||||
if have != want {
|
if have != want {
|
||||||
println!("parsed text: {unindented}");
|
println!("parsed text: {unindented}");
|
||||||
@ -1000,7 +999,7 @@ mod tests {
|
|||||||
) {
|
) {
|
||||||
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
||||||
|
|
||||||
match Parser::parse(0, &PathBuf::new(), &tokens) {
|
match Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens) {
|
||||||
Ok(_) => panic!("Parsing unexpectedly succeeded"),
|
Ok(_) => panic!("Parsing unexpectedly succeeded"),
|
||||||
Err(have) => {
|
Err(have) => {
|
||||||
let want = CompileError {
|
let want = CompileError {
|
||||||
|
18
src/ran.rs
Normal file
18
src/ran.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct Ran<'src>(BTreeMap<Namepath<'src>, BTreeSet<Vec<String>>>);
|
||||||
|
|
||||||
|
impl<'src> Ran<'src> {
|
||||||
|
pub(crate) fn has_run(&self, recipe: &Namepath<'src>, arguments: &[String]) -> bool {
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.get(recipe)
|
||||||
|
.map(|ran| ran.contains(arguments))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn ran(&mut self, recipe: &Namepath<'src>, arguments: Vec<String>) {
|
||||||
|
self.0.entry(recipe.clone()).or_default().insert(arguments);
|
||||||
|
}
|
||||||
|
}
|
@ -25,17 +25,18 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
|
|||||||
pub(crate) attributes: BTreeSet<Attribute>,
|
pub(crate) attributes: BTreeSet<Attribute>,
|
||||||
pub(crate) body: Vec<Line<'src>>,
|
pub(crate) body: Vec<Line<'src>>,
|
||||||
pub(crate) dependencies: Vec<D>,
|
pub(crate) dependencies: Vec<D>,
|
||||||
pub(crate) doc: Option<&'src str>,
|
|
||||||
pub(crate) name: Name<'src>,
|
|
||||||
pub(crate) parameters: Vec<Parameter<'src>>,
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) depth: u32,
|
||||||
|
pub(crate) doc: Option<&'src str>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) file_path: PathBuf,
|
||||||
|
pub(crate) name: Name<'src>,
|
||||||
|
pub(crate) namepath: Namepath<'src>,
|
||||||
|
pub(crate) parameters: Vec<Parameter<'src>>,
|
||||||
pub(crate) priors: usize,
|
pub(crate) priors: usize,
|
||||||
pub(crate) private: bool,
|
pub(crate) private: bool,
|
||||||
pub(crate) quiet: bool,
|
pub(crate) quiet: bool,
|
||||||
pub(crate) shebang: bool,
|
pub(crate) shebang: bool,
|
||||||
#[serde(skip)]
|
|
||||||
pub(crate) depth: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src, D> Recipe<'src, D> {
|
impl<'src, D> Recipe<'src, D> {
|
||||||
@ -223,7 +224,7 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
|
|
||||||
if self.change_directory() {
|
if self.change_directory() {
|
||||||
cmd.current_dir(if self.depth > 0 {
|
cmd.current_dir(if self.depth > 0 {
|
||||||
self.path.parent().unwrap()
|
self.file_path.parent().unwrap()
|
||||||
} else {
|
} else {
|
||||||
&context.search.working_directory
|
&context.search.working_directory
|
||||||
});
|
});
|
||||||
@ -363,7 +364,7 @@ impl<'src, D> Recipe<'src, D> {
|
|||||||
&path,
|
&path,
|
||||||
if self.change_directory() {
|
if self.change_directory() {
|
||||||
if self.depth > 0 {
|
if self.depth > 0 {
|
||||||
Some(self.path.parent().unwrap())
|
Some(self.file_path.parent().unwrap())
|
||||||
} else {
|
} else {
|
||||||
Some(&context.search.working_directory)
|
Some(&context.search.working_directory)
|
||||||
}
|
}
|
||||||
|
33
src/source.rs
Normal file
33
src/source.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(crate) struct Source<'src> {
|
||||||
|
pub(crate) path: PathBuf,
|
||||||
|
pub(crate) depth: u32,
|
||||||
|
pub(crate) namepath: Namepath<'src>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'src> Source<'src> {
|
||||||
|
pub(crate) fn root(path: &Path) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.into(),
|
||||||
|
depth: 0,
|
||||||
|
namepath: Namepath::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn import(&self, path: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
depth: self.depth + 1,
|
||||||
|
path,
|
||||||
|
namepath: self.namepath.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn module(&self, name: Name<'src>, path: PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
path,
|
||||||
|
depth: self.depth + 1,
|
||||||
|
namepath: self.namepath.join(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,7 +59,8 @@ pub(crate) fn analysis_error(
|
|||||||
) {
|
) {
|
||||||
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
|
||||||
|
|
||||||
let ast = Parser::parse(0, &PathBuf::new(), &tokens).expect("Parsing failed in analysis test...");
|
let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)
|
||||||
|
.expect("Parsing failed in analysis test...");
|
||||||
|
|
||||||
let root = PathBuf::from("justfile");
|
let root = PathBuf::from("justfile");
|
||||||
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
pub(crate) struct Token<'src> {
|
pub(crate) struct Token<'src> {
|
||||||
pub(crate) column: usize,
|
pub(crate) column: usize,
|
||||||
pub(crate) kind: TokenKind,
|
pub(crate) kind: TokenKind,
|
||||||
|
@ -50,9 +50,10 @@ impl<'src> UnresolvedRecipe<'src> {
|
|||||||
dependencies,
|
dependencies,
|
||||||
depth: self.depth,
|
depth: self.depth,
|
||||||
doc: self.doc,
|
doc: self.doc,
|
||||||
|
file_path: self.file_path,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
|
namepath: self.namepath,
|
||||||
parameters: self.parameters,
|
parameters: self.parameters,
|
||||||
path: self.path,
|
|
||||||
priors: self.priors,
|
priors: self.priors,
|
||||||
private: self.private,
|
private: self.private,
|
||||||
quiet: self.quiet,
|
quiet: self.quiet,
|
||||||
|
@ -60,7 +60,7 @@ impl<'expression, 'src> Iterator for Variables<'expression, 'src> {
|
|||||||
self.stack.push(rhs);
|
self.stack.push(rhs);
|
||||||
self.stack.push(lhs);
|
self.stack.push(lhs);
|
||||||
}
|
}
|
||||||
Expression::Variable { name, .. } => return Some(name.token()),
|
Expression::Variable { name, .. } => return Some(name.token),
|
||||||
Expression::Concatenation { lhs, rhs } => {
|
Expression::Concatenation { lhs, rhs } => {
|
||||||
self.stack.push(rhs);
|
self.stack.push(rhs);
|
||||||
self.stack.push(lhs);
|
self.stack.push(lhs);
|
||||||
|
@ -34,6 +34,7 @@ fn alias() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -118,6 +119,7 @@ fn body() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -161,6 +163,7 @@ fn dependencies() {
|
|||||||
"attributes": [],
|
"attributes": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
|
"namepath": "bar",
|
||||||
"body": [],
|
"body": [],
|
||||||
"dependencies": [{
|
"dependencies": [{
|
||||||
"arguments": [],
|
"arguments": [],
|
||||||
@ -177,6 +180,7 @@ fn dependencies() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -239,6 +243,7 @@ fn dependency_argument() {
|
|||||||
"bar": {
|
"bar": {
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
|
"namepath": "bar",
|
||||||
"body": [],
|
"body": [],
|
||||||
"dependencies": [{
|
"dependencies": [{
|
||||||
"arguments": [
|
"arguments": [
|
||||||
@ -267,6 +272,7 @@ fn dependency_argument() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "args",
|
"name": "args",
|
||||||
@ -328,6 +334,7 @@ fn duplicate_recipes() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
@ -377,6 +384,7 @@ fn doc_comment() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": "hello",
|
"doc": "hello",
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -456,6 +464,7 @@ fn parameters() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "a",
|
"name": "a",
|
||||||
|
"namepath": "a",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -467,6 +476,7 @@ fn parameters() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "b",
|
"name": "b",
|
||||||
|
"namepath": "b",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "x",
|
"name": "x",
|
||||||
@ -486,6 +496,7 @@ fn parameters() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "c",
|
"name": "c",
|
||||||
|
"namepath": "c",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "x",
|
"name": "x",
|
||||||
@ -505,6 +516,7 @@ fn parameters() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "d",
|
"name": "d",
|
||||||
|
"namepath": "d",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "x",
|
"name": "x",
|
||||||
@ -524,6 +536,7 @@ fn parameters() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "e",
|
"name": "e",
|
||||||
|
"namepath": "e",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "x",
|
"name": "x",
|
||||||
@ -543,6 +556,7 @@ fn parameters() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "f",
|
"name": "f",
|
||||||
|
"namepath": "f",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "x",
|
"name": "x",
|
||||||
@ -596,6 +610,7 @@ fn priors() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "a",
|
"name": "a",
|
||||||
|
"namepath": "a",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -617,6 +632,7 @@ fn priors() {
|
|||||||
],
|
],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "b",
|
"name": "b",
|
||||||
|
"namepath": "b",
|
||||||
"private": false,
|
"private": false,
|
||||||
"quiet": false,
|
"quiet": false,
|
||||||
"shebang": false,
|
"shebang": false,
|
||||||
@ -629,6 +645,7 @@ fn priors() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "c",
|
"name": "c",
|
||||||
|
"namepath": "c",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"private": false,
|
"private": false,
|
||||||
"quiet": false,
|
"quiet": false,
|
||||||
@ -672,6 +689,7 @@ fn private() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "_foo",
|
"name": "_foo",
|
||||||
|
"namepath": "_foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -714,6 +732,7 @@ fn quiet() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -767,6 +786,7 @@ fn settings() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -815,6 +835,7 @@ fn shebang() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -857,6 +878,7 @@ fn simple() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -903,6 +925,7 @@ fn attribute() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
"namepath": "foo",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
@ -961,6 +984,7 @@ fn module() {
|
|||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"doc": null,
|
"doc": null,
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
|
"namepath": "foo::bar",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"priors": 0,
|
"priors": 0,
|
||||||
"private": false,
|
"private": false,
|
||||||
|
@ -684,3 +684,23 @@ fn module_paths_beginning_with_tilde_are_expanded_to_homdir() {
|
|||||||
.env("HOME", "foobar")
|
.env("HOME", "foobar")
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recipes_with_same_name_are_both_run() {
|
||||||
|
Test::new()
|
||||||
|
.write("foo.just", "bar:\n @echo MODULE")
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
mod foo
|
||||||
|
|
||||||
|
bar:
|
||||||
|
@echo ROOT
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.test_round_trip(false)
|
||||||
|
.arg("--unstable")
|
||||||
|
.arg("foo::bar")
|
||||||
|
.arg("bar")
|
||||||
|
.stdout("MODULE\nROOT\n")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user