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

View File

@ -67,6 +67,8 @@ pub(crate) fn get(name: &str) -> Option<Function> {
"kebabcase" => Unary(kebabcase),
"lowercamelcase" => Unary(lowercamelcase),
"lowercase" => Unary(lowercase),
"module_directory" => Nullary(module_directory),
"module_file" => Nullary(module_file),
"num_cpus" => Nullary(num_cpus),
"os" => Nullary(os),
"os_family" => Nullary(os_family),
@ -406,6 +408,44 @@ fn lowercase(_context: Context, s: &str) -> Result<String, String> {
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> {
let num = num_cpus::get();
Ok(num.to_string())

View File

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

View File

@ -18,11 +18,11 @@ use {super::*, TokenKind::*};
/// 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
/// all tokens which would have been accepted at the current point in the parse.
/// Whenever the parser tests for a token that would be accepted, but does not
/// 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
/// contents of the set is printed in the resultant error message.
/// all tokens which would have been accepted at the current point in the
/// parse. Whenever the parser tests for a token that would be accepted, but
/// does not 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 elements of the set are printed in the resultant error message.
pub(crate) struct Parser<'run, 'src> {
expected_tokens: BTreeSet<TokenKind>,
file_depth: u32,

View File

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

View File

@ -2,6 +2,8 @@ use super::*;
pub(crate) struct RecipeContext<'src: 'run, 'run> {
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) search: &'run Search,
pub(crate) settings: &'run Settings<'src>,

View File

@ -904,3 +904,126 @@ fn source_directory() {
.stdout_regex(r".*[/\\]foo\n")
.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();
}