Refactor template rendering (#1583)
* Update _errorToJson to _serializeError * Remove async * Refactor render * Simplify _getModifiedData * Rename data => commonData * Rename templates => template for consistency * Improve errors check * Update tests
This commit is contained in:
parent
36b7e34cce
commit
8179846e38
@ -30,7 +30,7 @@ class AnkiNoteBuilder {
|
|||||||
definition,
|
definition,
|
||||||
mode,
|
mode,
|
||||||
context,
|
context,
|
||||||
templates,
|
template,
|
||||||
deckName,
|
deckName,
|
||||||
modelName,
|
modelName,
|
||||||
fields,
|
fields,
|
||||||
@ -51,10 +51,10 @@ class AnkiNoteBuilder {
|
|||||||
duplicateScopeCheckChildren = true;
|
duplicateScopeCheckChildren = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this._createData(definition, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, injectedMedia);
|
const commonData = this._createData(definition, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, injectedMedia);
|
||||||
const formattedFieldValuePromises = [];
|
const formattedFieldValuePromises = [];
|
||||||
for (const [, fieldValue] of fields) {
|
for (const [, fieldValue] of fields) {
|
||||||
const formattedFieldValuePromise = this._formatField(fieldValue, data, templates, errors);
|
const formattedFieldValuePromise = this._formatField(fieldValue, commonData, template, errors);
|
||||||
formattedFieldValuePromises.push(formattedFieldValuePromise);
|
formattedFieldValuePromises.push(formattedFieldValuePromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,12 +110,12 @@ class AnkiNoteBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async _formatField(field, data, templates, errors=null) {
|
async _formatField(field, commonData, template, errors=null) {
|
||||||
return await this._stringReplaceAsync(field, this._markerPattern, async (g0, marker) => {
|
return await this._stringReplaceAsync(field, this._markerPattern, async (g0, marker) => {
|
||||||
try {
|
try {
|
||||||
return await this._renderTemplate(templates, data, marker);
|
return await this._renderTemplate(template, marker, commonData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (errors) {
|
if (Array.isArray(errors)) {
|
||||||
const error = new Error(`Template render error for {${marker}}`);
|
const error = new Error(`Template render error for {${marker}}`);
|
||||||
error.data = {error: e};
|
error.data = {error: e};
|
||||||
errors.push(error);
|
errors.push(error);
|
||||||
@ -140,7 +140,7 @@ class AnkiNoteBuilder {
|
|||||||
return (await Promise.all(parts)).join('');
|
return (await Promise.all(parts)).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _renderTemplate(template, data, marker) {
|
async _renderTemplate(template, marker, commonData) {
|
||||||
return await this._templateRenderer.render(template, {data, marker}, 'ankiNote');
|
return await this._templateRenderer.render(template, {marker, commonData}, 'ankiNote');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1463,7 +1463,7 @@ class Display extends EventDispatcher {
|
|||||||
|
|
||||||
async _createNote(definition, mode, context, injectMedia, errors) {
|
async _createNote(definition, mode, context, injectMedia, errors) {
|
||||||
const options = this._options;
|
const options = this._options;
|
||||||
const templates = this._ankiFieldTemplates;
|
const template = this._ankiFieldTemplates;
|
||||||
const {
|
const {
|
||||||
general: {resultOutputMode, glossaryLayoutMode, compactTags},
|
general: {resultOutputMode, glossaryLayoutMode, compactTags},
|
||||||
anki: ankiOptions
|
anki: ankiOptions
|
||||||
@ -1488,7 +1488,7 @@ class Display extends EventDispatcher {
|
|||||||
definition,
|
definition,
|
||||||
mode,
|
mode,
|
||||||
context,
|
context,
|
||||||
templates,
|
template,
|
||||||
deckName,
|
deckName,
|
||||||
modelName,
|
modelName,
|
||||||
fields,
|
fields,
|
||||||
|
@ -176,14 +176,14 @@ class AnkiTemplatesController {
|
|||||||
sentence: {text: definition.rawSource, offset: 0},
|
sentence: {text: definition.rawSource, offset: 0},
|
||||||
documentTitle: document.title
|
documentTitle: document.title
|
||||||
};
|
};
|
||||||
let templates = options.anki.fieldTemplates;
|
let template = options.anki.fieldTemplates;
|
||||||
if (typeof templates !== 'string') { templates = this._defaultFieldTemplates; }
|
if (typeof template !== 'string') { template = this._defaultFieldTemplates; }
|
||||||
const {general: {resultOutputMode, glossaryLayoutMode, compactTags}} = options;
|
const {general: {resultOutputMode, glossaryLayoutMode, compactTags}} = options;
|
||||||
const note = await this._ankiNoteBuilder.createNote({
|
const note = await this._ankiNoteBuilder.createNote({
|
||||||
definition,
|
definition,
|
||||||
mode,
|
mode,
|
||||||
context,
|
context,
|
||||||
templates,
|
template,
|
||||||
deckName: '',
|
deckName: '',
|
||||||
modelName: '',
|
modelName: '',
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -19,8 +19,8 @@ class TemplateRendererFrameApi {
|
|||||||
constructor(templateRenderer) {
|
constructor(templateRenderer) {
|
||||||
this._templateRenderer = templateRenderer;
|
this._templateRenderer = templateRenderer;
|
||||||
this._windowMessageHandlers = new Map([
|
this._windowMessageHandlers = new Map([
|
||||||
['render', {async: true, handler: this._onRender.bind(this)}],
|
['render', {async: false, handler: this._onRender.bind(this)}],
|
||||||
['getModifiedData', {async: true, handler: this._onGetModifiedData.bind(this)}]
|
['getModifiedData', {async: false, handler: this._onGetModifiedData.bind(this)}]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,25 +47,25 @@ class TemplateRendererFrameApi {
|
|||||||
}
|
}
|
||||||
response = {result};
|
response = {result};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
response = {error: this._errorToJson(error)};
|
response = {error: this._serializeError(error)};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof id === 'undefined') { return; }
|
if (typeof id === 'undefined') { return; }
|
||||||
source.postMessage({action: `${action}.response`, params: response, id}, '*');
|
source.postMessage({action: `${action}.response`, params: response, id}, '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onRender({template, data, type}) {
|
_onRender({template, data, type}) {
|
||||||
return await this._templateRenderer.render(template, data, type);
|
return this._templateRenderer.render(template, data, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onGetModifiedData({data, type}) {
|
_onGetModifiedData({data, type}) {
|
||||||
const result = await this._templateRenderer.getModifiedData(data, type);
|
const result = this._templateRenderer.getModifiedData(data, type);
|
||||||
return this._clone(result);
|
return this._clone(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
_errorToJson(error) {
|
_serializeError(error) {
|
||||||
try {
|
try {
|
||||||
if (error !== null && typeof error === 'object') {
|
if (typeof error === 'object' && error !== null) {
|
||||||
return {
|
return {
|
||||||
name: error.name,
|
name: error.name,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
const templateRenderer = new TemplateRenderer(japaneseUtil);
|
const templateRenderer = new TemplateRenderer(japaneseUtil);
|
||||||
const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
|
const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil);
|
||||||
templateRenderer.registerDataType('ankiNote', {
|
templateRenderer.registerDataType('ankiNote', {
|
||||||
modifier: ({data, marker}) => ankiNoteDataCreator.create(marker, data)
|
modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData)
|
||||||
});
|
});
|
||||||
const templateRendererFrameApi = new TemplateRendererFrameApi(templateRenderer);
|
const templateRendererFrameApi = new TemplateRendererFrameApi(templateRenderer);
|
||||||
templateRendererFrameApi.prepare();
|
templateRendererFrameApi.prepare();
|
||||||
|
@ -33,10 +33,21 @@ class TemplateRenderer {
|
|||||||
this._dataTypes.set(name, {modifier});
|
this._dataTypes.set(name, {modifier});
|
||||||
}
|
}
|
||||||
|
|
||||||
async render(template, data, type) {
|
render(template, data, type) {
|
||||||
|
const instance = this._getTemplateInstance(template);
|
||||||
|
data = this._getModifiedData(data, type);
|
||||||
|
return this._renderTemplate(instance, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
getModifiedData(data, type) {
|
||||||
|
return this._getModifiedData(data, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_getTemplateInstance(template) {
|
||||||
if (!this._helpersRegistered) {
|
if (!this._helpersRegistered) {
|
||||||
this._registerHelpers();
|
this._registerHelpers();
|
||||||
this._helpersRegistered = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache = this._cache;
|
const cache = this._cache;
|
||||||
@ -47,8 +58,11 @@ class TemplateRenderer {
|
|||||||
cache.set(template, instance);
|
cache.set(template, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderTemplate(instance, data) {
|
||||||
try {
|
try {
|
||||||
data = this._getModifiedData(data, type);
|
|
||||||
this._stateStack = [new Map()];
|
this._stateStack = [new Map()];
|
||||||
return instance(data).trim();
|
return instance(data).trim();
|
||||||
} finally {
|
} finally {
|
||||||
@ -56,27 +70,16 @@ class TemplateRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getModifiedData(data, type) {
|
_getModifiedData(data, type) {
|
||||||
return this._getModifiedData(data, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private
|
|
||||||
|
|
||||||
_getModifier(type) {
|
|
||||||
if (typeof type === 'string') {
|
if (typeof type === 'string') {
|
||||||
const typeInfo = this._dataTypes.get(type);
|
const typeInfo = this._dataTypes.get(type);
|
||||||
if (typeof typeInfo !== 'undefined') {
|
if (typeof typeInfo !== 'undefined') {
|
||||||
return typeInfo.modifier;
|
const {modifier} = typeInfo;
|
||||||
|
if (typeof modifier === 'function') {
|
||||||
|
data = modifier(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getModifiedData(data, type) {
|
|
||||||
const modifier = this._getModifier(type);
|
|
||||||
if (typeof modifier === 'function') {
|
|
||||||
data = modifier(data);
|
|
||||||
}
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +125,8 @@ class TemplateRenderer {
|
|||||||
for (const [name, helper] of helpers) {
|
for (const [name, helper] of helpers) {
|
||||||
this._registerHelper(name, helper);
|
this._registerHelper(name, helper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._helpersRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerHelper(name, helper) {
|
_registerHelper(name, helper) {
|
||||||
|
@ -55,7 +55,7 @@ async function createVM() {
|
|||||||
const japaneseUtil = new JapaneseUtil(null);
|
const japaneseUtil = new JapaneseUtil(null);
|
||||||
this._templateRenderer = new TemplateRenderer(japaneseUtil);
|
this._templateRenderer = new TemplateRenderer(japaneseUtil);
|
||||||
this._templateRenderer.registerDataType('ankiNote', {
|
this._templateRenderer.registerDataType('ankiNote', {
|
||||||
modifier: ({data, marker}) => ankiNoteDataCreator.create(marker, data)
|
modifier: ({marker, commonData}) => ankiNoteDataCreator.create(marker, commonData)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ function getFieldMarkers(type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRenderResults(dictionaryEntries, type, mode, templates, AnkiNoteBuilder, write) {
|
async function getRenderResults(dictionaryEntries, type, mode, template, AnkiNoteBuilder, write) {
|
||||||
const markers = getFieldMarkers(type);
|
const markers = getFieldMarkers(type);
|
||||||
const fields = [];
|
const fields = [];
|
||||||
for (const marker of markers) {
|
for (const marker of markers) {
|
||||||
@ -158,7 +158,7 @@ async function getRenderResults(dictionaryEntries, type, mode, templates, AnkiNo
|
|||||||
definition: dictionaryEntry,
|
definition: dictionaryEntry,
|
||||||
mode: null,
|
mode: null,
|
||||||
context,
|
context,
|
||||||
templates,
|
template,
|
||||||
deckName: 'deckName',
|
deckName: 'deckName',
|
||||||
modelName: 'modelName',
|
modelName: 'modelName',
|
||||||
fields,
|
fields,
|
||||||
@ -193,7 +193,7 @@ async function main() {
|
|||||||
const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'}));
|
const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'}));
|
||||||
const actualResults1 = [];
|
const actualResults1 = [];
|
||||||
|
|
||||||
const templates = fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'});
|
const template = fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'});
|
||||||
|
|
||||||
for (let i = 0, ii = tests.length; i < ii; ++i) {
|
for (let i = 0, ii = tests.length; i < ii; ++i) {
|
||||||
const test = tests[i];
|
const test = tests[i];
|
||||||
@ -204,7 +204,7 @@ async function main() {
|
|||||||
const {name, mode, text} = test;
|
const {name, mode, text} = test;
|
||||||
const options = vm.buildOptions(optionsPresets, test.options);
|
const options = vm.buildOptions(optionsPresets, test.options);
|
||||||
const {dictionaryEntries} = clone(await vm.translator.findTerms(mode, text, options));
|
const {dictionaryEntries} = clone(await vm.translator.findTerms(mode, text, options));
|
||||||
const results = mode !== 'simple' ? clone(await getRenderResults(dictionaryEntries, 'terms', mode, templates, AnkiNoteBuilder, write)) : null;
|
const results = mode !== 'simple' ? clone(await getRenderResults(dictionaryEntries, 'terms', mode, template, AnkiNoteBuilder, write)) : null;
|
||||||
actualResults1.push({name, results});
|
actualResults1.push({name, results});
|
||||||
if (!write) {
|
if (!write) {
|
||||||
assert.deepStrictEqual(results, expected1.results);
|
assert.deepStrictEqual(results, expected1.results);
|
||||||
@ -216,7 +216,7 @@ async function main() {
|
|||||||
const {name, text} = test;
|
const {name, text} = test;
|
||||||
const options = vm.buildOptions(optionsPresets, test.options);
|
const options = vm.buildOptions(optionsPresets, test.options);
|
||||||
const dictionaryEntries = clone(await vm.translator.findKanji(text, options));
|
const dictionaryEntries = clone(await vm.translator.findKanji(text, options));
|
||||||
const results = clone(await getRenderResults(dictionaryEntries, 'kanji', null, templates, AnkiNoteBuilder, write));
|
const results = clone(await getRenderResults(dictionaryEntries, 'kanji', null, template, AnkiNoteBuilder, write));
|
||||||
actualResults1.push({name, results});
|
actualResults1.push({name, results});
|
||||||
if (!write) {
|
if (!write) {
|
||||||
assert.deepStrictEqual(results, expected1.results);
|
assert.deepStrictEqual(results, expected1.results);
|
||||||
|
Loading…
Reference in New Issue
Block a user