├── .prettierignore ├── index.ts ├── .gitignore ├── src ├── index.ts ├── array-executors │ ├── index.ts │ ├── array-executor.ts │ └── conditional.ts ├── internal.ts ├── value-executors │ ├── value-executor.ts │ ├── index.ts │ ├── template.ts │ ├── include.ts │ └── ternary.ts ├── key-executors │ ├── key-executor.ts │ ├── index.ts │ ├── concat.ts │ ├── optional.ts │ ├── flatten.ts │ ├── unwrap.ts │ ├── merge.ts │ ├── lets.ts │ ├── let.ts │ ├── for.ts │ └── each.ts ├── st.ts ├── transform.ts ├── common.ts └── select.ts ├── browser.js ├── .npmignore ├── .prettierrc ├── tslint.json ├── jest.config.js ├── tsconfig.json ├── .vscode └── settings.json ├── __tests__ └── sunny │ ├── basic-sync │ ├── key-transform.test.js │ ├── ternary.test.js │ ├── general.test.js │ ├── unwrap.test.js │ ├── $index.test.js │ ├── $root.test.js │ ├── $this.test.js │ ├── concat.test.js │ ├── condition.test.js │ ├── merge.test.js │ ├── flatten.test.js │ ├── each.test.js │ ├── let.test.js │ ├── lets.test.js │ └── optional.test.js │ ├── basic │ ├── key-transform.test.js │ ├── ternary.test.js │ ├── general.test.js │ ├── unwrap.test.js │ ├── $index.test.js │ ├── merge.test.js │ ├── $root.test.js │ ├── $this.test.js │ ├── concat.test.js │ ├── condition.test.js │ ├── flatten.test.js │ ├── each.test.js │ ├── for.test.js │ ├── let.test.js │ ├── lets.test.js │ ├── optional.test.js │ └── $root.2.test.js │ ├── common │ └── key-transform.common.js │ ├── combined-sync │ ├── each-template.js │ └── each-let.test.js │ └── combined │ ├── each-template.test.js │ └── each-let.test.js ├── index.js ├── package.json ├── README.md └── LICENSE /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.json 3 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './dist'; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | log* 3 | node_modules 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { SelectTransform } from './internal'; 2 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | const d = require('./dist'); 2 | var ST = new d.SelectTransform(); 3 | Object.assign($context, { ST }); 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .gitignore 3 | .prettierignore 4 | .prettierrc 5 | .jest.config.js 6 | yarn.lock 7 | __tests__ 8 | browser.js 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": true, 3 | "singleQuote": true, 4 | "printWidth": 80, 5 | "trailingComma": "all", 6 | "tabWidth": 2, 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-airbnb-base", 3 | "rules": { 4 | "no-magic-numbers": false 5 | }, 6 | "linterOptions": { 7 | "exclude": [ 8 | "node_modules/**", 9 | "**/*.d.ts" 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/__tests__'], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | //verbose: true, 7 | testMatch: ['**/*.test.js'], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | }; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "declaration": true 5 | }, 6 | "include": [ 7 | "src", "tests" 8 | ], 9 | "exclude": [ 10 | "node_modules/**", 11 | "**/*.d.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/array-executors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './array-executor'; 2 | 3 | import { Conditional } from './conditional'; 4 | import { ArrayExecutor } from './array-executor'; 5 | 6 | /** 7 | * Array of executors for arrays in templates. 8 | */ 9 | export const executors: ArrayExecutor[] = [new Conditional()]; 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true, 3 | "editor.insertSpaces": true, 4 | "workbench.editor.tabSizing": "fit", 5 | "editor.autoIndent": true, 6 | "editor.formatOnSave": true, 7 | "editor.tabSize": 2, 8 | "files.trimTrailingWhitespace": true, 9 | "files.insertFinalNewline": true, 10 | "editor.rulers": [ 11 | 80 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/key-transform.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | const allData = require('../common/key-transform.common'); 3 | 4 | const st = new ST(); 5 | 6 | test('transform is correct', done => { 7 | const result = st.transformSync(allData.generalTemplate, allData.generalData); 8 | expect(result).toEqual(allData.generalExpected); 9 | done(); 10 | }); 11 | -------------------------------------------------------------------------------- /src/internal.ts: -------------------------------------------------------------------------------- 1 | import * as ae from './array-executors'; 2 | import * as ke from './key-executors'; 3 | import * as ve from './value-executors'; 4 | export const arrayExecutors = ae.executors; 5 | export const keyExecutors = ke.executors; 6 | export const valueExecutors = ve.executors; 7 | export * from './common'; 8 | export * from './select'; 9 | export * from './transform'; 10 | export * from './st'; 11 | -------------------------------------------------------------------------------- /src/value-executors/value-executor.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Transform } from '../internal'; 2 | 3 | export interface ValueExecutor { 4 | getName(): string; 5 | fits(template: string): boolean; 6 | executeSync(template: string, data: DataObject, ts: Transform): DataObject; 7 | execute( 8 | template: string, 9 | data: DataObject, 10 | ts: Transform, 11 | ): Promise; 12 | } 13 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/key-transform.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | const allData = require('../common/key-transform.common'); 3 | 4 | const st = new ST(); 5 | 6 | test('transform is correct', done => { 7 | st.transform(allData.generalTemplate, allData.generalData).then(result => { 8 | expect(result).toEqual(allData.generalExpected); 9 | done(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/value-executors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './value-executor'; 2 | 3 | import { Template } from './template'; 4 | import { Include } from './include'; 5 | import { ValueExecutor } from './value-executor'; 6 | import { Ternary } from './ternary'; 7 | 8 | /** 9 | * Array of executors for values in templates. 10 | */ 11 | export const executors: ValueExecutor[] = [ 12 | new Template(), 13 | new Include(), 14 | new Ternary(), 15 | ]; 16 | -------------------------------------------------------------------------------- /src/array-executors/array-executor.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Transform } from '../internal'; 2 | 3 | export interface ArrayExecutor { 4 | getName(): string; 5 | fits(template: Array): boolean; 6 | executeSync( 7 | template: Array, 8 | data: DataObject, 9 | ts: Transform, 10 | ): DataObject; 11 | execute( 12 | template: Array, 13 | data: DataObject, 14 | ts: Transform, 15 | ): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/ternary.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | }; 8 | 9 | const template = { 10 | name: '{{ #? name }}', 11 | age: '{{ #? age }}', 12 | }; 13 | 14 | const expected = { 15 | name: 'Jakub', 16 | }; 17 | 18 | test('transform is correct', done => { 19 | const result = st.transformSync(template, data); 20 | 21 | expect(result).toEqual(expected); 22 | done(); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/ternary.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | }; 8 | 9 | const template = { 10 | name: '{{ #? name }}', 11 | age: '{{ #? age }}', 12 | }; 13 | 14 | const expected = { 15 | name: 'Jakub', 16 | }; 17 | 18 | test('transform is correct', done => { 19 | st.transform(template, data).then(result => { 20 | expect(result).toEqual(expected); 21 | done(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /__tests__/sunny/common/key-transform.common.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | generalData: { 3 | name: 'Jakub', 4 | surname: 'Mifek', 5 | age: 24, 6 | car: 'sedan', 7 | }, 8 | 9 | generalTemplate: { 10 | name: '{{ name }}', 11 | surname: '{{ surname }}', 12 | age: '{{ age }}', 13 | '{{ "the-car-of-" + name }}': '{{ car }}', 14 | }, 15 | 16 | generalExpected: { 17 | name: 'Jakub', 18 | surname: 'Mifek', 19 | age: 24, 20 | 'the-car-of-Jakub': 'sedan', 21 | }, 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /src/key-executors/key-executor.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Transform } from '../internal'; 2 | 3 | export interface KeyExecutor { 4 | getName(): string; 5 | fits(template: string): boolean; 6 | executeSync( 7 | template: DataObject, 8 | data: DataObject, 9 | ts: Transform, 10 | key: string, 11 | result: DataObject, 12 | ): DataObject; 13 | execute( 14 | template: DataObject, 15 | data: DataObject, 16 | ts: Transform, 17 | key: string, 18 | result: DataObject, 19 | ): Promise; 20 | } 21 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/general.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | name: '{{ name }}', 13 | surname: '{{ surname }}', 14 | age: '{{ age }}', 15 | test: 'test', 16 | }; 17 | 18 | const expected = { 19 | name: 'Jakub', 20 | surname: 'Mifek', 21 | age: 24, 22 | test: 'test', 23 | }; 24 | 25 | test('transform is correct', done => { 26 | st.transform(template, data).then(result => { 27 | expect(result).toEqual(expected); 28 | done(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/general.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | name: '{{ name }}', 13 | surname: '{{ surname }}', 14 | age: '{{ age }}', 15 | test: 'test', 16 | }; 17 | 18 | const expected = { 19 | name: 'Jakub', 20 | surname: 'Mifek', 21 | age: 24, 22 | test: 'test', 23 | }; 24 | 25 | test('transform is correct', done => { 26 | st.transform(template, data).then(result => { 27 | expect(result).toEqual(expected); 28 | done(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/unwrap.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | '{{ #unwrap }}': { 13 | attach: { 14 | nickname: 'Kuba', 15 | }, 16 | exclude: ['age'], 17 | }, 18 | }; 19 | 20 | const expected = { 21 | name: 'Jakub', 22 | surname: 'Mifek', 23 | nickname: 'Kuba', 24 | }; 25 | 26 | test('transform is correct', done => { 27 | const result = st.transformSync(template, data); 28 | 29 | expect(result).toEqual(expected); 30 | done(); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/unwrap.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | '{{ #unwrap }}': { 13 | attach: { 14 | nickname: 'Kuba', 15 | }, 16 | exclude: ['age'], 17 | }, 18 | }; 19 | 20 | const expected = { 21 | name: 'Jakub', 22 | surname: 'Mifek', 23 | nickname: 'Kuba', 24 | }; 25 | 26 | test('transform is correct', done => { 27 | st.transform(template, data).then(result => { 28 | expect(result).toEqual(expected); 29 | done(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/$index.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | friends: [ 8 | { 9 | name: 'Michal', 10 | }, 11 | ], 12 | }; 13 | 14 | const template = { 15 | name: '{{ $root.name }}', 16 | friends: { 17 | '{{ #each friends }}': { 18 | name: '{{ $index }}', 19 | }, 20 | }, 21 | }; 22 | 23 | const expected = { 24 | name: 'Jakub', 25 | friends: [ 26 | { 27 | name: 0, 28 | }, 29 | ], 30 | }; 31 | 32 | test('transform is correct', done => { 33 | const result = st.transformSync(template, data); 34 | 35 | expect(result).toEqual(expected); 36 | done(); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/$index.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | friends: [ 8 | { 9 | name: 'Michal', 10 | }, 11 | ], 12 | }; 13 | 14 | const template = { 15 | name: '{{ $root.name }}', 16 | friends: { 17 | '{{ #each friends }}': { 18 | name: '{{ $index }}', 19 | }, 20 | }, 21 | }; 22 | 23 | const expected = { 24 | name: 'Jakub', 25 | friends: [ 26 | { 27 | name: 0, 28 | }, 29 | ], 30 | }; 31 | 32 | test('transform is correct', done => { 33 | st.transform(template, data).then(result => { 34 | expect(result).toEqual(expected); 35 | done(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/merge.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | '{{ #merge }}': [ 13 | { 14 | name: '{{ name }}', 15 | surname: '{{ surname }}', 16 | }, 17 | { 18 | surname: '{{ surname }}', 19 | age: '{{ age }}', 20 | }, 21 | ], 22 | }; 23 | 24 | const expected = { 25 | name: 'Jakub', 26 | surname: 'Mifek', 27 | age: 24, 28 | }; 29 | 30 | test('transform is correct', done => { 31 | st.transform(template, data).then(result => { 32 | expect(result).toEqual(expected); 33 | done(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/$root.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | friends: [ 8 | { 9 | name: 'Michal', 10 | }, 11 | ], 12 | }; 13 | 14 | const template = { 15 | name: '{{ $root.name }}', 16 | friends: { 17 | '{{ #each friends }}': { 18 | name: '{{ $root.name }}', 19 | }, 20 | }, 21 | }; 22 | 23 | const expected = { 24 | name: 'Jakub', 25 | friends: [ 26 | { 27 | name: 'Jakub', 28 | }, 29 | ], 30 | }; 31 | 32 | test('transform is correct', done => { 33 | const result = st.transformSync(template, data); 34 | 35 | expect(result).toEqual(expected); 36 | done(); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/$this.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | friends: [ 8 | { 9 | name: 'Michal', 10 | }, 11 | ], 12 | }; 13 | 14 | const template = { 15 | name: '{{ $root.name }}', 16 | friends: { 17 | '{{ #each friends }}': { 18 | name: '{{ $this.name }}', 19 | }, 20 | }, 21 | }; 22 | 23 | const expected = { 24 | name: 'Jakub', 25 | friends: [ 26 | { 27 | name: 'Michal', 28 | }, 29 | ], 30 | }; 31 | 32 | test('transform is correct', done => { 33 | const result = st.transformSync(template, data); 34 | 35 | expect(result).toEqual(expected); 36 | done(); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/concat.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | name: '{{ name }}', 13 | surname: '{{ surname }}', 14 | age: '{{ age }}', 15 | test: { 16 | '{{ #concat }}': ['{{ name }}', '{{ surname }}', '{{ age }}'], 17 | }, 18 | }; 19 | 20 | const expected = { 21 | name: 'Jakub', 22 | surname: 'Mifek', 23 | age: 24, 24 | test: ['Jakub', 'Mifek', 24], 25 | }; 26 | 27 | test('transform is correct', done => { 28 | const result = st.transformSync(template, data); 29 | 30 | expect(result).toEqual(expected); 31 | done(); 32 | }); 33 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/condition.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | name: [ 13 | { '{{ #if name === "Jakub" }}': 'Kuba' }, 14 | { '{{ #else }}': '{{ name }}' }, 15 | ], 16 | surname: '{{ surname }}', 17 | age: '{{ age }}', 18 | test: 'test', 19 | }; 20 | 21 | const expected = { 22 | name: 'Kuba', 23 | surname: 'Mifek', 24 | age: 24, 25 | test: 'test', 26 | }; 27 | 28 | test('transform is correct', done => { 29 | const result = st.transformSync(template, data); 30 | 31 | expect(result).toEqual(expected); 32 | done(); 33 | }); 34 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/merge.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | '{{ #merge }}': [ 13 | { 14 | name: '{{ name }}', 15 | surname: '{{ surname }}', 16 | }, 17 | { 18 | surname: '{{ surname }}', 19 | age: '{{ age }}', 20 | }, 21 | ], 22 | }; 23 | 24 | const expected = { 25 | name: 'Jakub', 26 | surname: 'Mifek', 27 | age: 24, 28 | }; 29 | 30 | test('transform is correct', done => { 31 | st.transform(template, data).then(result => { 32 | expect(result).toEqual(expected); 33 | done(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/$root.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | friends: [ 8 | { 9 | name: 'Michal', 10 | }, 11 | ], 12 | }; 13 | 14 | const template = { 15 | name: '{{ $root.name }}', 16 | friends: { 17 | '{{ #each friends }}': { 18 | name: '{{ $root.name }}', 19 | }, 20 | }, 21 | }; 22 | 23 | const expected = { 24 | name: 'Jakub', 25 | friends: [ 26 | { 27 | name: 'Jakub', 28 | }, 29 | ], 30 | }; 31 | 32 | test('transform is correct', done => { 33 | st.transform(template, data).then(result => { 34 | expect(result).toEqual(expected); 35 | done(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/$this.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | friends: [ 8 | { 9 | name: 'Michal', 10 | }, 11 | ], 12 | }; 13 | 14 | const template = { 15 | name: '{{ $root.name }}', 16 | friends: { 17 | '{{ #each friends }}': { 18 | name: '{{ $this.name }}', 19 | }, 20 | }, 21 | }; 22 | 23 | const expected = { 24 | name: 'Jakub', 25 | friends: [ 26 | { 27 | name: 'Michal', 28 | }, 29 | ], 30 | }; 31 | 32 | test('transform is correct', done => { 33 | st.transform(template, data).then(result => { 34 | expect(result).toEqual(expected); 35 | done(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/concat.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | name: '{{ name }}', 13 | surname: '{{ surname }}', 14 | age: '{{ age }}', 15 | test: { 16 | '{{ #concat }}': ['{{ name }}', '{{ surname }}', '{{ age }}'], 17 | }, 18 | }; 19 | 20 | const expected = { 21 | name: 'Jakub', 22 | surname: 'Mifek', 23 | age: 24, 24 | test: ['Jakub', 'Mifek', 24], 25 | }; 26 | 27 | test('transform is correct', done => { 28 | st.transform(template, data).then(result => { 29 | expect(result).toEqual(expected); 30 | done(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/condition.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | const template = { 12 | name: [ 13 | { '{{ #if name === "Jakub" }}': 'Kuba' }, 14 | { '{{ #else }}': '{{ name }}' }, 15 | ], 16 | surname: '{{ surname }}', 17 | age: '{{ age }}', 18 | test: 'test', 19 | }; 20 | 21 | const expected = { 22 | surname: 'Mifek', 23 | age: 24, 24 | test: 'test', 25 | name: 'Kuba', 26 | }; 27 | 28 | test('transform is correct', done => { 29 | st.transform(template, data).then(result => { 30 | expect(result).toEqual(expected); 31 | done(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/key-executors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './key-executor'; 2 | 3 | import { Concat } from './concat'; 4 | import { Each } from './each'; 5 | import { Flatten } from './flatten'; 6 | import { KeyExecutor } from './key-executor'; 7 | import { Let } from './let'; 8 | import { Lets } from './lets'; 9 | import { Merge } from './merge'; 10 | import { Optional } from './optional'; 11 | import { For } from './for'; 12 | import { Unwrap } from './unwrap'; 13 | 14 | /** 15 | * Array of executors for functions in templates. 16 | */ 17 | export const executors: KeyExecutor[] = [ 18 | new Concat(), 19 | new Each(), 20 | new Flatten(), 21 | new For(), 22 | new Let(), 23 | new Lets(), 24 | new Merge(), 25 | new Optional(), 26 | new Unwrap(), 27 | ]; 28 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/flatten.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | items: [12, 13, [14, 15, [15.3, 15.6]], 16, ['seventeen', 'eighteen']], 10 | }; 11 | 12 | const template = { 13 | name: '{{ name }}', 14 | surname: '{{ surname }}', 15 | age: '{{ age }}', 16 | test: { 17 | '{{ #flatten }}': '{{ items }}', 18 | }, 19 | }; 20 | 21 | const expected = { 22 | name: 'Jakub', 23 | surname: 'Mifek', 24 | age: 24, 25 | test: [12, 13, 14, 15, [15.3, 15.6], 16, 'seventeen', 'eighteen'], 26 | }; 27 | 28 | test('transform is correct', done => { 29 | const result = st.transformSync(template, data); 30 | 31 | expect(result).toEqual(expected); 32 | done(); 33 | }); 34 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/flatten.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | items: [12, 13, [14, 15, [15.3, 15.6]], 16, ['seventeen', 'eighteen']], 10 | }; 11 | 12 | const template = { 13 | name: '{{ name }}', 14 | surname: '{{ surname }}', 15 | age: '{{ age }}', 16 | test: { 17 | '{{ #flatten }}': '{{ items }}', 18 | }, 19 | }; 20 | 21 | const expected = { 22 | name: 'Jakub', 23 | surname: 'Mifek', 24 | age: 24, 25 | test: [12, 13, 14, 15, [15.3, 15.6], 16, 'seventeen', 'eighteen'], 26 | }; 27 | 28 | test('transform is correct', done => { 29 | st.transform(template, data).then(result => { 30 | expect(result).toEqual(expected); 31 | done(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __createBinding = 3 | (this && this.__createBinding) || 4 | (Object.create 5 | ? function (o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | Object.defineProperty(o, k2, { 8 | enumerable: true, 9 | get: function () { 10 | return m[k]; 11 | }, 12 | }); 13 | } 14 | : function (o, m, k, k2) { 15 | if (k2 === undefined) k2 = k; 16 | o[k2] = m[k]; 17 | }); 18 | var __exportStar = 19 | (this && this.__exportStar) || 20 | function (m, exports) { 21 | for (var p in m) 22 | if (p !== 'default' && !exports.hasOwnProperty(p)) 23 | __createBinding(exports, m, p); 24 | }; 25 | Object.defineProperty(exports, '__esModule', { value: true }); 26 | __exportStar(require('./dist'), exports); 27 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/each.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: [ 10 | { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | ], 23 | }; 24 | 25 | const template = { 26 | name: '{{ name }}', 27 | surname: '{{ surname }}', 28 | age: '{{ age }}', 29 | friendList: { 30 | '{{ #each friends }}': '{{ name + " " + surname }}', 31 | }, 32 | }; 33 | 34 | const expected = { 35 | name: 'Jakub', 36 | surname: 'Mifek', 37 | age: 24, 38 | friendList: ['Michal Mozik', 'Marian Baca', 'Antonin Malik'], 39 | }; 40 | 41 | test('transform is correct', done => { 42 | const result = st.transformSync(template, data); 43 | 44 | expect(result).toEqual(expected); 45 | done(); 46 | }); 47 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/each.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: [ 10 | { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | ], 23 | }; 24 | 25 | const template = { 26 | name: '{{ name }}', 27 | surname: '{{ surname }}', 28 | age: '{{ age }}', 29 | friendList: { 30 | '{{ #each friends }}': '{{ name + " " + surname }}', 31 | }, 32 | }; 33 | 34 | const expected = { 35 | name: 'Jakub', 36 | surname: 'Mifek', 37 | age: 24, 38 | friendList: ['Michal Mozik', 'Marian Baca', 'Antonin Malik'], 39 | }; 40 | 41 | test('transform is correct', done => { 42 | st.transform(template, data).then(result => { 43 | expect(result).toEqual(expected); 44 | done(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/for.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: { 10 | a: { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | b: { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | c: { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | }, 23 | }; 24 | 25 | const template = { 26 | name: '{{ name }}', 27 | surname: '{{ surname }}', 28 | age: '{{ age }}', 29 | friendList: { 30 | '{{ #for friends }}': '{{ name + " " + surname }}', 31 | }, 32 | }; 33 | 34 | const expected = { 35 | name: 'Jakub', 36 | surname: 'Mifek', 37 | age: 24, 38 | friendList: { a: 'Michal Mozik', b: 'Marian Baca', c: 'Antonin Malik' }, 39 | }; 40 | 41 | test('transform is correct', done => { 42 | st.transform(template, data).then(result => { 43 | expect(result).toEqual(expected); 44 | done(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/let.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | item: { 10 | name: 'item', 11 | quantity: 2, 12 | price: 20, 13 | }, 14 | }; 15 | 16 | const template = { 17 | name: '{{ name }}', 18 | surname: '{{ surname }}', 19 | age: '{{ age }}', 20 | test: { 21 | '{{ #let }}': [ 22 | { 23 | wholePrice: '{{ item.quantity * item.price }}', 24 | }, 25 | { 26 | name: '{{ item.quantity + " " + item.name + "s" }}', 27 | price: '{{ wholePrice }}', 28 | }, 29 | ], 30 | }, 31 | }; 32 | 33 | const expected = { 34 | name: 'Jakub', 35 | surname: 'Mifek', 36 | age: 24, 37 | test: { 38 | name: '2 items', 39 | price: 40, 40 | }, 41 | }; 42 | 43 | test('transform is correct', done => { 44 | st.transform(template, data).then(result => { 45 | expect(result).toEqual(expected); 46 | done(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/let.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | item: { 10 | name: 'item', 11 | quantity: 2, 12 | price: 20, 13 | }, 14 | }; 15 | 16 | const template = { 17 | name: '{{ name }}', 18 | surname: '{{ surname }}', 19 | age: '{{ age }}', 20 | test: { 21 | '{{ #let }}': [ 22 | { 23 | wholePrice: '{{ item.quantity * item.price }}', 24 | }, 25 | { 26 | name: '{{ item.quantity + " " + item.name + "s" }}', 27 | price: '{{ wholePrice }}', 28 | }, 29 | ], 30 | }, 31 | }; 32 | 33 | const expected = { 34 | name: 'Jakub', 35 | surname: 'Mifek', 36 | age: 24, 37 | test: { 38 | name: '2 items', 39 | price: 40, 40 | }, 41 | }; 42 | 43 | test('transform is correct', done => { 44 | st.transform(template, data).then(result => { 45 | expect(result).toEqual(expected); 46 | done(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/lets.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | item: { 10 | name: 'item', 11 | quantity: 2, 12 | price: 20, 13 | }, 14 | }; 15 | 16 | const template = { 17 | name: '{{ name }}', 18 | surname: '{{ surname }}', 19 | age: '{{ age }}', 20 | test: { 21 | '{{ #lets }}': [ 22 | { 23 | partialPrice: '{{ item.price }}', 24 | wholePrice: '{{ item.quantity * partialPrice }}', 25 | }, 26 | { 27 | name: '{{ item.quantity + " " + item.name + "s" }}', 28 | price: '{{ wholePrice }}', 29 | }, 30 | ], 31 | }, 32 | }; 33 | 34 | const expected = { 35 | name: 'Jakub', 36 | surname: 'Mifek', 37 | age: 24, 38 | test: { 39 | name: '2 items', 40 | price: 40, 41 | }, 42 | }; 43 | 44 | test('transform is correct', done => { 45 | st.transform(template, data).then(result => { 46 | expect(result).toEqual(expected); 47 | done(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/lets.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | item: { 10 | name: 'item', 11 | quantity: 2, 12 | price: 20, 13 | }, 14 | }; 15 | 16 | const template = { 17 | name: '{{ name }}', 18 | surname: '{{ surname }}', 19 | age: '{{ age }}', 20 | test: { 21 | '{{ #lets }}': [ 22 | { 23 | partialPrice: '{{ item.price }}', 24 | wholePrice: '{{ item.quantity * partialPrice }}', 25 | }, 26 | { 27 | name: '{{ item.quantity + " " + item.name + "s" }}', 28 | price: '{{ wholePrice }}', 29 | }, 30 | ], 31 | }, 32 | }; 33 | 34 | const expected = { 35 | name: 'Jakub', 36 | surname: 'Mifek', 37 | age: 24, 38 | test: { 39 | name: '2 items', 40 | price: 40, 41 | }, 42 | }; 43 | 44 | test('transform is correct', done => { 45 | st.transform(template, data).then(result => { 46 | expect(result).toEqual(expected); 47 | done(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /__tests__/sunny/combined-sync/each-template.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: [ 10 | { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | ], 23 | }; 24 | 25 | const subtemplate = '{{ name + " " + surname }}'; 26 | 27 | const template = { 28 | name: '{{ name }}', 29 | surname: '{{ surname }}', 30 | age: '{{ age }}', 31 | friendList: { 32 | '{{ #each friends }}': '{{ #template subtemplate }}', 33 | }, 34 | }; 35 | 36 | const expected = { 37 | name: 'Jakub', 38 | surname: 'Mifek', 39 | age: 24, 40 | friendList: ['Michal Mozik', 'Marian Baca', 'Antonin Malik'], 41 | }; 42 | 43 | test('transform is correct', done => { 44 | const result = st.addTemplates({ subtemplate }).transformSync(template, data); 45 | 46 | expect(result).toEqual(expected); 47 | done(); 48 | }); 49 | -------------------------------------------------------------------------------- /src/value-executors/template.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, Transform } from '../internal'; 2 | import { ValueExecutor } from './value-executor'; 3 | 4 | export class Template implements ValueExecutor { 5 | private name: string; 6 | constructor() { 7 | this.name = Template.name; 8 | } 9 | 10 | getName() { 11 | return this.name; 12 | } 13 | 14 | fits(template: string): boolean { 15 | return ( 16 | typeof template === 'string' && 17 | /^\s*\{\{\s*#template\s+[^\s]+\s*\}\}\s*$/g.test(template.toLowerCase()) 18 | ); 19 | } 20 | 21 | executeSync(template: string, data: DataObject, ts: Transform): DataObject { 22 | const fun = Helper.tokenize(template); 23 | // insert one of stored templates 24 | const ptemplate = ts.st.getTemplate(fun.expression); 25 | return ts.runSync(ptemplate, data); 26 | } 27 | 28 | async execute( 29 | template: string, 30 | data: DataObject, 31 | ts: Transform, 32 | ): Promise { 33 | const fun = Helper.tokenize(template); 34 | // insert one of stored templates 35 | const ptemplate = ts.st.getTemplate(fun.expression); 36 | return await ts.run(ptemplate, data); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /__tests__/sunny/combined/each-template.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: [ 10 | { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | ], 23 | }; 24 | 25 | const subtemplate = '{{ name + " " + surname }}'; 26 | 27 | const template = { 28 | name: '{{ name }}', 29 | surname: '{{ surname }}', 30 | age: '{{ age }}', 31 | friendList: { 32 | '{{ #each friends }}': '{{ #template subtemplate }}', 33 | }, 34 | }; 35 | 36 | const expected = { 37 | name: 'Jakub', 38 | surname: 'Mifek', 39 | age: 24, 40 | friendList: ['Michal Mozik', 'Marian Baca', 'Antonin Malik'], 41 | }; 42 | 43 | test('transform is correct', done => { 44 | st.addTemplates({ subtemplate }) 45 | .transform(template, data) 46 | .then(result => { 47 | expect(Object.keys(result).length).toEqual(Object.keys(expected).length); 48 | for (const k of Object.keys(result)) { 49 | expect(result[k]).toEqual(expected[k]); 50 | } 51 | done(); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /__tests__/sunny/basic-sync/optional.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | let data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | let template = { 12 | name: '{{ name }}', 13 | surname: '{{ surname }}', 14 | age: '{{ age }}', 15 | '{{ #optional nickname }}': [ 16 | { 17 | "{{ #if name === 'Jakub' }}": 'Kuba', 18 | }, 19 | ], 20 | }; 21 | 22 | let expected = { 23 | name: 'Jakub', 24 | surname: 'Mifek', 25 | age: 24, 26 | nickname: 'Kuba', 27 | }; 28 | 29 | test('transform is correct', done => { 30 | st.transform(template, data).then(result => { 31 | expect(result).toEqual(expected); 32 | done(); 33 | }); 34 | }); 35 | 36 | data = { 37 | name: 'Jakub', 38 | surname: 'Mifek', 39 | age: 24, 40 | }; 41 | 42 | template = { 43 | name: '{{ name }}', 44 | surname: '{{ surname }}', 45 | age: '{{ age }}', 46 | '{{ #optional nickname }}': [ 47 | { 48 | "{{ #if name === 'Jan' }}": 'Honza', 49 | }, 50 | ], 51 | }; 52 | 53 | expected = { 54 | name: 'Jakub', 55 | surname: 'Mifek', 56 | age: 24, 57 | }; 58 | 59 | test('transform is correct', done => { 60 | st.transform(template, data).then(result => { 61 | expect(result).toEqual(expected); 62 | done(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/value-executors/include.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, Transform } from '../internal'; 2 | import { ValueExecutor } from './value-executor'; 3 | 4 | export class Include implements ValueExecutor { 5 | private name: string; 6 | 7 | constructor() { 8 | this.name = Include.name; 9 | } 10 | 11 | getName(): string { 12 | return this.name; 13 | } 14 | 15 | fits(template: string): boolean { 16 | return ( 17 | typeof template === 'string' && 18 | /^\s*\{\{\s*#include\s+[^\s]+\s*\}\}\s*$/g.test(template.toLowerCase()) 19 | ); 20 | } 21 | 22 | executeSync(template: string, data: DataObject, ts: Transform): DataObject { 23 | const fun = Helper.tokenize(template); 24 | if (fun.expression) { 25 | // if #include has arguments, evaluate it before attaching 26 | return Helper.fillout(`{{${fun.expression}}`, data, true); 27 | } 28 | 29 | // shouldn't happen => 30 | // {'wrapper': '{{#include}}'} 31 | return template; 32 | } 33 | 34 | async execute( 35 | template: string, 36 | data: DataObject, 37 | ts: Transform, 38 | ): Promise { 39 | const fun = Helper.tokenize(template); 40 | if (fun.expression) { 41 | // if #include has arguments, evaluate it before attaching 42 | return Helper.fillout(`{{${fun.expression}}`, data, true); 43 | } 44 | 45 | // shouldn't happen => 46 | // {'wrapper': '{{#include}}'} 47 | return template; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/optional.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | let data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | }; 10 | 11 | let template = { 12 | name: '{{ name }}', 13 | surname: '{{ surname }}', 14 | age: '{{ age }}', 15 | '{{ #optional nickname }}': [ 16 | { 17 | "{{ #if name === 'Jakub' }}": 'Kuba', 18 | }, 19 | ], 20 | }; 21 | 22 | let expected = { 23 | name: 'Jakub', 24 | surname: 'Mifek', 25 | age: 24, 26 | nickname: 'Kuba', 27 | }; 28 | 29 | test('transform is correct', done => { 30 | st.transform(template, data).then(result => { 31 | expect(Object.keys(result).length).toEqual(Object.keys(expected).length); 32 | for (const k of Object.keys(result)) { 33 | expect(result[k]).toEqual(expected[k]); 34 | } 35 | done(); 36 | }); 37 | }); 38 | 39 | data = { 40 | name: 'Jakub', 41 | surname: 'Mifek', 42 | age: 24, 43 | }; 44 | 45 | template = { 46 | name: '{{ name }}', 47 | surname: '{{ surname }}', 48 | age: '{{ age }}', 49 | '{{ #optional nickname }}': [ 50 | { 51 | "{{ #if name === 'Jan' }}": 'Honza', 52 | }, 53 | ], 54 | }; 55 | 56 | expected = { 57 | name: 'Jakub', 58 | surname: 'Mifek', 59 | age: 24, 60 | }; 61 | 62 | test('transform is correct', done => { 63 | st.transform(template, data).then(result => { 64 | expect(result).toEqual(expected); 65 | done(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /__tests__/sunny/combined-sync/each-let.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: [ 10 | { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | ], 23 | }; 24 | 25 | const subtemplate = { 26 | '{{ #let }}': [ 27 | { 28 | wholeName: '{{ name + " " + surname }}', 29 | }, 30 | { 31 | name: '{{ wholeName }}', 32 | friendOf: '{{ userName }}', 33 | }, 34 | ], 35 | }; 36 | 37 | const template = { 38 | name: '{{ name }}', 39 | surname: '{{ surname }}', 40 | age: '{{ age }}', 41 | friendList: { 42 | '{{ #let }}': [ 43 | { 44 | userName: '{{ name + " " + surname }}', 45 | }, 46 | { 47 | '{{ #each friends }}': '{{ #template subtemplate }}', 48 | }, 49 | ], 50 | }, 51 | }; 52 | 53 | const expected = { 54 | name: 'Jakub', 55 | surname: 'Mifek', 56 | age: 24, 57 | friendList: [ 58 | { name: 'Michal Mozik', friendOf: 'Jakub Mifek' }, 59 | { name: 'Marian Baca', friendOf: 'Jakub Mifek' }, 60 | { name: 'Antonin Malik', friendOf: 'Jakub Mifek' }, 61 | ], 62 | }; 63 | 64 | test('transform is correct', done => { 65 | const result = st.addTemplates({ subtemplate }).transformSync(template, data); 66 | 67 | expect(result).toEqual(expected); 68 | done(); 69 | }); 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selecttransform", 3 | "version": "1.6.1", 4 | "main": "dist/index.js", 5 | "types": "dist/index.d.ts", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/JakubMifek/SelectTransform.git" 9 | }, 10 | "keywords": [ 11 | "stjs", 12 | "stts", 13 | "select-transform", 14 | "selectransform", 15 | "library", 16 | "typescript", 17 | "javascript", 18 | "json", 19 | "transform", 20 | "select", 21 | "mapping", 22 | "template" 23 | ], 24 | "author": "Jakub Mifek", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/JakubMifek/SelectTransform/issues" 28 | }, 29 | "homepage": "https://github.com/JakubMifek/SelectTransform#readme", 30 | "scripts": { 31 | "lint": "tslint -c './tslint.json' './src/**/*.ts'", 32 | "clean": "rm -rf *.tgz dist", 33 | "build": "npm run clean && tsc --outDir dist && npm run browsify", 34 | "browsify": "browserify browser.js > st.js", 35 | "prepare": "npm run build", 36 | "pretest": "npm run build", 37 | "test": "jest" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.5.4", 41 | "@babel/preset-env": "^7.5.4", 42 | "@babel/preset-typescript": "^7.3.3", 43 | "@types/node": "^11.13.4", 44 | "babel-jest": "^26.6.3", 45 | "jest": "^26.6.3", 46 | "prettier": "^2.0.5", 47 | "ts-jest": "^26.5.5", 48 | "browserify": "^17.0.0", 49 | "eslint": "^7.25.0", 50 | "typescript": "^4.2.4" 51 | }, 52 | "dependencies": { 53 | "tsify": "^5.0.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/key-executors/concat.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, ST_ERRORS, Transform } from '../internal'; 2 | import { KeyExecutor } from './key-executor'; 3 | 4 | export class Concat implements KeyExecutor { 5 | private name: string; 6 | constructor() { 7 | this.name = Concat.name; 8 | } 9 | 10 | getName(): string { 11 | return this.name; 12 | } 13 | 14 | fits(template: string): boolean { 15 | return ( 16 | typeof template === 'string' && 17 | /^\s*\{\{\s*#concat\s*\}\}\s*$/g.test(template.toLowerCase()) 18 | ); 19 | } 20 | 21 | executeSync( 22 | template: DataObject, 23 | data: DataObject, 24 | ts: Transform, 25 | key: string, 26 | result: DataObject, 27 | ): DataObject { 28 | if (!Helper.isArray(template[key])) { 29 | const err = ST_ERRORS.format; 30 | err.message += ` - Wrong ${Concat.name} format - expected an array as the value.`; 31 | throw err; 32 | } 33 | 34 | result = []; 35 | for (const item of template[key]) { 36 | result = (result as Array).concat(ts.runSync(item, data)); 37 | } 38 | return result; 39 | } 40 | 41 | async execute( 42 | template: DataObject, 43 | data: DataObject, 44 | ts: Transform, 45 | key: string, 46 | result: DataObject, 47 | ): Promise { 48 | if (!Helper.isArray(template[key])) { 49 | const err = ST_ERRORS.format; 50 | err.message += ` - Wrong ${Concat.name} format - expected an array as the value.`; 51 | throw err; 52 | } 53 | 54 | const promises = []; 55 | for (const item of template[key]) { 56 | promises.push(ts.run(item, data)); 57 | } 58 | return await Promise.all(promises); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /__tests__/sunny/combined/each-let.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data = { 6 | name: 'Jakub', 7 | surname: 'Mifek', 8 | age: 24, 9 | friends: [ 10 | { 11 | name: 'Michal', 12 | surname: 'Mozik', 13 | }, 14 | { 15 | name: 'Marian', 16 | surname: 'Baca', 17 | }, 18 | { 19 | name: 'Antonin', 20 | surname: 'Malik', 21 | }, 22 | ], 23 | }; 24 | 25 | const subtemplate = { 26 | '{{ #let }}': [ 27 | { 28 | wholeName: '{{ name + " " + surname }}', 29 | }, 30 | { 31 | name: '{{ wholeName }}', 32 | friendOf: '{{ userName }}', 33 | }, 34 | ], 35 | }; 36 | 37 | const template = { 38 | name: '{{ name }}', 39 | surname: '{{ surname }}', 40 | age: '{{ age }}', 41 | friendList: { 42 | '{{ #let }}': [ 43 | { 44 | userName: '{{ name + " " + surname }}', 45 | }, 46 | { 47 | '{{ #each friends }}': '{{ #template subtemplate }}', 48 | }, 49 | ], 50 | }, 51 | }; 52 | 53 | const expected = { 54 | name: 'Jakub', 55 | surname: 'Mifek', 56 | age: 24, 57 | friendList: [ 58 | { name: 'Michal Mozik', friendOf: 'Jakub Mifek' }, 59 | { name: 'Marian Baca', friendOf: 'Jakub Mifek' }, 60 | { name: 'Antonin Malik', friendOf: 'Jakub Mifek' }, 61 | ], 62 | }; 63 | 64 | test('transform is correct', done => { 65 | st.addTemplates({ subtemplate }) 66 | .transform(template, data) 67 | .then(result => { 68 | expect(Object.keys(result).length).toEqual(Object.keys(expected).length); 69 | for (const k of Object.keys(result)) { 70 | expect(result[k]).toEqual(expected[k]); 71 | } 72 | done(); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/value-executors/ternary.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, Transform } from '../internal'; 2 | import { ValueExecutor } from './value-executor'; 3 | 4 | export class Ternary implements ValueExecutor { 5 | private name: string; 6 | 7 | constructor() { 8 | this.name = Ternary.name; 9 | } 10 | 11 | getName(): string { 12 | return this.name; 13 | } 14 | 15 | fits(template: string): boolean { 16 | return ( 17 | typeof template === 'string' && 18 | /^\s*\{\{\s*#\?\s+[^\s]+\s*\}\}\s*$/g.test(template.toLowerCase()) 19 | ); 20 | } 21 | 22 | executeSync(template: string, data: DataObject, ts: Transform): DataObject { 23 | const fun = Helper.tokenize(template); 24 | const filled = Helper.fillout(`{{${fun.expression}}}`, data, false, true); 25 | if (filled === undefined || filled === `{{${fun.expression}}}`) { 26 | // case 1. 27 | // not parsed, which means the evaluation failed. 28 | // case 2. 29 | // returns undefined value 30 | // both cases mean this key should be excluded 31 | } else { 32 | // only include if the evaluation is truthy 33 | return filled; 34 | } 35 | return undefined; 36 | } 37 | 38 | async execute( 39 | template: string, 40 | data: DataObject, 41 | ts: Transform, 42 | ): Promise { 43 | const fun = Helper.tokenize(template); 44 | const filled = Helper.fillout(`{{${fun.expression}}}`, data, false, true); 45 | if (!filled || filled === `{{${fun.expression}}}`) { 46 | // case 1. 47 | // not parsed, which means the evaluation failed. 48 | // case 2. 49 | // returns fasly value 50 | // both cases mean this key should be excluded 51 | } else { 52 | // only include if the evaluation is truthy 53 | return filled; 54 | } 55 | return undefined; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /__tests__/sunny/basic/$root.2.test.js: -------------------------------------------------------------------------------- 1 | const ST = require('../../../dist')['SelectTransform']; 2 | 3 | const st = new ST(); 4 | 5 | const data1 = { 6 | name: 'Jakub', 7 | surname: { value: 'Mifek' }, 8 | friend: { 9 | name: 'Michal', 10 | surname: 'Pekny', 11 | }, 12 | }; 13 | 14 | const data2 = { 15 | name: 'Jakub', 16 | friend: { 17 | name: 'Michal', 18 | surname: 'Pekny', 19 | }, 20 | }; 21 | 22 | const data3 = { 23 | name: 'Jakub', 24 | surname: {}, 25 | friend: { 26 | name: 'Michal', 27 | surname: 'Pekny', 28 | }, 29 | }; 30 | 31 | const template = { 32 | name: '{{ name }}', 33 | friend: { 34 | '{{ #unwrap friend }}': { 35 | attach: { 36 | '{{ #optional originSurname }}': [ 37 | { 38 | '{{ #if $root.surname !== undefined && $root.surname.value !== undefined }}': 39 | '{{ surname.value }}', 40 | }, 41 | ], 42 | }, 43 | }, 44 | }, 45 | }; 46 | 47 | const expected1 = { 48 | name: 'Jakub', 49 | friend: { 50 | name: 'Michal', 51 | surname: 'Pekny', 52 | originSurname: 'Mifek', 53 | }, 54 | }; 55 | 56 | const expected2 = { 57 | name: 'Jakub', 58 | friend: { 59 | name: 'Michal', 60 | surname: 'Pekny', 61 | }, 62 | }; 63 | 64 | const expected3 = { 65 | name: 'Jakub', 66 | friend: { 67 | name: 'Michal', 68 | surname: 'Pekny', 69 | }, 70 | }; 71 | 72 | test('transform is correct', done => { 73 | st.transform(template, data1).then(result => { 74 | expect(result).toEqual(expected1); 75 | done(); 76 | }); 77 | }); 78 | 79 | test('transform is correct', done => { 80 | st.transform(template, data2).then(result => { 81 | expect(result).toEqual(expected2); 82 | done(); 83 | }); 84 | }); 85 | 86 | test('transform is correct', done => { 87 | st.transform(template, data3).then(result => { 88 | expect(result).toEqual(expected3); 89 | done(); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/key-executors/optional.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, Transform } from '../internal'; 2 | import { KeyExecutor } from './key-executor'; 3 | 4 | export class Optional implements KeyExecutor { 5 | private name: string; 6 | constructor() { 7 | this.name = Optional.name; 8 | } 9 | 10 | getName() { 11 | return this.name; 12 | } 13 | 14 | fits(template: string): boolean { 15 | return ( 16 | typeof template === 'string' && 17 | /^\s*\{\{\s*#optional\s+[^\s]+\s*\}\}\s*$/g.test(template.toLowerCase()) 18 | ); 19 | } 20 | 21 | executeSync( 22 | template: DataObject, 23 | data: DataObject, 24 | ts: Transform, 25 | key: string, 26 | result: DataObject, 27 | ): DataObject { 28 | const fun = Helper.tokenize(key); 29 | // Resolve the value 30 | const ret: DataObject = ts.runSync(template[key], data); 31 | 32 | // If value does not fit conditions 33 | if ( 34 | !ret || 35 | ret === null || 36 | ret === undefined || 37 | (typeof ret === 'object' && Object.keys(ret).length === 0) || 38 | (Helper.isArray(ret as object) && (ret as Array).length === 0) 39 | ) { 40 | // We want to ignore these cases 41 | } else { 42 | // Otw, include it into the result 43 | result[fun.expression] = ret; 44 | } 45 | 46 | return result; 47 | } 48 | 49 | async execute( 50 | template: DataObject, 51 | data: DataObject, 52 | ts: Transform, 53 | key: string, 54 | result: DataObject, 55 | ): Promise { 56 | const fun = Helper.tokenize(key); 57 | // Resolve the value 58 | const ret: DataObject = await ts.run(template[key], data); 59 | 60 | // If value does not fit conditions 61 | if ( 62 | !ret || 63 | ret === null || 64 | ret === undefined || 65 | (typeof ret === 'object' && Object.keys(ret).length === 0) || 66 | (Helper.isArray(ret as object) && (ret as Array).length === 0) 67 | ) { 68 | // We want to ignore these cases 69 | } else { 70 | // Otw, include it into the result 71 | result[fun.expression] = ret; 72 | } 73 | 74 | return result; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/key-executors/flatten.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, Transform } from '../internal'; 2 | import { KeyExecutor } from './key-executor'; 3 | 4 | export class Flatten implements KeyExecutor { 5 | private name: string; 6 | constructor() { 7 | this.name = Flatten.name; 8 | } 9 | 10 | getName(): string { 11 | return this.name; 12 | } 13 | 14 | fits(template: string): boolean { 15 | return ( 16 | typeof template === 'string' && 17 | /^\s*\{\{\s*#flatten\s*\}\}\s*$/g.test(template.toLowerCase()) 18 | ); 19 | } 20 | 21 | executeSync( 22 | template: DataObject, 23 | data: DataObject, 24 | ts: Transform, 25 | key: string, 26 | result: DataObject, 27 | ): DataObject { 28 | const obj: DataObject = ts.runSync(template[key], data); 29 | result = []; 30 | 31 | if (Helper.isArray(obj)) { 32 | const arr = obj as Array; 33 | // For each item in the array 34 | for (let i = 0; i < arr.length; i++) { 35 | if (Helper.isArray(arr[i])) { 36 | // If array then flatten 37 | for (let j = 0; j < arr[i].length; j++) { 38 | (result as Array).push(arr[i][j]); 39 | } 40 | } else { 41 | // Just push if anything else 42 | (result as Array).push(arr[i]); 43 | } 44 | } 45 | } 46 | 47 | return result; 48 | } 49 | 50 | async execute( 51 | template: DataObject, 52 | data: DataObject, 53 | ts: Transform, 54 | key: string, 55 | result: DataObject, 56 | ): Promise { 57 | const obj = await ts.run(template[key], data); 58 | result = []; 59 | 60 | if (Helper.isArray(obj)) { 61 | const arr = obj as Array; 62 | // For each item in the array 63 | for (let i = 0; i < arr.length; i++) { 64 | if (Helper.isArray(arr[i])) { 65 | // If array then flatten 66 | for (let j = 0; j < arr[i].length; j++) { 67 | (result as Array).push(arr[i][j]); 68 | } 69 | } else { 70 | // Just push if anything else 71 | (result as Array).push(arr[i]); 72 | } 73 | } 74 | } 75 | return result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/key-executors/unwrap.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, ST_ERRORS, Transform } from '../internal'; 2 | import { KeyExecutor } from './key-executor'; 3 | 4 | export class Unwrap implements KeyExecutor { 5 | private name: string; 6 | constructor() { 7 | this.name = Unwrap.name; 8 | } 9 | 10 | getName(): string { 11 | return this.name; 12 | } 13 | 14 | fits(template: string): boolean { 15 | return ( 16 | typeof template === 'string' && 17 | /^\s*\{\{\s*#unwrap\s*[^\s]*\s*\}\}\s*$/g.test(template.toLowerCase()) 18 | ); 19 | } 20 | 21 | executeSync( 22 | template: DataObject, 23 | data: DataObject, 24 | ts: Transform, 25 | key: string, 26 | result: DataObject, 27 | ): DataObject { 28 | if (typeof template[key] !== 'object') { 29 | const err = ST_ERRORS.format; 30 | err.message += ` - Wrong ${Unwrap.name} format - expected an object as the value.`; 31 | throw err; 32 | } 33 | 34 | const fun = Helper.tokenize(key); 35 | 36 | const config = template[key]; 37 | const exclude = config.exclude || []; 38 | const attach = ts.runSync(config.attach || {}, data) as object; 39 | 40 | result = fun.expression !== '' ? data[fun.expression] : data; 41 | 42 | for (const k of exclude) { 43 | delete result[k]; 44 | } 45 | 46 | // tslint:disable-next-line: forin 47 | for (const k in attach) { 48 | result[k] = attach[k]; 49 | } 50 | 51 | return result; 52 | } 53 | 54 | async execute( 55 | template: DataObject, 56 | data: DataObject, 57 | ts: Transform, 58 | key: string, 59 | result: DataObject, 60 | ): Promise { 61 | if (typeof template[key] !== 'object') { 62 | const err = ST_ERRORS.format; 63 | err.message += ` - Wrong ${Unwrap.name} format - expected an object as the value.`; 64 | throw err; 65 | } 66 | 67 | const fun = Helper.tokenize(key); 68 | 69 | const config = template[key]; 70 | const exclude = config.exclude || []; 71 | const attach = (await ts.run(config.attach || {}, data)) as object; 72 | 73 | result = fun.expression !== '' ? data[fun.expression] : data; 74 | 75 | for (const k of exclude) { 76 | delete result[k]; 77 | } 78 | 79 | // tslint:disable-next-line: forin 80 | for (const k in attach) { 81 | result[k] = attach[k]; 82 | } 83 | 84 | return result; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/key-executors/merge.ts: -------------------------------------------------------------------------------- 1 | import { DataObject, Helper, ST_ERRORS, Transform } from '../internal'; 2 | import { KeyExecutor } from './key-executor'; 3 | 4 | export class Merge implements KeyExecutor { 5 | private name: string; 6 | constructor() { 7 | this.name = Merge.name; 8 | } 9 | 10 | getName(): string { 11 | return this.name; 12 | } 13 | 14 | fits(template: string): boolean { 15 | return ( 16 | typeof template === 'string' && 17 | /^\s*\{\{\s*#merge\s*\}\}\s*$/g.test(template.toLowerCase()) 18 | ); 19 | } 20 | 21 | executeSync( 22 | template: DataObject, 23 | data: DataObject, 24 | ts: Transform, 25 | key: string, 26 | result: DataObject, 27 | ) { 28 | if (!Helper.isArray(template[key])) { 29 | const err = ST_ERRORS.format; 30 | err.message += ` - Wrong ${Merge.name} format - expected an array as the value.`; 31 | throw err; 32 | } 33 | 34 | // Merge all sub-objects 35 | result = {}; 36 | for (const item of template[key]) { 37 | Object.assign(result, ts.runSync(item, data)); 38 | } 39 | 40 | // clean up $index from the result 41 | // necessary because #merge merges multiple objects into one, 42 | // and one of them may be 'this', in which case the $index attribute 43 | // will have snuck into the final result 44 | if (typeof data === 'object') { 45 | delete result['$index']; 46 | } else { 47 | delete (String.prototype as any).$index; 48 | delete (Number.prototype as any).$index; 49 | delete (Function.prototype as any).$index; 50 | delete (Array.prototype as any).$index; 51 | delete (Boolean.prototype as any).$index; 52 | } 53 | 54 | return result; 55 | } 56 | 57 | async execute( 58 | template: DataObject, 59 | data: DataObject, 60 | ts: Transform, 61 | key: string, 62 | result: DataObject, 63 | ): Promise { 64 | if (!Helper.isArray(template[key])) { 65 | const err = ST_ERRORS.format; 66 | err.message += ` - Wrong ${Merge.name} format - expected an array as the value.`; 67 | throw err; 68 | } 69 | 70 | // Merge all sub-objects 71 | result = {}; 72 | const promises = []; 73 | for (const item of template[key]) { 74 | promises.push(ts.run(item, data)); 75 | } 76 | Object.assign(result, ...(await Promise.all(promises))); 77 | 78 | // clean up $index from the result 79 | // necessary because #merge merges multiple objects into one, 80 | // and one of them may be 'this', in which case the $index attribute 81 | // will have snuck into the final result 82 | if (typeof data === 'object') { 83 | delete result['$index']; 84 | } 85 | 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/st.ts: -------------------------------------------------------------------------------- 1 | import { Select } from './internal'; 2 | 3 | export type Templates = { [index: string]: object }; 4 | 5 | /** 6 | * Select-Transform class 7 | */ 8 | export class SelectTransform { 9 | private templates: Templates = {}; 10 | 11 | /** 12 | * 13 | * @param {boolean} keepTemplate Whether to keep templates instead of firing errors 14 | */ 15 | constructor(public readonly keepTemplate = false) {} 16 | 17 | /** 18 | * Adds subtemplates that should be used into the class. 19 | * If a template with same name is already present, it will be overriden by this method. 20 | * 21 | * @param {object} subtemplates Provided subtemplates 22 | */ 23 | addTemplates(subtemplates: Templates): SelectTransform { 24 | Object.assign(this.templates, subtemplates); 25 | 26 | return this; 27 | } 28 | 29 | /** 30 | * Clears template library. 31 | */ 32 | clearTemplates(): SelectTransform { 33 | this.templates = {}; 34 | 35 | return this; 36 | } 37 | 38 | getTemplate(name: string): object { 39 | return this.templates[name]; 40 | } 41 | 42 | transformSync(template: object, data: object, serialized: boolean): any { 43 | // no need for separate template resolution step 44 | // select the template with selector and transform data 45 | const res = new Select(this, true) 46 | .select(template, undefined, serialized) 47 | .transformSync(data, serialized) 48 | .root(); 49 | 50 | if (serialized) { 51 | // needs to return stringified version 52 | return JSON.stringify(res); 53 | } 54 | 55 | return res; 56 | } 57 | 58 | async transform( 59 | template: object, 60 | data: object, 61 | serialized: boolean, 62 | ): Promise { 63 | // no need for separate template resolution step 64 | // select the template with selector and transform data 65 | const res = ( 66 | await new Select(this, true) 67 | .select(template, undefined, serialized) 68 | .transform(data, serialized) 69 | ).root(); 70 | 71 | if (serialized) { 72 | // needs to return stringified version 73 | return JSON.stringify(res); 74 | } 75 | 76 | return res; 77 | } 78 | 79 | selectSync( 80 | template: object, 81 | selector?: Function, 82 | serialized: boolean = false, 83 | ): Select | string { 84 | const res = new Select(this, true).select(template, selector, serialized); 85 | 86 | if (serialized) { 87 | // needs to return stringified version 88 | return JSON.stringify(res.root()); 89 | } 90 | 91 | return res; 92 | } 93 | 94 | async select( 95 | template: object, 96 | selector?: Function, 97 | serialized: boolean = false, 98 | ): Promise { 180 | this.$progress = null; 181 | /* 182 | 'selected' is an array that contains items that looks like this: 183 | { 184 | key: The selected key, 185 | path: The path leading down to the selected key, 186 | object: The entire object that contains the currently selected key/val pair 187 | value: The selected value 188 | } 189 | */ 190 | let data: DataObject; 191 | try { 192 | if (serialized) data = JSON.parse(obj as string); 193 | else data = obj; 194 | } catch (error) { 195 | data = obj; 196 | } 197 | 198 | // since we're assuming that the template has been already selected, 199 | // the $templateRoot is $selectedRoot 200 | this.$templateRoot = this.$selectedRoot; 201 | 202 | if (typeof data === 'object') { 203 | (data as AnyObject).$root = data; 204 | } 205 | 206 | (String.prototype as AnyObject).$root = data; 207 | (Number.prototype as AnyObject).$root = data; 208 | (Function.prototype as AnyObject).$root = data; 209 | (Array.prototype as AnyObject).$root = data; 210 | (Boolean.prototype as AnyObject).$root = data; 211 | 212 | if (typeof data === 'object') { 213 | (data as AnyObject).$this = data; 214 | } 215 | 216 | (String.prototype as AnyObject).$this = data; 217 | (Number.prototype as AnyObject).$this = data; 218 | (Function.prototype as AnyObject).$this = data; 219 | (Array.prototype as AnyObject).$this = data; 220 | (Boolean.prototype as AnyObject).$this = data; 221 | 222 | if (this.$selected && this.$selected.length > 0) { 223 | const sorted = this.$selected.sort( 224 | (a, b) => 225 | // sort by path length, so that deeper level items will be replaced first 226 | // TODO: may need to look into edge cases 227 | b.path.length - a.path.length, 228 | ); 229 | 230 | const promises = []; 231 | for (const selection of sorted) { 232 | promises.push( 233 | new Promise(async () => { 234 | // parse selected 235 | const parsedObject = await new Transform(this, this.st).run( 236 | selection.object, 237 | data, 238 | ); 239 | // apply the result to root 240 | this.$templateRoot = Helper.resolve( 241 | this.$templateRoot, 242 | selection.path, 243 | parsedObject, 244 | ); 245 | this.$selectedRoot = this.$templateRoot; 246 | 247 | // update selected object with the parsed result 248 | selection.object = parsedObject; 249 | }), 250 | ); 251 | } 252 | 253 | await Promise.all(promises); 254 | 255 | this.$selected.sort((a, b) => a.index - b.index); 256 | } else { 257 | const parsedObject = await new Transform(this, this.st, this.sync).run( 258 | this.$selectedRoot, 259 | data, 260 | ); 261 | // apply the result to root 262 | this.$templateRoot = Helper.resolve(this.$templateRoot, '', parsedObject); 263 | this.$selectedRoot = this.$templateRoot; 264 | } 265 | 266 | if (typeof data === 'object') { 267 | (data as AnyObject).$root = undefined; 268 | } 269 | 270 | delete (String.prototype as AnyObject).$root; 271 | delete (Number.prototype as AnyObject).$root; 272 | delete (Function.prototype as AnyObject).$root; 273 | delete (Array.prototype as AnyObject).$root; 274 | delete (Boolean.prototype as AnyObject).$root; 275 | 276 | if (typeof data === 'object') { 277 | (data as AnyObject).$this = undefined; 278 | } 279 | 280 | delete (String.prototype as AnyObject).$this; 281 | delete (Number.prototype as AnyObject).$this; 282 | delete (Function.prototype as AnyObject).$this; 283 | delete (Array.prototype as AnyObject).$this; 284 | delete (Boolean.prototype as AnyObject).$this; 285 | 286 | return this; 287 | } 288 | 289 | transformWithSync(obj: DataObject, serialized: boolean): Select { 290 | this.$progress = null; 291 | /* 292 | * 'selected' is an array that contains items that looks like this: 293 | * { 294 | * key: The selected key, 295 | * path: The path leading down to the selected key, 296 | * object: The entire object that contains the currently selected key/val pair 297 | * value: The selected value 298 | * } 299 | */ 300 | let template: DataObject; 301 | try { 302 | if (serialized) template = JSON.parse(obj as string); 303 | else template = obj; 304 | } catch (error) { 305 | template = obj; 306 | } 307 | 308 | // Setting $root 309 | this.$templateRoot = template; 310 | 311 | if (typeof template === 'object') { 312 | (template as AnyObject).$root = this.$templateRoot; 313 | } 314 | 315 | (String.prototype as AnyObject).$root = this.$templateRoot; 316 | (Number.prototype as AnyObject).$root = this.$templateRoot; 317 | (Function.prototype as AnyObject).$root = this.$templateRoot; 318 | (Array.prototype as AnyObject).$root = this.$templateRoot; 319 | (Boolean.prototype as AnyObject).$root = this.$templateRoot; 320 | 321 | if (typeof template === 'object') { 322 | (template as AnyObject).$this = this.$templateRoot; 323 | } 324 | 325 | (String.prototype as AnyObject).$this = this.$templateRoot; 326 | (Number.prototype as AnyObject).$this = this.$templateRoot; 327 | (Function.prototype as AnyObject).$this = this.$templateRoot; 328 | (Array.prototype as AnyObject).$this = this.$templateRoot; 329 | (Boolean.prototype as AnyObject).$this = this.$templateRoot; 330 | try { 331 | // generate new $selected_root 332 | if (this.$selected && this.$selected.length > 0) { 333 | this.$selected 334 | .sort( 335 | (a, b) => 336 | // sort by path length, so that deeper level items will be replaced first 337 | // TODO: may need to look into edge cases 338 | b.path.length - a.path.length, 339 | ) 340 | .forEach((selection) => 341 | this.transformSelectedItemWith(selection, template), 342 | ); 343 | this.$selected.sort((a, b) => a.index - b.index); 344 | } else { 345 | const parsedObject = new Transform(this, this.st, this.sync).runSync( 346 | template, 347 | this.$selectedRoot, 348 | ); 349 | // apply the result to root 350 | this.$selectedRoot = Helper.resolve( 351 | this.$selectedRoot, 352 | '', 353 | parsedObject, 354 | ); 355 | } 356 | } finally { 357 | if (typeof template === 'object') { 358 | (template as AnyObject).$root = undefined; 359 | } 360 | 361 | delete (String.prototype as AnyObject).$root; 362 | delete (Number.prototype as AnyObject).$root; 363 | delete (Function.prototype as AnyObject).$root; 364 | delete (Array.prototype as AnyObject).$root; 365 | delete (Boolean.prototype as AnyObject).$root; 366 | 367 | if (typeof template === 'object') { 368 | (template as AnyObject).$this = undefined; 369 | } 370 | 371 | delete (String.prototype as AnyObject).$this; 372 | delete (Number.prototype as AnyObject).$this; 373 | delete (Function.prototype as AnyObject).$this; 374 | delete (Array.prototype as AnyObject).$this; 375 | delete (Boolean.prototype as AnyObject).$this; 376 | } 377 | return this; 378 | } 379 | 380 | async transformWith(obj: DataObject, serialized: boolean): Promise