Add /
operator (#1237)
This commit is contained in:
parent
9c719abf91
commit
a46be41699
@ -66,6 +66,7 @@ setting : 'set' 'dotenv-load' boolean?
|
||||
boolean : ':=' ('true' | 'false')
|
||||
|
||||
expression : 'if' condition '{' expression '}' 'else' '{' expression '}'
|
||||
| value '/' expression
|
||||
| value '+' expression
|
||||
| value
|
||||
|
||||
|
38
README.md
38
README.md
@ -803,11 +803,12 @@ Starting server with database localhost:6379 on port 1337…
|
||||
|
||||
### Variables and Substitution
|
||||
|
||||
Variables, strings, concatenation, and substitution using `{{…}}` are supported:
|
||||
Variables, strings, concatenation, path joining, and substitution using `{{…}}` are supported:
|
||||
|
||||
```make
|
||||
tmpdir := `mktemp`
|
||||
version := "0.2.7"
|
||||
tardir := "awesomesauce-" + version
|
||||
tardir := tmpdir / "awesomesauce-" + version
|
||||
tarball := tardir + ".tar.gz"
|
||||
|
||||
publish:
|
||||
@ -819,6 +820,33 @@ publish:
|
||||
rm -rf {{tarball}} {{tardir}}
|
||||
```
|
||||
|
||||
#### Joining Paths
|
||||
|
||||
The `/` operator can be used to join two strings with a slash:
|
||||
|
||||
```make
|
||||
foo := "a" / "b"
|
||||
```
|
||||
|
||||
```
|
||||
$ just --evaluate foo
|
||||
a/b
|
||||
```
|
||||
|
||||
Note that a `/` is added even if one is already present:
|
||||
|
||||
```make
|
||||
foo := "a/"
|
||||
bar := foo / "b"
|
||||
```
|
||||
|
||||
```
|
||||
$ just --evaluate bar
|
||||
a//b
|
||||
```
|
||||
|
||||
The `/` operator uses the `/` character, even on Windows. Thus, using the `/` operator should be avoided with paths that use universal naming convention (UNC), i.e., those that start with `\?`, since forward slashes are not supported with UNC paths.
|
||||
|
||||
#### Escaping `{{`
|
||||
|
||||
To write a recipe containing `{{`, use `{{{{`:
|
||||
@ -1064,7 +1092,7 @@ These functions can fail, for example if a path does not have an extension, whic
|
||||
|
||||
##### Infallible
|
||||
|
||||
- `join(a, b…)` - Join path `a` with path `b`. `join("foo/bar", "baz")` is `foo/bar/baz`. Accepts two or more arguments.
|
||||
- `join(a, b…)` - *This function uses `/` on Unix and `\` on Windows, which can be lead to unwanted behavior. The `/` operator, e.g., `a / b`, which always uses `/`, should be considered as a replacement unless `\`s are specifically desired on Windows.* Join path `a` with path `b`. `join("foo/bar", "baz")` is `foo/bar/baz`. Accepts two or more arguments.
|
||||
|
||||
- `clean(path)` - Simplify `path` by removing extra path separators, intermediate `.` components, and `..` where possible. `clean("foo//bar")` is `foo/bar`, `clean("foo/..")` is `.`, `clean("foo/./bar")` is `foo/bar`.
|
||||
|
||||
@ -1360,12 +1388,12 @@ Testing server:unit…
|
||||
./test --tests unit server
|
||||
```
|
||||
|
||||
Default values may be arbitrary expressions, but concatenations must be parenthesized:
|
||||
Default values may be arbitrary expressions, but concatenations or path joins must be parenthesized:
|
||||
|
||||
```make
|
||||
arch := "wasm"
|
||||
|
||||
test triple=(arch + "-unknown-unknown"):
|
||||
test triple=(arch + "-unknown-unknown") input=(arch / "input.dat"):
|
||||
./test {{triple}}
|
||||
```
|
||||
|
||||
|
3
justfile
3
justfile
@ -58,9 +58,6 @@ check: fmt clippy test forbid
|
||||
git diff --no-ext-diff --quiet --exit-code
|
||||
VERSION=`sed -En 's/version[[:space:]]*=[[:space:]]*"([^"]+)"/\1/p' Cargo.toml | head -1`
|
||||
grep "^\[$VERSION\]" CHANGELOG.md
|
||||
cargo +nightly generate-lockfile -Z minimal-versions
|
||||
cargo test
|
||||
git checkout Cargo.lock
|
||||
|
||||
# publish current GitHub master branch
|
||||
publish:
|
||||
|
@ -101,7 +101,7 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
|
||||
self.resolve_expression(c)
|
||||
}
|
||||
},
|
||||
Expression::Concatenation { lhs, rhs } => {
|
||||
Expression::Concatenation { lhs, rhs } | Expression::Join { lhs, rhs } => {
|
||||
self.resolve_expression(lhs)?;
|
||||
self.resolve_expression(rhs)
|
||||
}
|
||||
|
@ -176,6 +176,9 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
||||
}
|
||||
}
|
||||
Expression::Group { contents } => self.evaluate_expression(contents),
|
||||
Expression::Join { lhs, rhs } => {
|
||||
Ok(self.evaluate_expression(lhs)? + "/" + &self.evaluate_expression(rhs)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,11 @@ pub(crate) enum Expression<'src> {
|
||||
},
|
||||
/// `(contents)`
|
||||
Group { contents: Box<Expression<'src>> },
|
||||
/// `lhs / rhs`
|
||||
Join {
|
||||
lhs: Box<Expression<'src>>,
|
||||
rhs: Box<Expression<'src>>,
|
||||
},
|
||||
/// `"string_literal"` or `'string_literal'`
|
||||
StringLiteral { string_literal: StringLiteral<'src> },
|
||||
/// `variable`
|
||||
@ -46,6 +51,7 @@ impl<'src> Display for Expression<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
Expression::Backtick { token, .. } => write!(f, "{}", token.lexeme()),
|
||||
Expression::Join { lhs, rhs } => write!(f, "{} / {}", lhs, rhs),
|
||||
Expression::Concatenation { lhs, rhs } => write!(f, "{} + {}", lhs, rhs),
|
||||
Expression::Conditional {
|
||||
lhs,
|
||||
@ -86,6 +92,13 @@ impl<'src> Serialize for Expression<'src> {
|
||||
seq.serialize_element(rhs)?;
|
||||
seq.end()
|
||||
}
|
||||
Self::Join { lhs, rhs } => {
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
seq.serialize_element("join")?;
|
||||
seq.serialize_element(lhs)?;
|
||||
seq.serialize_element(rhs)?;
|
||||
seq.end()
|
||||
}
|
||||
Self::Conditional {
|
||||
lhs,
|
||||
rhs,
|
||||
|
@ -485,6 +485,7 @@ impl<'src> Lexer<'src> {
|
||||
'*' => self.lex_single(Asterisk),
|
||||
'+' => self.lex_single(Plus),
|
||||
',' => self.lex_single(Comma),
|
||||
'/' => self.lex_single(Slash),
|
||||
':' => self.lex_colon(),
|
||||
'=' => self.lex_choices('=', &[('=', EqualsEquals), ('~', EqualsTilde)], Equals),
|
||||
'@' => self.lex_single(At),
|
||||
@ -942,6 +943,7 @@ mod tests {
|
||||
ParenL => "(",
|
||||
ParenR => ")",
|
||||
Plus => "+",
|
||||
Slash => "/",
|
||||
Whitespace => " ",
|
||||
|
||||
// Empty lexemes
|
||||
|
@ -118,6 +118,7 @@ impl<'src> Node<'src> for Expression<'src> {
|
||||
} => Tree::string(cooked),
|
||||
Expression::Backtick { contents, .. } => Tree::atom("backtick").push(Tree::string(contents)),
|
||||
Expression::Group { contents } => Tree::List(vec![contents.tree()]),
|
||||
Expression::Join { lhs, rhs } => Tree::atom("/").push(lhs.tree()).push(rhs.tree()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -408,7 +408,11 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
||||
} else {
|
||||
let value = self.parse_value()?;
|
||||
|
||||
if self.accepted(Plus)? {
|
||||
if self.accepted(Slash)? {
|
||||
let lhs = Box::new(value);
|
||||
let rhs = Box::new(self.parse_expression()?);
|
||||
Expression::Join { lhs, rhs }
|
||||
} else if self.accepted(Plus)? {
|
||||
let lhs = Box::new(value);
|
||||
let rhs = Box::new(self.parse_expression()?);
|
||||
Expression::Concatenation { lhs, rhs }
|
||||
|
@ -196,6 +196,10 @@ pub enum Expression {
|
||||
otherwise: Box<Expression>,
|
||||
operator: ConditionalOperator,
|
||||
},
|
||||
Join {
|
||||
lhs: Box<Expression>,
|
||||
rhs: Box<Expression>,
|
||||
},
|
||||
String {
|
||||
text: String,
|
||||
},
|
||||
@ -253,6 +257,10 @@ impl Expression {
|
||||
lhs: Box::new(Expression::new(lhs)),
|
||||
rhs: Box::new(Expression::new(rhs)),
|
||||
},
|
||||
Join { lhs, rhs } => Expression::Join {
|
||||
lhs: Box::new(Expression::new(lhs)),
|
||||
rhs: Box::new(Expression::new(rhs)),
|
||||
},
|
||||
Conditional {
|
||||
lhs,
|
||||
operator,
|
||||
|
@ -30,6 +30,7 @@ pub(crate) enum TokenKind {
|
||||
ParenL,
|
||||
ParenR,
|
||||
Plus,
|
||||
Slash,
|
||||
StringToken,
|
||||
Text,
|
||||
Unspecified,
|
||||
@ -71,6 +72,7 @@ impl Display for TokenKind {
|
||||
ParenL => "'('",
|
||||
ParenR => "')'",
|
||||
Plus => "'+'",
|
||||
Slash => "'/'",
|
||||
StringToken => "string",
|
||||
Text => "command text",
|
||||
Unspecified => "unspecified",
|
||||
|
@ -53,7 +53,7 @@ impl<'expression, 'src> Iterator for Variables<'expression, 'src> {
|
||||
self.stack.push(lhs);
|
||||
}
|
||||
Expression::Variable { name, .. } => return Some(name.token()),
|
||||
Expression::Concatenation { lhs, rhs } => {
|
||||
Expression::Concatenation { lhs, rhs } | Expression::Join { lhs, rhs } => {
|
||||
self.stack.push(rhs);
|
||||
self.stack.push(lhs);
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ test! {
|
||||
",
|
||||
stdout: "",
|
||||
stderr: "
|
||||
error: Expected '!=', '==', '=~', or '+', but found identifier
|
||||
error: Expected '!=', '==', '=~', '+', or '/', but found identifier
|
||||
|
|
||||
1 | a := if '' a '' { '' } else { b }
|
||||
| ^
|
||||
|
@ -43,7 +43,7 @@ test! {
|
||||
}
|
||||
|
||||
test! {
|
||||
name: evaluate_single,
|
||||
name: evaluate_single_free,
|
||||
justfile: "
|
||||
a := 'x'
|
||||
b := 'y'
|
||||
|
@ -69,6 +69,7 @@ mod search;
|
||||
mod shebang;
|
||||
mod shell;
|
||||
mod show;
|
||||
mod slash_operator;
|
||||
mod string;
|
||||
mod sublime_syntax;
|
||||
mod subsequents;
|
||||
|
54
tests/slash_operator.rs
Normal file
54
tests/slash_operator.rs
Normal file
@ -0,0 +1,54 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn once() {
|
||||
Test::new()
|
||||
.justfile("x := 'a' / 'b'")
|
||||
.args(&["--evaluate", "x"])
|
||||
.stdout("a/b")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn twice() {
|
||||
Test::new()
|
||||
.justfile("x := 'a' / 'b' / 'c'")
|
||||
.args(&["--evaluate", "x"])
|
||||
.stdout("a/b/c")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_un_parenthesized() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo x='a' / 'b':
|
||||
echo {{x}}
|
||||
",
|
||||
)
|
||||
.stderr(
|
||||
"
|
||||
error: Expected '*', ':', '$', identifier, or '+', but found '/'
|
||||
|
|
||||
1 | foo x='a' / 'b':
|
||||
| ^
|
||||
",
|
||||
)
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_parenthesized() {
|
||||
Test::new()
|
||||
.justfile(
|
||||
"
|
||||
foo x=('a' / 'b'):
|
||||
echo {{x}}
|
||||
",
|
||||
)
|
||||
.stderr("echo a/b\n")
|
||||
.stdout("a/b\n")
|
||||
.run();
|
||||
}
|
Loading…
Reference in New Issue
Block a user