Add module_file() and module_directory() functions (#2105)

This commit is contained in:
Casey Rodarmor 2024-05-28 20:06:30 -07:00 committed by GitHub
parent b8044e789b
commit db52d95146
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 233 additions and 43 deletions

View File

@ -166,6 +166,7 @@ impl<'src> Analyzer<'src> {
name, name,
recipes, recipes,
settings, settings,
source: root.into(),
warnings, warnings,
}) })
} }

View File

@ -4,9 +4,10 @@ pub(crate) struct Evaluator<'src: 'run, 'run> {
pub(crate) assignments: Option<&'run Table<'src, Assignment<'src>>>, pub(crate) assignments: Option<&'run Table<'src, Assignment<'src>>>,
pub(crate) config: &'run Config, pub(crate) config: &'run Config,
pub(crate) dotenv: &'run BTreeMap<String, String>, pub(crate) dotenv: &'run BTreeMap<String, String>,
pub(crate) module_source: &'run Path,
pub(crate) scope: Scope<'src, 'run>, pub(crate) scope: Scope<'src, 'run>,
pub(crate) settings: &'run Settings<'run>,
pub(crate) search: &'run Search, pub(crate) search: &'run Search,
pub(crate) settings: &'run Settings<'run>,
} }
impl<'src, 'run> Evaluator<'src, 'run> { impl<'src, 'run> Evaluator<'src, 'run> {
@ -14,17 +15,19 @@ impl<'src, 'run> Evaluator<'src, 'run> {
assignments: &'run Table<'src, Assignment<'src>>, assignments: &'run Table<'src, Assignment<'src>>,
config: &'run Config, config: &'run Config,
dotenv: &'run BTreeMap<String, String>, dotenv: &'run BTreeMap<String, String>,
overrides: Scope<'src, 'run>, module_source: &'run Path,
settings: &'run Settings<'run>, scope: Scope<'src, 'run>,
search: &'run Search, search: &'run Search,
settings: &'run Settings<'run>,
) -> RunResult<'src, Scope<'src, 'run>> { ) -> RunResult<'src, Scope<'src, 'run>> {
let mut evaluator = Self { let mut evaluator = Self {
scope: overrides,
assignments: Some(assignments), assignments: Some(assignments),
config, config,
dotenv, dotenv,
settings, module_source,
scope,
search, search,
settings,
}; };
for assignment in assignments.values() { for assignment in assignments.values() {
@ -250,21 +253,23 @@ impl<'src, 'run> Evaluator<'src, 'run> {
} }
pub(crate) fn evaluate_parameters( pub(crate) fn evaluate_parameters(
arguments: &[String],
config: &'run Config, config: &'run Config,
dotenv: &'run BTreeMap<String, String>, dotenv: &'run BTreeMap<String, String>,
module_source: &'run Path,
parameters: &[Parameter<'src>], parameters: &[Parameter<'src>],
arguments: &[String],
scope: &'run Scope<'src, 'run>, scope: &'run Scope<'src, 'run>,
settings: &'run Settings,
search: &'run Search, search: &'run Search,
settings: &'run Settings,
) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> { ) -> RunResult<'src, (Scope<'src, 'run>, Vec<String>)> {
let mut evaluator = Self { let mut evaluator = Self {
assignments: None, assignments: None,
config,
dotenv,
module_source,
scope: scope.child(), scope: scope.child(),
search, search,
settings, settings,
dotenv,
config,
}; };
let mut scope = scope.child(); let mut scope = scope.child();
@ -307,17 +312,19 @@ impl<'src, 'run> Evaluator<'src, 'run> {
pub(crate) fn recipe_evaluator( pub(crate) fn recipe_evaluator(
config: &'run Config, config: &'run Config,
dotenv: &'run BTreeMap<String, String>, dotenv: &'run BTreeMap<String, String>,
module_source: &'run Path,
scope: &'run Scope<'src, 'run>, scope: &'run Scope<'src, 'run>,
settings: &'run Settings,
search: &'run Search, search: &'run Search,
) -> Evaluator<'src, 'run> { settings: &'run Settings,
) -> Self {
Self { Self {
assignments: None, assignments: None,
config,
dotenv,
module_source,
scope: Scope::child(scope), scope: Scope::child(scope),
search, search,
settings, settings,
dotenv,
config,
} }
} }
} }

View File

@ -67,6 +67,8 @@ pub(crate) fn get(name: &str) -> Option<Function> {
"kebabcase" => Unary(kebabcase), "kebabcase" => Unary(kebabcase),
"lowercamelcase" => Unary(lowercamelcase), "lowercamelcase" => Unary(lowercamelcase),
"lowercase" => Unary(lowercase), "lowercase" => Unary(lowercase),
"module_directory" => Nullary(module_directory),
"module_file" => Nullary(module_file),
"num_cpus" => Nullary(num_cpus), "num_cpus" => Nullary(num_cpus),
"os" => Nullary(os), "os" => Nullary(os),
"os_family" => Nullary(os_family), "os_family" => Nullary(os_family),
@ -406,6 +408,44 @@ fn lowercase(_context: Context, s: &str) -> Result<String, String> {
Ok(s.to_lowercase()) Ok(s.to_lowercase())
} }
fn module_directory(context: Context) -> Result<String, String> {
context
.evaluator
.search
.justfile
.parent()
.unwrap()
.join(context.evaluator.module_source)
.parent()
.unwrap()
.to_str()
.map(str::to_owned)
.ok_or_else(|| {
format!(
"Module directory is not valid unicode: {}",
context.evaluator.module_source.parent().unwrap().display(),
)
})
}
fn module_file(context: Context) -> Result<String, String> {
context
.evaluator
.search
.justfile
.parent()
.unwrap()
.join(context.evaluator.module_source)
.to_str()
.map(str::to_owned)
.ok_or_else(|| {
format!(
"Module file path is not valid unicode: {}",
context.evaluator.module_source.display(),
)
})
}
fn num_cpus(_context: Context) -> Result<String, String> { fn num_cpus(_context: Context) -> Result<String, String> {
let num = num_cpus::get(); let num = num_cpus::get();
Ok(num.to_string()) Ok(num.to_string())

View File

@ -3,9 +3,10 @@ use {super::*, serde::Serialize};
#[derive(Debug)] #[derive(Debug)]
struct Invocation<'src: 'run, 'run> { struct Invocation<'src: 'run, 'run> {
arguments: Vec<&'run str>, arguments: Vec<&'run str>,
module_source: &'run Path,
recipe: &'run Recipe<'src>, recipe: &'run Recipe<'src>,
settings: &'run Settings<'src>,
scope: &'run Scope<'src, 'run>, scope: &'run Scope<'src, 'run>,
settings: &'run Settings<'src>,
} }
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, Serialize)]
@ -21,6 +22,8 @@ pub(crate) struct Justfile<'src> {
pub(crate) name: Option<Name<'src>>, pub(crate) name: Option<Name<'src>>,
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>, pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
pub(crate) settings: Settings<'src>, pub(crate) settings: Settings<'src>,
#[serde(skip)]
pub(crate) source: PathBuf,
pub(crate) warnings: Vec<Warning>, pub(crate) warnings: Vec<Warning>,
} }
@ -106,9 +109,10 @@ impl<'src> Justfile<'src> {
&self.assignments, &self.assignments,
config, config,
dotenv, dotenv,
&self.source,
scope, scope,
&self.settings,
search, search,
&self.settings,
) )
} }
@ -276,10 +280,12 @@ impl<'src> Justfile<'src> {
let mut ran = Ran::default(); let mut ran = Ran::default();
for invocation in invocations { for invocation in invocations {
let context = RecipeContext { let context = RecipeContext {
settings: invocation.settings,
config, config,
dotenv: &dotenv,
module_source: invocation.module_source,
scope: invocation.scope, scope: invocation.scope,
search, search,
settings: invocation.settings,
}; };
Self::run_recipe( Self::run_recipe(
@ -290,7 +296,6 @@ impl<'src> Justfile<'src> {
.map(str::to_string) .map(str::to_string)
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
&context, &context,
&dotenv,
&mut ran, &mut ran,
invocation.recipe, invocation.recipe,
search, search,
@ -346,6 +351,7 @@ impl<'src> Justfile<'src> {
recipe, recipe,
arguments: Vec::new(), arguments: Vec::new(),
scope, scope,
module_source: &self.source,
}, },
depth, depth,
))); )));
@ -373,6 +379,7 @@ impl<'src> Justfile<'src> {
recipe, recipe,
scope: parent, scope: parent,
settings: &self.settings, settings: &self.settings,
module_source: &self.source,
}, },
depth, depth,
))) )))
@ -394,6 +401,7 @@ impl<'src> Justfile<'src> {
recipe, recipe,
scope: parent, scope: parent,
settings: &self.settings, settings: &self.settings,
module_source: &self.source,
}, },
depth + argument_count, depth + argument_count,
))) )))
@ -410,7 +418,6 @@ impl<'src> Justfile<'src> {
fn run_recipe( fn run_recipe(
arguments: &[String], arguments: &[String],
context: &RecipeContext<'src, '_>, context: &RecipeContext<'src, '_>,
dotenv: &BTreeMap<String, String>,
ran: &mut Ran<'src>, ran: &mut Ran<'src>,
recipe: &Recipe<'src>, recipe: &Recipe<'src>,
search: &Search, search: &Search,
@ -426,19 +433,26 @@ impl<'src> Justfile<'src> {
} }
let (outer, positional) = Evaluator::evaluate_parameters( let (outer, positional) = Evaluator::evaluate_parameters(
context.config,
dotenv,
&recipe.parameters,
arguments, arguments,
context.config,
context.dotenv,
context.module_source,
&recipe.parameters,
context.scope, context.scope,
context.settings,
search, search,
context.settings,
)?; )?;
let scope = outer.child(); let scope = outer.child();
let mut evaluator = let mut evaluator = Evaluator::recipe_evaluator(
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search); context.config,
context.dotenv,
context.module_source,
&scope,
search,
context.settings,
);
if !context.config.no_dependencies { if !context.config.no_dependencies {
for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) { for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
@ -447,11 +461,11 @@ 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(&arguments, context, dotenv, ran, recipe, search)?; Self::run_recipe(&arguments, context, ran, recipe, search)?;
} }
} }
recipe.run(context, dotenv, scope.child(), search, &positional)?; recipe.run(context, &scope, &positional)?;
if !context.config.no_dependencies { if !context.config.no_dependencies {
let mut ran = Ran::default(); let mut ran = Ran::default();
@ -463,11 +477,12 @@ impl<'src> Justfile<'src> {
evaluated.push(evaluator.evaluate_expression(argument)?); evaluated.push(evaluator.evaluate_expression(argument)?);
} }
Self::run_recipe(&evaluated, context, dotenv, &mut ran, recipe, search)?; Self::run_recipe(&evaluated, context, &mut ran, recipe, search)?;
} }
} }
ran.ran(&recipe.namepath, arguments.to_vec()); ran.ran(&recipe.namepath, arguments.to_vec());
Ok(()) Ok(())
} }

