Compare commits

...

17 commits

Author SHA1 Message Date
6e119d7583
fix(README): 🐛 fix command 2023-08-22 15:51:18 +03:00
8409c44d0d
fix(tests&meta): Make tests work and edit metadata 2023-08-22 15:49:03 +03:00
79e54384f3
fix(README): 🐛 fix address 2023-08-19 18:29:35 +03:00
559b2b0506
chore: 🎨 rebrand 2023-08-19 18:27:59 +03:00
davidodenwald
bea4b534bc update readme to include prettier v3 instructions 2023-08-12 21:13:40 +02:00
davidodenwald
c0f1ca478d Release 1.0.0
update plugin to support prettier v3
2023-08-12 20:34:06 +02:00
davidodenwald
be28ec949f update plugin to support prettier v3 2023-08-12 20:32:28 +02:00
davidodenwald
ac512dd0b3 Release 0.5.0 2023-05-14 12:20:13 +02:00
davidodenwald
f54a1ff40a update dependecies 2023-05-14 12:16:14 +02:00
davidodenwald
fd332a60e1 fix indentation after empty lines 2023-05-14 11:26:48 +02:00
Matthijs van der Vleuten
c854cfd82d Fix 'closung' typo 2023-05-14 10:47:53 +02:00
Matthijs van der Vleuten
1aec2a655e Add pluralize as non-closing statement 2023-05-09 11:33:12 +02:00
davidodenwald
85a635f03b Release 0.4.0
preserve empty lines between jinja elements

cleanup statements if they only contain whitespace
2023-05-07 14:33:48 +02:00
davidodenwald
d72173d3fe cleanup statements if they only contain whitespace 2023-05-07 14:33:06 +02:00
davidodenwald
60a2cc487e preserve empty lines between jinja elements 2023-05-07 14:30:29 +02:00
davidodenwald
65dd4f1aea Release 0.3.0
Add support for delimiters in expressions
2023-05-01 09:39:46 +02:00
David Odenwald
7620e52d97 Add support for delimiters in expressions 2023-05-01 09:22:18 +02:00
18 changed files with 1408 additions and 1153 deletions

1
.npmrc Normal file
View file

@ -0,0 +1 @@
registry=https://git.fetbuk.ru/api/packages/WerySkok/npm/

View file

