chai-latte
Version:
Build expressive & readable fluent interface libraries.
125 lines (102 loc) • 3.5 kB
text/typescript
import { WordNode } from './WordNode';
import { parse, ParsedSentence } from './parse';
import { TemplateBuilder } from './template';
export { WordNode } from './WordNode';
export { parse } from './parse';
export interface ExecutableSentence {
sentence: any;
execute: any;
};
export const buildFluentTree = (builder: (parse: any) => ParsedSentence[]) : WordNode => {
const executables = builder(parse).map((parsed) => {
return {
sentence: () => parsed,
execute: () => {},
};
});
const executableRoot = createExecutableFluentAPI(executables);
return executableRoot.node;
}
export const createExecutableFluentAPI = (executables: ExecutableSentence[]) : any => {
const executableByTemplate = new Map<string, any>();
const parsed : ParsedSentence[] = [];
executables.forEach((executable) => {
executableByTemplate.set(executable.sentence.template, executable.execute);
parsed.push(executable.sentence);
});
const callback = (template: TemplateBuilder) => {
const isfunctionAvailable = executableByTemplate.has(template.template);
if (isfunctionAvailable) {
const execute = executableByTemplate.get(template.template);
execute(...template.variables);
}
};
return createFluentAPI(parsed, callback);
};
export const createFluentAPI = (sentences: ParsedSentence[], onExecute?: (template: TemplateBuilder) => void) => {
const root : any = {};
root.node = WordNode.createRootNode({ accessor: root })
let lastFunctionCalled: any = null;
const template = new TemplateBuilder(onExecute);
template.disable();
sentences.forEach((parsedSentence) => {
let prev = root;
root.node.addparsedSentence(parsedSentence);
parsedSentence.words.map((parsedChunk, i) => {
const word = parsedChunk.name;
const isImplemented = prev[word]
&& prev[word].node;
if (!isImplemented) {
createWordAccessor(prev, word);
}
const wordNode = prev[word].node
wordNode.addChunk(parsedChunk);
const previousWordNode = prev.node as WordNode;
const currentWordNode = prev[word].node as WordNode;
const previousChunk = parsedSentence.words[i-1];
previousWordNode.addNextWord(currentWordNode, previousChunk);
prev = prev[word];
});
});
function createWordAccessor(parent: any, attribute: any) {
const accessor = function(variable: any) {
if(!variable) {
return;
}
const isFirstWord = parent === root;
if (isFirstWord) {
template.reset();
template.trace({ word: attribute });
lastFunctionCalled = accessor;
}
template.trace({ variable });
return parent[attribute]
};
accessor.node = new WordNode({
word: attribute,
accessor: accessor,
});
Object.defineProperty(parent, attribute, {
get() {
tracePropertyAccess(parent, attribute, accessor);
return accessor;
}
});
}
function tracePropertyAccess(parent: any, attribute: any, accessor: any) {
const isAlreadyTraced = lastFunctionCalled === accessor
if (isAlreadyTraced) {
return;
}
const isFirstWord = parent === root;
const isReAccessed = lastFunctionCalled === parent
if (!isFirstWord && !isReAccessed) {
template.reset();
template.trace({ word: parent.node.word })
}
template.trace({ word: attribute })
lastFunctionCalled = accessor;
}
template.enable();
return root;
}