Added code blocks and improved escape handling
This commit is contained in:
parent
e23382f729
commit
98c6c442fb
5
example_templates/code_block.n0m
Normal file
5
example_templates/code_block.n0m
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Hello</h1>
|
||||
${
|
||||
echo "hello world";
|
||||
echo "\}$";
|
||||
}$
|
@ -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) {
|
||||
|
@ -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 = '<!%';
|
||||
@ -64,10 +66,12 @@ export class TokenStream {
|
||||
// interpreter that first builds the template and then executes)
|
||||
this._skipShebang();
|
||||
} else if (this._cs.isNext(TokenStream.ESCAPE)) {
|
||||
// Skip space
|
||||
this._cs.next();
|
||||
// Skip escape
|
||||
this._cs.next(TokenStream.ESCAPE.length);
|
||||
// Treat everything as raw until next space
|
||||
raw += this._cs.nextWhile((c) => /\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)) {
|
||||
// Handle any escaped closing tag
|
||||
if (this._cs.isNext(TokenStream.ESCAPE)) {
|
||||
// Skip escape
|
||||
this._cs.next(TokenStream.ESCAPE.length);
|
||||
|
||||
// Treat everything as raw until next space or eof
|
||||
value += this._cs.nextWhile((c) => /\S/.test(c));
|
||||
} else {
|
||||
const char = this._cs.next();
|
||||
|
||||
if (this._cs.isNext(closeTag)) {
|
||||
if (char !== ' ') {
|
||||
this._missingInlineSpaceError(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
|
||||
|
Loading…
Reference in New Issue
Block a user