View File

@ -18,11 +18,11 @@ use {super::*, TokenKind::*};
/// All methods starting with `parse_*` parse and return a language construct. /// All methods starting with `parse_*` parse and return a language construct.
/// ///
/// The parser tracks an expected set of tokens as it parses. This set contains /// The parser tracks an expected set of tokens as it parses. This set contains
/// all tokens which would have been accepted at the current point in the parse. /// all tokens which would have been accepted at the current point in the
/// Whenever the parser tests for a token that would be accepted, but does not /// parse. Whenever the parser tests for a token that would be accepted, but
/// find it, it adds that token to the set. When the parser accepts a token, the /// does not find it, it adds that token to the set. When the parser accepts a
/// set is cleared. If the parser finds a token which is unexpected, the /// token, the set is cleared. If the parser finds a token which is unexpected,
/// contents of the set is printed in the resultant error message. /// the elements of the set are printed in the resultant error message.
pub(crate) struct Parser<'run, 'src> { pub(crate) struct Parser<'run, 'src> {
expected_tokens: BTreeSet<TokenKind>, expected_tokens: BTreeSet<TokenKind>,
file_depth: u32, file_depth: u32,

View File

@ -147,9 +147,7 @@ impl<'src, D> Recipe<'src, D> {
pub(crate) fn run<'run>( pub(crate) fn run<'run>(
&self, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>, scope: &Scope<'src, 'run>,
scope: Scope<'src, 'run>,
search: &'run Search,
positional: &[String], positional: &[String],
) -> RunResult<'src, ()> { ) -> RunResult<'src, ()> {
let config = &context.config; let config = &context.config;
@ -164,20 +162,25 @@ impl<'src, D> Recipe<'src, D> {
); );
} }
let evaluator = let evaluator = Evaluator::recipe_evaluator(
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search); context.config,
context.dotenv,
context.module_source,
scope,
context.search,
context.settings,
);
if self.shebang { if self.shebang {
self.run_shebang(context, dotenv, &scope, positional, config, evaluator) self.run_shebang(context, scope, positional, config, evaluator)
} else { } else {
self.run_linewise(context, dotenv, &scope, positional, config, evaluator) self.run_linewise(context, scope, positional, config, evaluator)
} }
} }
fn run_linewise<'run>( fn run_linewise<'run>(
&self, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>,
scope: &Scope<'src, 'run>, scope: &Scope<'src, 'run>,
positional: &[String], positional: &[String],
config: &Config, config: &Config,
@ -264,7 +267,7 @@ impl<'src, D> Recipe<'src, D> {
cmd.stdout(Stdio::null()); cmd.stdout(Stdio::null());
} }
cmd.export(context.settings, dotenv, scope); cmd.export(context.settings, context.dotenv, scope);
match InterruptHandler::guard(|| cmd.status()) { match InterruptHandler::guard(|| cmd.status()) {
Ok(exit_status) => { Ok(exit_status) => {
@ -298,7 +301,6 @@ impl<'src, D> Recipe<'src, D> {
pub(crate) fn run_shebang<'run>( pub(crate) fn run_shebang<'run>(
&self, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>,
scope: &Scope<'src, 'run>, scope: &Scope<'src, 'run>,
positional: &[String], positional: &[String],
config: &Config, config: &Config,
@ -411,7 +413,7 @@ impl<'src, D> Recipe<'src, D> {
command.args(positional); command.args(positional);
} }
command.export(context.settings, dotenv, scope); command.export(context.settings, context.dotenv, scope);
// run it! // run it!
match InterruptHandler::guard(|| command.status()) { match InterruptHandler::guard(|| command.status()) {

View File

@ -2,6 +2,8 @@ use super::*;
pub(crate) struct RecipeContext<'src: 'run, 'run> { pub(crate) struct RecipeContext<'src: 'run, 'run> {
pub(crate) config: &'run Config, pub(crate) config: &'run Config,
pub(crate) dotenv: &'run BTreeMap<String, String>,
pub(crate) module_source: &'run Path,
pub(crate) scope: &'run Scope<'src, 'run>, pub(crate) scope: &'run Scope<'src, 'run>,
pub(crate) search: &'run Search, pub(crate) search: &'run Search,
pub(crate) settings: &'run Settings<'src>, pub(crate) settings: &'run Settings<'src>,

View File

@ -904,3 +904,126 @@ fn source_directory() {
.stdout_regex(r".*[/\\]foo\n") .stdout_regex(r".*[/\\]foo\n")
.run(); .run();
} }
#[test]
fn module_paths() {
Test::new()
.write(
"foo/bar.just",
"
imf := module_file()
imd := module_directory()
import-outer: import-inner
@import-inner pmf=module_file() pmd=module_directory():
echo import
echo '{{ imf }}'
echo '{{ imd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.write(
"baz/mod.just",
"
import 'foo/bar.just'
mmf := module_file()
mmd := module_directory()
outer: inner
@inner pmf=module_file() pmd=module_directory():
echo module
echo '{{ mmf }}'
echo '{{ mmd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.write(
"baz/foo/bar.just",
"
imf := module_file()
imd := module_directory()
import-outer: import-inner
@import-inner pmf=module_file() pmd=module_directory():
echo import
echo '{{ imf }}'
echo '{{ imd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.justfile(
"
import 'foo/bar.just'
mod baz
rmf := module_file()
rmd := module_directory()
outer: inner
@inner pmf=module_file() pmd=module_directory():
echo root
echo '{{ rmf }}'
echo '{{ rmd }}'
echo '{{ pmf }}'
echo '{{ pmd }}'
echo '{{ module_file() }}'
echo '{{ module_directory() }}'
",
)
.test_round_trip(false)
.args([
"--unstable",
"outer",
"import-outer",
"baz",
"outer",
"baz",
"import-outer",
])
.stdout_regex(
r"root
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
import
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
.*[/\\]just-test-tempdir......[/\\]justfile
.*[/\\]just-test-tempdir......
module
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
import
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
.*[/\\]just-test-tempdir......[/\\]baz[/\\]mod.just
.*[/\\]just-test-tempdir......[/\\]baz
",
)
.run();
}