Add string manipulation functions (#888)

This commit is contained in:
Liam 2021-07-03 15:39:45 -04:00 committed by GitHub
parent ee3b7714f6
commit a24c86ed5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 0 deletions

View File

@ -764,6 +764,13 @@ $ just
The executable is at: /bin/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 ==== 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. `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.

View File

@ -82,6 +82,13 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
self.resolve_expression(a)?; self.resolve_expression(a)?;
self.resolve_expression(b) 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 } => { Expression::Concatination { lhs, rhs } => {
self.resolve_expression(lhs)?; self.resolve_expression(lhs)?;

View File

@ -105,6 +105,21 @@ impl<'src, 'run> Evaluator<'src, 'run> {
function: *name, function: *name,
message, 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()), Expression::StringLiteral { string_literal } => Ok(string_literal.cooked.clone()),

View File

@ -5,6 +5,7 @@ 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>),
Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>), Binary(fn(&FunctionContext, &str, &str) -> Result<String, String>),
Ternary(fn(&FunctionContext, &str, &str, &str) -> Result<String, String>),
} }
lazy_static! { lazy_static! {
@ -21,9 +22,13 @@ lazy_static! {
("just_executable", Nullary(just_executable)), ("just_executable", Nullary(just_executable)),
("justfile", Nullary(justfile)), ("justfile", Nullary(justfile)),
("justfile_directory", Nullary(justfile_directory)), ("justfile_directory", Nullary(justfile_directory)),
("lowercase", Unary(lowercase)),
("os", Nullary(os)), ("os", Nullary(os)),
("os_family", Nullary(os_family)), ("os_family", Nullary(os_family)),
("parent_directory", Unary(parent_directory)), ("parent_directory", Unary(parent_directory)),
("replace", Ternary(replace)),
("trim", Unary(trim)),
("uppercase", Unary(uppercase)),
("without_extension", Unary(without_extension)), ("without_extension", Unary(without_extension)),
] ]
.into_iter() .into_iter()
@ -36,6 +41,7 @@ impl Function {
Nullary(_) => 0, Nullary(_) => 0,
Unary(_) => 1, Unary(_) => 1,
Binary(_) => 2, 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> { fn os(_context: &FunctionContext) -> Result<String, String> {
Ok(target::os().to_owned()) 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)) .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> { fn without_extension(_context: &FunctionContext, path: &str) -> Result<String, String> {
let parent = Utf8Path::new(path) let parent = Utf8Path::new(path)
.parent() .parent()

View File

@ -90,6 +90,16 @@ impl<'src> Node<'src> for Expression<'src> {
tree.push_mut(a.tree()); tree.push_mut(a.tree());
tree.push_mut(b.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 tree

View File

@ -230,6 +230,14 @@ impl Expression {
name: name.lexeme().to_owned(), name: name.lexeme().to_owned(),
arguments: vec![Expression::new(a), Expression::new(b)], 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 { Concatination { lhs, rhs } => Expression::Concatination {
lhs: Box::new(Expression::new(lhs)), lhs: Box::new(Expression::new(lhs)),

View File

@ -20,6 +20,12 @@ pub(crate) enum Thunk<'src> {
function: fn(&FunctionContext, &str, &str) -> Result<String, String>, function: fn(&FunctionContext, &str, &str) -> Result<String, String>,
args: [Box<Expression<'src>>; 2], 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> { impl<'src> Thunk<'src> {
@ -47,6 +53,16 @@ impl<'src> Thunk<'src> {
name, 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( _ => Err(
name.error(CompilationErrorKind::FunctionArgumentCountMismatch { name.error(CompilationErrorKind::FunctionArgumentCountMismatch {
function: name.lexeme(), function: name.lexeme(),
@ -72,6 +88,11 @@ impl Display for Thunk<'_> {
Binary { Binary {
name, args: [a, b], .. name, args: [a, b], ..
} => write!(f, "{}({}, {})", name.lexeme(), a, b), } => write!(f, "{}({}, {})", name.lexeme(), a, b),
Ternary {
name,
args: [a, b, c],
..
} => write!(f, "{}({}, {}, {})", name.lexeme(), a, b, c),
} }
} }
} }

View File

@ -262,3 +262,43 @@ test! {
stdout: "b\n", stdout: "b\n",
stderr: "echo 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",
}