export variables done
This commit is contained in:
parent
f925520101
commit
69f8e07a30
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -4,6 +4,7 @@ version = "0.2.8"
|
||||
dependencies = [
|
||||
"brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.16.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -51,11 +52,24 @@ dependencies = [
|
||||
"vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"either 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
@ -184,7 +198,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum brev 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "79571b60a8aa293f43b46370d8ba96fed28a5bee1303ea0e015d175ed0c63b40"
|
||||
"checksum clap 2.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0ab91429d96eece6d6cf8a737105f0e255fea039fed86e2118ff8d3fd69601dd"
|
||||
"checksum either 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8aa2c82b7e1abd89a8a59fd89c4a51576ea76a894edf5d5b28944dd46edfed8d"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum itertools 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ef81b0a15a9e1808cfd3ebe6a87277d29ee88b34ac1197cc7547f1dd6d9f5424"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
|
||||
"checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"
|
||||
|
@ -13,3 +13,4 @@ clap = "^2.0.0"
|
||||
tempdir = "^0.3.5"
|
||||
lazy_static = "^0.2.1"
|
||||
brev = "^0.1.6"
|
||||
itertools = "^0.5.5"
|
||||
|
6
notes
6
notes
@ -1,11 +1,7 @@
|
||||
todo
|
||||
----
|
||||
|
||||
- allow setting and exporting environment variables
|
||||
. export a as "HELLO_BAR"
|
||||
. export a
|
||||
. export HELLO_BAR = a
|
||||
. export CC_FLAGS = "-g"
|
||||
clean up export parsing
|
||||
|
||||
- raw strings with ''
|
||||
- multi line strings (not in recipe interpolations)
|
||||
|
@ -5,14 +5,13 @@ use tempdir::TempDir;
|
||||
use super::std::process::Command;
|
||||
|
||||
fn integration_test(
|
||||
name: &str,
|
||||
args: &[&str],
|
||||
justfile: &str,
|
||||
expected_status: i32,
|
||||
expected_stdout: &str,
|
||||
expected_stderr: &str,
|
||||
) {
|
||||
let tmp = TempDir::new(name)
|
||||
let tmp = TempDir::new("just-integration")
|
||||
.unwrap_or_else(|err| panic!("tmpdir: failed to create temporary directory: {}", err));
|
||||
let mut path = tmp.path().to_path_buf();
|
||||
path.push("justfile");
|
||||
@ -53,7 +52,6 @@ fn integration_test(
|
||||
#[test]
|
||||
fn default() {
|
||||
integration_test(
|
||||
"default",
|
||||
&[],
|
||||
"default:\n echo hello\nother: \n echo bar",
|
||||
0,
|
||||
@ -65,7 +63,6 @@ fn default() {
|
||||
#[test]
|
||||
fn quiet() {
|
||||
integration_test(
|
||||
"quiet",
|
||||
&[],
|
||||
"default:\n @echo hello",
|
||||
0,
|
||||
@ -94,7 +91,6 @@ c: b
|
||||
echo c
|
||||
@mv b c";
|
||||
integration_test(
|
||||
"order",
|
||||
&["a", "d"],
|
||||
text,
|
||||
0,
|
||||
@ -111,7 +107,6 @@ a:
|
||||
d: c
|
||||
c: b";
|
||||
integration_test(
|
||||
"list",
|
||||
&["--list"],
|
||||
text,
|
||||
0,
|
||||
@ -132,7 +127,6 @@ d:
|
||||
c:
|
||||
@echo c";
|
||||
integration_test(
|
||||
"select",
|
||||
&["d", "c"],
|
||||
text,
|
||||
0,
|
||||
@ -153,7 +147,6 @@ d:
|
||||
c:
|
||||
echo c";
|
||||
integration_test(
|
||||
"select",
|
||||
&["d", "c"],
|
||||
text,
|
||||
0,
|
||||
@ -171,7 +164,6 @@ bar = hello + hello
|
||||
recipe:
|
||||
echo {{hello + "bar" + bar}}"#;
|
||||
integration_test(
|
||||
"show",
|
||||
&["--show", "recipe"],
|
||||
text,
|
||||
0,
|
||||
@ -190,7 +182,6 @@ bar = hello + hello
|
||||
recipe:
|
||||
echo {{hello + "bar" + bar}}"#;
|
||||
integration_test(
|
||||
"debug",
|
||||
&["--debug"],
|
||||
text,
|
||||
0,
|
||||
@ -213,7 +204,6 @@ fn status() {
|
||||
recipe:
|
||||
@function f { return 100; }; f";
|
||||
integration_test(
|
||||
"status",
|
||||
&[],
|
||||
text,
|
||||
100,
|
||||
@ -225,7 +215,6 @@ recipe:
|
||||
#[test]
|
||||
fn error() {
|
||||
integration_test(
|
||||
"error",
|
||||
&[],
|
||||
"bar:\nhello:\nfoo: bar baaaaaaaz hello",
|
||||
255,
|
||||
@ -241,7 +230,6 @@ fn error() {
|
||||
#[test]
|
||||
fn backtick_success() {
|
||||
integration_test(
|
||||
"backtick_success",
|
||||
&[],
|
||||
"a = `printf Hello,`\nbar:\n printf '{{a + `printf ' world!'`}}'",
|
||||
0,
|
||||
@ -253,7 +241,6 @@ fn backtick_success() {
|
||||
#[test]
|
||||
fn backtick_trimming() {
|
||||
integration_test(
|
||||
"backtick_trimming",
|
||||
&[],
|
||||
"a = `echo Hello,`\nbar:\n echo '{{a + `echo ' world!'`}}'",
|
||||
0,
|
||||
@ -265,7 +252,6 @@ fn backtick_trimming() {
|
||||
#[test]
|
||||
fn backtick_code_assignment() {
|
||||
integration_test(
|
||||
"backtick_code_assignment",
|
||||
&[],
|
||||
"b = a\na = `function f { return 100; }; f`\nbar:\n echo '{{`function f { return 200; }; f`}}'",
|
||||
100,
|
||||
@ -281,7 +267,6 @@ fn backtick_code_assignment() {
|
||||
#[test]
|
||||
fn backtick_code_interpolation() {
|
||||
integration_test(
|
||||
"backtick_code_interpolation",
|
||||
&[],
|
||||
"b = a\na = `echo hello`\nbar:\n echo '{{`function f { return 200; }; f`}}'",
|
||||
200,
|
||||
@ -297,7 +282,6 @@ fn backtick_code_interpolation() {
|
||||
#[test]
|
||||
fn shebang_backtick_failure() {
|
||||
integration_test(
|
||||
"shebang_backtick_failure",
|
||||
&[],
|
||||
"foo:
|
||||
#!/bin/sh
|
||||
@ -316,7 +300,6 @@ fn shebang_backtick_failure() {
|
||||
#[test]
|
||||
fn command_backtick_failure() {
|
||||
integration_test(
|
||||
"command_backtick_failure",
|
||||
&[],
|
||||
"foo:
|
||||
echo hello
|
||||
@ -334,7 +317,6 @@ fn command_backtick_failure() {
|
||||
#[test]
|
||||
fn assignment_backtick_failure() {
|
||||
integration_test(
|
||||
"assignment_backtick_failure",
|
||||
&[],
|
||||
"foo:
|
||||
echo hello
|
||||
@ -353,7 +335,6 @@ a = `exit 222`",
|
||||
#[test]
|
||||
fn unknown_override_options() {
|
||||
integration_test(
|
||||
"unknown_override_options",
|
||||
&["--set", "foo", "bar", "a", "b", "--set", "baz", "bob", "--set", "a", "b"],
|
||||
"foo:
|
||||
echo hello
|
||||
@ -368,7 +349,6 @@ a = `exit 222`",
|
||||
#[test]
|
||||
fn unknown_override_args() {
|
||||
integration_test(
|
||||
"unknown_override_args",
|
||||
&["foo=bar", "baz=bob", "a=b", "a", "b"],
|
||||
"foo:
|
||||
echo hello
|
||||
@ -383,7 +363,6 @@ a = `exit 222`",
|
||||
#[test]
|
||||
fn overrides_first() {
|
||||
integration_test(
|
||||
"unknown_override_args",
|
||||
&["foo=bar", "a=b", "recipe", "baz=bar"],
|
||||
r#"
|
||||
foo = "foo"
|
||||
@ -404,7 +383,6 @@ recipe arg:
|
||||
#[test]
|
||||
fn dry_run() {
|
||||
integration_test(
|
||||
"dry_run",
|
||||
&["--dry-run", "shebang", "command"],
|
||||
r#"
|
||||
var = `echo stderr 1>&2; echo backtick`
|
||||
@ -436,7 +414,6 @@ echo command interpolation
|
||||
#[test]
|
||||
fn evaluate() {
|
||||
integration_test(
|
||||
"evaluate",
|
||||
&["--evaluate"],
|
||||
r#"
|
||||
foo = "a\t"
|
||||
@ -457,3 +434,102 @@ foo = "a "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_success() {
|
||||
integration_test(
|
||||
&[],
|
||||
r#"
|
||||
export foo = "a"
|
||||
baz = "c"
|
||||
export bar = "b"
|
||||
export abc = foo + bar + baz
|
||||
|
||||
wut:
|
||||
echo $foo $bar $abc
|
||||
"#,
|
||||
0,
|
||||
"a b abc\n",
|
||||
"echo $foo $bar $abc\n",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn export_failure() {
|
||||
integration_test(
|
||||
&[],
|
||||
r#"
|
||||
export foo = "a"
|
||||
baz = "c"
|
||||
export bar = "b"
|
||||
export abc = foo + bar + baz
|
||||
|
||||
wut:
|
||||
echo $foo $bar $baz
|
||||
"#,
|
||||
127,
|
||||
"",
|
||||
r#"echo $foo $bar $baz
|
||||
sh: baz: unbound variable
|
||||
Recipe "wut" failed with exit code 127
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_shebang() {
|
||||
integration_test(
|
||||
&[],
|
||||
r#"
|
||||
export foo = "a"
|
||||
baz = "c"
|
||||
export bar = "b"
|
||||
export abc = foo + bar + baz
|
||||
|
||||
wut:
|
||||
#!/bin/sh
|
||||
echo $foo $bar $abc
|
||||
"#,
|
||||
0,
|
||||
"a b abc\n",
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_assignment_backtick() {
|
||||
integration_test(
|
||||
&[],
|
||||
r#"
|
||||
export exported_variable = "A"
|
||||
b = `echo $exported_variable`
|
||||
|
||||
recipe:
|
||||
echo {{b}}
|
||||
"#,
|
||||
127,
|
||||
"",
|
||||
"sh: exported_variable: unbound variable
|
||||
backtick failed with exit code 127
|
||||
|
|
||||
3 | b = `echo $exported_variable`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_recipe_backtick() {
|
||||
integration_test(
|
||||
&[],
|
||||
r#"
|
||||
export exported_variable = "A-IS-A"
|
||||
|
||||
recipe:
|
||||
echo {{`echo recipe $exported_variable`}}
|
||||
"#,
|
||||
0,
|
||||
"recipe A-IS-A\n",
|
||||
"echo recipe A-IS-A\n",
|
||||
);
|
||||
}
|
||||
|
113
src/lib.rs
113
src/lib.rs
@ -12,6 +12,7 @@ pub use app::app;
|
||||
extern crate lazy_static;
|
||||
extern crate regex;
|
||||
extern crate tempdir;
|
||||
extern crate itertools;
|
||||
|
||||
use std::io::prelude::*;
|
||||
|
||||
@ -148,14 +149,39 @@ fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'sta
|
||||
RunError::BacktickUnknownFailure
|
||||
}
|
||||
|
||||
fn run_backtick<'a>(raw: &str, token: &Token<'a>) -> Result<String, RunError<'a>> {
|
||||
let output = process::Command::new("sh")
|
||||
.arg("-cu")
|
||||
.arg(raw)
|
||||
.stderr(process::Stdio::inherit())
|
||||
.output();
|
||||
fn export_env<'a>(
|
||||
command: &mut process::Command,
|
||||
scope: &Map<&'a str, String>,
|
||||
exports: &Set<&'a str>,
|
||||
) -> Result<(), RunError<'a>> {
|
||||
for name in exports {
|
||||
if let Some(value) = scope.get(name) {
|
||||
command.env(name, value);
|
||||
} else {
|
||||
return Err(RunError::InternalError {
|
||||
message: format!("scope does not contain exported variable `{}`", name),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
match output {
|
||||
|
||||
fn run_backtick<'a>(
|
||||
raw: &str,
|
||||
token: &Token<'a>,
|
||||
scope: &Map<&'a str, String>,
|
||||
exports: &Set<&'a str>,
|
||||
) -> Result<String, RunError<'a>> {
|
||||
let mut cmd = process::Command::new("sh");
|
||||
|
||||
try!(export_env(&mut cmd, scope, exports));
|
||||
|
||||
cmd.arg("-cu")
|
||||
.arg(raw)
|
||||
.stderr(process::Stdio::inherit());
|
||||
|
||||
match cmd.output() {
|
||||
Ok(output) => {
|
||||
if let Some(code) = output.status.code() {
|
||||
if code != 0 {
|
||||
@ -189,6 +215,7 @@ impl<'a> Recipe<'a> {
|
||||
&self,
|
||||
arguments: &[&'a str],
|
||||
scope: &Map<&'a str, String>,
|
||||
exports: &Set<&'a str>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), RunError<'a>> {
|
||||
let argument_map = arguments .iter().enumerate()
|
||||
@ -197,6 +224,7 @@ impl<'a> Recipe<'a> {
|
||||
let mut evaluator = Evaluator {
|
||||
evaluated: Map::new(),
|
||||
scope: scope,
|
||||
exports: exports,
|
||||
assignments: &Map::new(),
|
||||
overrides: &Map::new(),
|
||||
};
|
||||
@ -257,8 +285,11 @@ impl<'a> Recipe<'a> {
|
||||
try!(fs::set_permissions(&path, perms).map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error}));
|
||||
|
||||
// run it!
|
||||
let status = process::Command::new(path).status();
|
||||
try!(match status {
|
||||
let mut command = process::Command::new(path);
|
||||
|
||||
try!(export_env(&mut command, scope, exports));
|
||||
|
||||
try!(match command.status() {
|
||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||
if code == 0 {
|
||||
Ok(())
|
||||
@ -284,11 +315,14 @@ impl<'a> Recipe<'a> {
|
||||
if dry_run {
|
||||
continue;
|
||||
}
|
||||
let status = process::Command::new("sh")
|
||||
.arg("-cu")
|
||||
.arg(command)
|
||||
.status();
|
||||
try!(match status {
|
||||
|
||||
let mut cmd = process::Command::new("sh");
|
||||
|
||||
cmd.arg("-cu").arg(command);
|
||||
|
||||
try!(export_env(&mut cmd, scope, exports));
|
||||
|
||||
try!(match cmd.status() {
|
||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||
if code == 0 {
|
||||
Ok(())
|
||||
@ -513,6 +547,7 @@ fn evaluate_assignments<'a>(
|
||||
let mut evaluator = Evaluator {
|
||||
evaluated: Map::new(),
|
||||
scope: &Map::new(),
|
||||
exports: &Set::new(),
|
||||
assignments: assignments,
|
||||
overrides: overrides,
|
||||
};
|
||||
@ -527,6 +562,7 @@ fn evaluate_assignments<'a>(
|
||||
struct Evaluator<'a: 'b, 'b> {
|
||||
evaluated: Map<&'a str, String>,
|
||||
scope: &'b Map<&'a str, String>,
|
||||
exports: &'b Set<&'a str>,
|
||||
assignments: &'b Map<&'a str, Expression<'a>>,
|
||||
overrides: &'b Map<&'b str, &'b str>,
|
||||
}
|
||||
@ -593,7 +629,9 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
||||
}
|
||||
}
|
||||
Expression::String{ref cooked, ..} => cooked.clone(),
|
||||
Expression::Backtick{raw, ref token} => try!(run_backtick(raw, token)),
|
||||
Expression::Backtick{raw, ref token} => {
|
||||
try!(run_backtick(raw, token, &self.scope, &self.exports))
|
||||
}
|
||||
Expression::Concatination{ref lhs, ref rhs} => {
|
||||
try!(self.evaluate_expression(lhs, arguments))
|
||||
+
|
||||
@ -790,6 +828,7 @@ impl<'a> Display for Error<'a> {
|
||||
struct Justfile<'a> {
|
||||
recipes: Map<&'a str, Recipe<'a>>,
|
||||
assignments: Map<&'a str, Expression<'a>>,
|
||||
exports: Set<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
||||
@ -890,7 +929,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
||||
try!(self.run_recipe(&self.recipes[dependency_name], &[], scope, ran, dry_run));
|
||||
}
|
||||
}
|
||||
try!(recipe.run(arguments, &scope, dry_run));
|
||||
try!(recipe.run(arguments, &scope, &self.exports, dry_run));
|
||||
ran.insert(recipe.name);
|
||||
Ok(())
|
||||
}
|
||||
@ -904,6 +943,9 @@ impl<'a> Display for Justfile<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
let mut items = self.recipes.len() + self.assignments.len();
|
||||
for (name, expression) in &self.assignments {
|
||||
if self.exports.contains(name) {
|
||||
try!(write!(f, "export "));
|
||||
}
|
||||
try!(write!(f, "{} = {}", name, expression));
|
||||
items -= 1;
|
||||
if items != 0 {
|
||||
@ -1343,9 +1385,9 @@ fn tokenize(text: &str) -> Result<Vec<Token>, Error> {
|
||||
fn parse(text: &str) -> Result<Justfile, Error> {
|
||||
let tokens = try!(tokenize(text));
|
||||
let filtered: Vec<_> = tokens.into_iter().filter(|token| token.kind != Comment).collect();
|
||||
let parser = Parser{
|
||||
text: text,
|
||||
tokens: filtered.into_iter().peekable()
|
||||
let parser = Parser {
|
||||
text: text,
|
||||
tokens: itertools::put_back(filtered),
|
||||
};
|
||||
let justfile = try!(parser.file());
|
||||
Ok(justfile)
|
||||
@ -1353,12 +1395,15 @@ fn parse(text: &str) -> Result<Justfile, Error> {
|
||||
|
||||
struct Parser<'a> {
|
||||
text: &'a str,
|
||||
tokens: std::iter::Peekable<std::vec::IntoIter<Token<'a>>>
|
||||
tokens: itertools::PutBack<std::vec::IntoIter<Token<'a>>>
|
||||
}
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
fn peek(&mut self, kind: TokenKind) -> bool {
|
||||
self.tokens.peek().unwrap().kind == kind
|
||||
let next = self.tokens.next().unwrap();
|
||||
let result = next.kind == kind;
|
||||
self.tokens.put_back(next);
|
||||
result
|
||||
}
|
||||
|
||||
fn accept(&mut self, kind: TokenKind) -> Option<Token<'a>> {
|
||||
@ -1560,7 +1605,28 @@ impl<'a> Parser<'a> {
|
||||
Some(token) => match token.kind {
|
||||
Eof => break,
|
||||
Eol => continue,
|
||||
Name => if self.accepted(Equals) {
|
||||
Name => if token.lexeme == "export" {
|
||||
let next = self.tokens.next().unwrap();
|
||||
if next.kind == Name && self.accepted(Equals) {
|
||||
if assignments.contains_key(next.lexeme) {
|
||||
return Err(token.error(ErrorKind::DuplicateVariable {
|
||||
variable: next.lexeme,
|
||||
}));
|
||||
}
|
||||
exports.insert(next.lexeme);
|
||||
assignments.insert(next.lexeme, try!(self.expression(false)));
|
||||
assignment_tokens.insert(next.lexeme, next);
|
||||
} else {
|
||||
self.tokens.put_back(next);
|
||||
if let Some(recipe) = recipes.get(token.lexeme) {
|
||||
return Err(token.error(ErrorKind::DuplicateRecipe {
|
||||
recipe: recipe.name,
|
||||
first: recipe.line_number
|
||||
}));
|
||||
}
|
||||
recipes.insert(token.lexeme, try!(self.recipe(token.lexeme, token.line)));
|
||||
}
|
||||
} else if self.accepted(Equals) {
|
||||
if assignments.contains_key(token.lexeme) {
|
||||
return Err(token.error(ErrorKind::DuplicateVariable {
|
||||
variable: token.lexeme,
|
||||
@ -1569,7 +1635,7 @@ impl<'a> Parser<'a> {
|
||||
assignments.insert(token.lexeme, try!(self.expression(false)));
|
||||
assignment_tokens.insert(token.lexeme, token);
|
||||
} else {
|
||||
if let Some(recipe) = recipes.remove(token.lexeme) {
|
||||
if let Some(recipe) = recipes.get(token.lexeme) {
|
||||
return Err(token.error(ErrorKind::DuplicateRecipe {
|
||||
recipe: recipe.name,
|
||||
first: recipe.line_number
|
||||
@ -1627,6 +1693,7 @@ impl<'a> Parser<'a> {
|
||||
Ok(Justfile {
|
||||
recipes: recipes,
|
||||
assignments: assignments,
|
||||
exports: exports,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -246,6 +246,15 @@ fn parse_empty() {
|
||||
", "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_export() {
|
||||
parse_summary(r#"
|
||||
export a = "hello"
|
||||
|
||||
"#, r#"export a = "hello""#);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn parse_complex() {
|
||||
parse_summary("
|
||||
|
Loading…
Reference in New Issue
Block a user