From d3b277c04c5b459b1915afc177eded20bf37202b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 24 Mar 2021 19:46:53 -0700 Subject: [PATCH] Allow escaping double braces with `{{{{` (#765) --- README.adoc | 11 +++++++++-- src/evaluator.rs | 11 +++++++---- src/lexer.rs | 31 +++++++++++++++++++++++++++++++ tests/misc.rs | 24 ++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/README.adoc b/README.adoc index e61e1fd..70e00d5 100644 --- a/README.adoc +++ b/README.adoc @@ -446,11 +446,11 @@ publish: ==== Escaping `{{` -To write a recipe containing `{{`, use `{{ "{{" }}`: +To write a recipe containing `{{`, use `{{{{`: ```make braces: - echo 'I {{ "{{" }}LOVE}} curly braces!' + echo 'I {{{{LOVE}} curly braces!' ``` (An unmatched `}}` is ignored, so it doesn't need to be escaped.) @@ -462,6 +462,13 @@ braces: echo '{{'I {{LOVE}} curly braces!'}}' ``` +Yet another option is to use `{{ "{{" }}`: + +```make +braces: + echo 'I {{ "{{" }}LOVE}} curly braces!' +``` + === Strings Double-quoted strings support escape sequences: diff --git a/src/evaluator.rs b/src/evaluator.rs index 70da605..6b382be 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -169,12 +169,15 @@ impl<'src, 'run> Evaluator<'src, 'run> { let mut evaluated = String::new(); for (i, fragment) in line.fragments.iter().enumerate() { match fragment { - Fragment::Text { token } => + Fragment::Text { token } => { + let lexeme = token.lexeme().replace("{{{{", "{{"); + if i == 0 && continued { - evaluated += token.lexeme().trim_start(); + evaluated += lexeme.trim_start(); } else { - evaluated += token.lexeme(); - }, + evaluated += &lexeme; + } + }, Fragment::Interpolation { expression } => { evaluated += &self.evaluate_expression(expression)?; }, diff --git a/src/lexer.rs b/src/lexer.rs index 4be599a..5857e1f 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -91,6 +91,15 @@ impl<'src> Lexer<'src> { } } + /// Advance over N characters. + fn skip(&mut self, n: usize) -> CompilationResult<'src, ()> { + for _ in 0..n { + self.advance()?; + } + + Ok(()) + } + /// Lexeme of in-progress token fn lexeme(&self) -> &'src str { &self.src[self.token_start.offset..self.token_end.offset] @@ -515,6 +524,11 @@ impl<'src> Lexer<'src> { use Terminator::*; let terminator = loop { + if self.rest_starts_with("{{{{") { + self.skip(4)?; + continue; + } + if let Some('\n') = self.next { break Newline; } @@ -1239,6 +1253,23 @@ mod tests { ) } + test! { + name: brace_escape, + text: " + foo: + {{{{ + ", + tokens: ( + Identifier:"foo", + Colon, + Eol, + Indent, + Text:"{{{{", + Eol, + Dedent, + ) + } + test! { name: indented_block_followed_by_item, text: " diff --git a/tests/misc.rs b/tests/misc.rs index e427871..36e2e08 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -2600,6 +2600,30 @@ test! { shell: false, } +test! { + name: brace_escape, + justfile: " + foo: + echo '{{{{' + ", + stdout: "{{\n", + stderr: " + echo '{{' + ", +} + +test! { + name: brace_escape_extra, + justfile: " + foo: + echo '{{{{{' + ", + stdout: "{{{\n", + stderr: " + echo '{{{' + ", +} + #[cfg(windows)] test! { name: windows_interpreter_path_no_base,