Skip to content

Commit

Permalink
xarc/tag-renderer support rendering sub templates (#1702)
Browse files Browse the repository at this point in the history
  • Loading branch information
jchip authored Jul 20, 2020
1 parent 7b0e312 commit 3ea2fe7
Show file tree
Hide file tree
Showing 15 changed files with 428 additions and 250 deletions.
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

0 comments on commit 3ea2fe7

Please sign in to comment.