diff --git a/example_templates/code_block.n0m b/example_templates/code_block.n0m
new file mode 100644
index 0000000..bcacfaf
--- /dev/null
+++ b/example_templates/code_block.n0m
@@ -0,0 +1,5 @@
+
Hello
+${
+ echo "hello world";
+ echo "\}$";
+}$
diff --git a/src/template_engine.mjs b/src/template_engine.mjs
index 4fe5246..ef55b95 100644
--- a/src/template_engine.mjs
+++ b/src/template_engine.mjs
@@ -1,5 +1,15 @@
import { TokenStream } from './token_stream.mjs';
+
+const htmlEscapeSed = `sed '` +
+ `s/\\&/\\&/g;` +
+ `s/\\</g;` +
+ `s/>/\\>/g;` +
+ `s/"/\\"/g;` +
+ `s/'\\''/\\'/g` +
+ `'`;
+
+
export class TemplateEngine {
async render(fileName) {
const ts = new TokenStream();
@@ -14,6 +24,9 @@ export class TemplateEngine {
case 'raw':
buffer += this._renderRaw(token.value);
break;
+ case 'block_statement':
+ buffer += this._renderBlockStatement(token.value);
+ break;
case 'inline':
buffer += this._renderInline(token.value);
break;
@@ -40,6 +53,10 @@ export class TemplateEngine {
return buffer;
}
+ _renderBlockStatement(value) {
+ return value.replace(/\s*$/, '\n');
+ }
+
_renderComment(value) {
return `# ${value}\n`;
}
@@ -49,7 +66,7 @@ export class TemplateEngine {
}
_renderInline(value) {
- return `printf '%s' "${value}" | jq -Rr @html | head -c -1;\n`;
+ return `printf '%s' "${value}" | ${htmlEscapeSed}\n`;
}
_renderInlineUnescaped(value) {
@@ -57,7 +74,7 @@ export class TemplateEngine {
}
_renderInlineStatement(value) {
- return `printf '%s' "$(${value})" | jq -Rr @html | head -c -1;\n`;
+ return `printf '%s' "$(${value})" | ${htmlEscapeSed}\n`;
}
_renderInlineStatementUnescaped(value) {
diff --git a/src/token_stream.mjs b/src/token_stream.mjs
index 4318979..e76a074 100644
--- a/src/token_stream.mjs
+++ b/src/token_stream.mjs
@@ -3,6 +3,8 @@ import { TemplateSyntaxError } from './errors/template_syntax_error.mjs';
export class TokenStream {
static ESCAPE = '\\';
+ static OPEN_STATEMENT = '${';
+ static CLOSE_STATEMENT = '}$';
static OPEN_INLINE = '<%';
static CLOSE_INLINE = '%>';
static OPEN_INLINE_UNESCAPED = ' /\S/.test(c));
+ } else if (this._cs.isNext(TokenStream.OPEN_STATEMENT)) {
+ return flushRaw() || this._readBlockStatement();
} else if (this._cs.isNext(TokenStream.OPEN_INLINE)) {
return flushRaw() || this._readInline();
} else if (this._cs.isNext(TokenStream.OPEN_INLINE_UNESCAPED)) {
@@ -102,40 +106,64 @@ export class TokenStream {
this._cs.next();
}
- _missingInlineSpaceError(tag) {
+ _missingOpenSpaceError(tag) {
throw new TemplateSyntaxError(
this._cs,
- `Inline tag: "${tag}" must be followed by a whitespace`
+ `Tag: "${tag}" must be followed by a whitespace or new line`
);
}
- _readInlineGeneric(tokenType, openTag, closeTag) {
+ _missingCloseSpaceError(tag) {
+ throw new TemplateSyntaxError(
+ this._cs,
+ `Tag: "${tag}" must be preceded by a whitespace or new line`
+ );
+ }
+
+ _readBlockGeneric(tokenType, openTag, closeTag) {
this._cs.next(openTag.length);
// Check for mandatory space
- if (this._cs.peek() !== ' ') {
- this._missingInlineSpaceError(openTag);
+ if (/\S/.test(this._cs.peek())) {
+ this._missingOpenSpaceError(openTag);
}
this._cs.next();
let value = '';
while (!this._cs.eof() && !this._cs.isNext(closeTag)) {
- const char = this._cs.next();
+ // Handle any escaped closing tag
+ if (this._cs.isNext(TokenStream.ESCAPE)) {
+ // Skip escape
+ this._cs.next(TokenStream.ESCAPE.length);
- if (this._cs.isNext(closeTag)) {
- if (char !== ' ') {
- this._missingInlineSpaceError(closeTag);
- }
+ // Treat everything as raw until next space or eof
+ value += this._cs.nextWhile((c) => /\S/.test(c));
} else {
- value += char;
+ const char = this._cs.next();
+
+ if (this._cs.isNext(closeTag)) {
+ if (/\S/.test(char)) {
+ this._missingCloseSpaceError(closeTag);
+ }
+ } else {
+ value += char;
+ }
}
}
this._cs.next(closeTag.length);
return {type: tokenType, value};
}
+ _readBlockStatement() {
+ return this._readBlockGeneric(
+ 'block_statement',
+ TokenStream.OPEN_STATEMENT,
+ TokenStream.CLOSE_STATEMENT
+ );
+ }
+
_readInline() {
- return this._readInlineGeneric(
+ return this._readBlockGeneric(
'inline',
TokenStream.OPEN_INLINE,
TokenStream.CLOSE_INLINE
@@ -143,7 +171,7 @@ export class TokenStream {
}
_readInlineUnescaped() {
- return this._readInlineGeneric(
+ return this._readBlockGeneric(
'inline_unescaped',
TokenStream.OPEN_INLINE_UNESCAPED,
TokenStream.CLOSE_INLINE_UNESCAPED
@@ -151,7 +179,7 @@ export class TokenStream {
}
_readInlineStatement() {
- return this._readInlineGeneric(
+ return this._readBlockGeneric(
'inline_statement',
TokenStream.OPEN_INLINE_STATEMENT,
TokenStream.CLOSE_INLINE_STATEMENT
@@ -159,7 +187,7 @@ export class TokenStream {
}
_readInlineStatementUnescaped() {
- return this._readInlineGeneric(
+ return this._readBlockGeneric(
'inline_statement_unescaped',
TokenStream.OPEN_INLINE_STATEMENT_UNESCAPED,
TokenStream.CLOSE_INLINE_STATEMENT_UNESCAPED