docutils-ts
Version:
Port of the Python Docutils library to TypeScript
137 lines (136 loc) • 5 kB
JavaScript
import { ApplicationError } from "./exceptions.js";
function leftPad(num, len, pad) {
return pad.repeat(len - num.toString().length) + num.toString();
}
/**
* Transformer class responsible for transforming document output
*/
class Transformer {
/**
* Create transformer class
* @param {nodes.myDoc} myDoc - document to transform
*/
constructor(myDoc, logger) {
this.logger = logger;
this.transforms = [];
this.unknownReferenceResolvers = [];
this.document = myDoc;
this.applied = [];
this.sorted = 0;
this.components = {};
this.serialno = 0;
}
/**
* populateFromComponents
*/
populateFromComponents(...components) {
this.logger.silly('populateFromComponents');
for (const component of components) {
if (!component) {
this.logger.warn('component is undefined');
continue;
}
this.logger.silly(`processing ${component.toString()} ${component.componentType}`);
const transforms = component.getTransforms() || [];
transforms.forEach((t) => {
if (typeof t === 'undefined') {
throw new Error(`got invalid transform from ${component}`);
}
});
if (transforms.filter((x) => typeof x === 'undefined').length !== 0) {
throw new Error(`got invalid transform from ${component}`);
}
this.logger.silly('adding transforms for component');
this.addTransforms(transforms);
// check for existing key?
this.components[component.componentType] = component;
}
this.sorted = 0;
const urr = [];
for (const i of components) {
if (i !== undefined) {
this.logger.silly(`collecting unknownReferenceResolver from component ${i}`);
if (i.unknownReferenceResolvers) {
urr.push(...i.unknownReferenceResolvers);
}
}
else {
// console.log('component is undefined. fixme');
}
}
for (const f of urr) {
if (typeof f === 'undefined') {
throw new ApplicationError('Unexpected undefined value in ist of unknown reference resolvers');
}
}
const decoratedList = urr.map((f) => [f.priority, f]);
decoratedList.sort();
this.unknownReferenceResolvers.push(...decoratedList.map((f) => f[1]));
}
/**
* apply the transforms
*/
applyTransforms() {
this.document.reporter.attachObserver(this.document.noteTransformMessage
.bind(this.document));
while (this.transforms.length) {
if (!this.sorted) {
this.transforms.sort((el1, el2) => {
if (el1[0] < el2[0]) {
return -1;
}
if (el1[0] > el2[0]) {
return 1;
}
return 0;
});
this.transforms.reverse();
this.sorted = 1;
}
const t = this.transforms.pop();
const [priority, TransformClass, pending, kwargs] = t;
try {
const transform = new TransformClass(this.document, pending);
this.logger.info('applying transfomer class', { value: typeof TransformClass });
transform.apply(kwargs);
}
catch (error) {
throw error;
}
this.applied.push([priority, TransformClass, pending, kwargs]);
}
}
/**
* Store multiple transforms, with default priorities.
* @param {Array} transformList - Array of transform classes (not instances).
*/
addTransforms(transformList) {
transformList.forEach((transformClass) => {
if (!transformClass) {
throw new Error('invalid argument');
}
const priorityString = this.getPriorityString(transformClass, transformClass.defaultPriority);
// console.log(`priority string is ${priorityString}`);
// console.log(`I have ${transformClass}`);
this.transforms.push([priorityString, transformClass, null, {}]);
this.sorted = 0;
});
}
/**
*
* Return a string, `priority` combined with `self.serialno`.
*
* This ensures FIFO order on transforms with identical priority.
*/
getPriorityString(class_, priority) {
if (typeof class_ === 'undefined') {
throw new Error('undefined');
}
this.serialno += 1;
return `${leftPad(priority, 3, '0')}-${leftPad(this.serialno, 3, '0')}`;
}
addPending(pending, priority) {
// fixme implement
}
}
export default Transformer;