Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: engine and add more utilites #73

Merged
merged 2 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ This library generates a javascript function code from the template and then use

[Engine](src/engine.ts) class abstracts the above steps and provides a convenient way to use the json templates to evaluate the inputs.

## Getting started

### Use npm package

`npm install @rudderstack/json-template-engine`

```ts
const { JsonTemplateEngine } = require('@rudderstack/json-template-engine');
const engine = JsonTemplateEngine.create(`'Hello ' + .name`);
engine.evaluate({ name: 'World' }); // => 'Hello World'
```

## Features

Template is a set of statements and result the last statement is the output of the template.
Expand Down Expand Up @@ -376,23 +388,14 @@ JsonTemplateEngine.create(`let a = {{$.a.b.c}};`, {

We can use compile time expressions to generate a template and then recompile it as expression. Refer these examples [simple compilation](test/scenarios/compile_time_expressions/template.jt) and [complex compilation](test/scenarios/compile_time_expressions/two_level_path_processing.jt) for more details.


### Comments

Supports both c style single line (`//`) and block comments (`/* .. */`).
Refer this [example](test/scenarios/comments/template.jt) for more details.

For more examples, refer [Scenarios](test/scenarios)

## Getting started

`npm install @rudderstack/json-template-engine`

```ts
const { JsonTemplateEngine } = require('@rudderstack/json-template-engine');
const engine = JsonTemplateEngine.create(`'Hello ' + .name`);
engine.evaluate({ name: 'World' }); // => 'Hello World'
```

## Testing

`npm test`
Expand Down
41 changes: 28 additions & 13 deletions src/engine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable import/no-cycle */
import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY } from './constants';
import { BINDINGS_PARAM_KEY, DATA_PARAM_KEY, EMPTY_EXPR } from './constants';
import { JsonTemplateLexer } from './lexer';
import { JsonTemplateParser } from './parser';
import { JsonTemplateReverseTranslator } from './reverse_translator';
Expand All @@ -14,15 +14,12 @@ export class JsonTemplateEngine {
this.fn = fn;
}

private static compileAsSync(
templateOrExpr: string | Expression,
options?: EngineOptions,
): Function {
private static compileAsSync(template: TemplateInput, options?: EngineOptions): Function {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return Function(
DATA_PARAM_KEY,
BINDINGS_PARAM_KEY,
JsonTemplateEngine.translate(templateOrExpr, options),
JsonTemplateEngine.translate(template, options),
);
}

Expand Down Expand Up @@ -55,14 +52,14 @@ export class JsonTemplateEngine {
return new JsonTemplateEngine(JsonTemplateEngine.compileAsAsync(templateOrExpr, options));
}

static createAsSync(
templateOrExpr: string | Expression,
options?: EngineOptions,
): JsonTemplateEngine {
return new JsonTemplateEngine(JsonTemplateEngine.compileAsSync(templateOrExpr, options));
static createAsSync(template: TemplateInput, options?: EngineOptions): JsonTemplateEngine {
return new JsonTemplateEngine(JsonTemplateEngine.compileAsSync(template, options));
}

static parse(template: TemplateInput, options?: EngineOptions): Expression {
if (!template) {
return EMPTY_EXPR;
}
if (isExpression(template)) {
return template as Expression;
}
Expand Down Expand Up @@ -90,7 +87,25 @@ export class JsonTemplateEngine {
);
}

evaluate(data: unknown, bindings: Record<string, unknown> = {}): unknown {
return this.fn(data ?? {}, bindings);
static evaluateAsSync(
template: TemplateInput,
options: EngineOptions = {},
data: unknown = {},
bindings: Record<string, unknown> = {},
): unknown {
return JsonTemplateEngine.createAsSync(template, options).evaluate(data, bindings);
}

static evaluate(
template: TemplateInput,
options: EngineOptions = {},
data: unknown = {},
bindings: Record<string, unknown> = {},
): unknown {
return JsonTemplateEngine.create(template, options).evaluate(data, bindings);
}

evaluate(data: unknown = {}, bindings: Record<string, unknown> = {}): unknown {
return this.fn(data, bindings);
}
}
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,4 @@ export type FlatMappingAST = FlatMappingPaths & {
outputExpr: PathExpression;
};

export type TemplateInput = string | Expression | FlatMappingPaths[];
export type TemplateInput = string | Expression | FlatMappingPaths[] | undefined;
3 changes: 1 addition & 2 deletions test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ describe('Scenarios tests', () => {
scenarios.forEach((scenario, index) => {
it(`Scenario ${index}: ${Scenario.getTemplatePath(scenario)}`, async () => {
try {
const templateEngine = ScenarioUtils.createTemplateEngine(scenarioDir, scenario);
const result = await ScenarioUtils.evaluateScenario(templateEngine, scenario);
const result = await ScenarioUtils.evaluateScenario(scenarioDir, scenario);
expect(result).toEqual(scenario.output);
} catch (error: any) {
expect(error.message).toContain(scenario.error);
Expand Down
3 changes: 1 addition & 2 deletions test/scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ describe(`${scenarioName}:`, () => {
scenario.templatePath || 'template.jt'
}`,
);
const templateEngine = ScenarioUtils.createTemplateEngine(scenarioDir, scenario);
result = await ScenarioUtils.evaluateScenario(templateEngine, scenario);
result = await ScenarioUtils.evaluateScenario(scenarioDir, scenario);
expect(result).toEqual(scenario.output);
} catch (error: any) {
console.error(error);
Expand Down
8 changes: 8 additions & 0 deletions test/scenarios/base/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Scenario } from '../../types';

export const data: Scenario[] = [
{
template: '',
output: undefined,
},
];
28 changes: 20 additions & 8 deletions test/utils/scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { join } from 'node:path';
import { FlatMappingPaths, JsonTemplateEngine, PathType } from '../../src';
import { Scenario } from '../types';

function getTemplate(scenarioDir: string, scenario: Scenario): string {
const templatePath = join(scenarioDir, Scenario.getTemplatePath(scenario));
return readFileSync(templatePath, 'utf-8');
}
function initializeScenario(scenarioDir: string, scenario: Scenario) {
scenario.options = scenario.options || {};
scenario.options.defaultPathType = scenario.options.defaultPathType || PathType.SIMPLE;
const templatePath = join(scenarioDir, Scenario.getTemplatePath(scenario));
let template: string = readFileSync(templatePath, 'utf-8');
let template = scenario.template ?? getTemplate(scenarioDir, scenario);
if (scenario.containsMappings) {
template = JsonTemplateEngine.convertMappingsToTemplate(
JSON.parse(template) as FlatMappingPaths[],
Expand All @@ -19,13 +22,22 @@ function initializeScenario(scenarioDir: string, scenario: Scenario) {
);
}

export function createTemplateEngine(scenarioDir: string, scenario: Scenario): JsonTemplateEngine {
export function evaluateScenario(scenarioDir: string, scenario: Scenario): any {
initializeScenario(scenarioDir, scenario);
return JsonTemplateEngine.create(scenario.template as string, scenario.options);
}

export function evaluateScenario(templateEngine: JsonTemplateEngine, scenario: Scenario): any {
return templateEngine.evaluate(scenario.input, scenario.bindings);
if (scenario.containsMappings) {
return JsonTemplateEngine.evaluateAsSync(
scenario.template,
scenario.options,
scenario.input,
scenario.bindings,
);
}
return JsonTemplateEngine.evaluate(
scenario.template,
scenario.options,
scenario.input,
scenario.bindings,
);
}

export function extractScenarios(scenarioDir: string): Scenario[] {
Expand Down
Loading