Allow constructing absolute paths with /
operator (#1320)
This commit is contained in:
parent
baaa8cb194
commit
154930cc8a
11
README.md
11
README.md
@ -924,6 +924,17 @@ $ just --evaluate bar
|
|||||||
a//b
|
a//b
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Absolute paths can also be constructed:
|
||||||
|
|
||||||
|
```make
|
||||||
|
foo := / "b"
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ just --evaluate foo
|
||||||
|
/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.
|
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 `{{`
|
#### Escaping `{{`
|
||||||
|
@ -101,10 +101,16 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
|
|||||||
self.resolve_expression(c)
|
self.resolve_expression(c)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Expression::Concatenation { lhs, rhs } | Expression::Join { lhs, rhs } => {
|
Expression::Concatenation { lhs, rhs } => {
|
||||||
self.resolve_expression(lhs)?;
|
self.resolve_expression(lhs)?;
|
||||||
self.resolve_expression(rhs)
|
self.resolve_expression(rhs)
|
||||||
}
|
}
|
||||||
|
Expression::Join { lhs, rhs } => {
|
||||||
|
if let Some(lhs) = lhs {
|
||||||
|
self.resolve_expression(lhs)?;
|
||||||
|
}
|
||||||
|
self.resolve_expression(rhs)
|
||||||
|
}
|
||||||
Expression::Conditional {
|
Expression::Conditional {
|
||||||
lhs,
|
lhs,
|
||||||
rhs,
|
rhs,
|
||||||
|
@ -176,9 +176,11 @@ impl<'src, 'run> Evaluator<'src, 'run> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Group { contents } => self.evaluate_expression(contents),
|
Expression::Group { contents } => self.evaluate_expression(contents),
|
||||||
Expression::Join { lhs, rhs } => {
|
Expression::Join { lhs: None, rhs } => Ok("/".to_string() + &self.evaluate_expression(rhs)?),
|
||||||
Ok(self.evaluate_expression(lhs)? + "/" + &self.evaluate_expression(rhs)?)
|
Expression::Join {
|
||||||
}
|
lhs: Some(lhs),
|
||||||
|
rhs,
|
||||||
|
} => Ok(self.evaluate_expression(lhs)? + "/" + &self.evaluate_expression(rhs)?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ pub(crate) enum Expression<'src> {
|
|||||||
Group { contents: Box<Expression<'src>> },
|
Group { contents: Box<Expression<'src>> },
|
||||||
/// `lhs / rhs`
|
/// `lhs / rhs`
|
||||||
Join {
|
Join {
|
||||||
lhs: Box<Expression<'src>>,
|
lhs: Option<Box<Expression<'src>>>,
|
||||||
rhs: Box<Expression<'src>>,
|
rhs: Box<Expression<'src>>,
|
||||||
},
|
},
|
||||||
/// `"string_literal"` or `'string_literal'`
|
/// `"string_literal"` or `'string_literal'`
|
||||||
@ -51,7 +51,11 @@ impl<'src> Display for Expression<'src> {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||||
match self {
|
match self {
|
||||||
Expression::Backtick { token, .. } => write!(f, "{}", token.lexeme()),
|
Expression::Backtick { token, .. } => write!(f, "{}", token.lexeme()),
|
||||||
Expression::Join { lhs, rhs } => write!(f, "{} / {}", lhs, rhs),
|
Expression::Join { lhs: None, rhs } => write!(f, "/ {}", rhs),
|
||||||
|
Expression::Join {
|
||||||
|
lhs: Some(lhs),
|
||||||
|
rhs,
|
||||||
|
} => write!(f, "{} / {}", lhs, rhs),
|
||||||
Expression::Concatenation { lhs, rhs } => write!(f, "{} + {}", lhs, rhs),
|
Expression::Concatenation { lhs, rhs } => write!(f, "{} + {}", lhs, rhs),
|
||||||
Expression::Conditional {
|
Expression::Conditional {
|
||||||
lhs,
|
lhs,
|
||||||
|
@ -118,7 +118,11 @@ impl<'src> Node<'src> for Expression<'src> {
|
|||||||
} => Tree::string(cooked),
|
} => Tree::string(cooked),
|
||||||
Expression::Backtick { contents, .. } => Tree::atom("backtick").push(Tree::string(contents)),
|
Expression::Backtick { contents, .. } => Tree::atom("backtick").push(Tree::string(contents)),
|
||||||
Expression::Group { contents } => Tree::List(vec![contents.tree()]),
|
Expression::Group { contents } => Tree::List(vec![contents.tree()]),
|
||||||
Expression::Join { lhs, rhs } => Tree::atom("/").push(lhs.tree()).push(rhs.tree()),
|
Expression::Join { lhs: None, rhs } => Tree::atom("/").push(rhs.tree()),
|
||||||
|
Expression::Join {
|
||||||
|
lhs: Some(lhs),
|
||||||
|
rhs,
|
||||||
|
} => Tree::atom("/").push(lhs.tree()).push(rhs.tree()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,11 +406,15 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
|
|
||||||
let expression = if self.accepted_keyword(Keyword::If)? {
|
let expression = if self.accepted_keyword(Keyword::If)? {
|
||||||
self.parse_conditional()?
|
self.parse_conditional()?
|
||||||
|
} else if self.accepted(Slash)? {
|
||||||
|
let lhs = None;
|
||||||
|
let rhs = Box::new(self.parse_expression()?);
|
||||||
|
Expression::Join { lhs, rhs }
|
||||||
} else {
|
} else {
|
||||||
let value = self.parse_value()?;
|
let value = self.parse_value()?;
|
||||||
|
|
||||||
if self.accepted(Slash)? {
|
if self.accepted(Slash)? {
|
||||||
let lhs = Box::new(value);
|
let lhs = Some(Box::new(value));
|
||||||
let rhs = Box::new(self.parse_expression()?);
|
let rhs = Box::new(self.parse_expression()?);
|
||||||
Expression::Join { lhs, rhs }
|
Expression::Join { lhs, rhs }
|
||||||
} else if self.accepted(Plus)? {
|
} else if self.accepted(Plus)? {
|
||||||
@ -1991,6 +1995,7 @@ mod tests {
|
|||||||
Identifier,
|
Identifier,
|
||||||
ParenL,
|
ParenL,
|
||||||
ParenR,
|
ParenR,
|
||||||
|
Slash,
|
||||||
StringToken,
|
StringToken,
|
||||||
],
|
],
|
||||||
found: Eof,
|
found: Eof,
|
||||||
@ -2010,6 +2015,7 @@ mod tests {
|
|||||||
Identifier,
|
Identifier,
|
||||||
ParenL,
|
ParenL,
|
||||||
ParenR,
|
ParenR,
|
||||||
|
Slash,
|
||||||
StringToken,
|
StringToken,
|
||||||
],
|
],
|
||||||
found: InterpolationEnd,
|
found: InterpolationEnd,
|
||||||
|
@ -197,7 +197,7 @@ pub enum Expression {
|
|||||||
operator: ConditionalOperator,
|
operator: ConditionalOperator,
|
||||||
},
|
},
|
||||||
Join {
|
Join {
|
||||||
lhs: Box<Expression>,
|
lhs: Option<Box<Expression>>,
|
||||||
rhs: Box<Expression>,
|
rhs: Box<Expression>,
|
||||||
},
|
},
|
||||||
String {
|
String {
|
||||||
@ -258,7 +258,7 @@ impl Expression {
|
|||||||
rhs: Box::new(Expression::new(rhs)),
|
rhs: Box::new(Expression::new(rhs)),
|
||||||
},
|
},
|
||||||
Join { lhs, rhs } => Expression::Join {
|
Join { lhs, rhs } => Expression::Join {
|
||||||
lhs: Box::new(Expression::new(lhs)),
|
lhs: lhs.as_ref().map(|lhs| Box::new(Expression::new(lhs))),
|
||||||
rhs: Box::new(Expression::new(rhs)),
|
rhs: Box::new(Expression::new(rhs)),
|
||||||
},
|
},
|
||||||
Conditional {
|
Conditional {
|
||||||
|
@ -53,10 +53,16 @@ impl<'expression, 'src> Iterator for Variables<'expression, 'src> {
|
|||||||
self.stack.push(lhs);
|
self.stack.push(lhs);
|
||||||
}
|
}
|
||||||
Expression::Variable { name, .. } => return Some(name.token()),
|
Expression::Variable { name, .. } => return Some(name.token()),
|
||||||
Expression::Concatenation { lhs, rhs } | Expression::Join { lhs, rhs } => {
|
Expression::Concatenation { lhs, rhs } => {
|
||||||
self.stack.push(rhs);
|
self.stack.push(rhs);
|
||||||
self.stack.push(lhs);
|
self.stack.push(lhs);
|
||||||
}
|
}
|
||||||
|
Expression::Join { lhs, rhs } => {
|
||||||
|
self.stack.push(rhs);
|
||||||
|
if let Some(lhs) = lhs {
|
||||||
|
self.stack.push(lhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
Expression::Group { contents } => {
|
Expression::Group { contents } => {
|
||||||
self.stack.push(contents);
|
self.stack.push(contents);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,45 @@ fn twice() {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_lhs_once() {
|
||||||
|
Test::new()
|
||||||
|
.justfile("x := / 'a'")
|
||||||
|
.args(&["--evaluate", "x"])
|
||||||
|
.stdout("/a")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_lhs_twice() {
|
||||||
|
Test::new()
|
||||||
|
.justfile("x := / 'a' / 'b'")
|
||||||
|
.args(&["--evaluate", "x"])
|
||||||
|
.stdout("/a/b")
|
||||||
|
.run();
|
||||||
|
Test::new()
|
||||||
|
.justfile("x := // 'a'")
|
||||||
|
.args(&["--evaluate", "x"])
|
||||||
|
.stdout("//a")
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_rhs_once() {
|
||||||
|
Test::new()
|
||||||
|
.justfile("x := 'a' /")
|
||||||
|
.stderr(
|
||||||
|
"
|
||||||
|
error: Expected backtick, identifier, '(', '/', or string, but found end of file
|
||||||
|
|
|
||||||
|
1 | x := 'a' /
|
||||||
|
| ^
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.status(EXIT_FAILURE)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_un_parenthesized() {
|
fn default_un_parenthesized() {
|
||||||
Test::new()
|
Test::new()
|
||||||
@ -39,6 +78,27 @@ fn default_un_parenthesized() {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_lhs_un_parenthesized() {
|
||||||
|
Test::new()
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
foo x=/ 'a' / 'b':
|
||||||
|
echo {{x}}
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.stderr(
|
||||||
|
"
|
||||||
|
error: Expected backtick, identifier, '(', or string, but found '/'
|
||||||
|
|
|
||||||
|
1 | foo x=/ 'a' / 'b':
|
||||||
|
| ^
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.status(EXIT_FAILURE)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_parenthesized() {
|
fn default_parenthesized() {
|
||||||
Test::new()
|
Test::new()
|
||||||
@ -52,3 +112,17 @@ fn default_parenthesized() {
|
|||||||
.stdout("a/b\n")
|
.stdout("a/b\n")
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_lhs_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