-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Support builder pattern in template engine #10
Conversation
d9e8b9a
to
66f161f
Compare
993c66d
to
719d8a9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent , beside some question and suggestion, other is LGTM 👍
this.compileTimeEnv.extensionsList, | ||
{} | ||
); | ||
this.traverseAST(ast); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could let AST
to Ast
for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, fixed.
nextToken = parser.peekToken(); | ||
let mainBuilder = false; | ||
if (nextToken.type !== lexer.TOKEN_BLOCK_END) { | ||
if (nextToken.type !== lexer.TOKEN_SYMBOL || nextToken.value !== 'main') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is TOKEN_SYMBOL
( like %
) and why need to check is not TOKEN_SYMBOL
?
If it is a symbol but not main
, you also make it a main builder default, too? ( If true, maybe we could give a comment for notice ? )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is TOKEN_SYMBOL ( like % ) and why need to check is not TOKEN_SYMBOL ?
A symbol token is something like main
, roughly speaking it is some char wich isn't contain "()[]{}%*-+~/#,:|.<>=!", not start with int ..... You can check here to see how it works.
I checked it must be SYMBOL or I threw error here to prevent following cases:
{% req main %} -- correct
---- incorrect
{% req "main" %}
{% req { main } %}
....
If it is a symbol but not main, you also make it a main builder default, too? ( If true, maybe we could give a comment for notice ? )
No, I won't. parser.fail
will throw an error and the code below won't be executed.
I'll update the return of this function to "never" to indicate that:
- fail(message: string, lineno?: number, colno?: number): void;
+ fail(message: string, lineno?: number, colno?: number): never;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for replying to me and the logistic part I misunderstood, thanks !
private checkMainBuilder(node: nunjucks.nodes.CallExtensionAsync) { | ||
const isMainBuilder = | ||
node.extName === this.getName() && | ||
(node.args.children[1] as nunjucks.nodes.Literal).value === 'true'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the first argument indicates the variable name, and the second one indicates whether it is the main builder.
this.hasMainBuilder = true; | ||
} | ||
|
||
private wrapOutputWithBuilder() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just would like to check my comprehsion. Is this function used to wrap the builder from raw sql and the builder default is the main builder, like the below sample?
{% req user %}
select * from users where id = '{{params.id}}'
{% endreq %}
select * from public.orders where userId = '{{ user.id.value() }}';
then I have a question when we face the below case, the not wrap req
statement will still show the result? because since the main builder will output ?
{% req user main %}
select * from users where id = '{{params.id}}'
{% endreq %}
select * from public.orders where userId = '{{ user.id.value() }}';
----
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only check whether you have a main builder in your code to decide to send output or not, so:
1.
{% req user %}
select * from users where id = '{{params.id}}'
{% endreq %}
select * from public.orders where userId = '{{ user.id.value() }}';
This statement will be wrapped, the finial query will be select * from public.orders where userId = 'xxxx';
{% req user main %}
select * from users where id = '{{params.id}}'
{% endreq %}
select * from public.orders where userId = '{{ user.id.value() }}';
In this case, because we have defined main builder, the second query select * from public.orders where userId = '{{ user.id.value() }}';
won't be executed. But the first one select * from users where id = '{{params.id}}'
will be executed twice (because .value() call)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for relying to me about the logistic question, thanks !
const args = originalArgs | ||
.slice(1, originalArgs.length - 1) | ||
.filter((value) => typeof value !== 'function'); | ||
const contentArgs = originalArgs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you could some comment to hint what is the difference between the args
and contentArgs
or give some sample to let us know because even saw the type in the TagRunnerOptions
, still could not actually know what they are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that there are some differences between these two arguments:
- args will be passed to function directly: Literal("main") => extension.__run(context, "main"), our options of the extension should be set on these values.
- contentArgs will be "rendered" and passed to function: Output('main') => t = ""; t += "main", the content we want the output should be set on these values.
I've updated the comments of the interface:
protected createAsyncExtensionNode(
/**
* The arguments of this extension, they'll be rendered and passed to run function.
* It usually contains the configuration of the extension, e.g. {% req variable %} The variable name of req extension.
* Note that these arguments will be pass to run function directly: Literal('123') => "123", so adding Output nodes causes compiling issues. Output("123") => t += "123"
*/
argsNodeList: nunjucks.nodes.NodeList,
/** The content (usually the body) of this extension, they'll be passed to run function as render functions
* It usually contains the Output of your extension, e.g. {% req variable %} select * from user {% endreq %}, the "select * from user" should be put in this field.
* Note that these nodes will be rendered as the output of template: Output("123") => t = ""; t += "123", so adding nodes with no output like Symbol, Literal ... might cause compiling issues. Literal('123') => t = ""; 123
*/
contentNodes: nunjucks.nodes.Node[] = []
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, it will be more clear and thanks for searching the different parts and adding the rich comment 👍
01687ba
to
1c5e76f
Compare
- Add builder value visitor to modify AST - Add execute filter to call builder.execute() - Support replace callback while visit nodes' children
add a new argument (the second one) of extension, its value should be "true" or "false", indicating whether this builder is the final builder or not.
- Extensions now have two kinds: runtime and compileTime extensions. - Put the extensions with same feature togeter instead of grouping by types. - We only test with a full extension now, instead of test by files.
e878082
to
20a8dce
Compare
Hi @kokokuo , all issues have been fixed. |
It has been reviewed again, thanks for fixing and replying to me! |
.value()
function call, and replace then with filters.main
buildertypes.
Besides some mock interfaces and functions, almost all code are covered by unit testing :D

