From a24c86ed5a07ef9bf6b487ce1d85ab411bf5e0f1 Mon Sep 17 00:00:00 2001 From: Liam <31192478+terror@users.noreply.github.com> Date: Sat, 3 Jul 2021 15:39:45 -0400 Subject: [PATCH] Add string manipulation functions (#888) --- README.adoc | 7 +++++++ src/assignment_resolver.rs | 7 +++++++ src/evaluator.rs | 15 ++++++++++++++ src/function.rs | 22 +++++++++++++++++++++ src/node.rs | 10 ++++++++++ src/summary.rs | 8 ++++++++ src/thunk.rs | 21 ++++++++++++++++++++ tests/functions.rs | 40 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 130 insertions(+) diff --git a/README.adoc b/README.adoc index e82689e..e7fa32d 100644 --- a/README.adoc +++ b/README.adoc @@ -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. diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs index e6db4b2..9192850 100644 --- a/src/assignment_resolver.rs +++ b/src/assignment_resolver.rs @@ -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)?; diff --git a/src/evaluator.rs b/src/evaluator.rs index f259cab..e19a1cc 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -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()), diff --git a/src/function.rs b/src/function.rs index c0cc9db..80394c1 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,6 +5,7 @@ pub(crate) enum Function { Nullary(fn(&FunctionContext) -> Result), Unary(fn(&FunctionContext, &str) -> Result), Binary(fn(&FunctionContext, &str, &str) -> Result), + Ternary(fn(&FunctionContext, &str, &str, &str) -> Result), } 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 { }) } +fn lowercase(_context: &FunctionContext, s: &str) -> Result { + Ok(s.to_lowercase()) +} + fn os(_context: &FunctionContext) -> Result { Ok(target::os().to_owned()) } @@ -179,6 +189,18 @@ fn parent_directory(_context: &FunctionContext, path: &str) -> Result Result { + Ok(s.replace(from, to)) +} + +fn trim(_context: &FunctionContext, s: &str) -> Result { + Ok(s.trim().to_owned()) +} + +fn uppercase(_context: &FunctionContext, s: &str) -> Result { + Ok(s.to_uppercase()) +} + fn without_extension(_context: &FunctionContext, path: &str) -> Result { let parent = Utf8Path::new(path) .parent() diff --git a/src/node.rs b/src/node.rs index aebfbbf..ee66667 100644 --- a/src/node.rs +++ b/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 diff --git a/src/summary.rs b/src/summary.rs index 9e4c760..fbee456 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -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)), diff --git a/src/thunk.rs b/src/thunk.rs index bbc45c7..5a50483 100644 --- a/src/thunk.rs +++ b/src/thunk.rs @@ -20,6 +20,12 @@ pub(crate) enum Thunk<'src> { function: fn(&FunctionContext, &str, &str) -> Result, args: [Box>; 2], }, + Ternary { + name: Name<'src>, + #[derivative(Debug = "ignore", PartialEq = "ignore")] + function: fn(&FunctionContext, &str, &str, &str) -> Result, + args: [Box>; 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), } } } diff --git a/tests/functions.rs b/tests/functions.rs index d4bcd27..dc8be3e 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -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", +}