1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-30 08:26:59 +02:00
joplin/packages/lib/services/WhenClause.ts
2021-05-16 15:21:55 +02:00

104 lines
2.8 KiB
TypeScript

import { ContextKeyExpr, ContextKeyExpression, IContext } from './contextkey/contextkey';
// We would like to support expressions with brackets but VSCode When Clauses
// don't support this. To support this, we split the expressions with brackets
// into sub-expressions, which can then be parsed and executed separately by the
// When Clause library.
interface AdvancedExpression {
// (test1 && test2) || test3
original: string;
// __sub_1 || test3
compiledText: string;
// { __sub_1: "test1 && test2" }
subExpressions: any;
}
function parseAdvancedExpression(advancedExpression: string): AdvancedExpression {
let subExpressionIndex = -1;
let subExpressions: string = '';
let currentSubExpressionKey = '';
const subContext: any = {};
let inBrackets = false;
for (let i = 0; i < advancedExpression.length; i++) {
const c = advancedExpression[i];
if (c === '(') {
if (inBrackets) throw new Error('Nested brackets not supported');
inBrackets = true;
subExpressionIndex++;
currentSubExpressionKey = `__sub_${subExpressionIndex}`;
subContext[currentSubExpressionKey] = '';
continue;
}
if (c === ')') {
if (!inBrackets) throw new Error('Closing bracket without an opening one');
inBrackets = false;
subExpressions += currentSubExpressionKey;
currentSubExpressionKey = '';
continue;
}
if (inBrackets) {
subContext[currentSubExpressionKey] += c;
} else {
subExpressions += c;
}
}
return {
compiledText: subExpressions,
subExpressions: subContext,
original: advancedExpression,
};
}
export default class WhenClause {
private expression_: AdvancedExpression;
private validate_: boolean;
private ruleCache_: Record<string, ContextKeyExpression> = {};
public constructor(expression: string, validate: boolean = true) {
this.expression_ = parseAdvancedExpression(expression);
this.validate_ = validate;
}
private createContext(ctx: any): IContext {
return {
getValue: (key: string) => {
return ctx[key];
},
};
}
private rules(exp: string): ContextKeyExpression {
if (this.ruleCache_[exp]) return this.ruleCache_[exp];
this.ruleCache_[exp] = ContextKeyExpr.deserialize(exp);
return this.ruleCache_[exp];
}
public evaluate(context: any): boolean {
if (this.validate_) this.validate(context);
const subContext: any = {};
for (const k in this.expression_.subExpressions) {
const subExp = this.expression_.subExpressions[k];
subContext[k] = this.rules(subExp).evaluate(this.createContext(context));
}
const fullContext = { ...context, ...subContext };
return this.rules(this.expression_.compiledText).evaluate(this.createContext(fullContext));
}
public validate(context: any) {
const keys = this.rules(this.expression_.original.replace(/[()]/g, ' ')).keys();
for (const key of keys) {
if (!(key in context)) throw new Error(`No such key: ${key}`);
}
}
}