Skip to content
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

xarc/tag-renderer support rendering sub templates #1702

Merged
merged 1 commit into from
Jul 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 72 additions & 19 deletions packages/xarc-tag-renderer/src/render-execute.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,57 @@
/* eslint-disable complexity, max-statements */
/* eslint-disable complexity, max-statements, max-params */
/* eslint-disable @typescript-eslint/no-use-before-define */

import { TOKEN_HANDLER } from "@xarc/render-context";
import { TagTemplate } from "./tag-template";
import { TAG_TYPE } from "./symbols";

export const executeSteps = {
STEP_HANDLER: 0,
STEP_STR_TOKEN: 1,
STEP_NO_HANDLER: 2,
STEP_LITERAL_HANDLER: 3,
STEP_FUNC_HANDLER: 4
STEP_FUNC_HANDLER: 4,
STEP_SUB_TEMPLATE: 5
};

const {
STEP_HANDLER,
STEP_STR_TOKEN,
STEP_NO_HANDLER,
STEP_LITERAL_HANDLER,
STEP_FUNC_HANDLER
STEP_FUNC_HANDLER,
STEP_SUB_TEMPLATE
} = executeSteps;

export function renderNext(err: Error, xt) {
const { renderSteps, context } = xt;
function handleSubTemplate(tkId: string, step, result: any, xt: any, cb: Function) {
if (!result) {
return cb();
}

const handle = res => {
if (res[TAG_TYPE] && res[TAG_TYPE] === "template") {
const step2 = xt.template.handleSubTemplate(step, res);
return executeTagTemplate(step2.template, step2.tk, xt.context, true).then(cb, cb);
} else {
return xt.context.handleTokenResult(tkId, res, cb);
}
};

if (result.then) {
return result.then(handle, cb);
} else {
return handle(result);
}
}
/**
* Execute the next step for the token tags
* @param err - error from previous step
* @param xt - execution context
*
* @returns any - non-significant
*/
export function renderNext(err: Error, xt: any) {
const { template, tagTokens, context } = xt;
if (err) {
context.handleError(err);
}
Expand All @@ -32,30 +64,46 @@ export function renderNext(err: Error, xt) {
context.output.add(`<!-- ${tk.id} END -->\n`);
};

if (context.isFullStop || context.isVoidStop || xt.stepIndex >= renderSteps.length) {
const r = context.output.close();
xt.resolve(r);
if (context.isFullStop || context.isVoidStop || xt.stepIndex >= tagTokens.length) {
if (!xt.subTemplate) {
xt.resolve(context.output.close());
} else {
xt.resolve();
}
return null;
} else {
// TODO: support soft stop
const step = renderSteps[xt.stepIndex++];
const tk = step.tk;
const tagIndex = xt.stepIndex++;
const tk = tagTokens[tagIndex];
const step = template.getTagOpCode(tagIndex);

if (!step) {
return renderNext(null, xt);
}

// const tk = step.tk;
const withId = step.insertTokenId;
switch (step.code) {
case STEP_FUNC_HANDLER:
return context.handleTokenResult("", tk.func(context), e => {
return renderNext(e, xt);
});
case STEP_HANDLER:
case STEP_SUB_TEMPLATE:
return executeTagTemplate(step.template, step.tk, context, true).then(
() => renderNext(null, xt),
(err2: Error) => renderNext(err2, xt)
);
case STEP_FUNC_HANDLER: {
const result = tk(context);
return handleSubTemplate("", step, result, xt, (e: Error) => renderNext(e, xt));
}
case STEP_HANDLER: {
if (withId) {
insertTokenId(tk);
}
return context.handleTokenResult(tk.id, tk[TOKEN_HANDLER](context, tk), e => {
const result = tk[TOKEN_HANDLER](context, tk);
return handleSubTemplate(tk.id, step, result, xt, (e: Error) => {
if (withId) {
insertTokenIdEnd(tk);
}
return renderNext(e, xt);
});
}
case STEP_STR_TOKEN:
context.output.add(tk.str);
break;
Expand All @@ -76,9 +124,14 @@ export function renderNext(err: Error, xt) {
}
}

export function executeRenderSteps(renderSteps, context) {
export function executeTagTemplate(
template: TagTemplate,
tagTokens: any[],
context,
subTemplate = false
) {
return new Promise(resolve => {
const xt = { stepIndex: 0, renderSteps, context, resolve };
const xt = { stepIndex: 0, template, tagTokens, context, resolve, subTemplate };
return renderNext(null, xt);
});
}
121 changes: 69 additions & 52 deletions packages/xarc-tag-renderer/src/render-processor.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
/* eslint-disable max-statements */

import { executeRenderSteps, executeSteps } from "./render-execute";
import { executeTagTemplate, executeSteps } from "./render-execute";
import { TAG_TYPE } from "./symbols";
import { TokenModule } from "@xarc/render-context";
import { TagTemplate } from "./tag-template";
import { RenderContext } from "@xarc/render-context";

const {
STEP_HANDLER,
STEP_STR_TOKEN,
STEP_NO_HANDLER,
STEP_LITERAL_HANDLER,
STEP_FUNC_HANDLER
STEP_FUNC_HANDLER,
STEP_SUB_TEMPLATE
} = executeSteps;

export class RenderProcessor {
renderSteps: any;
_options: any;
_insertTokenIds: boolean;

constructor(options) {
constructor(options: {
/** Add debugging comment to rendered output with token IDs */
insertTokenIds?: boolean;
/** The renderer instance */
asyncTemplate?: any;
}) {
this._options = options;
this._insertTokenIds = Boolean(options.insertTokenIds);
this.renderSteps = this.makeSteps(options.htmlTokens);
}

makeNullRemovedStep(tk, cause) {
/**
* Generate an exec step for a tag that has a null handler
*
* @param tk - tag
* @param cause - reason a null handler is needed
*/
makeNullRemovedStep(tk: any, cause: string) {
return {
tk,
insertTokenId: false,
Expand All @@ -32,7 +43,12 @@ export class RenderProcessor {
};
}

makeHandlerStep(tk) {
/**
* Make a execution step for a token with a handler
*
* @param tk
*/
makeHandlerStep(tk: any) {
const options = this._options;
const insertTokenIds = this._insertTokenIds;

Expand All @@ -48,8 +64,7 @@ export class RenderProcessor {
}

const msg = `@xarc/tag-renderer: no handler found for token id ${tk.id}`;
console.error(msg); // eslint-disable-line
return { tk, code: STEP_NO_HANDLER };
return { tk, msg, code: STEP_NO_HANDLER };
}

if (typeof tkFunc !== "function") {
Expand All @@ -67,61 +82,63 @@ export class RenderProcessor {
return { tk, code: STEP_HANDLER, insertTokenId: insertTokenIds && !tk.props._noInsertId };
}

/**
* Make a execution step for a token tag
* @param tk - token tag
* @returns execution step
*/
makeStep(tk: any) {
let opCode;

const options = this._options;
const insertTokenIds = this._insertTokenIds;

if (tk[TAG_TYPE] === "function") {
return {
opCode = {
tk,
code: STEP_FUNC_HANDLER
};
}

if (tk[TAG_TYPE] === "register-token-ids") {
} else if (tk[TAG_TYPE] === "register-token-ids") {
tk({ asyncTemplate: options.asyncTemplate });
return null;
}

if (tk[TAG_TYPE] === "template") {
return this.makeSteps(tk);
}

// token is a literal string, just add it to output
if (tk.hasOwnProperty("str")) {
return { tk, code: STEP_STR_TOKEN };
}

// token is not pointing to a module, so lookup from token handlers
if (!tk.isModule) {
return this.makeHandlerStep(tk);
}

if (tk.custom === null) {
opCode = null;
} else if (tk[TAG_TYPE] === "template") {
opCode = {
tk,
template: new TagTemplate({ templateTags: tk, processor: this }),
code: STEP_SUB_TEMPLATE
};
} else if (tk.hasOwnProperty("str")) {
// token is a literal string, just add it to output
opCode = { tk, code: STEP_STR_TOKEN };
} else if (!tk.isModule) {
// token is not pointing to a module, so lookup from token handlers
opCode = this.makeHandlerStep(tk);
} else if (tk.custom === null) {
if (insertTokenIds) {
return this.makeNullRemovedStep(tk, "process return null");
}
return null;
}
return {
tk,
code: STEP_HANDLER,
insertTokenId: options.insertTokenIds && !tk.props._noInsertId
};
}

makeSteps(tokens) {
let steps = [];
for (const htk of tokens) {
const step = this.makeStep(htk);
if (step) {
steps = steps.concat(step);
opCode = this.makeNullRemovedStep(tk, "process return null");
} else {
opCode = null;
}
} else {
opCode = {
tk,
code: STEP_HANDLER,
insertTokenId: options.insertTokenIds && !tk.props._noInsertId
};
}

return steps;
return opCode;
}

render(context) {
return executeRenderSteps(this.renderSteps, context);
/**
* Run rendering for a template
* @param template - the template
* @param context - RenderContext
* @param tagTokens - template tag tokens
*
* @returns Promise that resolves after rendering completed
*/
render(template: TagTemplate, context: RenderContext, tagTokens: any[]) {
return executeTagTemplate(template, tagTokens, context);
}
}
Loading