Add string manipulation functions (#888)
This commit is contained in:
parent
ee3b7714f6
commit
a24c86ed5a
@ -764,6 +764,13 @@ $ just
|
||||
The executable is at: /bin/just
|
||||
```
|
||||
|
||||
==== String Manipulation
|
||||
|
||||
- `uppercase(s)` - Convert `s` to uppercase.
|
||||
- `lowercase(s)` - Convert `s` to lowercase.
|
||||
- `trim(s)` - Remove leading and trailing whitespace from `s`.
|
||||
- `replace(s, from, to)` - Replace all occurrences of `from` in `s` to `to`.
|
||||
|
||||
==== Dotenv Integration
|
||||
|
||||
`just` will load environment variables from a file named `.env`. This file can be located in the same directory as your justfile or in a parent directory. These variables are environment variables, not `just` variables, and so must be accessed using `$VARIABLE_NAME` in recipes and backticks.
|
||||
|
@ -82,6 +82,13 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
|
||||
self.resolve_expression(a)?;
|
||||
self.resolve_expression(b)
|
||||
},
|
||||
Thunk::Ternary {
|
||||
args: [a, b, c], ..
|
||||
} => {
|
||||
self.resolve_expression(a)?;
|
||||
self.resolve_expression(b)?;
|
||||
self.resolve_expression(c)
|
||||
},
|
||||
},
|
||||
Expression::Concatination { lhs, rhs } => {
|
||||
self.resolve_expression(lhs)?;
|
||||
|
@ -105,6 +105,21 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
||||
function: *name,
|
||||
message,
|
||||
}),
|
||||
Ternary {
|
||||
name,
|
||||
function,
|
||||
args: [a, b, c],
|
||||
..
|
||||
} => function(
|
||||
&context,
|
||||
&self.evaluate_expression(a)?,
|
||||
&self.evaluate_expression(b)?,
|
||||
&self.evaluate_expression(c)?,
|
||||
)
|
||||
.map_err(|message| RuntimeError::FunctionCall {
|
||||
function: *name,
|
||||
message,
|
||||
}),
|
||||
}
|
||||
},
|
||||
Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.clone()),
|
||||
|
@ -5,6 +5,7 @@ pub(crate) enum Function {
|
||||
Nullary(fn(&FunctionContext) -> Result<String, String>),
|
||||
Unary(fn(&FunctionContext, &str) -> Result<String, String>),
|
||||
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
|
||||
Ternary(fn(&FunctionContext, &str, &str, &str) -> Result<String, String>),
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
@ -21,9 +22,13 @@ lazy_static! {
|
||||
("just_executable", Nullary(just_executable)),
|
||||
("justfile", Nullary(justfile)),
|
||||
("justfile_directory", Nullary(justfile_directory)),
|
||||
("lowercase", Unary(lowercase)),
|
||||
("os", Nullary(os)),
|
||||
("os_family", Nullary(os_family)),
|
||||
("parent_directory", Unary(parent_directory)),
|
||||
("replace", Ternary(replace)),
|
||||
("trim", Unary(trim)),
|
||||
("uppercase", Unary(uppercase)),
|
||||
("without_extension", Unary(without_extension)),
|
||||
]
|
||||
.into_iter()
|
||||
@ -36,6 +41,7 @@ impl Function {
|
||||
Nullary(_) => 0,
|
||||
Unary(_) => 1,
|
||||
Binary(_) => 2,
|
||||
Ternary(_) => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,6 +170,10 @@ fn justfile_directory(context: &FunctionContext) -> Result<String, String> {
|
||||
})
|
||||
}
|
||||
|
||||
fn lowercase(_context: &FunctionContext, s: &str) -> Result<String, String> {
|
||||
Ok(s.to_lowercase())
|
||||
}
|
||||
|
||||
fn os(_context: &FunctionContext) -> Result<String, String> {
|
||||
Ok(target::os().to_owned())
|
||||
}
|
||||
@ -179,6 +189,18 @@ fn parent_directory(_context: &FunctionContext, path: &str) -> Result<String, St
|
||||
.ok_or_else(|| format!("Could not extract parent directory from `{}`", path))
|
||||
}
|
||||
|
||||
fn replace(_context: &FunctionContext, s: &str, from: &str, to: &str) -> Result<String, String> {
|
||||
Ok(s.replace(from, to))
|
||||
}
|
||||
|
||||
fn trim(_context: &FunctionContext, s: &str) -> Result<String, String> {
|
||||
Ok(s.trim().to_owned())
|
||||
}
|
||||
|
||||
fn uppercase(_context: &FunctionContext, s: &str) -> Result<String, String> {
|
||||
Ok(s.to_uppercase())
|
||||
}
|
||||
|
||||
fn without_extension(_context: &FunctionContext, path: &str) -> Result<String, String> {
|
||||
let parent = Utf8Path::new(path)
|
||||
.parent()
|
||||
|
10
src/node.rs
10
src/node.rs
@ -90,6 +90,16 @@ impl<'src> Node<'src> for Expression<'src> {
|
||||
tree.push_mut(a.tree());
|
||||
tree.push_mut(b.tree());
|
||||
},
|
||||
Ternary {
|
||||
name,
|
||||
args: [a, b, c],
|
||||
..
|
||||
} => {
|
||||
tree.push_mut(name.lexeme());
|
||||
tree.push_mut(a.tree());
|
||||
tree.push_mut(b.tree());
|
||||
tree.push_mut(c.tree());
|
||||
},
|
||||
}
|
||||
|
||||
tree
|
||||
|
@ -230,6 +230,14 @@ impl Expression {
|
||||
name: name.lexeme().to_owned(),
|
||||
arguments: vec![Expression::new(a), Expression::new(b)],
|
||||
},
|
||||
full::Thunk::Ternary {
|
||||
name,
|
||||
args: [a, b, c],
|
||||
..
|
||||
} => Expression::Call {
|
||||
name: name.lexeme().to_owned(),
|
||||
arguments: vec![Expression::new(a), Expression::new(b), Expression::new(c)],
|
||||
},
|
||||
},
|
||||
Concatination { lhs, rhs } => Expression::Concatination {
|
||||
lhs: Box::new(Expression::new(lhs)),
|
||||
|
21
src/thunk.rs
21
src/thunk.rs
@ -20,6 +20,12 @@ pub(crate) enum Thunk<'src> {
|
||||
function: fn(&FunctionContext, &str, &str) -> Result<String, String>,
|
||||
args: [Box<Expression<'src>>; 2],
|
||||
},
|
||||
Ternary {
|
||||
name: Name<'src>,
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
function: fn(&FunctionContext, &str, &str, &str) -> Result<String, String>,
|
||||
args: [Box<Expression<'src>>; 3],
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> Thunk<'src> {
|
||||
@ -47,6 +53,16 @@ impl<'src> Thunk<'src> {
|
||||
name,
|
||||
})
|
||||
},
|
||||
(Function::Ternary(function), 3) => {
|
||||
let c = Box::new(arguments.pop().unwrap());
|
||||
let b = Box::new(arguments.pop().unwrap());
|
||||
let a = Box::new(arguments.pop().unwrap());
|
||||
Ok(Thunk::Ternary {
|
||||
function: *function,
|
||||
args: [a, b, c],
|
||||
name,
|
||||
})
|
||||
},
|
||||
_ => Err(
|
||||
name.error(CompilationErrorKind::FunctionArgumentCountMismatch {
|
||||
function: name.lexeme(),
|
||||
@ -72,6 +88,11 @@ impl Display for Thunk<'_> {
|
||||
Binary {
|
||||
name, args: [a, b], ..
|
||||
} => write!(f, "{}({}, {})", name.lexeme(), a, b),
|
||||
Ternary {
|
||||
name,
|
||||
args: [a, b, c],
|
||||
..
|
||||
} => write!(f, "{}({}, {}, {})", name.lexeme(), a, b, c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -262,3 +262,43 @@ test! {
|
||||
stdout: "b\n",
|
||||
stderr: "echo b\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: uppercase,
|
||||
justfile: "
|
||||
foo:
|
||||
echo {{ uppercase('bar') }}
|
||||
",
|
||||
stdout: "BAR\n",
|
||||
stderr: "echo BAR\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: lowercase,
|
||||
justfile: "
|
||||
foo:
|
||||
echo {{ lowercase('BAR') }}
|
||||
",
|
||||
stdout: "bar\n",
|
||||
stderr: "echo bar\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: trim,
|
||||
justfile: "
|
||||
foo:
|
||||
echo {{ trim(' bar ') }}
|
||||
",
|
||||
stdout: "bar\n",
|
||||
stderr: "echo bar\n",
|
||||
}
|
||||
|
||||
test! {
|
||||
name: replace,
|
||||
justfile: "
|
||||
foo:
|
||||
echo {{ replace('barbarbar', 'bar', 'foo') }}
|
||||
",
|
||||
stdout: "foofoofoo\n",
|
||||
stderr: "echo foofoofoo\n",
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user