@ -1,11 +1,17 @@
# prettier-plugin-jinja-template # prettier-plugin-askama-template
[prettier-plugin-jinja-template](https://github.com/davidodenwald/prettier-plugin-jinja-template) but with patches to work with Askama.
Formatter plugin for jinja2 template files.
## Install ## Install
```bash ```bash
npm install --save-dev prettier prettier-plugin-jinja-template npm install --save-dev prettier https://git.fetbuk.ru/WerySkok/-/packages/npm/prettier-plugin-askama-template/1.0.0/files/2
```
Add the plugin to your `.prettierrc`:
```json
{
"plugins": ["prettier-plugin-askama-template"]
}
``` ```
## Use ## Use
@ -17,7 +23,7 @@ To format basic .html files, you'll have to override the used parser inside your
{ {
"files": ["*.html"], "files": ["*.html"],
"options": { "options": {
"parser": "jinja-template" "parser": "askama-template"
} }
} }
] ]
@ -27,4 +33,9 @@ To format basic .html files, you'll have to override the used parser inside your
Run it on all html files in your project: Run it on all html files in your project:
```bash ```bash
npx prettier --write **/*.html npx prettier --write **/*.html
```
If you don't have a prettier config you can run the plugin with this command:
```bash
npx prettier --write **/*.html --plugin=prettier-plugin-askama-template
``` ```

2036
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,43 +1,41 @@
{ {
"name": "prettier-plugin-jinja-template", "name": "prettier-plugin-askama-template",
"version": "0.2.0", "version": "1.0.0",
"description": "Prettier plugin for formatting jinja templates.", "description": "prettier-plugin-jinja-template but with patches to work with Askama.",
"author": "David Odenwald", "author": "Alexander Minkin",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/davidodenwald/prettier-plugin-jinja-template.git" "url": "git+https://git.fetbuk.ru/WerySkok/prettier-plugin-askama-template.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/davidodenwald/prettier-plugin-jinja-template/issues" "url": "https://git.fetbuk.ru/WerySkok/prettier-plugin-askama-template/issues"
}, },
"homepage": "https://github.com/davidodenwald/prettier-plugin-jinja-template#readme", "homepage": "https://git.fetbuk.ru/WerySkok/prettier-plugin-askama-template#readme",
"keywords": [ "keywords": [
"prettier", "prettier",
"plugin", "plugin",
"template", "template",
"html", "html",
"jinja", "askama"
"jinja2",
"flask"
], ],
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"watch": "npm run build -- --watch", "watch": "npm run build -- --watch",
"test": "jest --verbose", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --verbose",
"test:watch": "jest --watch --verbose", "test:watch": "jest --watch --verbose",
"publish:release": "npm run test && npm run build && npm publish" "publish:release": "npm run test && npm run build && npm publish"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-typescript": "^7.21.0", "@babel/preset-typescript": "^7.22.5",
"@types/jest": "^29.5.0", "@types/jest": "^29.5.3",
"jest": "^29.5.0", "jest": "^29.6.2",
"prettier": "^2.8.4", "prettier": "^3.0.1",
"ts-jest": "^29.0.5", "ts-jest": "^29.1.1",
"typescript": "^5.0.2" "typescript": "^5.1.6"
}, },
"peerDependencies": { "peerDependencies": {
"prettier": "^2.0.0" "prettier": "^3.0.0"
} }
} }

View file

@ -1,16 +1,16 @@
import { Node } from "./jinja"; import { Node } from "./jinja";
import { parse } from "./parser"; import { parse } from "./parser";
import { print, embed } from "./printer"; import { print, embed, getVisitorKeys } from "./printer";
import { Parser, Printer, SupportLanguage } from "prettier"; import { Parser, Printer, SupportLanguage } from "prettier";
const PLUGIN_KEY = "jinja-template"; const PLUGIN_KEY = "askama-template";
export const languages: SupportLanguage[] = [ export const languages: SupportLanguage[] = [
{ {
name: "JinjaTemplate", name: "AskamaTemplate",
parsers: [PLUGIN_KEY], parsers: [PLUGIN_KEY],
extensions: [".jinja", ".jinja2", ".j2", ".html"], extensions: [".jinja", ".jinja2", ".j2", ".html", "askama"],
vscodeLanguageIds: ["jinja"], vscodeLanguageIds: ["askama"],
}, },
]; ];
@ -27,5 +27,6 @@ export const printers = {
[PLUGIN_KEY]: <Printer<Node>>{ [PLUGIN_KEY]: <Printer<Node>>{
print, print,
embed, embed,
getVisitorKeys,
}, },
}; };

View file

@ -5,9 +5,9 @@ export const Placeholder = {
export interface Node { export interface Node {
id: string; id: string;
type: "root" | "expression" | "statement" | "block" | "ignore"; type: "root" | "expression" | "statement" | "block" | "comment" | "ignore";
content: string; content: string;
ownLine: boolean; preNewLines: number;
originalText: string; originalText: string;
index: number; index: number;
length: number; length: number;
@ -16,6 +16,7 @@ export interface Node {
export interface Expression extends Node { export interface Expression extends Node {
type: "expression"; type: "expression";
delimiter: Delimiter;
} }
export type Delimiter = "" | "-" | "+"; export type Delimiter = "" | "-" | "+";
@ -30,10 +31,7 @@ export interface Block extends Node {
type: "block"; type: "block";
start: Statement; start: Statement;
end: Statement; end: Statement;
} containsNewLines: boolean;
export interface IgnoreBlock extends Node {
type: "ignore";
} }
export const nonClosingStatements = [ export const nonClosingStatements = [
@ -43,4 +41,6 @@ export const nonClosingStatements = [
"import", "import",
"from", "from",
"extends", "extends",
"pluralize",
"when"
]; ];

View file

@ -3,6 +3,7 @@ import {
Delimiter, Delimiter,
Node, Node,
Placeholder, Placeholder,
Expression,
Statement, Statement,
Block, Block,
nonClosingStatements, nonClosingStatements,
@ -11,7 +12,7 @@ import {
const NOT_FOUND = -1; const NOT_FOUND = -1;
const regex = const regex =
/(?<pre>(?<newline>\n)?(\s*?))(?<node>{{\s*(?<expression>'([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*}}|{%(?<startDelimiter>[-+]?)\s*(?<statement>(?<keyword>\w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(?<endDelimiter>[-+]?)%}|(?<comment>{#[\S\s]*?#})|(?<scriptBlock><(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(?<styleBlock><(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>)|(?<ignoreBlock><!-- prettier-ignore-start -->[\s\S]*<!-- prettier-ignore-end -->))/; /(?<node>{{(?<startDelimiterEx>[-+]?)\s*(?<expression>'([^']|\\')*'|"([^"]|\\")*"|[\S\s]*?)\s*(?<endDelimiterEx>[-+]?)}}|{%(?<startDelimiter>[-+]?)\s*(?<statement>(?<keyword>\w+)('([^']|\\')*'|"([^"]|\\")*"|[\S\s])*?)\s*(?<endDelimiter>[-+]?)%}|(?<comment>{#[\S\s]*?#})|(?<scriptBlock><(script)((?!<)[\s\S])*>((?!<\/script)[\s\S])*?{{[\s\S]*?<\/(script)>)|(?<styleBlock><(style)((?!<)[\s\S])*>((?!<\/style)[\s\S])*?{{[\s\S]*?<\/(style)>)|(?<ignoreBlock><!-- prettier-ignore-start -->[\s\S]*<!-- prettier-ignore-end -->))/;
export const parse: Parser<Node>["parse"] = (text) => { export const parse: Parser<Node>["parse"] = (text) => {
const statementStack: Statement[] = []; const statementStack: Statement[] = [];
@ -20,7 +21,7 @@ export const parse: Parser<Node>["parse"] = (text) => {
id: "0", id: "0",
type: "root" as const, type: "root" as const,
content: text, content: text,
ownLine: false, preNewLines: 0,
originalText: text, originalText: text,
index: 0, index: 0,
length: 0, length: 0,
@ -43,9 +44,6 @@ export const parse: Parser<Node>["parse"] = (text) => {
continue; continue;
} }
const pre = match.groups.pre || "";
const newline = !!match.groups.newline;
const matchText = match.groups.node; const matchText = match.groups.node;
const expression = match.groups.expression; const expression = match.groups.expression;
const statement = match.groups.statement; const statement = match.groups.statement;
@ -57,31 +55,42 @@ export const parse: Parser<Node>["parse"] = (text) => {
} }
const placeholder = generatePlaceholder(); const placeholder = generatePlaceholder();
const emptyLinesBetween = text.slice(i, i + match.index).match(/^\s+$/) || [
"",
];
const preNewLines = emptyLinesBetween.length
? emptyLinesBetween[0].split("\n").length - 1
: 0;
const node = { const node = {
id: placeholder, id: placeholder,
ownLine: newline, preNewLines,
originalText: matchText, originalText: matchText,
index: match.index + i + pre.length, index: match.index + i,
length: matchText.length, length: matchText.length,
nodes: root.nodes, nodes: root.nodes,
}; };
if (ignoreBlock || comment) { if (comment || ignoreBlock) {
root.content = root.content.replace(matchText, placeholder); root.content = root.content.replace(matchText, placeholder);
root.nodes[node.id] = { root.nodes[node.id] = {
...node, ...node,
type: "ignore", type: comment ? "comment" : "ignore",
content: ignoreBlock || comment, content: comment || ignoreBlock,
}; };
} }
if (expression) { if (expression) {
const delimiter = (match.groups.startDelimiterEx ||
match.groups.endDelimiterEx) as Delimiter;
root.content = root.content.replace(matchText, placeholder); root.content = root.content.replace(matchText, placeholder);
root.nodes[node.id] = { root.nodes[node.id] = {
...node, ...node,
type: "expression", type: "expression",
content: expression, content: expression,
}; delimiter,
} as Expression;
} }
if (statement) { if (statement) {
@ -127,7 +136,7 @@ export const parse: Parser<Node>["parse"] = (text) => {
} }
throw new Error( throw new Error(
`Closung statement "${statement}" doesn't match Opening Statement "${start.content}".` `Closing statement "${statement}" doesn't match Opening Statement "${start.content}".`
); );
} }
} }
@ -153,7 +162,8 @@ export const parse: Parser<Node>["parse"] = (text) => {
start: start, start: start,
end: end, end: end,
content: blockText.slice(start.length, blockText.length - end.length), content: blockText.slice(start.length, blockText.length - end.length),
ownLine: originalText.search("\n") !== NOT_FOUND, preNewLines: start.preNewLines,
containsNewLines: originalText.search("\n") !== NOT_FOUND,
originalText, originalText,
index: start.index, index: start.index,
length: end.index + end.length - start.index, length: end.index + end.length - start.index,

View file

@ -1,18 +1,24 @@
import { Printer } from "prettier"; import { AstPath, Printer, Options, Doc } from "prettier";
import { builders, utils } from "prettier/doc"; import { builders, utils } from "prettier/doc";
import { import { Placeholder, Node, Expression, Statement, Block } from "./jinja";
Placeholder,
Node,
Expression,
Statement,
Block,
IgnoreBlock,
} from "./jinja";
const NOT_FOUND = -1; const NOT_FOUND = -1;
process.env.PRETTIER_DEBUG = "true"; process.env.PRETTIER_DEBUG = "true";
export const getVisitorKeys = (
ast: Node | { [id: string]: Node },
): string[] => {
if ("type" in ast) {
return ast.type === "root" ? ["nodes"] : [];
}
return Object.values(ast)
.filter((node) => {
return node.type === "block";
})
.map((e) => e.id);
};
export const print: Printer<Node>["print"] = (path) => { export const print: Printer<Node>["print"] = (path) => {
const node = path.getNode(); const node = path.getNode();
if (!node) { if (!node) {
@ -24,8 +30,10 @@ export const print: Printer<Node>["print"] = (path) => {
return printExpression(node as Expression); return printExpression(node as Expression);
case "statement": case "statement":
return printStatement(node as Statement); return printStatement(node as Statement);
case "comment":
return printCommentBlock(node);
case "ignore": case "ignore":
return printIgnoreBlock(node as IgnoreBlock); return printIgnoreBlock(node);
} }
return []; return [];
}; };
@ -33,18 +41,24 @@ export const print: Printer<Node>["print"] = (path) => {
const printExpression = (node: Expression): builders.Doc => { const printExpression = (node: Expression): builders.Doc => {
const multiline = node.content.includes("\n"); const multiline = node.content.includes("\n");
return builders.group( const expression = builders.group(
builders.join(" ", [ builders.join(" ", [
"{{", ["{{", node.delimiter],
multiline multiline
? builders.indent([getMultilineGroup(node.content)]) ? builders.indent(getMultilineGroup(node.content))
: node.content, : node.content,
multiline ? [builders.hardline, "}}"] : "}}", multiline
? [builders.hardline, node.delimiter, "}}"]
: [node.delimiter, "}}"],
]), ]),
{ {
shouldBreak: node.ownLine, shouldBreak: node.preNewLines > 0,
} },
); );
return node.preNewLines > 1
? builders.group([builders.trim, builders.hardline, expression])
: expression;
}; };
const printStatement = (node: Statement): builders.Doc => { const printStatement = (node: Statement): builders.Doc => {
@ -60,110 +74,113 @@ const printStatement = (node: Statement): builders.Doc => {
? [builders.hardline, node.delimiter, "%}"] ? [builders.hardline, node.delimiter, "%}"]
: [node.delimiter, "%}"], : [node.delimiter, "%}"],
]), ]),
{ shouldBreak: node.ownLine } { shouldBreak: node.preNewLines > 0 },
); );
if ( if (
["else", "elif"].includes(node.keyword) && ["else", "elif"].includes(node.keyword) &&
surroundingBlock(node)?.ownLine surroundingBlock(node)?.containsNewLines
) { ) {
return [builders.dedent(builders.hardline), statemnt, builders.hardline]; return [builders.dedent(builders.hardline), statemnt, builders.hardline];
} }
return statemnt; return statemnt;
}; };
const printIgnoreBlock = (node: IgnoreBlock): builders.Doc => { const printCommentBlock = (node: Node): builders.Doc => {
return builders.group(node.content, { shouldBreak: node.ownLine }); const comment = builders.group(node.content, {
}; shouldBreak: node.preNewLines > 0,
export const embed: Printer<Node>["embed"] = (
path,
print,
textToDoc,
options
) => {
const node = path.getNode();
if (!node || !["root", "block"].includes(node.type)) {
return null;
}
const mapped = splitAtElse(node).map((content) => {
let doc;
if (content in node.nodes) {
doc = content;
} else {
doc = utils.stripTrailingHardline(
textToDoc(content, {
...options,
parser: "html",
})
);
}
let ignoreDoc = false;
return utils.mapDoc(doc, (currentDoc) => {
if (typeof currentDoc !== "string") {
return currentDoc;
}
if (currentDoc === "<!-- prettier-ignore -->") {
ignoreDoc = true;
return currentDoc;
}
const idxs = findPlaceholders(currentDoc).filter(
([start, end]) => currentDoc.slice(start, end + 1) in node.nodes
);
if (!idxs.length) {
ignoreDoc = false;
return currentDoc;
}
const res: builders.Doc = [];
let lastEnd = 0;
for (const [start, end] of idxs) {
if (lastEnd < start) {
res.push(currentDoc.slice(lastEnd, start));
}
const p = currentDoc.slice(start, end + 1) as string;
if (ignoreDoc) {
res.push(node.nodes[p].originalText);
} else {
res.push(path.call(print, "nodes", p));
}
lastEnd = end + 1;
}
if (lastEnd > 0 && currentDoc.length > lastEnd) {
res.push(currentDoc.slice(lastEnd));
}
ignoreDoc = false;
return res;
});
}); });
return node.preNewLines > 1
? builders.group([builders.trim, builders.hardline, comment])
: comment;
};
const printIgnoreBlock = (node: Node): builders.Doc => {
return node.content;
};
export const embed: Printer<Node>["embed"] = () => {
return _embed;
};
const _embed = async (
textToDoc: (text: string, options: Options) => Promise<Doc>,
print: (selector?: string | number | Array<string | number> | AstPath) => Doc,
path: AstPath,
options: Options,
): Promise<Doc | undefined> => {
const node = path.getNode();
if (!node || !["root", "block"].includes(node.type)) {
return undefined;
}
const mapped = await Promise.all(
splitAtElse(node).map(async (content) => {
let doc;
if (content in node.nodes) {
doc = content;
} else {
doc = await textToDoc(content, {
...options,
parser: "html",
});
}
let ignoreDoc = false;
return utils.mapDoc(doc, (currentDoc) => {
if (typeof currentDoc !== "string") {
return currentDoc;
}
if (currentDoc === "<!-- prettier-ignore -->") {
ignoreDoc = true;
return currentDoc;
}
const idxs = findPlaceholders(currentDoc).filter(
([start, end]) => currentDoc.slice(start, end + 1) in node.nodes,
);
if (!idxs.length) {
ignoreDoc = false;
return currentDoc;
}
const res: builders.Doc = [];
let lastEnd = 0;
for (const [start, end] of idxs) {
if (lastEnd < start) {
res.push(currentDoc.slice(lastEnd, start));
}
const p = currentDoc.slice(start, end + 1) as string;
if (ignoreDoc) {
res.push(node.nodes[p].originalText);
} else {
res.push(path.call(print, "nodes", p));
}
lastEnd = end + 1;
}
if (lastEnd > 0 && currentDoc.length > lastEnd) {
res.push(currentDoc.slice(lastEnd));
}
ignoreDoc = false;
return res;
});
}),
);
if (node.type === "block") { if (node.type === "block") {
if (node.content.includes("\n")) { const block = buildBlock(path, print, node as Block, mapped);
return builders.group([
path.call(print, "nodes", (node as Block).start.id), return node.preNewLines > 1
builders.indent([ ? builders.group([builders.trim, builders.hardline, block])
builders.softline, : block;
utils.stripTrailingHardline(mapped),
]),
builders.hardline,
path.call(print, "nodes", (node as Block).end.id),
]);
}
return builders.group([
path.call(print, "nodes", (node as Block).start.id),
utils.stripTrailingHardline(mapped),
path.call(print, "nodes", (node as Block).end.id),
]);
} }
return [...mapped, builders.hardline]; return [...mapped, builders.hardline];
}; };
@ -172,7 +189,7 @@ const getMultilineGroup = (content: String): builders.Group => {
return builders.group( return builders.group(
content.split("\n").map((line, i) => { content.split("\n").map((line, i) => {
return [builders.hardline, line.trim()]; return [builders.hardline, line.trim()];
}) }),
); );
}; };
@ -181,7 +198,7 @@ const splitAtElse = (node: Node): string[] => {
(n) => (n) =>
n.type === "statement" && n.type === "statement" &&
["else", "elif"].includes((n as Statement).keyword) && ["else", "elif"].includes((n as Statement).keyword) &&
node.content.search(n.id) !== NOT_FOUND node.content.search(n.id) !== NOT_FOUND,
); );
if (!elseNodes.length) { if (!elseNodes.length) {
return [node.content]; return [node.content];
@ -218,6 +235,35 @@ export const findPlaceholders = (text: string): [number, number][] => {
export const surroundingBlock = (node: Node): Block | undefined => { export const surroundingBlock = (node: Node): Block | undefined => {
return Object.values(node.nodes).find( return Object.values(node.nodes).find(
(n) => n.type === "block" && n.content.search(node.id) !== NOT_FOUND (n) => n.type === "block" && n.content.search(node.id) !== NOT_FOUND,
) as Block; ) as Block;
}; };
const buildBlock = (
path: AstPath<Node>,
print: (path: AstPath<Node>) => builders.Doc,
block: Block,
mapped: (string | builders.Doc[] | builders.DocCommand)[],
): builders.Doc => {
// if the content is empty or whitespace only.
if (block.content.match(/^\s*$/)) {
return builders.fill([
path.call(print, "nodes", block.start.id),
builders.softline,
path.call(print, "nodes", block.end.id),
]);
}
if (block.containsNewLines) {
return builders.group([
path.call(print, "nodes", block.start.id),
builders.indent([builders.softline, mapped]),
builders.hardline,
path.call(print, "nodes", block.end.id),
]);
}
return builders.group([
path.call(print, "nodes", block.start.id),
mapped,
path.call(print, "nodes", block.end.id),
]);
};

View file

@ -1,5 +1,3 @@
<div> <div>{{ user.name }}</div>
{{ user.name }}
</div>
<div>{{ user.age }}</div> <div>{{ user.age }}</div>

View file

@ -0,0 +1,9 @@
<div class="extemely_long__class_name extemely_long__class_name--modifier">
{{- some_variable -}}
</div>
<div class="extemely_long__class_name extemely_long__class_name--modifier">
{{- some_variable -}}
</div>
<div class="extemely_long__class_name extemely_long__class_name--modifier">
{{- some_variable -}}
</div>

View file

@ -0,0 +1,9 @@
<div class="extemely_long__class_name extemely_long__class_name--modifier">
{{- some_variable -}}
</div>
<div class="extemely_long__class_name extemely_long__class_name--modifier">
{{ some_variable -}}
</div>
<div class="extemely_long__class_name extemely_long__class_name--modifier">
{{- some_variable }}
</div>

View file

@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important {
color: #336699;
}
</style>
{% endblock %}
{{ title }}
{# content #}
{% block content %}
<h1>Index</h1>
<p class="important">Welcome to my awesome homepage.</p>
{% endblock %}
<div></div>
{{ name }}
{% for user in users %}
<li>{{ user.username|e }}</li>
{% else %}
<li><em>no users found</em></li>
{% endfor %}
{% macro input(name, value='', type='text', size=20) %}
{{ my_variable|default('my_variable is not defined') }}
{% for city, items in users|groupby("city") %}
<li>
{{ city }}
<ul>
{% for user in items %}
<li>{{ user.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
{% endmacro %}

View file

@ -0,0 +1,53 @@
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{{title}}
{# content #}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}
<div></div>
{{name}}
{% for user in users %}
<li>{{ user.username|e }}</li>
{% else %}
<li><em>no users found</em></li>
{% endfor %}
{% macro input(name, value='', type='text', size=20) %}
{{ my_variable|default('my_variable is not defined') }}
{% for city, items in users|groupby("city") %}
<li>{{ city }}
<ul>{% for user in items %}
<li>{{ user.name }}
{% endfor %}</ul>
</li>
{% endfor %}
{% endmacro %}

View file

@ -1 +1 @@
Error('Closung statement "endif" doesn't match Opening Statement "for item in seq".') Error('Closing statement "endif" doesn't match Opening Statement "for item in seq".')

View file

@ -0,0 +1,2 @@
{% block title %}
{% endblock %}

View file

@ -0,0 +1,5 @@
{% block title %}
{% endblock %}

View file

@ -5,7 +5,7 @@ import * as jinjaPlugin from "../src/index";
const prettify = (code: string, options: Options) => const prettify = (code: string, options: Options) =>
format(code, { format(code, {
parser: "jinja-template", parser: "askama-template",
plugins: [jinjaPlugin], plugins: [jinjaPlugin],
...options, ...options,
}); });
@ -17,7 +17,7 @@ tests.forEach((test) => {
if (test.startsWith("_")) { if (test.startsWith("_")) {
return; return;
} }
return it(test, () => { return it(test, async () => {
const path = join(testFolder, test); const path = join(testFolder, test);
const input = readFileSync(join(path, "input.html")).toString(); const input = readFileSync(join(path, "input.html")).toString();
const expected = readFileSync(join(path, "expected.html")).toString(); const expected = readFileSync(join(path, "expected.html")).toString();
@ -34,11 +34,11 @@ tests.forEach((test) => {
if (expectedError) { if (expectedError) {
jest.spyOn(console, "error").mockImplementation(() => {}); jest.spyOn(console, "error").mockImplementation(() => {});
expect(format).toThrow(expectedError); await expect(format()).rejects.toThrow(expectedError);
} else { } else {
const result = format(); const result = await format();
expect(result).toEqual(expected); expect(result).toEqual(expected);
expect(prettify(result, configObject)).toEqual(expected); expect(await prettify(result, configObject)).toEqual(expected);
} }
}); });
}); });

View file

@ -1,8 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./lib", "outDir": "./lib",
"target": "ES6", "target": "esnext",
"module": "commonjs", "module": "NodeNext",
"moduleResolution": "node", "moduleResolution": "node",
"lib": ["esnext"], "lib": ["esnext"],
"declaration": true, "declaration": true,