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';
|
import { TokenStream } from './token_stream.mjs';
|
||||||
|
|
||||||
|
|
||||||
|
const htmlEscapeSed = `sed '` +
|
||||||
|
`s/\\&/\\&/g;` +
|
||||||
|
`s/</\\</g;` +
|
||||||
|
`s/>/\\>/g;` +
|
||||||
|
`s/"/\\"/g;` +
|
||||||
|
`s/'\\''/\\'/g` +
|
||||||
|
`'`;
|
||||||
|
|
||||||
|
|
||||||
export class TemplateEngine {
|
export class TemplateEngine {
|
||||||
async render(fileName) {
|
async render(fileName) {
|
||||||
const ts = new TokenStream();
|
const ts = new TokenStream();
|
||||||
@ -14,6 +24,9 @@ export class TemplateEngine {
|
|||||||
case 'raw':
|
case 'raw':
|
||||||
buffer += this._renderRaw(token.value);
|
buffer += this._renderRaw(token.value);
|
||||||
break;
|
break;
|
||||||
|
case 'block_statement':
|
||||||
|
buffer += this._renderBlockStatement(token.value);
|
||||||
|
break;
|
||||||
case 'inline':
|
case 'inline':
|
||||||
buffer += this._renderInline(token.value);
|
buffer += this._renderInline(token.value);
|
||||||
break;
|
break;
|
||||||
@ -40,6 +53,10 @@ export class TemplateEngine {
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderBlockStatement(value) {
|
||||||
|
return value.replace(/\s*$/, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
_renderComment(value) {
|
_renderComment(value) {
|
||||||
return `# ${value}\n`;
|
return `# ${value}\n`;
|
||||||
}
|
}
|
||||||
@ -49,7 +66,7 @@ export class TemplateEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_renderInline(value) {
|
_renderInline(value) {
|
||||||
return `printf '%s' "${value}" | jq -Rr @html | head -c -1;\n`;
|
return `printf '%s' "${value}" | ${htmlEscapeSed}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderInlineUnescaped(value) {
|
_renderInlineUnescaped(value) {
|
||||||
@ -57,7 +74,7 @@ export class TemplateEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_renderInlineStatement(value) {
|
_renderInlineStatement(value) {
|
||||||
return `printf '%s' "$(${value})" | jq -Rr @html | head -c -1;\n`;
|
return `printf '%s' "$(${value})" | ${htmlEscapeSed}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderInlineStatementUnescaped(value) {
|
_renderInlineStatementUnescaped(value) {
|
||||||
|
@ -3,6 +3,8 @@ import { TemplateSyntaxError } from './errors/template_syntax_error.mjs';
|
|||||||
|
|
||||||
export class TokenStream {
|
export class TokenStream {
|
||||||
static ESCAPE = '\\';
|
static ESCAPE = '\\';
|
||||||
|
static OPEN_STATEMENT = '${';
|
||||||
|
static CLOSE_STATEMENT = '}$';
|
||||||
static OPEN_INLINE = '<%';
|
static OPEN_INLINE = '<%';
|
||||||
static CLOSE_INLINE = '%>';
|
static CLOSE_INLINE = '%>';
|
||||||
static OPEN_INLINE_UNESCAPED = '<!%';
|
static OPEN_INLINE_UNESCAPED = '<!%';
|
||||||
@ -64,10 +66,12 @@ export class TokenStream {
|
|||||||
// interpreter that first builds the template and then executes)
|
// interpreter that first builds the template and then executes)
|
||||||
this._skipShebang();
|
this._skipShebang();
|
||||||
} else if (this._cs.isNext(TokenStream.ESCAPE)) {
|
} else if (this._cs.isNext(TokenStream.ESCAPE)) {
|
||||||
// Skip space
|
// Skip escape
|
||||||
this._cs.next();
|
this._cs.next(TokenStream.ESCAPE.length);
|
||||||
// Treat everything as raw until next space
|
// Treat everything as raw until next space
|
||||||
raw += this._cs.nextWhile((c) => /\S/.test(c));
|
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)) {
|
} else if (this._cs.isNext(TokenStream.OPEN_INLINE)) {
|
||||||
return flushRaw() || this._readInline();
|
return flushRaw() || this._readInline();
|
||||||
} else if (this._cs.isNext(TokenStream.OPEN_INLINE_UNESCAPED)) {
|
} else if (this._cs.isNext(TokenStream.OPEN_INLINE_UNESCAPED)) {
|
||||||
@ -102,40 +106,64 @@ export class TokenStream {
|
|||||||
this._cs.next();
|
this._cs.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
_missingInlineSpaceError(tag) {
|
_missingOpenSpaceError(tag) {
|
||||||
throw new TemplateSyntaxError(
|
throw new TemplateSyntaxError(
|
||||||
this._cs,
|
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);
|
this._cs.next(openTag.length);
|
||||||
|
|
||||||
// Check for mandatory space
|
// Check for mandatory space
|
||||||
if (this._cs.peek() !== ' ') {
|
if (/\S/.test(this._cs.peek())) {
|
||||||
this._missingInlineSpaceError(openTag);
|
this._missingOpenSpaceError(openTag);
|
||||||
}
|
}
|
||||||
this._cs.next();
|
this._cs.next();
|
||||||
|
|
||||||
let value = '';
|
let value = '';
|
||||||
while (!this._cs.eof() && !this._cs.isNext(closeTag)) {
|
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)) {
|
// Treat everything as raw until next space or eof
|
||||||
if (char !== ' ') {
|
value += this._cs.nextWhile((c) => /\S/.test(c));
|
||||||
this._missingInlineSpaceError(closeTag);
|
|
||||||
}
|
|
||||||
} else {
|
} 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);
|
this._cs.next(closeTag.length);
|
||||||
return {type: tokenType, value};
|
return {type: tokenType, value};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_readBlockStatement() {
|
||||||
|
return this._readBlockGeneric(
|
||||||
|
'block_statement',
|
||||||
|
TokenStream.OPEN_STATEMENT,
|
||||||
|
TokenStream.CLOSE_STATEMENT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_readInline() {
|
_readInline() {
|
||||||
return this._readInlineGeneric(
|
return this._readBlockGeneric(
|
||||||
'inline',
|
'inline',
|
||||||
TokenStream.OPEN_INLINE,
|
TokenStream.OPEN_INLINE,
|
||||||
TokenStream.CLOSE_INLINE
|
TokenStream.CLOSE_INLINE
|
||||||
@ -143,7 +171,7 @@ export class TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_readInlineUnescaped() {
|
_readInlineUnescaped() {
|
||||||
return this._readInlineGeneric(
|
return this._readBlockGeneric(
|
||||||
'inline_unescaped',
|
'inline_unescaped',
|
||||||
TokenStream.OPEN_INLINE_UNESCAPED,
|
TokenStream.OPEN_INLINE_UNESCAPED,
|
||||||
TokenStream.CLOSE_INLINE_UNESCAPED
|
TokenStream.CLOSE_INLINE_UNESCAPED
|
||||||
@ -151,7 +179,7 @@ export class TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_readInlineStatement() {
|
_readInlineStatement() {
|
||||||
return this._readInlineGeneric(
|
return this._readBlockGeneric(
|
||||||
'inline_statement',
|
'inline_statement',
|
||||||
TokenStream.OPEN_INLINE_STATEMENT,
|
TokenStream.OPEN_INLINE_STATEMENT,
|
||||||
TokenStream.CLOSE_INLINE_STATEMENT
|
TokenStream.CLOSE_INLINE_STATEMENT
|
||||||
@ -159,7 +187,7 @@ export class TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_readInlineStatementUnescaped() {
|
_readInlineStatementUnescaped() {
|
||||||
return this._readInlineGeneric(
|
return this._readBlockGeneric(
|
||||||
'inline_statement_unescaped',
|
'inline_statement_unescaped',
|
||||||
TokenStream.OPEN_INLINE_STATEMENT_UNESCAPED,
|
TokenStream.OPEN_INLINE_STATEMENT_UNESCAPED,
|
||||||
TokenStream.CLOSE_INLINE_STATEMENT_UNESCAPED
|
TokenStream.CLOSE_INLINE_STATEMENT_UNESCAPED
|
||||||
|
Loading…
Reference in New Issue
Block a user