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
|
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.
|
||||||
|
@ -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)?;
|
||||||
|
@ -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()),
|
||||||
|
@ -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()
|
||||||
|
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(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
|
||||||
|
@ -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)),
|
||||||
|
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>,
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user