Add env() function (#1613)
This commit is contained in:
parent
19c887fded
commit
bba673fd79
@ -73,6 +73,13 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
|
|||||||
Expression::Call { thunk } => match thunk {
|
Expression::Call { thunk } => match thunk {
|
||||||
Thunk::Nullary { .. } => Ok(()),
|
Thunk::Nullary { .. } => Ok(()),
|
||||||
Thunk::Unary { arg, .. } => self.resolve_expression(arg),
|
Thunk::Unary { arg, .. } => self.resolve_expression(arg),
|
||||||
|
Thunk::UnaryOpt { args: (a, b), .. } => {
|
||||||
|
self.resolve_expression(a)?;
|
||||||
|
if let Some(b) = b.as_ref() {
|
||||||
|
self.resolve_expression(b)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Thunk::Binary { args: [a, b], .. } => {
|
Thunk::Binary { args: [a, b], .. } => {
|
||||||
self.resolve_expression(a)?;
|
self.resolve_expression(a)?;
|
||||||
self.resolve_expression(b)
|
self.resolve_expression(b)
|
||||||
|
@ -92,6 +92,23 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
|||||||
message,
|
message,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
UnaryOpt {
|
||||||
|
name,
|
||||||
|
function,
|
||||||
|
args: (a, b),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let a = self.evaluate_expression(a)?;
|
||||||
|
let b = match b.as_ref() {
|
||||||
|
Some(b) => Some(self.evaluate_expression(b)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
function(&context, &a, b.as_deref()).map_err(|message| Error::FunctionCall {
|
||||||
|
function: *name,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
}
|
||||||
Binary {
|
Binary {
|
||||||
name,
|
name,
|
||||||
function,
|
function,
|
||||||
|
@ -10,6 +10,7 @@ use {
|
|||||||
pub(crate) enum Function {
|
pub(crate) enum Function {
|
||||||
Nullary(fn(&FunctionContext) -> Result<String, String>),
|
Nullary(fn(&FunctionContext) -> Result<String, String>),
|
||||||
Unary(fn(&FunctionContext, &str) -> Result<String, String>),
|
Unary(fn(&FunctionContext, &str) -> Result<String, String>),
|
||||||
|
UnaryOpt(fn(&FunctionContext, &str, Option<&str>) -> Result<String, String>),
|
||||||
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
|
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
|
||||||
BinaryPlus(fn(&FunctionContext, &str, &str, &[String]) -> Result<String, String>),
|
BinaryPlus(fn(&FunctionContext, &str, &str, &[String]) -> Result<String, String>),
|
||||||
Ternary(fn(&FunctionContext, &str, &str, &str) -> Result<String, String>),
|
Ternary(fn(&FunctionContext, &str, &str, &str) -> Result<String, String>),
|
||||||
@ -21,6 +22,7 @@ pub(crate) fn get(name: &str) -> Option<Function> {
|
|||||||
"arch" => Nullary(arch),
|
"arch" => Nullary(arch),
|
||||||
"capitalize" => Unary(capitalize),
|
"capitalize" => Unary(capitalize),
|
||||||
"clean" => Unary(clean),
|
"clean" => Unary(clean),
|
||||||
|
"env" => UnaryOpt(env),
|
||||||
"env_var" => Unary(env_var),
|
"env_var" => Unary(env_var),
|
||||||
"env_var_or_default" => Binary(env_var_or_default),
|
"env_var_or_default" => Binary(env_var_or_default),
|
||||||
"error" => Unary(error),
|
"error" => Unary(error),
|
||||||
@ -70,6 +72,7 @@ impl Function {
|
|||||||
match *self {
|
match *self {
|
||||||
Nullary(_) => 0..0,
|
Nullary(_) => 0..0,
|
||||||
Unary(_) => 1..1,
|
Unary(_) => 1..1,
|
||||||
|
UnaryOpt(_) => 1..2,
|
||||||
Binary(_) => 2..2,
|
Binary(_) => 2..2,
|
||||||
BinaryPlus(_) => 2..usize::MAX,
|
BinaryPlus(_) => 2..usize::MAX,
|
||||||
Ternary(_) => 3..3,
|
Ternary(_) => 3..3,
|
||||||
@ -144,6 +147,13 @@ fn env_var_or_default(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn env(context: &FunctionContext, key: &str, default: Option<&str>) -> Result<String, String> {
|
||||||
|
match default {
|
||||||
|
Some(val) => env_var_or_default(context, key, val),
|
||||||
|
None => env_var(context, key),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn error(_context: &FunctionContext, message: &str) -> Result<String, String> {
|
fn error(_context: &FunctionContext, message: &str) -> Result<String, String> {
|
||||||
Err(message.to_owned())
|
Err(message.to_owned())
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,15 @@ impl<'src> Node<'src> for Expression<'src> {
|
|||||||
tree.push_mut(name.lexeme());
|
tree.push_mut(name.lexeme());
|
||||||
tree.push_mut(arg.tree());
|
tree.push_mut(arg.tree());
|
||||||
}
|
}
|
||||||
|
UnaryOpt {
|
||||||
|
name, args: (a, b), ..
|
||||||
|
} => {
|
||||||
|
tree.push_mut(name.lexeme());
|
||||||
|
tree.push_mut(a.tree());
|
||||||
|
if let Some(b) = b.as_ref() {
|
||||||
|
tree.push_mut(b.tree());
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary {
|
Binary {
|
||||||
name, args: [a, b], ..
|
name, args: [a, b], ..
|
||||||
} => {
|
} => {
|
||||||
|
@ -2333,6 +2333,34 @@ mod tests {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error! {
|
||||||
|
name: function_argument_count_too_high_unary_opt,
|
||||||
|
input: "x := env('foo', 'foo', 'foo')",
|
||||||
|
offset: 5,
|
||||||
|
line: 0,
|
||||||
|
column: 5,
|
||||||
|
width: 3,
|
||||||
|
kind: FunctionArgumentCountMismatch {
|
||||||
|
function: "env",
|
||||||
|
found: 3,
|
||||||
|
expected: 1..2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
error! {
|
||||||
|
name: function_argument_count_too_low_unary_opt,
|
||||||
|
input: "x := env()",
|
||||||
|
offset: 5,
|
||||||
|
line: 0,
|
||||||
|
column: 5,
|
||||||
|
width: 3,
|
||||||
|
kind: FunctionArgumentCountMismatch {
|
||||||
|
function: "env",
|
||||||
|
found: 0,
|
||||||
|
expected: 1..2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
error! {
|
error! {
|
||||||
name: function_argument_count_binary,
|
name: function_argument_count_binary,
|
||||||
input: "x := env_var_or_default('foo')",
|
input: "x := env_var_or_default('foo')",
|
||||||
|
@ -225,6 +225,23 @@ impl Expression {
|
|||||||
name: name.lexeme().to_owned(),
|
name: name.lexeme().to_owned(),
|
||||||
arguments: vec![Expression::new(arg)],
|
arguments: vec![Expression::new(arg)],
|
||||||
},
|
},
|
||||||
|
full::Thunk::UnaryOpt {
|
||||||
|
name,
|
||||||
|
args: (a, opt_b),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut arguments = vec![];
|
||||||
|
|
||||||
|
if let Some(b) = opt_b.as_ref() {
|
||||||
|
arguments.push(Expression::new(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.push(Expression::new(a));
|
||||||
|
Expression::Call {
|
||||||
|
name: name.lexeme().to_owned(),
|
||||||
|
arguments,
|
||||||
|
}
|
||||||
|
}
|
||||||
full::Thunk::Binary {
|
full::Thunk::Binary {
|
||||||
name, args: [a, b], ..
|
name, args: [a, b], ..
|
||||||
} => Expression::Call {
|
} => Expression::Call {
|
||||||
|
36
src/thunk.rs
36
src/thunk.rs
@ -14,6 +14,12 @@ pub(crate) enum Thunk<'src> {
|
|||||||
function: fn(&FunctionContext, &str) -> Result<String, String>,
|
function: fn(&FunctionContext, &str) -> Result<String, String>,
|
||||||
arg: Box<Expression<'src>>,
|
arg: Box<Expression<'src>>,
|
||||||
},
|
},
|
||||||
|
UnaryOpt {
|
||||||
|
name: Name<'src>,
|
||||||
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
function: fn(&FunctionContext, &str, Option<&str>) -> Result<String, String>,
|
||||||
|
args: (Box<Expression<'src>>, Box<Option<Expression<'src>>>),
|
||||||
|
},
|
||||||
Binary {
|
Binary {
|
||||||
name: Name<'src>,
|
name: Name<'src>,
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
@ -39,6 +45,7 @@ impl<'src> Thunk<'src> {
|
|||||||
match self {
|
match self {
|
||||||
Self::Nullary { name, .. }
|
Self::Nullary { name, .. }
|
||||||
| Self::Unary { name, .. }
|
| Self::Unary { name, .. }
|
||||||
|
| Self::UnaryOpt { name, .. }
|
||||||
| Self::Binary { name, .. }
|
| Self::Binary { name, .. }
|
||||||
| Self::BinaryPlus { name, .. }
|
| Self::BinaryPlus { name, .. }
|
||||||
| Self::Ternary { name, .. } => name,
|
| Self::Ternary { name, .. } => name,
|
||||||
@ -60,6 +67,18 @@ impl<'src> Thunk<'src> {
|
|||||||
arg: Box::new(arguments.pop().unwrap()),
|
arg: Box::new(arguments.pop().unwrap()),
|
||||||
name,
|
name,
|
||||||
}),
|
}),
|
||||||
|
(Function::UnaryOpt(function), 1..=2) => {
|
||||||
|
let a = Box::new(arguments.remove(0));
|
||||||
|
let b = match arguments.pop() {
|
||||||
|
Some(value) => Box::new(Some(value)),
|
||||||
|
None => Box::new(None),
|
||||||
|
};
|
||||||
|
Ok(Thunk::UnaryOpt {
|
||||||
|
function,
|
||||||
|
args: (a, b),
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
(Function::Binary(function), 2) => {
|
(Function::Binary(function), 2) => {
|
||||||
let b = Box::new(arguments.pop().unwrap());
|
let b = Box::new(arguments.pop().unwrap());
|
||||||
let a = Box::new(arguments.pop().unwrap());
|
let a = Box::new(arguments.pop().unwrap());
|
||||||
@ -105,6 +124,15 @@ impl Display for Thunk<'_> {
|
|||||||
match self {
|
match self {
|
||||||
Nullary { name, .. } => write!(f, "{}()", name.lexeme()),
|
Nullary { name, .. } => write!(f, "{}()", name.lexeme()),
|
||||||
Unary { name, arg, .. } => write!(f, "{}({arg})", name.lexeme()),
|
Unary { name, arg, .. } => write!(f, "{}({arg})", name.lexeme()),
|
||||||
|
UnaryOpt {
|
||||||
|
name, args: (a, b), ..
|
||||||
|
} => {
|
||||||
|
if let Some(b) = b.as_ref() {
|
||||||
|
write!(f, "{}({a}, {b})", name.lexeme())
|
||||||
|
} else {
|
||||||
|
write!(f, "{}({a})", name.lexeme())
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary {
|
Binary {
|
||||||
name, args: [a, b], ..
|
name, args: [a, b], ..
|
||||||
} => write!(f, "{}({a}, {b})", name.lexeme()),
|
} => write!(f, "{}({a}, {b})", name.lexeme()),
|
||||||
@ -139,6 +167,14 @@ impl<'src> Serialize for Thunk<'src> {
|
|||||||
match self {
|
match self {
|
||||||
Self::Nullary { .. } => {}
|
Self::Nullary { .. } => {}
|
||||||
Self::Unary { arg, .. } => seq.serialize_element(&arg)?,
|
Self::Unary { arg, .. } => seq.serialize_element(&arg)?,
|
||||||
|
Self::UnaryOpt {
|
||||||
|
args: (a, opt_b), ..
|
||||||
|
} => {
|
||||||
|
seq.serialize_element(a)?;
|
||||||
|
if let Some(b) = opt_b.as_ref() {
|
||||||
|
seq.serialize_element(b)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Self::Binary { args, .. } => {
|
Self::Binary { args, .. } => {
|
||||||
for arg in args {
|
for arg in args {
|
||||||
seq.serialize_element(arg)?;
|
seq.serialize_element(arg)?;
|
||||||
|
@ -20,6 +20,14 @@ impl<'expression, 'src> Iterator for Variables<'expression, 'src> {
|
|||||||
Expression::Call { thunk } => match thunk {
|
Expression::Call { thunk } => match thunk {
|
||||||
Thunk::Nullary { .. } => {}
|
Thunk::Nullary { .. } => {}
|
||||||
Thunk::Unary { arg, .. } => self.stack.push(arg),
|
Thunk::Unary { arg, .. } => self.stack.push(arg),
|
||||||
|
Thunk::UnaryOpt {
|
||||||
|
args: (a, opt_b), ..
|
||||||
|
} => {
|
||||||
|
self.stack.push(a);
|
||||||
|
if let Some(b) = opt_b.as_ref() {
|
||||||
|
self.stack.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
Thunk::Binary { args, .. } => {
|
Thunk::Binary { args, .. } => {
|
||||||
for arg in args.iter().rev() {
|
for arg in args.iter().rev() {
|
||||||
self.stack.push(arg);
|
self.stack.push(arg);
|
||||||
|
@ -1914,6 +1914,66 @@ test! {
|
|||||||
shell: false,
|
shell: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: env_function_as_env_var,
|
||||||
|
justfile: "
|
||||||
|
foo: (bar env('x'))
|
||||||
|
|
||||||
|
bar arg:
|
||||||
|
echo {{arg}}
|
||||||
|
",
|
||||||
|
args: (),
|
||||||
|
env: { "x": "z", },
|
||||||
|
stdout: "z\n",
|
||||||
|
stderr: "echo z\n",
|
||||||
|
shell: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: env_function_as_env_var_or_default,
|
||||||
|
justfile: "
|
||||||
|
foo: (bar env('x', 'y'))
|
||||||
|
|
||||||
|
bar arg:
|
||||||
|
echo {{arg}}
|
||||||
|
",
|
||||||
|
args: (),
|
||||||
|
env: { "x": "z", },
|
||||||
|
stdout: "z\n",
|
||||||
|
stderr: "echo z\n",
|
||||||
|
shell: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: env_function_as_env_var_with_existing_env_var,
|
||||||
|
justfile: "
|
||||||
|
foo: (bar env('x'))
|
||||||
|
|
||||||
|
bar arg:
|
||||||
|
echo {{arg}}
|
||||||
|
",
|
||||||
|
args: (),
|
||||||
|
env: { "x": "z", },
|
||||||
|
stdout: "z\n",
|
||||||
|
stderr: "echo z\n",
|
||||||
|
shell: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: env_function_as_env_var_or_default_with_existing_env_var,
|
||||||
|
justfile: "
|
||||||
|
foo: (bar env('x', 'y'))
|
||||||
|
|
||||||
|
bar arg:
|
||||||
|
echo {{arg}}
|
||||||
|
",
|
||||||
|
args: (),
|
||||||
|
env: { "x": "z", },
|
||||||
|
stdout: "z\n",
|
||||||
|
stderr: "echo z\n",
|
||||||
|
shell: false,
|
||||||
|
}
|
||||||
|
|
||||||
test! {
|
test! {
|
||||||
name: dependency_argument_backtick,
|
name: dependency_argument_backtick,
|
||||||
justfile: "
|
justfile: "
|
||||||
|
Loading…
Reference in New Issue
Block a user