├── .gitignore ├── test ├── 05_tpl-insert-template-library.test.js ├── 15_errors.test.js ├── 13_tools.test.js ├── 02_tpl-find-placeholders.test.js ├── 03_tpl-interpret-template.test.js ├── 07_process-insert.test.js ├── 01_tpl-chop.test.js ├── 06_process-validation.test.js ├── 04_tpl-insert-template.test.js ├── 09_process-tools.test.js ├── 10_hook-modifiers.test.js ├── 14_data.test.js ├── 11_code-assembly.test.js ├── 08_process-operations.test.js └── 12_run.test.js ├── src ├── general-tools.js ├── template-chop.js ├── errors.js ├── template-tools.js ├── process-tools.js ├── process-operations.js └── index.js ├── dist ├── index.html └── code-assembly-line.min.js ├── webpack.config.js ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .npmignore 3 | .vscode 4 | coverage/**/* 5 | package-lock.json 6 | node_modules 7 | types/ 8 | concept.md 9 | PLAY-test-samples.md -------------------------------------------------------------------------------- /test/05_tpl-insert-template-library.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , codeAssemblyLine = require ( '../src/index' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | describe ( 'Insert a template Library', () => { 12 | 13 | 14 | 15 | }) // Describe Add template 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/15_errors.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , CodeAssemblyLine = require ( '../src/index' ) 7 | , showError = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | 12 | describe ( 'Errors', () => { 13 | 14 | it ( 'Call a non-existing error', () => { 15 | const result = showError ( 'Fake') 16 | expect ( result ).to.be.equal ( 'Not defined error.' ) 17 | }) // it call a non-existing error 18 | 19 | }) // describe Tools 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/general-tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tools = { 4 | 5 | renameTemplate : function ( extLib, mod ) { // (externalTemplate, mod{}) -> externalTemplate 6 | // * Change-name tool for externalTemplate object 7 | // mod - { existingTemplateName: newTemplateName } 8 | const 9 | updateKeys = Object.keys ( mod ) 10 | , existingKeys = Object.keys ( extLib ) 11 | ; 12 | 13 | return existingKeys.reduce ( (res,key) => { 14 | if ( updateKeys.includes(key) ) res [ mod[key]] = extLib[key] 15 | else res [ key ] = extLib[key] 16 | return res 17 | },{}) 18 | } // modifyTemplate func. 19 | 20 | } // tools 21 | 22 | module.exports = tools -------------------------------------------------------------------------------- /test/13_tools.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , CodeAssemblyLine = require ( '../src/index' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | 12 | describe ( 'Tools', () => { 13 | 14 | it ( 'Rename templates', () => { 15 | const 16 | tplEngine = new CodeAssemblyLine() 17 | , tplLib = { 18 | 'random' : 'Some text with {{place}}.' 19 | , 'hi' : 'Hi {{user}}' 20 | } 21 | ; 22 | 23 | const result = tplEngine.tools.renameTemplate ( tplLib, { 'hi':'hello'} ) 24 | 25 | expect ( result ).to.have.property('hello') 26 | expect ( result ).to.not.have.property('hi') 27 | }) // it modify template 28 | 29 | }) // describe Tools -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Code Assembly Line 8 | 9 | 10 | 11 |

Code Assembly Line

12 |

13 | 28 | 29 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const 2 | path = require('path') 3 | , webpack = require('webpack') 4 | , webpackAnalyzer = require ('webpack-bundle-analyzer').BundleAnalyzerPlugin 5 | ; 6 | 7 | const 8 | PROD = (process.env.NODE_ENV === 'production') ? true : false 9 | , uglifyOptions = { 10 | minimize : true 11 | , mangle : true 12 | } 13 | ; 14 | 15 | 16 | 17 | module.exports = { 18 | entry: './src/index.js', 19 | output: { 20 | path: path.resolve(__dirname, 'dist') 21 | , filename: 'code-assembly-line.min.js' 22 | , library: 'CodeAssemblyLine' 23 | , libraryTarget : 'umd' 24 | , umdNamedDefine : true 25 | }, 26 | module: { 27 | loaders: [ 28 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['env'] } } 29 | ], 30 | }, 31 | plugins: PROD ? [ 32 | new webpack.optimize.UglifyJsPlugin( uglifyOptions ) 33 | ] 34 | : [] 35 | }; 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Peter Naydenov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-assembly-line", 3 | "version": "1.0.0", 4 | "description": "Javascript template engine. Works server and client side", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "NODE_ENV='test' mocha test", 8 | "cover": "istanbul cover _mocha", 9 | "build": "webpack", 10 | "prod": "NODE_ENV='production' webpack" 11 | }, 12 | "keywords": [ 13 | "template", 14 | "engine", 15 | "js", 16 | "render" 17 | ], 18 | "repository" : { 19 | "type" : "git" 20 | , "url" : "https://github.com/PeterNaydenov/code-assembly-line.git" 21 | }, 22 | "author": "Peter Naydenov", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "babel-core": "^6.26.0", 26 | "babel-loader": "^7.1.2", 27 | "babel-preset-env": "^1.6.1", 28 | "chai": "^4.1.2", 29 | "istanbul": "^0.4.5", 30 | "mocha": "^4.0.1", 31 | "webpack": "^3.10.0", 32 | "webpack-bundle-analyzer": "^2.9.1" 33 | }, 34 | "dependencies": {} 35 | } 36 | -------------------------------------------------------------------------------- /src/template-chop.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const showError = require ('./errors'); 4 | 5 | /* 6 | Converts string template to internal template format 7 | ( text: string ) -> string[] 8 | 9 | */ 10 | 11 | 12 | 13 | function chopTemplate ( text ) { 14 | 15 | let 16 | start // placeholder start 17 | , end // placeholder end 18 | , checkPoint // check 19 | , res = [] // template result array 20 | ; 21 | 22 | if ( typeof(text) != 'string' ) return [ showError('brokenTemplate') ] 23 | if ( text.length == 0 ) return [] 24 | 25 | start = text.indexOf ( '{{' ) 26 | if ( 0 < start ) res.push ( text.slice(0, start) ) 27 | if ( -1 == start ) { 28 | res.push( text ) 29 | return res; 30 | } 31 | else { 32 | checkPoint = text.indexOf ( '{{', start+2 ) 33 | end = text.indexOf("}}") 34 | 35 | if ( end == -1 ) return [ showError('brokenTemplate') ] // Placeholder with missing closing tags 36 | if ( end < start ) return [ showError('brokenTemplate') ] // Placeholder closing tags without starting ones 37 | else end += 2 38 | 39 | if ( checkPoint != -1 && checkPoint < end ) { 40 | // New placeholder before first ends 41 | return [ showError('brokenTemplate') ] 42 | } 43 | 44 | res.push( text.slice(start,end) ) 45 | let nextText = text.slice (end) 46 | let additional = chopTemplate ( nextText ) 47 | return res.concat ( additional ) 48 | } 49 | } // chopTemplate func. 50 | 51 | 52 | 53 | module.exports = chopTemplate; 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // * Error Messages 4 | 5 | 6 | const msg = { 7 | 'notDefined' : 'Not defined error.' 8 | // Template errors 9 | , 'brokenTemplate' : 'Broken string template. Placeholder with missing closing tag.' 10 | , 'wrongDataTemplate' : 'Broken data template. Object is not according the standards.' 11 | , 'overwriteTemplate' : 'Error: Overwrite of existing template is not permited by configuration.' 12 | , 'missingTemplate' : `Error: Template "%s" doesn't exist.` 13 | 14 | // Process errors 15 | , 'wrongExtProcess' : 'Wrong process data. Should be an array of "processSteps".' 16 | , 'emptyExtProcess' : 'Empty process! Process should contain steps(processSteps)' 17 | , 'brokenProcess' : 'Broken process description.' 18 | , 'missingOperation' : 'Process has step with missing operation. Set parameter "do".' 19 | , 'overwriteProcess' : 'Error: Process with name "%s" is already defined.' 20 | , 'notaValidOperation' : `Error: "%s" is not a valid operation` 21 | 22 | , 'blockExpectString' : `Block expects string data. %s` 23 | , 'dataExpectObject' : `Data operations require objects. %s` 24 | 25 | 26 | // Data errors 27 | , 'overwriteData' : 'Error: Data with name "%s" is already defined.' 28 | 29 | // Validation errors 30 | , 'processNotExists' : 'Error: Process "%s" does not exist.' 31 | , 'templateNotExists' : 'Error: Template "%s" is not available' 32 | , 'invalidStorageName' : 'Error: Process-step "save" has param "as: %s". Param "as" should be "data", "template" or "process". ' 33 | , 'invalidJSON' : 'Error: Invalid JSON format was provided for saving in "processes": %s' 34 | 35 | } 36 | 37 | 38 | 39 | const showError = function ( msgName, vars ) { 40 | let result = msg[msgName] || msg['notDefined']; 41 | if ( vars ) { 42 | if ( !(vars instanceof Array)) vars = [vars] 43 | vars.forEach ( v => result = result.replace('%s', v ) ) 44 | } 45 | return result 46 | } // msg func. 47 | 48 | module.exports = showError; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Assembly Line 2 | 3 | Yet another template engine... 4 | Documentation will late a bit. 5 | 6 | 7 | 8 | 9 | 10 | ## Installation 11 | Code-Assembly-Line works for node.js and browsers. 12 | 13 | 14 | 15 | ### Installation for Node.js based projects: 16 | From the console: 17 | ``` 18 | npm i -S code-assembly-line 19 | 20 | ``` 21 | It will install module in to the project. You can start using it by 22 | ```js 23 | const CodeAssemblyLine = require ( 'code-assembly-line' ); 24 | const tplEngine = new CodeAssemblyLine(); // Create instance of template engine 25 | ``` 26 | 27 | 28 | 29 | ### Installation for browsers 30 | Just go to '/dist' folder and get copy of '**code-assembly-line.min.js**'. Put the file inside your project folder and add a reference to it from HTML code by using this script tag: 31 | ```html 32 | 33 | ``` 34 | Library is avalable as '**CodeAssemblyLine**' and start using it. 35 | ```js 36 | const tplEngine = new CodeAssemblyLine(); 37 | ``` 38 | Find working example in file '**/dist/index.html**'. 39 | 40 | 41 | 42 | 43 | 44 | ## Motivation Factors for creating the 'Code Assembly Line' 45 | - Decouple render-processes from templates; 46 | - Decouple templates from data; 47 | - All compnonents(templates,processes,and data) provisioning in a JSON compatible format; 48 | - Built-in hooks for modifying render process if needed; 49 | - Posible default value for missing data; 50 | - Strategies on missing data: Hide a field, hide the record or provide alternative content; 51 | - Chaining render-processes; 52 | - Alternate template's placeholder names; 53 | - Enrich data by render new data-fields; 54 | - Partial rendering. Create new templates by render part of the available placeholders; 55 | - Optional spaces; 56 | 57 | 58 | 59 | 60 | 61 | ## Examples 62 | 63 | ### Example name 64 | 65 | ## Known bugs 66 | _(Nothing yet)_ 67 | 68 | 69 | 70 | 71 | 72 | ## Release History 73 | 74 | ### 1.0.0 (2017-12-17) 75 | 76 | - [x] Node.js module; 77 | - [x] Browser module; 78 | - [x] Test package; 79 | 80 | 81 | 82 | 83 | 84 | ## Credits 85 | 'code-assembly-line' was created by Peter Naydenov. 86 | 87 | 88 | 89 | 90 | 91 | ## License 92 | 'code-assembly-line' is released under the [MIT License](http://opensource.org/licenses/MIT). 93 | 94 | 95 | -------------------------------------------------------------------------------- /test/02_tpl-find-placeholders.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require ( 'chai' ); 4 | const expect = chai.expect 5 | const findPlaceholdersAndSpaces = require ('../src/template-tools')._findPlaceholdersAndSpaces; 6 | 7 | 8 | 9 | describe ( 'Find a placeholders', () => { 10 | 11 | it ( 'No placeholders', () => { 12 | const tpl = [ 'Good morning!' ]; 13 | const result = findPlaceholdersAndSpaces ( tpl ).placeholders; 14 | 15 | expect ( result ).to.be.an('object') 16 | expect ( result ).to.be.empty 17 | }) // it no placeholders 18 | 19 | 20 | 21 | it ( 'One placeholder', () => { 22 | const tpl = [ 'Good morning, ', '{{user}}' ]; 23 | const result = findPlaceholdersAndSpaces ( tpl ).placeholders; 24 | 25 | expect ( result ).to.be.an('object') 26 | expect ( result ).to.contain.property( 'user' ) 27 | expect ( result.user ).to.be.an('array') 28 | expect ( result.user ).to.includes(1) 29 | }) // it one placeholder 30 | 31 | 32 | 33 | it ( 'Many placeholders', () => { 34 | const tpl = [ 'Today user ', '{{user}}', ' has a birthday. Turns ', '{{age}}', ' years.' ]; 35 | const result = findPlaceholdersAndSpaces ( tpl ).placeholders; 36 | 37 | expect ( result ).to.be.an('object') 38 | expect ( result ).to.contain.property( 'user' ).that.is.an ( 'array' ) 39 | expect ( result ).to.contain.property( 'age' ).that.is.an ( 'array' ) 40 | expect ( result.user ).to.be.an('array') 41 | expect ( result.user ).to.includes(1) 42 | expect ( result.age ).to.be.an('array') 43 | expect ( result.age ).to.includes(3) 44 | }) // it many placeholders 45 | 46 | 47 | 48 | it ( 'Multiple used placeholders', () => { 49 | const tpl = [ '{{age}}', ' is like ', '{{age}}', '. Happy birthday ', '{{user}}' ]; 50 | const result = findPlaceholdersAndSpaces ( tpl ).placeholders; 51 | 52 | expect ( result ).to.be.an('object'); 53 | expect ( result ).to.contain.property ('age' ).that.is.an ( 'array' ) 54 | expect ( result ).to.contain.property ('user').that.is.an ( 'array' ) 55 | expect ( result.age ).to.have.length(2) 56 | expect ( result.age ).to.includes (0) 57 | expect ( result.age ).to.includes (2) 58 | expect ( result.user).to.includes (4) 59 | }) // it multiple used placeholders 60 | 61 | }) // describe findPlaceholders 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/03_tpl-interpret-template.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 4 | chai = require ('chai') 5 | , showError = require ( '../src/errors' ) 6 | , templateTools = require ('../src/template-tools' ) 7 | 8 | , expect = chai.expect 9 | , str2intTemplate = templateTools.str2intTemplate 10 | , load_interpretTemplate = templateTools.load_interpretTemplate 11 | , interpret = load_interpretTemplate ( str2intTemplate ) 12 | ; 13 | 14 | 15 | 16 | describe ( 'Template interpreter', () => { 17 | 18 | it ( 'Well formated string template', () => { 19 | const str = 'Someone should {{action}} it.'; 20 | const result = interpret ( str ); 21 | 22 | expect ( result ).to.be.an('object') 23 | expect ( result ).to.have.property('tpl') 24 | expect ( result ).to.not.have.property ( 'errors' ) 25 | expect ( result ).to.have.property('placeholders').that.has.property('action') 26 | expect ( result.placeholders.action ).to.be.an ( 'array' ) 27 | expect ( result.placeholders.action ).to.have.length ( 1 ) 28 | expect ( result.placeholders.action ).to.contain ( 1 ) 29 | }) // it well formated template 30 | 31 | 32 | 33 | it ( 'Errors within string template', () => { 34 | const str = 'So }} errors happend{{'; 35 | const result = interpret ( str ); 36 | 37 | expect ( result ).to.have.property ('errors' ) 38 | expect ( result.errors[0] ).to.be.equal ( showError('brokenTemplate') ) 39 | expect ( result ).to.have.property ('tpl') 40 | expect ( result.tpl ).to.be.an('array') 41 | expect ( result.tpl ).contains ( showError('brokenTemplate') ) 42 | expect ( result ).to.have.property ( 'placeholders' ).that.is.empty 43 | }) // it error string template 44 | 45 | 46 | 47 | it ( 'Wrong data format: object', () => { 48 | const data = { hello: 'hello, {{user}}' }; 49 | const result = interpret ( data ); 50 | 51 | expect ( result ).to.have.property ( 'errors' ) 52 | expect ( result ).to.not.have.property ( 'tpl' ) 53 | expect ( result ).to.not.have.property ( 'placeholders' ) 54 | }) // it light data template 55 | 56 | 57 | 58 | it ( 'Wrong data format: number', () => { 59 | const data = 43; 60 | const result = interpret ( data ); 61 | 62 | expect ( result ).to.have.property ( 'errors' ); 63 | expect ( result.errors[0] ).to.be.equal ( showError('wrongDataTemplate') ) 64 | }) // it wrong data format 65 | 66 | }) // Describe interpret a template 67 | 68 | 69 | -------------------------------------------------------------------------------- /test/07_process-insert.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , processTools = require ( '../src/process-tools' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | 12 | describe ( 'Process interpreter', () => { 13 | 14 | it ('General', () => { 15 | const 16 | ext = [ 17 | { do: 'draw', tpl: 'any' } 18 | , { do: 'block', name: 'anyBlock' } 19 | ] 20 | , result = processTools.interpret ( ext ) 21 | ; 22 | 23 | expect ( result ).to.have.property ( 'steps' ) 24 | expect ( result ).to.have.property ( 'arguments' ) 25 | expect ( result ).to.have.property ( 'hooks' ) 26 | expect ( result ).to.not.have.property ( 'errors' ) 27 | 28 | expect ( result.steps ).to.be.an( 'array' ) 29 | expect ( result.steps ).includes ('draw') 30 | expect ( result.steps ).includes ('block') 31 | 32 | expect ( result.hooks).to.be.an('array') 33 | expect ( result.hooks).to.be.empty 34 | }) // it general 35 | 36 | 37 | 38 | it ( 'Hooks', () => { 39 | const 40 | ext = [ 41 | { do: 'draw', tpl: 'link', hook:'drawHook' } 42 | , { do: 'hook', name: 'test'} 43 | ] 44 | , result = processTools.interpret ( ext ) 45 | ; 46 | 47 | expect ( result ).to.not.have.property ( 'errors' ) 48 | expect ( result.hooks ).to.have.length (2) 49 | expect ( result.hooks ).to.includes( 'test' ) 50 | expect ( result.hooks ).to.includes( 'drawHook' ) 51 | 52 | expect ( result.steps ).to.have.length (2) 53 | expect ( result.steps ).to.includes ( 'draw' ) 54 | expect ( result.steps ).to.includes ( 'hook' ) 55 | }) // it hook 56 | 57 | 58 | 59 | it ( 'Error', () => { 60 | const 61 | ext = {} 62 | , result = processTools.interpret ( ext ) 63 | ; 64 | 65 | expect ( result ).to.have.property ( 'errors' ) 66 | expect ( result ).to.have.property ( 'steps' ) 67 | expect ( result ).to.have.property ( 'arguments' ) 68 | expect ( result ).to.have.property ( 'hooks' ) 69 | 70 | expect ( result.errors ).to.have.length (1) 71 | expect ( result.steps ).to.be.empty 72 | expect ( result.arguments ).to.be.empty 73 | expect ( result.hooks ).to.be.empty 74 | }) // it error 75 | 76 | }) // describe process interpreter -------------------------------------------------------------------------------- /test/01_tpl-chop.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require ( 'chai' ); 4 | const expect = chai.expect 5 | const chop = require ('../src/template-chop'); 6 | const callError = require ('../src/errors'); 7 | 8 | 9 | 10 | 11 | 12 | describe ( 'Chop string templates', () => { 13 | 14 | it ( 'No placeholder', () => { 15 | const tpl = 'Hello, Peter.'; 16 | const result = chop (tpl); 17 | 18 | expect ( result ).to.be.an('array') 19 | expect ( result ).to.have.length(1) 20 | }) // it no placeholder 21 | 22 | 23 | 24 | it ( 'Regular template', () => { 25 | const tpl = 'Hello, {{user}}.'; 26 | const result = chop (tpl); 27 | 28 | expect ( result ).to.be.an('array') 29 | expect ( result ).to.have.length(3) 30 | expect ( result[1] ).to.be.equal('{{user}}') 31 | }) // it regular 32 | 33 | 34 | 35 | it ( 'Starts with placeholder', () => { 36 | const tpl = '{{user}} is logged in.'; 37 | const result = chop (tpl); 38 | 39 | expect ( result ).to.be.an('array') 40 | expect ( result ).to.have.length(2) 41 | expect ( result[0] ).to.be.equal('{{user}}') 42 | }) // it Starts with 43 | 44 | 45 | 46 | it ( 'Many placeholders', () => { 47 | const tpl = '{{user}} is {{age}} years old.'; 48 | const result = chop (tpl); 49 | 50 | expect ( result ).to.be.an('array') 51 | expect ( result ).to.have.length(4) 52 | expect ( result[0] ).to.be.equal('{{user}}') 53 | expect ( result[2] ).to.be.equal('{{age}}') 54 | }) // it Many placeholders 55 | 56 | 57 | 58 | it ( 'Finish with placeholder', () => { 59 | const tpl = '{{user}} and {{user}}'; 60 | const result = chop (tpl); 61 | 62 | expect ( result ).to.be.an('array') 63 | expect ( result ).to.have.length(3) 64 | expect ( result[0] ).to.be.equal('{{user}}') 65 | expect ( result[2] ).to.be.equal('{{user}}') 66 | }) // it finish with placeholder 67 | 68 | 69 | 70 | it ( 'Missing closing tags', () => { 71 | const tpl = 'Pardon {{error is coming out.'; 72 | const result = chop (tpl); 73 | 74 | expect ( result ).to.be.an('array') 75 | expect ( result ).to.have.length(1) 76 | expect ( result[0] ).to.be.equal ( callError('brokenTemplate') ) 77 | }) // it missing closing tag 78 | 79 | 80 | 81 | it ( 'Other placeholder after missing closing tag', () => { 82 | const tpl = '{{user is {{age}} years old.'; 83 | const result = chop (tpl); 84 | 85 | expect ( result ).to.be.an('array') 86 | expect ( result ).to.have.length(1) 87 | expect ( result).to.include ( callError('brokenTemplate') ) 88 | }) // it 89 | 90 | 91 | 92 | it ( 'Closing tags before open ones', () => { 93 | const tpl = 'user}} is {{age years}} old.'; 94 | const result = chop (tpl); 95 | 96 | expect ( result ).to.be.an('array') 97 | expect ( result).to.include ( callError('brokenTemplate') ) 98 | }) // it Closing tags before open ones 99 | 100 | 101 | 102 | it ( 'Not a string template', () => { 103 | const tpl = { a: 'something', b: 12 }; 104 | const result = chop ( tpl ); 105 | 106 | expect ( result ).to.be.an ( 'array' ) 107 | expect ( result[0] ).to.be.equal ( callError('brokenTemplate') ) 108 | }) // it not a string template 109 | 110 | }) // describe -------------------------------------------------------------------------------- /test/06_process-validation.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | 4 | const 5 | chai = require ('chai') 6 | , expect = chai.expect 7 | , processTools = require ( '../src/process-tools' ) 8 | , callErrors = require ( '../src/errors' ) 9 | ; 10 | 11 | 12 | 13 | describe ( 'Process Validation', () => { 14 | 15 | it ( 'Valid data', () => { 16 | const ext = [ 17 | { do: 'draw', tpl: 'dummy' } 18 | , { do: 'block', name: 'dummyBlock' } 19 | ]; 20 | 21 | const result = processTools._validate ( ext ); 22 | 23 | expect ( result ).to.be.an ( 'array' ) 24 | expect ( result ).to.be.empty 25 | }) // it valid data 26 | 27 | 28 | 29 | it ( 'Empty array', () => { 30 | const 31 | ext = [] 32 | , result = processTools._validate ( ext ) 33 | ; 34 | 35 | expect ( result ).to.be.an ( 'array' ) 36 | expect ( result ).to.have.length ( 1 ) 37 | expect ( result[0] ).to.be.equal ( callErrors('emptyExtProcess') ) 38 | }) // it empty array 39 | 40 | 41 | 42 | it ( 'Wrong data format - primitive', () => { 43 | const 44 | ext = 'none' 45 | , result = processTools._validate ( ext ) 46 | ; 47 | 48 | expect ( result ).to.be.an ( 'array' ) 49 | expect ( result ).to.have.length ( 1 ) 50 | expect ( result[0] ).to.be.equal ( callErrors('wrongExtProcess') ) 51 | }) // it primitive 52 | 53 | 54 | 55 | it ( 'Wrong data format - object', () => { 56 | const 57 | ext = { do: 'draw', tpl: 'any' } 58 | , result = processTools._validate ( ext ) 59 | ; 60 | 61 | expect ( result ).to.be.an ( 'array' ) 62 | expect ( result ).to.have.length ( 1 ) 63 | expect ( result[0] ).to.be.equal ( callErrors('wrongExtProcess') ) 64 | }) // it object 65 | 66 | 67 | 68 | it ( 'Step with missing operation',() => { 69 | const 70 | ext = [ 71 | { do: 'draw', tpl: 'any' } 72 | , { name: 'newBlock' } 73 | ] 74 | , result = processTools._validate ( ext ) 75 | ; 76 | 77 | expect ( result ).to.be.an ( 'array' ) 78 | expect ( result ).to.have.length ( 1 ) 79 | expect ( result[0] ).to.be.equal ( callErrors('missingOperation') ) 80 | }) // it missing operation 81 | 82 | 83 | 84 | it ( 'Step with wrong operation', () => { 85 | const 86 | ext = [ 87 | { do: 'wrongOperation', tpl: 'any' } 88 | , { do: 'test', name: 'newBlock' } 89 | ] 90 | , result = processTools._validate ( ext ) 91 | ; 92 | 93 | expect ( result ).to.be.an ( 'array' ) 94 | expect ( result ).to.have.length ( 2 ) 95 | expect ( result[0] ).to.be.equal ( 'Error: "wrongOperation" is not a valid operation' ) 96 | expect ( result[1] ).to.be.equal ( 'Error: "test" is not a valid operation' ) 97 | }) // it wrong operation 98 | 99 | }) // describe process tools 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/template-tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chop = require ('./template-chop'); 4 | const showError = require ('./errors' ); 5 | 6 | /* 7 | Converts ExtTempalteString to InternalTpl. 8 | 9 | • Import string template 10 | ( stringTemplate: string ) -> internalTemplate 11 | internalTemplate: { tpl: tplData, placeholders?:indexOfPlaceholders, error?: errorMsg } 12 | indexOfPlaceholders: { placeholderName: indexInTplData } 13 | errorMsg: string 14 | 15 | tplData: string[]; Contains text and placeholders. 16 | Example: 17 | [ 18 | 'Some words from ' //text 19 | , '{{person}}' // placeholder 20 | , ' - See how simple is it' //text 21 | ] 22 | 23 | placeholderName is extracted value of placeholder. 24 | Example: 25 | placeholder: {{person}} 26 | placeholderName: person 27 | */ 28 | 29 | 30 | const lib = { 31 | 32 | str2intTemplate (str) { 33 | const r = chop ( str ); 34 | const error = r.includes ( showError('brokenTemplate') ); 35 | let 36 | placeholders = {} 37 | , spaces = {} // { placeholderName: spaceType } 38 | ; 39 | /* 40 | placeholderName: string. Name of the placeholder. 41 | spaceType:enum. 42 | 1-space before, 43 | 2-space after, 44 | 3-both spaces 45 | */ 46 | 47 | if ( !error ) { 48 | const t = lib._findPlaceholdersAndSpaces ( r ) 49 | spaces = t.spaces 50 | placeholders = t.placeholders 51 | } 52 | 53 | const template = { 54 | tpl:r 55 | , placeholders 56 | , spaces 57 | }; 58 | if ( error ) template.errors = [showError('brokenTemplate')] 59 | return template 60 | } // str2inTemplate func. 61 | 62 | 63 | 64 | , load_interpretTemplate ( convert2intTemplate ) { 65 | return ( tplItem ) => { 66 | let intTemplate = {}; // internal template 67 | const type = (typeof tplItem == 'string') ? 'string' : 'error'; 68 | 69 | if ( type == 'string' ) intTemplate = convert2intTemplate ( tplItem ) 70 | else intTemplate = { errors: [showError('wrongDataTemplate')] } 71 | 72 | return intTemplate 73 | }} // load_interpretTemplate func. 74 | 75 | 76 | 77 | , _findPlaceholdersAndSpaces ( tplArray ) { 78 | const 79 | placeholders = {} 80 | , spaces = {} 81 | ; 82 | tplArray.forEach ( (item,i) => { 83 | if ( item.slice(0,2) == '{{' ) { 84 | let start = 2, end = -2; 85 | const before = item.slice(2,4) == '~~'; 86 | const after = item.slice (-4,-2) == '~~'; 87 | if (before) start = 4 88 | if ( after ) end = -4 89 | const phName = item.slice ( start, end ); 90 | if ( before && after ) spaces [ phName ] = 3 // before and after 91 | else if ( after ) spaces [ phName ] = 2 // only after 92 | else if ( before ) spaces [ phName ] = 1 // only before 93 | if ( placeholders.hasOwnProperty(phName) ) placeholders [ phName ].push (i) 94 | else placeholders [ phName ] = [i] 95 | } 96 | }) 97 | return { spaces, placeholders } 98 | } // findPlaceholders func. 99 | 100 | } // lib 101 | 102 | 103 | 104 | module.exports = lib 105 | 106 | 107 | -------------------------------------------------------------------------------- /test/04_tpl-insert-template.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , codeAssemblyLine = require ( '../src/index' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | describe ( 'Templates', () => { 12 | 13 | it ( 'Start Code Assembly Line', () => { 14 | const tplEngine = new codeAssemblyLine (); 15 | 16 | expect ( tplEngine ).to.have.property ( 'templates' ) 17 | expect ( tplEngine ).to.have.property ( 'processes' ) 18 | expect ( tplEngine ).to.have.property ( 'data' ) 19 | expect ( tplEngine ).to.have.property ( 'config' ) 20 | }) // it create Grain Tuple 21 | 22 | 23 | 24 | it ( 'Insert ExternalTemplate', () => { 25 | const tplEngine = new codeAssemblyLine (); 26 | const tpl = { 'hello' : 'Hello, {{name}}!'}; 27 | 28 | tplEngine.insertTemplate( tpl ) 29 | 30 | expect ( tplEngine.templates ).to.have.property ( 'hello' ) 31 | expect ( tplEngine.templates.hello ).to.have.property ('tpl') 32 | expect ( tplEngine.templates.hello ).to.have.property ('placeholders') 33 | expect ( tplEngine.templates.hello.tpl ).to.be.an ('array') 34 | }) // it insert ExternalTemplate 35 | 36 | 37 | 38 | it ( 'Insert template as a library member', () => { 39 | const tplEngine = new codeAssemblyLine (); 40 | const tpl = { 'simple/ah': 'Hello, {{name}}!'}; 41 | 42 | tplEngine.insertTemplate( tpl ) 43 | 44 | expect ( tplEngine.templates ).to.have.property ('simple/ah') 45 | }) // it use alternative name 46 | 47 | 48 | 49 | it ( 'Wrong data-format', () => { 50 | const tplEngine = new codeAssemblyLine (); 51 | const tpl = { hello: { name: 'Hello', age: 43}}; 52 | 53 | tplEngine.insertTemplate( tpl ) 54 | 55 | expect ( tplEngine.templates.hello ).to.have.property('errors') 56 | expect ( tplEngine.templates.hello.errors[0] ).to.be.equal( errors('wrongDataTemplate') ) 57 | expect ( tplEngine.templates.hello ).to.not.have.property ('tpl') 58 | expect ( tplEngine.templates.hello ).to.not.have.property ('placeholders') 59 | }) // it wrong data-format 60 | 61 | 62 | 63 | it ( 'Insert many ExternalTemplate at once', () => { 64 | const tplEngine = new codeAssemblyLine (); 65 | const tpl = { 66 | hello: 'Hello, {{name}}!' 67 | , birthday: 'Happy birthday {{name}}' 68 | }; 69 | 70 | tplEngine.insertTemplate ( tpl ) 71 | 72 | expect ( tplEngine.templates ).to.have.property ( 'hello' ) 73 | expect ( tplEngine.templates ).to.have.property ( 'birthday' ) 74 | }) // it insert many ExtSimpleTemplate 75 | 76 | 77 | 78 | it ( 'Overwrite templates is forbidden', () => { 79 | const tplEngine = new codeAssemblyLine ({overwriteTemplates : false}); 80 | const tpl = { 'hello': 'Hello, {{name}}!'}; 81 | const tpl2 = { 'hello': 'Changes are allowed, {{name}}!'}; 82 | 83 | tplEngine.insertTemplate( tpl ) 84 | tplEngine.insertTemplate( tpl2 ) 85 | 86 | expect ( tplEngine.templates ).to.have.property ('hello') 87 | expect ( tplEngine.templates.hello.tpl ).to.contain ('Hello, ') 88 | }) // it use alternative name 89 | 90 | 91 | 92 | it ( 'Overwrite templates is allowed', () => { 93 | const tplEngine = new codeAssemblyLine ({overwriteTemplates : true}); 94 | const tpl = { 'hello': 'Hello, {{name}}!'}; 95 | const tpl2 = { 'hello': 'Changes are allowed, {{name}}!'}; 96 | 97 | tplEngine.insertTemplate( tpl ) 98 | tplEngine.insertTemplate( tpl2 ) 99 | 100 | expect ( tplEngine.templates ).to.have.property ('hello') 101 | expect ( tplEngine.templates.hello.tpl ).to.contain ('Changes are allowed, ') 102 | }) // it use alternative name 103 | 104 | }) // Describe Add template 105 | 106 | 107 | -------------------------------------------------------------------------------- /test/09_process-tools.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | 4 | const 5 | chai = require ('chai') 6 | , expect = chai.expect 7 | , processOps = require ( '../src/process-tools' ) 8 | , callErrors = require ( '../src/errors' ) 9 | ; 10 | 11 | describe ( 'Process Tools', () => { 12 | 13 | it ( 'Interpet: Valid process name', () => { 14 | const linkProcess = [ 15 | { do: 'draw', tpl: 'a' } 16 | , { do: 'hook', name: 'sanitize'} 17 | , { do: 'block' } 18 | , { do: 'set', as: 'text' } 19 | ] 20 | const result = processOps.interpret ( linkProcess ) 21 | 22 | expect ( result ).to.have.property ( 'steps' ) 23 | expect ( result ).to.have.property ( 'arguments' ) 24 | expect ( result ).to.have.property ( 'hooks' ) 25 | expect ( result ).to.not.have.property ( 'error' ) 26 | 27 | expect ( result.hooks ).to.contain ( 'sanitize' ) 28 | 29 | expect ( result.steps ).to.contain ( 'draw' ) 30 | expect ( result.steps ).to.contain ( 'hook' ) 31 | expect ( result.steps ).to.contain ( 'block' ) 32 | expect ( result.steps ).to.contain ( 'set' ) 33 | }) // it interpret: valid process name 34 | 35 | 36 | 37 | it ( 'Interpret: Not a valid process name', () => { 38 | const 39 | fake = 'alternate' 40 | , processChain = [ 41 | { do: fake, name: 'something' } 42 | ] 43 | ; 44 | 45 | const result = processOps.interpret ( processChain ) 46 | 47 | expect ( result ).to.have.property ( 'steps' ) 48 | expect ( result ).to.have.property ( 'arguments' ) 49 | expect ( result ).to.have.property ( 'hooks' ) 50 | expect ( result ).to.have.property ( 'errors' ) 51 | expect ( result.errors[0] ).to.be.equal ( `Error: "${fake}" is not a valid operation` ) 52 | }) // it interpret: not a valid process name 53 | 54 | 55 | it ( 'Interpret: Missing operation', () => { 56 | const processChain = [ { name: 'something' } ] 57 | const result = processOps.interpret ( processChain ) 58 | 59 | expect ( result ).to.have.property ( 'errors' ) 60 | expect ( result.errors[0] ).to.be.equal ( callErrors('missingOperation') ) 61 | }) // it interpret: missing operation 62 | 63 | 64 | 65 | it ( 'Interpret: Not a valid arguments', () => { 66 | const processFake = { name: 'fakeProcess' }; 67 | const result = processOps.interpret ( processFake ); 68 | 69 | expect ( result ).to.have.property ( 'errors' ) 70 | expect ( result.errors[0] ).to.be.equal ( callErrors('wrongExtProcess') ) 71 | }) // it interpret: not a valid arguments 72 | 73 | 74 | 75 | it ( 'Interpret: Empty process', () => { 76 | const emptyProcess = []; 77 | const result = processOps.interpret ( emptyProcess ); 78 | 79 | expect ( result ).to.have.property ( 'errors' ) 80 | expect ( result.errors[0] ).to.be.equal ( callErrors('emptyExtProcess') ) 81 | }) // it interpret: Empty process 82 | 83 | 84 | 85 | it ( 'string helper: string', () => { 86 | const a = 'ala-bala'; 87 | const result = processOps._findIfString ( a ) 88 | 89 | expect ( result ).to.be.equal ( true ) 90 | }) // it string helper: string 91 | 92 | 93 | 94 | it ( 'string helper: not a string', () => { 95 | const a = 4; 96 | const result = processOps._findIfString ( a ) 97 | 98 | expect ( result ).to.be.equal ( false ) 99 | }) // it string helper: not a string 100 | 101 | 102 | 103 | it ( 'List copier', () => { 104 | const a = [ 'one', 'two' ]; 105 | const result = processOps._copyList ( a ) 106 | a[0] = 'update' 107 | 108 | expect ( result[0] ).to.not.be.equal ( a[0] ) 109 | }) // it list copier 110 | 111 | // Note: method "run" has separate testing page 112 | 113 | }) // describe Process Tools 114 | 115 | 116 | -------------------------------------------------------------------------------- /test/10_hook-modifiers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , processOps = require ( '../src/process-operations' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | describe ( 'Hook Modifiers', () => { 12 | 13 | it ( 'Alter', () => { 14 | const 15 | data = [ 16 | { user: 'Peter' , age: 43 } 17 | , { user: 'Ivo' , age: 19 } 18 | , { user: 'Ivan' , age: 39 } 19 | ] 20 | , hook = {} 21 | ; 22 | 23 | let sample,update; 24 | 25 | hook.test = function ( data , modify ) { 26 | const step = { do:'alter', data: {'age':'alt'}, select: 'first' }; 27 | update = modify ( data, step ); 28 | sample = data; 29 | return update; 30 | } 31 | 32 | const final = processOps.hook ( data, hook.test ) 33 | expect ( sample[0] ).to.have.property ('age') 34 | expect ( sample[0] ).to.not.have.property ('alt') 35 | expect ( update[0] ).to.not.have.property ( 'age' ) 36 | expect ( update[0] ).to.have.property ( 'alt' ) 37 | expect ( final[0] ).to.not.have.property ( 'age' ) 38 | expect ( final[0] ).to.have.property ( 'alt' ) 39 | expect ( final[0]['alt'] ).to.be.equal ( 43 ) 40 | 41 | expect ( update[1] ).to.have.property ('age') 42 | expect ( update[1] ).to.not.have.property ('alt') 43 | expect ( final[1] ).to.have.property ('age') 44 | expect ( final[1] ).to.not.have.property ('alt') 45 | }) // it alter 46 | 47 | 48 | 49 | it ( 'Add', () => { 50 | const 51 | data = [{ user: 'Peter' }] 52 | , hook = {} 53 | ; 54 | 55 | let sample,update; 56 | 57 | hook.test = function ( data , modify ) { 58 | const step = { do:'add', data: {'age':43} }; 59 | update = modify ( data, step ); 60 | sample = data; 61 | return update; 62 | } 63 | 64 | const final = processOps.hook ( data, hook.test ) 65 | expect ( sample[0] ).to.not.have.property ('age') 66 | expect ( update[0] ).to.have.property ( 'age' ) 67 | expect ( final[0] ).to.have.property ( 'age' ) 68 | expect ( final[0]['age'] ).to.be.equal ( 43 ) 69 | }) // it add 70 | 71 | 72 | 73 | it ( 'Copy', () => { 74 | const 75 | data = [{ user: 'Peter', age:43 }] 76 | , hook = {} 77 | ; 78 | 79 | let sample,update; 80 | 81 | hook.test = function ( data , modify ) { 82 | const step = { do:'copy', data: {'age':'alt'} }; 83 | update = modify ( data, step ); 84 | sample = data; 85 | return update; 86 | } 87 | 88 | const final = processOps.hook ( data, hook.test ) 89 | expect ( sample[0] ).to.have.property ('age') 90 | expect ( sample[0] ).to.not.have.property ('alt') 91 | expect ( update[0] ).to.have.property ('age') 92 | expect ( update[0] ).to.have.property ('alt') 93 | expect ( update[0]['alt'] ).to.be.equal ( data[0].age ) 94 | expect ( final[0] ).to.have.property ('age') 95 | expect ( final[0] ).to.have.property ('alt') 96 | expect ( final[0]['alt'] ).to.be.equal ( data[0].age ) 97 | }) // it copy 98 | 99 | 100 | 101 | it ( 'Remove', () => { 102 | const 103 | todo = { do: 'hook', name: 'test' } 104 | , data = [{ user: 'Peter', age:43 }] 105 | , hook = {} 106 | ; 107 | let update, sample; 108 | 109 | hook.test = function ( data , modify ) { 110 | update = modify ( data, {do:'remove', keys:['age']} ); 111 | sample = data; 112 | return update; 113 | } 114 | 115 | const final = processOps.hook ( data, hook.test ) 116 | expect ( sample[0] ).to.have.property ('age') 117 | expect ( update[0] ).to.not.have.property ('age') 118 | expect ( final[0] ).to.not.have.property ('age') 119 | }) // it remove 120 | 121 | 122 | 123 | it ( 'Modification not allowed', () => { 124 | const 125 | todo = { do: 'hook', name: 'test' } 126 | , data = ['Hello Peter', 'Hello Ivan' ] 127 | , hook = {} 128 | ; 129 | let update, sample; 130 | 131 | hook.test = function ( data , modify ) { 132 | update = modify ( data, {do:'remove', keys:['age']} ); 133 | sample = data; 134 | return update; 135 | } 136 | 137 | const final = processOps.hook ( data, hook.test ) 138 | expect ( sample[0] ).to.be.a('string') 139 | expect ( update[0] ).to.be.equal ( 'Hello Peter' ) 140 | expect ( final[0] ).to.be.equal ( 'Hello Peter' ) 141 | }) // it not allowed 142 | 143 | 144 | 145 | }) // describe 146 | 147 | 148 | -------------------------------------------------------------------------------- /src/process-tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const 4 | showError = require ( './errors' ) 5 | , operation = require ( './process-operations') 6 | , validSteps = [ 'draw', 'alterTemplate', 'alter', 'set', 'add', 'copy', 'remove', 'hook', 'block', 'save' ] 7 | ; 8 | 9 | 10 | 11 | const lib = { 12 | 13 | interpret ( ext ) { // (ext: extProcess) -> int: intProcess 14 | const internal = {}; 15 | 16 | internal.steps = [] 17 | internal.arguments = [] 18 | internal.hooks = [] 19 | 20 | const errorLog = lib._validate ( ext ) 21 | if ( errorLog.length > 0 ) { 22 | internal.errors = errorLog; 23 | return internal; 24 | } 25 | 26 | ext.forEach ( step => { 27 | internal.steps.push ( step.do ) 28 | internal.arguments.push ( step ) 29 | if ( step.do == 'hook' ) internal.hooks.push ( step.name ) 30 | if ( step.hook ) internal.hooks.push ( step.hook ) 31 | }) 32 | 33 | return internal 34 | } // interpret func. 35 | 36 | 37 | 38 | , _validate (ext) { 39 | const 40 | log = [] 41 | , validType = ext instanceof Array 42 | ; 43 | 44 | if ( !validType ) return [ showError('wrongExtProcess') ] 45 | if ( ext.length == 0 ) return [ showError('emptyExtProcess') ] 46 | 47 | ext.forEach ( step => { 48 | let validOperation = false; 49 | if ( !step.do ) { 50 | log.push ( showError('missingOperation')) 51 | return 52 | } 53 | validOperation = validSteps.includes ( step.do ) 54 | if ( !validOperation ) log.push ( showError('notaValidOperation', [step.do] ) ) 55 | }) 56 | return log 57 | } // _validate func. 58 | 59 | 60 | 61 | , _findIfString : (list) => (typeof(list[0]) == 'string') 62 | 63 | 64 | 65 | , _copyList ( source ) { // (string[]) -> string[] 66 | let 67 | size = source.length 68 | , result = [] 69 | ; 70 | while (size--) result[size] = source[size] 71 | return result 72 | } // _copyList func. 73 | 74 | 75 | 76 | , _parse ( data ) { // (string) -> {} | false 77 | try { return JSON.parse(data) } 78 | catch (er) { return false } 79 | } // _parse func. 80 | 81 | 82 | 83 | , run ( proccessItems, data, hooks ) { 84 | // * Executes process/processes 85 | let 86 | me = this 87 | , libTemplates = me.templates 88 | , current = data 89 | , currentIsStr = lib._findIfString(current) // current is array of strings or array of objects 90 | , contextPlaceholders = {} 91 | , answer 92 | ; 93 | 94 | proccessItems.steps.forEach ( (step,id) => { 95 | const todo = proccessItems.arguments[id]; // Get full step instruction 96 | let tplName; 97 | 98 | switch ( step ) { 99 | case 'draw' : 100 | tplName = todo.tpl 101 | if ( currentIsStr ) console.warn ( showError ('dataExpectObject', `Step "draw" with template "${tplName}"`) ) 102 | 103 | const 104 | missField = todo.missField || false 105 | , missData = todo.missData || false 106 | , hookFn = hooks ? hooks[todo.hook] || false : false 107 | , tpl = libTemplates[tplName]['tpl'] 108 | , spaces = libTemplates[tplName]['spaces'] 109 | , originalPlaceholders = libTemplates[tplName]['placeholders'] 110 | , holdData = !(todo.as == null) 111 | ; 112 | 113 | let localTemplate = {}; 114 | localTemplate.tpl = tpl 115 | localTemplate.placeholders = contextPlaceholders[tplName] || originalPlaceholders 116 | localTemplate.spaces = spaces 117 | 118 | const update = operation[step] ( { template:localTemplate, data:current, sharedData:me.data, htmlAttributes:me.config.htmlAttributes, missField, missData, hookFn} ); 119 | if ( holdData ) { 120 | current = current.reduce ( (res,el,i) => { 121 | el[todo.as] = update[i] 122 | res.push(el) 123 | return res 124 | }, []) 125 | } 126 | else { 127 | current = update 128 | currentIsStr = true 129 | } 130 | break 131 | case 'block': 132 | if ( !currentIsStr ) { 133 | console.error ( showError ('blockExpectString', JSON.stringify(current)) ) 134 | return 135 | } 136 | const blockSpace = todo.space || ''; 137 | current = operation[step] ( current, blockSpace ) 138 | if ( todo.name ) { 139 | let newData = {}; 140 | newData[`block/${todo.name}`] = current.join('') 141 | me.insertData ( newData ) 142 | } 143 | break 144 | case 'alterTemplate' : 145 | tplName = todo.tpl 146 | const intTemplate = libTemplates[tplName]; 147 | contextPlaceholders[tplName] = operation[step] ( todo, intTemplate.placeholders ) 148 | break 149 | case 'set' : 150 | current = operation[step] (todo, current ) 151 | currentIsStr = false 152 | break 153 | case 'hook' : 154 | let hooked = operation[step] ( current, hooks[todo.name] ) 155 | if ( hooked instanceof Array ) current = hooked 156 | else current = [hooked] 157 | currentIsStr = lib._findIfString ( current ) 158 | break 159 | case 'save' : 160 | const saveName = (todo.as != 'block') ? todo.name : `block/${todo.name}`; 161 | let currentData = {}; 162 | 163 | switch ( todo.as ) { 164 | case 'block': 165 | case 'data': 166 | currentData[saveName] = current.join('') 167 | me.insertData ( currentData ) 168 | break 169 | case 'template': 170 | currentData[saveName] = current[0] 171 | me.insertTemplate ( currentData ) 172 | break 173 | case 'process': 174 | let newProcess = lib._parse ( current[0] ) 175 | if ( newProcess ) me.insertProcess ( newProcess, saveName ) 176 | else console.error ( showError ( 'invalidJSON', current[0] ) ) 177 | break 178 | default: 179 | console.error ( showError ('invalidStorageName', todo.as) ) 180 | return 181 | } 182 | break 183 | } // switch step 184 | }) // forEach step 185 | return current 186 | } // run func. 187 | } // lib 188 | 189 | 190 | 191 | module.exports = lib 192 | 193 | 194 | -------------------------------------------------------------------------------- /test/14_data.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , CodeAssemblyLine = require ( '../src/index' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | 12 | describe ( 'Data', () => { 13 | 14 | it ( 'Insert single data record', () => { 15 | const 16 | tplEngine = new CodeAssemblyLine() 17 | , defaultAlt = 'image description is missing' 18 | ; 19 | 20 | tplEngine.insertData ( { 'alt' : defaultAlt } ) 21 | 22 | expect ( tplEngine.data ).to.have.property ( 'alt' ) 23 | expect ( tplEngine.data.alt ).to.be.equal ( defaultAlt ) 24 | }) // it insert single data record 25 | 26 | 27 | 28 | it ( 'Insert many records', () => { 29 | const 30 | tplEngine = new CodeAssemblyLine() 31 | , records = { 32 | 'alt' : 'image description is missing' 33 | , 'home' : 'Sweet home' 34 | 35 | } 36 | ; 37 | 38 | tplEngine.insertData ( records ) 39 | 40 | expect ( tplEngine.data ).to.have.property ( 'alt' ) 41 | expect ( tplEngine.data ).to.have.property ( 'home' ) 42 | expect ( tplEngine.data.alt ).to.be.equal ( records.alt ) 43 | expect ( tplEngine.data.home ).to.be.equal ( records.home ) 44 | }) // it Insert many records 45 | 46 | 47 | 48 | it ( 'Insert deep nestled objects', () => { 49 | const 50 | tplEngine = new CodeAssemblyLine() 51 | , records = { 52 | 'alt' : 'image description is missing' 53 | , 'settings' : { 54 | 'access' : 'admin' 55 | , 'users' : [ 'Peter', 'Ivan', 'Stefan' ] 56 | } 57 | } 58 | ; 59 | 60 | tplEngine.insertData ( records ) 61 | 62 | expect ( tplEngine.data ).to.have.property ( 'alt' ) 63 | expect ( tplEngine.data ).to.have.property ( 'settings/access' ) 64 | expect ( tplEngine.data ).to.have.property ( 'settings/users/0' ) 65 | expect ( tplEngine.data ).to.not.have.property ( 'settings/users' ) 66 | }) // it Insert deep nestled objects 67 | 68 | 69 | 70 | it ( 'Try to overwrite existing data-record', () => { 71 | const 72 | tplEngine = new CodeAssemblyLine() 73 | , record = { 'alt' : 'image description is missing'} 74 | , nextrecord = { 'alt' : 'new alt '} 75 | ; 76 | 77 | tplEngine.insertData ( record ) 78 | tplEngine.insertData ( nextrecord ) 79 | 80 | expect ( tplEngine.data ).to.have.property ( 'alt' ) 81 | expect ( tplEngine.data.alt ).to.be.equal ( record.alt ) 82 | }) // it Try to overwrite existing data-record 83 | 84 | 85 | 86 | it ( 'Insert data as a library', () => { 87 | const 88 | tplEngine = new CodeAssemblyLine() 89 | , records = { 90 | 'alt' : 'image description is missing' 91 | , 'settings' : { 92 | 'access' : 'admin' 93 | , 'users' : [ 'Peter', 'Ivan', 'Stefan' ] 94 | } 95 | } 96 | ; 97 | 98 | tplEngine.insertDataLib ( records, 'special' ) 99 | const r = tplEngine.data; 100 | 101 | expect ( r ).to.not.have.property ( 'alt' ) 102 | expect ( r ).to.have.property ( 'special/alt' ) 103 | expect ( r ).to.have.property ( 'special/settings/users/0') 104 | expect ( r ).to.have.property ( 'special/settings/access') 105 | expect ( r['special/settings/users/0'] ).to.be.equal ( records.settings.users[0] ) 106 | }) // it Insert data as a library 107 | 108 | 109 | 110 | it ( 'Insert data as a library. Overwriting is forbidden', () => { 111 | const 112 | tplEngine = new CodeAssemblyLine () 113 | , oldRecord = { 'lib/alt' : 'old record' } 114 | , record = { 115 | 'alt' : 'new record' 116 | , 'other' : 'other record' 117 | } 118 | ; 119 | 120 | tplEngine.insertData ( oldRecord ) 121 | tplEngine.insertDataLib ( record, 'lib' ) 122 | 123 | const d = tplEngine.data; 124 | expect (d).to.have.property ('lib/alt' ) 125 | expect (d).to.have.property ('lib/other') 126 | 127 | expect(d['lib/alt']).to.be.equal ( oldRecord['lib/alt']) 128 | expect(d['lib/other']).to.be.equal ( record['other'] ) 129 | }) // it Insert data as a library. Overwriting is forbidden 130 | 131 | 132 | 133 | it ( 'Rename data-record', () => { 134 | const 135 | tplEngine = new CodeAssemblyLine() 136 | , record = { 'alt' : 'image description is missing'} 137 | ; 138 | 139 | tplEngine.insertData ( record ) 140 | tplEngine.renameData ( {'alt':'test'} ) 141 | 142 | expect ( tplEngine.data ).to.not.have.property ( 'alt' ) 143 | expect ( tplEngine.data ).to.have.property ( 'test' ) 144 | }) // it Rename data-record 145 | 146 | 147 | 148 | it ( 'Rename a non-existing data-record', () => { 149 | const 150 | tplEngine = new CodeAssemblyLine() 151 | , record = { 'alt' : 'record text' } 152 | ; 153 | 154 | tplEngine.insertData ( record ) 155 | tplEngine.renameData ( {'alt':'test', 'missing' : 'fake' } ) 156 | 157 | const d = tplEngine.data; 158 | expect (d).to.not.have.property ( 'missing' ) 159 | expect (d).to.not.have.property ( 'fake' ) 160 | expect (d).to.not.have.property ( 'alt' ) 161 | 162 | expect (d).to.have.property ( 'test') 163 | }) // it rename a non-existing data-record 164 | 165 | 166 | it ( 'Rename is not allowed', () => { 167 | const 168 | tplEngine = new CodeAssemblyLine() 169 | , record = { 170 | 'one' : 'first record' 171 | , 'two' : 'second record' 172 | } 173 | ; 174 | 175 | tplEngine.insertData ( record ) 176 | tplEngine.renameData ( {'two':'one'} ) 177 | 178 | const d = tplEngine.data; 179 | expect (d).to.have.property ( 'one' ) 180 | expect (d).to.have.property ( 'two' ) 181 | 182 | expect ( d.one ).to.be.equal ( record.one ) 183 | expect ( d.two ).to.be.equal ( record.two ) 184 | }) // it rename is not allowed 185 | 186 | 187 | 188 | it ( 'Remove data-record', () => { 189 | const 190 | tplEngine = new CodeAssemblyLine() 191 | , record = { 'alt' : 'image description is missing'} 192 | ; 193 | 194 | tplEngine.insertData ( record ) 195 | tplEngine.removeData ( 'alt' ) 196 | 197 | expect ( tplEngine.data ).to.not.have.property ( 'alt' ) 198 | }) // it Remove data-record 199 | 200 | 201 | 202 | it ( 'Remove data-records presented as array', () => { 203 | const 204 | tplEngine = new CodeAssemblyLine() 205 | , record = { 206 | 'one' : 'record one' 207 | , 'two' : 'record two' 208 | , 'three' : 'record three' 209 | } 210 | ; 211 | 212 | tplEngine.insertData ( record ) 213 | tplEngine.removeData ( ['one'] ) 214 | 215 | const d = tplEngine.data; 216 | expect (d).to.not.have.property ( 'one' ) 217 | expect (d).to.have.property ( 'two' ) 218 | expect (d).to.have.property ( 'three' ) 219 | }) // it Remove data-record presented as array 220 | 221 | 222 | 223 | it ( 'Get missing block', () => { 224 | const 225 | tplEngine = new CodeAssemblyLine() 226 | , template = { 'dummy' : 'image description is missing'} 227 | , pr = [ 228 | { do:'draw', tpl: 'dummy' } 229 | , { do: 'save', as: 'block', name: 'dummy' } 230 | ] 231 | ; 232 | 233 | tplEngine.insertTemplate ( template ) 234 | tplEngine.insertProcess ( pr, 'test' ) 235 | tplEngine.run ( 'test' ) 236 | 237 | const result = tplEngine.getBlock ( 'missing' ) 238 | expect ( result ).to.be.equal ( '' ) 239 | }) // it Get missing block 240 | 241 | 242 | 243 | it ( 'Get block with no arguments', () => { 244 | const 245 | tplEngine = new CodeAssemblyLine() 246 | , template = { 'dummy' : 'image description is missing'} 247 | , testProcess = [ 248 | { do:'draw', tpl: 'dummy' } 249 | , { do: 'save', as: 'block', name: 'dummy' } 250 | ] 251 | ; 252 | 253 | tplEngine 254 | .insertTemplate ( template ) 255 | .insertProcess ( testProcess, 'test' ) 256 | .run ( 'test' ) 257 | 258 | const result = tplEngine.getBlock ( ) 259 | expect ( result ).to.be.equal ( '' ) 260 | }) // it Get block with no arguments 261 | 262 | 263 | 264 | it ( 'Get single block', () => { 265 | const 266 | tplEngine = new CodeAssemblyLine() 267 | , template = { 'dummy' : 'image description is missing'} 268 | , pr = [ 269 | { do:'draw', tpl: 'dummy' } 270 | , { do: 'save', as: 'block', name: 'dummy' } 271 | ] 272 | ; 273 | 274 | tplEngine.insertTemplate ( template ) 275 | tplEngine.insertProcess ( pr, 'test' ) 276 | tplEngine.run ( 'test' ) 277 | 278 | const result = tplEngine.getBlock ( 'dummy' ) 279 | expect ( result ).to.be.equal ( template.dummy ) 280 | }) // it Get single block 281 | 282 | 283 | 284 | it ( 'Get blocks in order', () => { 285 | const 286 | tplEngine = new CodeAssemblyLine () 287 | , templates = { 288 | 'one' : 'First sentence.' 289 | , 'two' : 'Second sentence.' 290 | } 291 | , pr1 = [ 292 | { do:'draw', tpl: 'one' } 293 | , { do: 'save', as: 'block', name: 'first' } 294 | ] 295 | , pr2 = [ 296 | { do:'draw', tpl: 'two' } 297 | , { do: 'save', as: 'block', name: 'second' } 298 | ] 299 | 300 | tplEngine 301 | .insertTemplate ( templates ) 302 | .insertProcess ( pr1, 'doOne') 303 | .insertProcess ( pr2, 'doTwo') 304 | .run ( [ 'doTwo' , 'doOne'] ) 305 | 306 | const result = tplEngine.getBlock ( 'first', 'second' ) 307 | 308 | expect ( result ).to.be.a ( 'string' ) 309 | expect ( result ).to.be.equal ( templates.one + templates.two ) 310 | }) // it Get blocks in order 311 | 312 | 313 | 314 | it ( 'Get blocks in order as array', () => { 315 | const 316 | tplEngine = new CodeAssemblyLine () 317 | , templates = { 318 | 'one' : 'First sentence.' 319 | , 'two' : 'Second sentence.' 320 | } 321 | , pr1 = [ 322 | { do:'draw', tpl: 'one' } 323 | , { do: 'save', as: 'block', name: 'first' } 324 | ] 325 | , pr2 = [ 326 | { do:'draw', tpl: 'two' } 327 | , { do: 'save', as: 'block', name: 'second' } 328 | ] 329 | 330 | tplEngine 331 | .insertTemplate ( templates ) 332 | .insertProcess ( pr1, 'doOne') 333 | .insertProcess ( pr2, 'doTwo') 334 | .run ( [ 'doTwo' , 'doOne'] ) 335 | 336 | const result = tplEngine.getBlock ( ['first', 'second'] ) 337 | 338 | expect ( result ).to.be.a ( 'string' ) 339 | expect ( result ).to.be.equal ( templates.one + templates.two ) 340 | }) // it Get blocks in order as array 341 | 342 | 343 | 344 | it ( 'Try to insert function in data' , () => { 345 | const 346 | tplEngine = new CodeAssemblyLine() 347 | , data = { 348 | 'a' : 'Some string information' 349 | , 'fx' : function () { console.log('Hey, I am in!') } 350 | , 'b' : 12 351 | } 352 | ; 353 | 354 | tplEngine.insertData ( data ) 355 | 356 | const d = tplEngine.data; 357 | expect (d).to.have.property('a') 358 | expect (d).to.have.property('b') 359 | expect (d).to.not.have.property('fx') 360 | }) // it Try to insert function in data 361 | 362 | 363 | 364 | }) // describe Tools 365 | 366 | 367 | -------------------------------------------------------------------------------- /dist/code-assembly-line.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("CodeAssemblyLine",[],t):"object"==typeof exports?exports.CodeAssemblyLine=t():e.CodeAssemblyLine=t()}("undefined"!=typeof self?self:this,function(){return function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var r={};return t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(e,t,r){"use strict";var n={notDefined:"Not defined error.",brokenTemplate:"Broken string template. Placeholder with missing closing tag.",wrongDataTemplate:"Broken data template. Object is not according the standards.",overwriteTemplate:"Error: Overwrite of existing template is not permited by configuration.",missingTemplate:'Error: Template "%s" doesn\'t exist.',wrongExtProcess:'Wrong process data. Should be an array of "processSteps".',emptyExtProcess:"Empty process! Process should contain steps(processSteps)",brokenProcess:"Broken process description.",missingOperation:'Process has step with missing operation. Set parameter "do".',overwriteProcess:'Error: Process with name "%s" is already defined.',notaValidOperation:'Error: "%s" is not a valid operation',blockExpectString:"Block expects string data. %s",dataExpectObject:"Data operations require objects. %s",overwriteData:'Error: Data with name "%s" is already defined.',processNotExists:'Error: Process "%s" does not exist.',templateNotExists:'Error: Template "%s" is not available',invalidStorageName:'Error: Process-step "save" has param "as: %s". Param "as" should be "data", "template" or "process". ',invalidJSON:'Error: Invalid JSON format was provided for saving in "processes": %s'},o=function(e,t){var r=n[e]||n.notDefined;return t&&(t instanceof Array||(t=[t]),t.forEach(function(e){return r=r.replace("%s",e)})),r};e.exports=o},function(e,t,r){"use strict";function n(e){var t=this;this.templates={},this.processes={},this.data={},this.config={},Object.keys(l).forEach(function(e){return t.config[e]=l[e]}),e&&Object.keys(e).forEach(function(r){return t.config[r]=e[r]})}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},a=r(2),i=r(4),s=r(6),c=r(0),u=a.str2intTemplate,f=a.load_interpretTemplate(u),l={overwriteTemplates:!1,overwriteProcesses:!1,overwriteData:!1,htmlAttributes:["id","name","href","src","value","data","alt","role","class"]},p={_isWritingForbidden:function(e,t,r){var n=void 0;return n="templates"==t?"overwriteTemplates":"processes"==t?"overwriteProcesses":"overwriteData",e[t].hasOwnProperty(r)&&!e.config[n]},_normalizeExternalData:function(e){return e instanceof Array?e.map(function(e){return"object"==(void 0===e?"undefined":o(e))?p._flatten(e):e}):[e]},_validateProcess:function(e,t){var r=[],n=e.processes.hasOwnProperty(t),o=e.processes[t];return n&&o.errors&&(r=r.concat(o.errors)),n?o.arguments.reduce(function(e,t){return"draw"==t.do&&t.tpl&&e.push(t.tpl),e},[]).forEach(function(t){var n=e.templates[t];n&&n.errors&&(r=r.concat(n.errors)),n||r.push(c("templateNotExists",t))}):r.push(c("processNotExists",t)),r},_validateProcessLib:function(e){try{var t=JSON.parse(e);return Object.keys(t).reduce(function(e,r){return t[r]instanceof Array&&(e[r]=[].concat(t[r])),e},{})}catch(e){return!1}},_extractLib:function(e,t){var r=Object.keys(e).filter(function(e){return e.includes("/")});return t.reduce(function(t,n){return r.filter(function(e){return 0==e.indexOf(n)}).forEach(function(r){var n=r.indexOf("/"),o=r.slice(n+1);t[o]=e[r].tpl.join("")}),t},{})},_flatten:function(e,t,r){var n=t||{};r=r||"";for(var o in e){var a=e[o],i=o;if(r&&(i=r+"/"+o),"function"!=typeof a){if(!p._isPrimitive(a))return p._flatten(a,n,i);n[i]=a}}return n},_isPrimitive:function(e){return"object"!==(void 0===e?"undefined":o(e))}},d={insert:function(e){var t=this;return Object.keys(e).forEach(function(r){if(p._isWritingForbidden(t,"templates",r))return void console.error(c("overwriteTemplate"));t.templates[r]=f(e[r])}),t},insertLib:function(e,t){var r=this;return Object.keys(e).forEach(function(n){var o={},a=t+"/"+n;if(p._isWritingForbidden(r,"templates",a))return void console.error(c("overwriteTemplate"));o[a]=e[n],d.insert.call(r,o)}),r},rename:function(e){var t=this,r=Object.keys(e),n=t.config.overwriteTemplates;return r.forEach(function(r){if(t.templates[r]){var o=e[r];if(!!t.templates[o]&&!n)return void console.error(c("overwriteTemplate"));t.templates[o]=t.templates[r],delete t.templates[r]}}),t},remove:function(e){var t=arguments,r=this,n=void 0;if(e instanceof Array)n=e;else{n=Object.keys(arguments).map(function(e){return t[e]})}return n.forEach(function(e){return delete r.templates[e]}),r},get:function(e){var t=arguments,r=this,n=r.templates,o=void 0;if(e instanceof Array)o=e;else{o=Object.keys(arguments).map(function(e){return t[e]})}return o.reduce(function(e,t){return n[t]?e[t]=n[t].tpl.join(""):e[t]="",e},{})},getLib:function(e){var t=arguments,r=this,n=void 0;if(e instanceof Array)n=e;else{n=Object.keys(arguments).map(function(e){return t[e]})}return p._extractLib(r.templates,n)},getPlaceholders:function(e){var t=this.templates[e];return t?Object.keys(t.placeholders):[]}},v={insert:function(e,t){var r=this;return p._isWritingForbidden(r,"processes",t)?void console.error(c("overwriteProcess",t)):(r.processes[t]=i.interpret(e),r)},insertLib:function(e,t){var r=this,n=p._validateProcessLib(e),o=Object.keys(n);return n&&o.forEach(function(e){var o=t?t+"/"+e:e;v.insert.call(r,n[e],o)}),r},mix:function(e,t){var r=this,n=r.processes,o={};return p._isWritingForbidden(r,"processes",t)?void console.error(c("overwriteProcess",t)):(o.steps=[],o.arguments=[],o.hooks=[],e.forEach(function(e){if(n[e]){var t=n[e],r=t.hooks.map(function(t){return e+"/"+t}),a=t.arguments.map(function(t){return"hook"!=t.do?t:{do:"hook",name:e+"/"+t.name}});o.steps=o.steps.concat(t.steps),o.arguments=o.arguments.concat(a),o.hooks=o.hooks.concat(r)}}),r.processes[t]=o,r)},getLib:function(e){var t=this,r=Object.keys(t.processes),n=!e,o=r.reduce(function(r,o){if(n)r[o]=t.processes[o];else if(o.includes(e)){var a=o.split("/");a.shift();var i=a.join("/");r[i]=t.processes[o]}return r},{});return JSON.stringify(o)},rename:function(e){var t=this,r=Object.keys(e),n=t.config.overwriteProcesses;return r.forEach(function(r){if(t.processes[r]){var o=e[r];if(!!t.processes[o]&&!n)return void console.error(c("overwriteProcess",o));t.processes[o]=t.processes[r],delete t.processes[r]}}),t},remove:function(e){var t=arguments,r=this,n=void 0;if(e instanceof Array)n=e;else{n=Object.keys(arguments).map(function(e){return t[e]})}return n.forEach(function(e){return delete r.processes[e]}),r},getHooks:function(e){if(this.processes.hasOwnProperty(e))return this.processes[e].hooks.reduce(function(e,t){return e[t]=void 0,e},{});return{}},run:function(e,t,r){var n=this;e instanceof Array||(e=[e]);var o=p._normalizeExternalData(t),a=e.reduce(function(e,t){var r=p._validateProcess(n,t);return e.concat(r)},[]);if(0==a.length){var s=o;return e.forEach(function(e){s=i.run.call(n,n.processes[e],s,r)}),s}return a}},m={insert:function(e){var t=this,r=p._flatten(e);return Object.keys(r).forEach(function(e){if(p._isWritingForbidden(t,"data",e))return void console.error(c("overwriteData",e));t.data[e]=r[e]}),t},insertLib:function(e,t){var r=this,n=p._flatten(e);return Object.keys(n).forEach(function(e){var o=t+"/"+e;if(p._isWritingForbidden(r,"data",o))return void console.error(c("overwriteData",o));r.data[o]=n[e]}),r},rename:function(e){var t=this,r=Object.keys(e),n=t.config.overwriteData;return r.forEach(function(r){if(t.data[r]){var o=e[r];if(!!t.data[o]&&!n)return void console.error(c("overwriteData",o));t.data[o]=t.data[r],delete t.data[r]}}),t},remove:function(e){var t=arguments,r=this,n=void 0;if(e instanceof Array)n=e;else{n=Object.keys(arguments).map(function(e){return t[e]})}return n.forEach(function(e){return delete r.data[e]}),r},getBlock:function(e){var t=arguments,r=this,n=[];if(e instanceof Array)n=e;else{n=Object.keys(arguments).map(function(e){return t[e]})}return n.reduce(function(e,t){return e+=r.data["block/"+t]?r.data["block/"+t]:""},"")}};n.prototype={tools:s,insertTemplate:d.insert,insertTemplateLib:d.insertLib,getTemplate:d.get,getTemplateLib:d.getLib,getPlaceholders:d.getPlaceholders,renameTemplate:d.rename,removeTemplate:d.remove,insertProcess:v.insert,insertProcessLib:v.insertLib,mixProcess:v.mix,getProcessLib:v.getLib,getHooks:v.getHooks,run:v.run,renameProcess:v.rename,removeProcess:v.remove,insertData:m.insert,insertDataLib:m.insertLib,getBlock:m.getBlock,getBlocks:m.getBlock,renameData:m.rename,removeData:m.remove},e.exports=n},function(e,t,r){"use strict";var n=r(3),o=r(0),a={str2intTemplate:function(e){var t=n(e),r=t.includes(o("brokenTemplate")),i={},s={};if(!r){var c=a._findPlaceholdersAndSpaces(t);s=c.spaces,i=c.placeholders}var u={tpl:t,placeholders:i,spaces:s};return r&&(u.errors=[o("brokenTemplate")]),u},load_interpretTemplate:function(e){return function(t){return"string"==("string"==typeof t?"string":"error")?e(t):{errors:[o("wrongDataTemplate")]}}},_findPlaceholdersAndSpaces:function(e){var t={},r={};return e.forEach(function(e,n){if("{{"==e.slice(0,2)){var o=2,a=-2,i="~~"==e.slice(2,4),s="~~"==e.slice(-4,-2);i&&(o=4),s&&(a=-4);var c=e.slice(o,a);i&&s?r[c]=3:s?r[c]=2:i&&(r[c]=1),t.hasOwnProperty(c)?t[c].push(n):t[c]=[n]}}),{spaces:r,placeholders:t}}};e.exports=a},function(e,t,r){"use strict";function n(e){var t=void 0,r=void 0,a=void 0,i=[];if("string"!=typeof e)return[o("brokenTemplate")];if(0==e.length)return[];if(t=e.indexOf("{{"),00?(t.errors=r,t):(e.forEach(function(e){t.steps.push(e.do),t.arguments.push(e),"hook"==e.do&&t.hooks.push(e.name),e.hook&&t.hooks.push(e.hook)}),t)},_validate:function(e){var t=[];return e instanceof Array?0==e.length?[n("emptyExtProcess")]:(e.forEach(function(e){var r=!1;if(!e.do)return void t.push(n("missingOperation"));(r=a.includes(e.do))||t.push(n("notaValidOperation",[e.do]))}),t):[n("wrongExtProcess")]},_findIfString:function(e){return"string"==typeof e[0]},_copyList:function(e){for(var t=e.length,r=[];t--;)r[t]=e[t];return r},_parse:function(e){try{return JSON.parse(e)}catch(e){return!1}},run:function(e,t,r){var a=this,s=a.templates,c=t,u=i._findIfString(c),f={};return e.steps.forEach(function(t,l){var p=e.arguments[l],d=void 0;switch(t){case"draw":d=p.tpl,u&&console.warn(n("dataExpectObject",'Step "draw" with template "'+d+'"'));var v=p.missField||!1,m=p.missData||!1,h=!!r&&(r[p.hook]||!1),b=s[d].tpl,y=s[d].spaces,g=s[d].placeholders,k=!(null==p.as),_={};_.tpl=b,_.placeholders=f[d]||g,_.spaces=y;var O=o[t]({template:_,data:c,sharedData:a.data,htmlAttributes:a.config.htmlAttributes,missField:v,missData:m,hookFn:h});k?c=c.reduce(function(e,t,r){return t[p.as]=O[r],e.push(t),e},[]):(c=O,u=!0);break;case"block":if(!u)return void console.error(n("blockExpectString",JSON.stringify(c)));var E=p.space||"";if(c=o[t](c,E),p.name){var j={};j["block/"+p.name]=c.join(""),a.insertData(j)}break;case"alterTemplate":d=p.tpl;var w=s[d];f[d]=o[t](p,w.placeholders);break;case"set":c=o[t](p,c),u=!1;break;case"hook":var x=o[t](c,r[p.name]);c=x instanceof Array?x:[x],u=i._findIfString(c);break;case"save":var P="block"!=p.as?p.name:"block/"+p.name,S={};switch(p.as){case"block":case"data":S[P]=c.join(""),a.insertData(S);break;case"template":S[P]=c[0],a.insertTemplate(S);break;case"process":var T=i._parse(c[0]);T?a.insertProcess(T,P):console.error(n("invalidJSON",c[0]));break;default:return void console.error(n("invalidStorageName",p.as))}}}),c}};e.exports=i},function(e,t,r){"use strict";var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},o={draw:function(e){var t=e.template,r=e.data,n=e.sharedData,a=e.htmlAttributes,i=e.missField,s=e.missData,c=e.hookFn,u=[];return r.forEach(function(e,r){var f=e?Object.keys(e):[],l=o._copyList(t.tpl),p=Object.assign({},t.placeholders),d=t.spaces,v="",m="";if(p._attr){var h=p._attr[0];v=d._attr&&d._attr%2?" ":"",m=d._attr&&d._attr>1?" ":"";var b=o._createAttributes(e,a).trim();l[h]=b?""+v+b+m:"",delete p._attr}if(p._count){var y=p._count;v=d._count&&d._count%2?" ":"",m=d._count&&d._count>1?" ":"",y.forEach(function(e){return l[e]=""+v+(r+1)+m}),delete p._count}f.forEach(function(t){var r=p[t];if(v=d[t]&&d[t]%2?" ":"",m=d[t]&&d[t]>1?" ":"",r){var n=!0,o=!1,a=void 0;try{for(var i,s=r[Symbol.iterator]();!(n=(i=s.next()).done);n=!0){var c=i.value;l[c]=""+v+e[t]+m}}catch(e){o=!0,a=e}finally{try{!n&&s.return&&s.return()}finally{if(o)throw a}}delete p[t]}});var g=Object.keys(p),k=g.length>0;if(k&&n){var _=!0,O=!1,E=void 0;try{for(var j,w=g[Symbol.iterator]();!(_=(j=w.next()).done);_=!0){var x=j.value;!function(e){if(n[e]){var t=p[e];v=d[e]&&d[e]%2?" ":"",m=d[e]&&d[e]>1?" ":"",t.forEach(function(t){return l[t]=""+v+n[e]+m}),delete p[e]}}(x)}}catch(e){O=!0,E=e}finally{try{!_&&w.return&&w.return()}finally{if(O)throw E}}}if(g=Object.keys(p),k=g.length>0,k&&i&&function(){var e=void 0;switch("_fn"==i&&"function"!=typeof c&&(i="_hide"),i){case"_fn":e=c;break;case"_hide":e=function(){return""};break;case"_position":e=function(e){return e};break;default:e=function(){return i}}var t=!0,r=!1,n=void 0;try{for(var o,a=g[Symbol.iterator]();!(t=(o=a.next()).done);t=!0){var s=o.value;!function(t){p[t].forEach(function(r){return l[r]=e(t)})}(s)}}catch(e){r=!0,n=e}finally{try{!t&&a.return&&a.return()}finally{if(r)throw n}}}(),k&&s){var P=void 0;switch("_fn"==s&&"function"!=typeof c&&(s="_hide"),s){case"_fn":P=function(e){return[c(e)]};break;case"_hide":P=function(){return[]};break;default:P=function(){return[s]}}l=P(g)}l.length>0&&u.push(l.join(""))}),u},_createAttributes:function(e,t){var r={},n=new RegExp("^data-"),o="";for(var a in e)"name"!==a&&("id"!==a?"className"!==a?t.includes(a)?r[a]=a+'="'+e[a]+'"':n.test(a)&&(r[a]=a+'="'+e[a]+'"',o+=" "+r[a]):r.class='class="'+e[a]+'"':(r.id='id="'+e[a]+'"',r.name='name="'+e[a]+'"'));return t.reduce(function(e,t){return r[t]&&(e+=" "+r[t]),"data"==t&&(e+=" "+o),e},"")},_copyList:function(e){for(var t=e.length,r=[];t--;)r[t]=e[t];return r},alterTemplate:function(e,t){var r=e.data,n=Object.keys(t),o={};return n.forEach(function(e){var n=r[e]||e;o[n]=t[e]}),o},block:function(e,t){return[e.join(t)]},set:function(e,t){var r=e.as;return t.reduce(function(e,t){var n={};return n[r]=t,e.push(n),e},[])},alter:function(e,t){var r=e.data,n=Object.keys(r),a=o._normalizeSelection(e.select,t.length),i=t.map(function(e){return Object.assign({},e)});return a.forEach(function(e){var t=i[e],o=Object.keys(t),a={};o.forEach(function(e){var o=t[e];n.includes(e)&&(e=r[e]),a[e]=o}),i[e]=a}),i},_normalizeSelection:function(e,t){var r=[];return e?("object"!=(void 0===e?"undefined":n(e))&&(e=[e]),e.forEach(function(e){if("all"==e)return r=o._generateList(t);switch(e){case"first":r.push(0);break;case"last":r.push(t-1);break;default:e<0?r.push(t-1+e):r.push(e-1)}}),r):r=o._generateList(t)},_generateList:function(e){for(var t=[],r=0;r string[] 21 | // * Render template. 22 | /* 23 | Params: 24 | template:{intTemplate}. Selected template for this draw operation; 25 | data:{any}[]. Current data; 26 | sharedData:{any}. Engine data object; 27 | htmlAttributes: string[]. Render '_attr' will consider this list of items and their order; 28 | missField?:string. Strategy for fulfill missing data-fields; 29 | missData?:string. Strategy for substitute data-record on missing data-fields; 30 | hookFn: (placeholderName:string) -> renderResult:string. Result will be added to render result array; 31 | 32 | MissField argument could be a string but also has two predefined options: 33 | '_position': will render placeholder name; 34 | '_hide' : will not render anything; 35 | '_fn' : hook function will take care about missing fields; 36 | 37 | MissData argument could be a string or some of predefined options: 38 | '_hide': will not render anything from this data-record 39 | '_fn' : hook function will take care about the record; 40 | 41 | */ 42 | let 43 | { template, data, sharedData, htmlAttributes, missField, missData, hookFn } = params 44 | , result = [] 45 | ; 46 | 47 | data.forEach ( (obj, _count) => { 48 | const keys = obj ? Object.keys(obj) : []; 49 | let 50 | tpl = lib._copyList ( template.tpl ) 51 | , places = Object.assign ( {}, template.placeholders ) 52 | , spaces = template.spaces 53 | , spaceBefore = '' 54 | , spaceAfter = '' 55 | ; 56 | 57 | if ( places['_attr'] ) { // calculate html attributes only if '_attr' placeholder exists in template 58 | const attrTarget = places['_attr'][0] 59 | spaceBefore = ( spaces['_attr'] && spaces['_attr']%2 ) ? ' ' : '' 60 | spaceAfter = ( spaces['_attr'] && spaces['_attr']>1 ) ? ' ' : '' 61 | const attr = lib._createAttributes(obj, htmlAttributes ).trim() 62 | if ( attr ) tpl[attrTarget] = `${spaceBefore}${attr}${spaceAfter}` // generate attributes string 63 | else tpl[attrTarget] = '' 64 | delete places['_attr'] 65 | } 66 | 67 | if ( places['_count'] ) { // replace _count if '_count' placeholder exists in template 68 | const countTarget = places['_count']; 69 | spaceBefore = ( spaces['_count'] && spaces['_count']%2 ) ? ' ' : '' 70 | spaceAfter = ( spaces['_count'] && spaces['_count']>1 ) ? ' ' : '' 71 | countTarget.forEach ( position => tpl[position] = `${spaceBefore}${_count + 1}${spaceAfter}` ) 72 | delete places['_count'] 73 | } 74 | 75 | keys.forEach ( k => { 76 | const positions = places[k]; 77 | spaceBefore = ( spaces[k] && spaces[k]%2 ) ? ' ' : '' 78 | spaceAfter = ( spaces[k] && spaces[k]>1 ) ? ' ' : '' 79 | if ( positions ) { 80 | for (let position of positions ) tpl[position] = `${spaceBefore}${obj[k]}${spaceAfter}` 81 | delete places[k] 82 | } 83 | }) 84 | 85 | let neglected = Object.keys ( places ) 86 | , someNeglected = ( neglected.length > 0 ) 87 | ; 88 | 89 | if ( someNeglected && sharedData ) { // find values in sharedData 90 | for ( let placeholder of neglected ) { 91 | if ( sharedData[placeholder] ) { 92 | const list = places [placeholder] 93 | spaceBefore = ( spaces[placeholder] && spaces[placeholder]%2 ) ? ' ' : '' 94 | spaceAfter = ( spaces[placeholder] && spaces[placeholder]>1 ) ? ' ' : '' 95 | list.forEach ( id => tpl[id] = `${spaceBefore}${sharedData[placeholder]}${spaceAfter}` ) 96 | delete places[placeholder] 97 | } 98 | } 99 | } // if someNeglected 100 | 101 | neglected = Object.keys ( places ) 102 | someNeglected = ( neglected.length > 0 ) 103 | if ( someNeglected && missField ) { // miss field strategy 104 | let missFieldUpdate; 105 | if ( missField == '_fn' && typeof(hookFn) != 'function' ) missField = '_hide' 106 | switch ( missField ) { 107 | case '_fn' : 108 | missFieldUpdate = hookFn 109 | break 110 | case '_hide' : 111 | missFieldUpdate = () => '' 112 | break 113 | case '_position' : 114 | missFieldUpdate = (pos) => pos 115 | break 116 | default : 117 | missFieldUpdate = () => missField 118 | } // switch missField 119 | for ( let position of neglected ) { 120 | const list = places[position] 121 | list.forEach ( el => tpl[el] = missFieldUpdate(position) ) 122 | } 123 | } // if missField 124 | 125 | if ( someNeglected && missData ) { // miss data strategy 126 | let missDataUpdate; 127 | if ( missData =='_fn' && typeof(hookFn) != 'function' ) missData = '_hide' 128 | switch ( missData ) { 129 | case '_fn': 130 | missDataUpdate = (x) => [hookFn(x)] 131 | break 132 | case '_hide': 133 | missDataUpdate = () => [] 134 | break 135 | default: 136 | missDataUpdate = () => [ missData ] 137 | } // switch missData 138 | tpl = missDataUpdate(neglected) 139 | } // if missData 140 | 141 | if ( tpl.length > 0 ) result.push ( tpl.join('') ) 142 | }) 143 | return result 144 | } // draw func. 145 | 146 | 147 | 148 | 149 | 150 | , _createAttributes ( data, attributes ) { // ({}, string[]) -> string 151 | // *** 'attr' placeholder calculation 152 | const 153 | attr = {} 154 | , dataAttr = new RegExp('^data-') 155 | ; 156 | let dataItems = ''; 157 | 158 | for ( let item in data ) { 159 | if ( item === 'name' ) continue // Ignore 'name' 160 | if ( item === 'id' ) { 161 | attr['id'] = `id="${data[item]}"` 162 | attr['name'] = `name="${data[item]}"` 163 | continue 164 | } 165 | if ( item === 'className' ) { 166 | attr['class'] = `class="${data[item]}"` 167 | continue 168 | } 169 | if ( attributes.includes (item) ) { 170 | attr[item] = `${item}="${data[item]}"` 171 | continue 172 | } 173 | if ( dataAttr.test (item) ) { 174 | attr[item] = `${item}="${data[item]}"` 175 | dataItems += ` ${attr[item]}` 176 | } 177 | } 178 | 179 | return attributes.reduce ( (res,item) => { 180 | if ( attr[item] ) res += ` ${attr[item]}` 181 | if ( item == 'data' ) res += ` ${dataItems}` 182 | return res 183 | },'') 184 | } // _createAttributes func. 185 | 186 | 187 | 188 | 189 | 190 | , _copyList ( source ) { // (string[]) -> string[] 191 | let 192 | size = source.length 193 | , result = [] 194 | ; 195 | while (size--) result[size] = source[size] 196 | return result 197 | } // _copyList func. 198 | 199 | 200 | 201 | , alterTemplate ( step, sourcePlaceholders ) { // (step{}, internalTpl.placeholers) -> internalTpl.placeholders 202 | // * Rename of placeholders 203 | const 204 | changes = step.data // { oldPlaceholderName : newPlaceholderName } 205 | , keysPh = Object.keys ( sourcePlaceholders ) 206 | , placeholders = {}; 207 | ; 208 | 209 | keysPh.forEach ( key => { 210 | const k = changes[key] || key; 211 | placeholders[k] = sourcePlaceholders[key] 212 | }) 213 | 214 | return placeholders 215 | } // alterTemplate func. 216 | 217 | 218 | 219 | , block ( data, space ) { // ( string[] ) -> string[] 220 | // * Concatinate strings from array. Returns new array with a single entry 221 | return [ data.join( space ) ] 222 | } // block func. 223 | 224 | 225 | 226 | , set ( step, data) { // ( step{}, string[] ) -> {}[] 227 | // * Converts renders and blocks in object 228 | const 229 | name = step.as 230 | , result = data.reduce ( (res,item) => { 231 | let obj = {}; 232 | obj[name] = item; 233 | res.push ( obj ) 234 | return res 235 | },[]) 236 | return result; 237 | } // set func. 238 | 239 | 240 | 241 | , alter ( step, data ) { // ( step{}, {}[] ) -> {}[] 242 | // * Change property names. 243 | const 244 | changes = step.data 245 | , keys = Object.keys ( changes ) 246 | , forUpdate = lib._normalizeSelection ( step.select, data.length ) 247 | ; 248 | let result = data.map ( dataRecord => Object.assign ({}, dataRecord) ); 249 | forUpdate.forEach ( el => { 250 | const selected = result[el] 251 | const dataKeys = Object.keys(selected) 252 | let update = {} 253 | dataKeys.forEach ( k => { 254 | let value = selected[k] 255 | if ( keys.includes(k) ) k = changes[k] 256 | update[k] = value 257 | }) 258 | result[el] = update 259 | }) 260 | return result 261 | } // alter func. 262 | 263 | 264 | 265 | 266 | , _normalizeSelection ( list, length ) { 267 | let result = []; 268 | if ( !list ) { 269 | result = lib._generateList ( length ) 270 | return result 271 | } 272 | // if ( typeof(list) == 'string' ) list = [ list ] 273 | if ( typeof(list) != 'object' ) list = [ list ] 274 | 275 | list.forEach ( el => { 276 | if ( el == 'all') { 277 | result = lib._generateList(length) 278 | return result 279 | } 280 | switch ( el ) { 281 | case 'first': 282 | result.push(0) 283 | break; 284 | case 'last' : 285 | result.push(length-1) 286 | break 287 | default : 288 | if (el<0) result.push(length-1+el) 289 | else result.push(el-1) 290 | } 291 | }) 292 | return result 293 | } // _normalizeSelection func. 294 | 295 | 296 | 297 | , _generateList ( size ) { 298 | let result = []; 299 | for ( let i=0; i < size; i++ ) result.push(i) 300 | return result 301 | } // _generateList func. 302 | 303 | 304 | 305 | , add ( step, data ) { // ( step{}, {}[] ) -> {}[] 306 | // * Add property names 307 | const 308 | changes = step.data 309 | , keys = Object.keys ( changes ) 310 | , listForUpdate = lib._normalizeSelection ( step.select, data.length ) 311 | ; 312 | let result = data.map ( dataRecord => Object.assign ({}, dataRecord) ); 313 | listForUpdate.forEach ( el => { 314 | const selected = result[el] 315 | const dataKeys = Object.keys(selected) 316 | let update = keys.reduce ( (res,k) => { 317 | if ( !selected[k] ) res[k] = changes[k] 318 | else res[k] = lib._combineValues (selected[k], changes[k]) 319 | return res 320 | }, {}) 321 | result[el] = Object.assign( selected, update ) 322 | }) 323 | return result 324 | } // add func. 325 | 326 | 327 | 328 | , _combineValues ( existing, update ) { 329 | let 330 | primitive = false 331 | , result 332 | ; 333 | 334 | if ( typeof existing != 'object' ) primitive = true 335 | if ( primitive ) result = [ existing ] 336 | else result = existing 337 | 338 | return result.concat ( update ).join ( ' ' ) 339 | } // _combineValues func. 340 | 341 | 342 | 343 | , copy ( step, data ) { // ( step{}, {}[] ) -> {}[] 344 | // * Create new property copy the value. 345 | const 346 | changes = step.data 347 | , keys = Object.keys ( changes ) 348 | , forUpdate = lib._normalizeSelection ( step.select, data.length ) 349 | , result = data.map ( record => Object.assign ({},record) ) 350 | ; 351 | 352 | forUpdate.forEach ( dataRecord => { 353 | keys.forEach ( k => { 354 | const value = result[dataRecord][k] 355 | const copyKey = changes[k] 356 | result[dataRecord][copyKey] = value 357 | }) 358 | }) 359 | 360 | return result 361 | } // copy func. 362 | 363 | 364 | 365 | , remove ( step, data ) { // ( step{}, {}[] ) -> {}[] 366 | // * Remove existing property 367 | const 368 | keys = step.keys 369 | , forUpdate = lib._normalizeSelection ( step.select, data.length ) 370 | , result = data.map ( record => Object.assign ({}, record) ) 371 | ; 372 | 373 | forUpdate.forEach ( dataRecord => { 374 | keys.forEach ( k => { 375 | delete result[dataRecord][k] 376 | }) 377 | }) 378 | return result 379 | } // remove func. 380 | 381 | 382 | 383 | , hook ( data, cb ) { // ( {}[], Function ) -> {}[] 384 | // * Function placeholder within render process 385 | if ( !cb ) return data 386 | return cb ( data , lib.modify ) 387 | } // hook func. 388 | 389 | 390 | 391 | , _findIfString : ( list ) => (typeof(list[0]) == 'string') 392 | 393 | 394 | 395 | , modify ( data, step ) { // ( {}[], step{} ) -> {}[] 396 | // * Access step-operations inside hook callbacks. 397 | let 398 | act = step.do 399 | , dataIsStr = lib._findIfString ( data ) 400 | ; 401 | 402 | if ( dataIsStr ) { 403 | console.error (`Data operations require objects but have strings. Data: ${data}`) 404 | return data 405 | } 406 | return lib[act] ( step, data ) 407 | } // modify func. 408 | 409 | } // lib 410 | 411 | 412 | 413 | module.exports = lib 414 | 415 | 416 | -------------------------------------------------------------------------------- /test/11_code-assembly.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , CodeAssemblyLine = require ( '../src/index' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | describe ( 'Code Assembly', () => { 12 | 13 | it ( 'Start Code Assembly', () => { 14 | const tplEngine = new CodeAssemblyLine (); 15 | 16 | expect ( tplEngine ).have.property ( 'templates' ) 17 | expect ( tplEngine ).have.property ( 'processes' ) 18 | expect ( tplEngine ).have.property ( 'data' ) 19 | }) // it start code Assembly 20 | 21 | 22 | 23 | it ( 'Insert Template Library', () => { 24 | const 25 | tplEngine = new CodeAssemblyLine () 26 | , tplLib = { 27 | 'hello' : 'Hello {{user}}' 28 | , 'bye' : 'Good bye {{user}}' 29 | } 30 | , tplLib2 = { 31 | 'hello' : 'Hi {{user}}' 32 | , 'welcome' : 'Welcome {{user}}' 33 | } 34 | ; 35 | 36 | tplEngine.insertTemplateLib ( tplLib, 'custom' ) 37 | tplEngine.insertTemplateLib ( tplLib2, 'custom' ) 38 | 39 | expect ( tplEngine.templates ).to.have.property ('custom/hello' ) 40 | expect ( tplEngine.templates ).to.have.property ('custom/bye' ) 41 | expect ( tplEngine.templates ).to.have.property ('custom/welcome' ) 42 | const tpl = tplEngine.getTemplate ( 'custom/hello' ); 43 | expect ( tpl['custom/hello'] ).to.be.equal ( tplLib.hello ) 44 | }) // it insert template library 45 | 46 | 47 | 48 | it ( 'Get Template', () => { 49 | const 50 | tplEngine = new CodeAssemblyLine () 51 | , tpl = { 52 | 'hello' : 'Hello {{user}}' 53 | , 'bye' : 'Good bye {{user}}' 54 | } 55 | ; 56 | 57 | tplEngine.insertTemplate ( tpl ) 58 | const 59 | single = tplEngine.getTemplate ( 'hello' ) 60 | , multiple = tplEngine.getTemplate ( 'hello', 'bye', 'dummy' ) 61 | , altMultiple = tplEngine.getTemplate (['hello', 'bye']) 62 | ; 63 | 64 | expect ( single ).to.have.property ( 'hello' ) 65 | expect ( single.hello ).to.be.a ( 'string' ) 66 | expect ( single.hello ).to.be.equal ( tpl.hello ) 67 | 68 | expect ( multiple ).to.have.property ( 'hello' ) 69 | expect ( multiple ).to.have.property ( 'bye' ) 70 | expect ( multiple ).to.have.property ( 'dummy' ) 71 | expect ( multiple.hello ).to.be.a ( 'string' ) 72 | expect ( multiple.bye ).to.be.a ( 'string' ) 73 | expect ( multiple.dummy ).to.be.empty 74 | 75 | expect ( multiple.hello ).to.be.equal ( tpl.hello ) 76 | expect ( multiple.bye ).to.be.equal ( tpl.bye ) 77 | 78 | expect ( altMultiple.hello ).to.be.equal ( multiple.hello ) 79 | expect ( altMultiple.bye ).to.be.equal ( multiple.bye ) 80 | }) // it getTemplate 81 | 82 | 83 | 84 | it ( 'Get Template Library' , () => { 85 | const 86 | tplEngine = new CodeAssemblyLine () 87 | , tpl = { 'hi': 'Hi {{user}}' } 88 | , tplLib = { 89 | 'hello' : 'Hello {{user}}' 90 | , 'bye' : 'Good bye {{user}}' 91 | } 92 | ; 93 | 94 | tplEngine.insertTemplate ( tpl ) 95 | tplEngine.insertTemplateLib ( tplLib, 'test' ) 96 | 97 | const 98 | result = tplEngine.getTemplateLib ('test') 99 | , alt = tplEngine.getTemplateLib (['test']) 100 | ; 101 | 102 | expect ( result ).to.have.property ( 'hello' ) 103 | expect ( result ).to.have.property ( 'bye' ) 104 | expect ( result ).to.not.have.property ( 'hi' ) 105 | expect ( result.hello ).to.be.equal ( tplLib.hello ) 106 | expect ( result.bye ).to.be.equal ( tplLib.bye ) 107 | 108 | expect ( alt ).to.have.property ( 'hello' ) 109 | expect ( alt ).to.have.property ( 'bye' ) 110 | }) // it getTemplateLib 111 | 112 | 113 | 114 | it ( 'Get Placeholders', () => { 115 | const 116 | tplEngine = new CodeAssemblyLine () 117 | , tpl = { 118 | 'hello': 'Hello {{user}}. Welcome to {{place}}' 119 | } 120 | ; 121 | 122 | tplEngine.insertTemplate ( tpl ) 123 | const result = tplEngine.getPlaceholders ( 'hello' ) 124 | const emptyRes = tplEngine.getPlaceholders ( 'fail' ) 125 | 126 | expect ( result ).to.be.an ( 'array' ) 127 | expect ( result ).to.includes ( 'user' ) 128 | expect ( result ).to.includes ( 'place' ) 129 | 130 | expect ( emptyRes ).to.be.an ( 'array' ) 131 | expect ( emptyRes ).to.be.empty 132 | }) // it getPlaceholders 133 | 134 | 135 | 136 | it ( 'Template rename normal', () => { 137 | const 138 | tplEngine = new CodeAssemblyLine () 139 | , tpl = { 140 | 'hello' : 'Hello {{user}}' 141 | , 'bye' : 'Good bye {{user}}' 142 | } 143 | , instructions = { 'hello':'sayHello', 'bye': 'sayGoodbye' } 144 | ; 145 | 146 | tplEngine.insertTemplate ( tpl ) 147 | tplEngine.renameTemplate ( instructions ) 148 | 149 | expect ( tplEngine.templates).to.have.property ( 'sayHello' ) 150 | expect ( tplEngine.templates).to.have.property ( 'sayGoodbye' ) 151 | expect ( tplEngine.templates).to.not.have.property ( 'hello' ) 152 | expect ( tplEngine.templates).to.not.have.property ( 'bye' ) 153 | }) // it template rename normal 154 | 155 | 156 | 157 | it ( 'Template rename with overwrite', () => { 158 | const 159 | tplEngine = new CodeAssemblyLine () 160 | , tpl = { 161 | 'hello' : 'Hello {{user}}' 162 | , 'bye' : 'Good bye {{user}}' 163 | , 'sayHello' : 'Should stay' 164 | } 165 | , instructions = { 'hello':'sayHello', 'bye': 'sayGoodbye'} 166 | ; 167 | 168 | tplEngine.insertTemplate ( tpl ) 169 | tplEngine.renameTemplate ( instructions ) 170 | 171 | expect ( tplEngine.templates).to.have.property ( 'hello' ) 172 | expect ( tplEngine.templates).to.have.property ( 'sayHello' ) 173 | expect ( tplEngine.templates).to.have.property ( 'sayGoodbye' ) 174 | expect ( tplEngine.templates).to.not.have.property ( 'bye' ) 175 | }) // it template rename with overwrite 176 | 177 | 178 | 179 | it ( 'Rename a non existing template', () => { 180 | const 181 | tplEngine = new CodeAssemblyLine () 182 | , t = { 'bye' : 'Good bye {{user}}' } 183 | ; 184 | 185 | tplEngine 186 | .insertTemplate ( t ) 187 | .renameTemplate ( { 'fake': 'hello', 'bye' : 'goodBye'} ) 188 | 189 | const tpl = tplEngine.templates; 190 | expect ( tpl ).to.not.have.property ( 'hello' ) 191 | expect ( tpl ).to.not.have.property ( 'fake' ) 192 | expect ( tpl ).to.not.have.property ( 'bye' ) 193 | expect ( tpl ).to.have.property ( 'goodBye' ) 194 | }) // it rename a non existing template 195 | 196 | 197 | 198 | it ( 'Remove Template', () => { 199 | const 200 | tplEngine = new CodeAssemblyLine () 201 | , tpl = { 202 | 'hello' : 'Hello {{user}}' 203 | , 'bye' : 'Good bye {{user}}' 204 | } 205 | 206 | tplEngine.insertTemplate ( tpl ) 207 | tplEngine.removeTemplate ( 'hello' ) 208 | 209 | expect ( tplEngine.templates ).have.property('bye') 210 | expect ( tplEngine.templates ).not.have.property('hello') 211 | }) // it remove template 212 | 213 | 214 | 215 | it ( 'Remove list of templates with array ', () => { 216 | const 217 | tplEngine = new CodeAssemblyLine () 218 | , tpl = { 219 | 'hello' : 'Hello {{user}}' 220 | , 'bye' : 'Good bye {{user}}' 221 | } 222 | 223 | tplEngine.insertTemplate ( tpl ) 224 | tplEngine.removeTemplate ( ['hello'] ) 225 | 226 | expect ( tplEngine.templates ).have.property('bye') 227 | expect ( tplEngine.templates ).not.have.property('hello') 228 | }) // it remove list of templates with array 229 | 230 | 231 | 232 | it ( 'Insert Process', () => { 233 | const 234 | tplEngine = new CodeAssemblyLine () 235 | , process = [ { do: 'draw' , tpl: 'some'} ] 236 | , altProcess = [ {do:'draw', tpl: 'alt' } ] 237 | ; 238 | 239 | tplEngine.insertProcess ( process, 'test' ) 240 | tplEngine.insertProcess ( altProcess, 'test' ) 241 | 242 | expect ( tplEngine.processes ).to.have.property ('test') 243 | expect ( tplEngine.processes['test'] ).to.have.property ('steps' ) 244 | expect ( tplEngine.processes['test'] ).to.have.property ('arguments') 245 | expect ( tplEngine.processes['test'] ).to.have.property ('hooks' ) 246 | expect ( tplEngine.processes['test']['arguments'][0]['tpl']).to.be.equal('some') 247 | expect ( tplEngine.processes['test']['arguments'][0]['tpl']).to.not.be.equal('alt') 248 | }) // it insert process 249 | 250 | 251 | 252 | it ( 'Insert Process Lib', () => { 253 | const 254 | tplEngine = new CodeAssemblyLine () 255 | , processLib = JSON.stringify ({ 256 | one : [{ do: 'draw' , tpl: 'some'}] 257 | , two : [{do:'draw', tpl: 'alt' }] 258 | , broken : {do:'draw', tpl: 'broken' } 259 | }) 260 | ; 261 | 262 | tplEngine.insertProcessLib ( processLib , 'ext' ) 263 | const pr = tplEngine.processes; 264 | expect ( pr ).to.have.property ('ext/one') 265 | expect ( pr ).to.have.property ( 'ext/two' ) 266 | expect ( pr ).to.not.have.property ( 'two' ) 267 | expect ( pr ).to.not.have.property ( 'ext/broken' ) 268 | 269 | // insert lib without specify libName 270 | tplEngine.insertProcessLib ( processLib ) 271 | expect ( pr ).to.have.property ( 'two' ) 272 | expect ( pr ).to.not.have.property ( 'broken' ) 273 | 274 | // Try to insert a non valid JSON 275 | tplEngine.insertProcessLib ( 'fakeLib', 'fake') 276 | const count = Object.keys ( pr ) 277 | expect ( count.length ).to.be.equal ( 4 ) 278 | }) // it insert process lib 279 | 280 | 281 | 282 | it ( 'Get process library', () => { 283 | const 284 | tplEngine = new CodeAssemblyLine() 285 | , first = [{do: 'draw', tpl: 'first'}] 286 | , second = [{do: 'draw', tpl: 'second'}] 287 | , third = [{do:'draw', tpl: 'third'}] 288 | ; 289 | 290 | tplEngine 291 | .insertProcess ( first, 'first') 292 | .insertProcess ( second, 'test/second') 293 | .insertProcess ( third, 'test/third') 294 | 295 | const jsonResult = tplEngine.getProcessLib ( 'test' ); 296 | const r = JSON.parse ( jsonResult ); 297 | 298 | expect ( r ).to.have.property ( 'second' ) 299 | expect ( r ).to.have.property ( 'third' ) 300 | expect ( r ).to.not.have.property ( 'first' ) 301 | 302 | const jsonEverything = tplEngine.getProcessLib ( ); 303 | const allR = JSON.parse ( jsonEverything ); 304 | 305 | expect ( allR ).to.have.property ( 'first' ) 306 | expect ( allR ).to.have.property ( 'test/second' ) 307 | expect ( allR ).to.have.property ( 'test/third' ) 308 | }) // get process library 309 | 310 | 311 | it ( 'Mix Process', () => { 312 | const 313 | tplEngine = new CodeAssemblyLine () 314 | , liProcess = [ 315 | { do: 'draw', tpl: 'link'} 316 | , { do: 'set', as: 'text'} 317 | , { do: 'draw' , tpl: 'li' } 318 | , { do: 'hook' , name: 'navButtons'} 319 | ] 320 | , ulProcess = [ {do:'draw', tpl: 'ul'} ] 321 | , ulTransition = [ 322 | { do: 'set', as: 'text'} 323 | , { do: 'block' } 324 | ] 325 | ; 326 | 327 | tplEngine.insertProcess ( liProcess, 'li') 328 | tplEngine.insertProcess ( ulProcess, 'ul') 329 | tplEngine.insertProcess ( ulTransition, 'ulTransition') 330 | tplEngine.mixProcess ( ['li', 'ulTransition', 'ul','fake'], 'navigation' ) 331 | tplEngine.mixProcess ( ['li','fail'],'navigation' ) 332 | const res = tplEngine.processes['navigation']; 333 | 334 | expect ( res['steps'] ).to.have.length (7) 335 | expect ( res['arguments'] ).to.have.length (7) 336 | expect ( res['hooks'] ).to.have.length (1) 337 | expect ( res['hooks'][0]).to.be.equal ( 'li/navButtons' ) 338 | expect ( res['arguments'][3]['name']).to.be.equal ( 'li/navButtons' ) 339 | 340 | expect ( tplEngine.processes['li']['hooks'][0] ).to.be.equal('navButtons') 341 | }) // it mix process 342 | 343 | 344 | 345 | it ( 'Rename Process', () => { 346 | const 347 | tplEngine = new CodeAssemblyLine() 348 | , action = [ { do: 'draw', tpl:'random'} ] 349 | , willStay = [ {do:'draw', tpl:'nothing'} ] 350 | ; 351 | tplEngine 352 | .insertProcess ( action, 'action' ) 353 | .insertProcess ( willStay, 'willStay' ) 354 | .renameProcess ({ 355 | 'action' : 'activity' 356 | , 'empty' : 'fake' 357 | , 'willStay' : 'activity' 358 | }) 359 | const pr = tplEngine.processes; 360 | 361 | expect ( pr ).to.have.property ( 'activity' ) 362 | expect ( pr ).to.not.have.property ( 'action' ) 363 | expect ( pr ).to.not.have.property ( 'fake' ) 364 | expect ( pr ).to.not.have.property ( 'empty' ) 365 | }) // it rename process 366 | 367 | 368 | 369 | it ( 'Remove Process', () => { 370 | const 371 | tplEngine = new CodeAssemblyLine() 372 | , action = [ { do: 'draw', tpl:'random'} ] 373 | , willStay = [ {do:'draw', tpl:'nothing'} ] 374 | ; 375 | tplEngine 376 | .insertProcess ( action, 'action' ) 377 | .insertProcess ( action, 'duplicate' ) 378 | .insertProcess ( action, 'again' ) 379 | .insertProcess ( willStay, 'willStay' ) 380 | .removeProcess ( 'action' ) 381 | .removeProcess ( [ 'duplicate', 'again'] ) 382 | 383 | const pr = tplEngine.processes; 384 | expect ( pr ).to.have.property ( 'willStay' ) 385 | expect ( pr ).to.not.have.property ( 'action' ) 386 | expect ( pr ).to.not.have.property ( 'duplicate' ) 387 | expect ( pr ).to.not.have.property ( 'again' ) 388 | }) // it remove process 389 | 390 | 391 | 392 | it ( 'Get existing hooks', () => { 393 | const 394 | tplEngine = new CodeAssemblyLine() 395 | , templateLib = { random: 'Some text with {{place}}.' } 396 | , processData = [ 397 | { do: 'draw', tpl: 'random' } 398 | , { do: 'hook', name: 'testingHook' } 399 | ] 400 | , renderData = [{ place: 'test string'}] 401 | ; 402 | 403 | tplEngine.insertProcess ( processData, 'test') 404 | tplEngine.insertTemplate ( templateLib ) 405 | 406 | const result = tplEngine.getHooks ( 'test' ) 407 | 408 | expect ( result ).to.have.property ( 'testingHook' ) 409 | expect ( result['testingHook'] ).to.be.undefined 410 | }) // it get existing hooks 411 | 412 | 413 | 414 | it ( 'Get non existing hooks', () => { 415 | const 416 | tplEngine = new CodeAssemblyLine() 417 | , templateLib = { random: 'Some text with {{place}}.' } 418 | , processData = [ 419 | { do: 'draw', tpl: 'random' } 420 | ] 421 | , renderData = [{ place: 'test string'}] 422 | ; 423 | 424 | tplEngine.insertProcess ( processData, 'test') 425 | tplEngine.insertTemplate ( templateLib ) 426 | 427 | const result = tplEngine.getHooks ( 'test' ) 428 | 429 | expect ( result ).to.be.empty 430 | }) // it get existing hooks 431 | 432 | 433 | 434 | it ( 'Get hooks of non existing process', () => { 435 | const 436 | tplEngine = new CodeAssemblyLine() 437 | , templateLib = { random: 'Some text with {{place}}.' } 438 | , processData = [ 439 | { do: 'draw', tpl: 'random' } 440 | ] 441 | , renderData = [{ place: 'test string'}] 442 | ; 443 | 444 | tplEngine.insertProcess ( processData, 'test') 445 | tplEngine.insertTemplate ( templateLib ) 446 | 447 | const result = tplEngine.getHooks ( 'fail' ) 448 | 449 | expect ( result ).to.be.empty 450 | }) // it get hooks of non existing process 451 | 452 | }) // describe Run 453 | 454 | 455 | -------------------------------------------------------------------------------- /test/08_process-operations.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , processOps = require ( '../src/process-operations' ) 7 | , errors = require ( '../src/errors' ) 8 | ; 9 | 10 | describe ( 'Process Operations', () => { 11 | 12 | it ( 'Draw: One placeholder', () => { 13 | const 14 | template = { 15 | tpl : [ 'Hello, ', '{{name}}', '.' ] 16 | , placeholders: { name: [1] } 17 | , spaces: {} 18 | } 19 | , data = [ { name : 'Peter' }, {name: 'Ivan'} ] 20 | ; 21 | const result = processOps.draw ( {template, data} ) 22 | expect ( result ).to.be.an ( 'array' ) 23 | expect ( result ).to.have.length (2) 24 | expect ( result[0] ).to.be.equal ( 'Hello, Peter.') 25 | expect ( result[1] ).to.be.equal ( 'Hello, Ivan.' ) 26 | }) // it Draw: one placeholder 27 | 28 | 29 | 30 | it ( 'Draw: Two placeholder', () => { 31 | const 32 | template = { 33 | tpl : [ '{{name1}}', ' and ', '{{name2}}', ' are working together.' ] 34 | , placeholders: { name1: [0], name2: [2] } 35 | , spaces: {} 36 | } 37 | , data = [ { name1: 'Peter', name2: 'Ivan'} ] 38 | ; 39 | 40 | const result = processOps.draw ( {template, data} ) 41 | expect ( result ).to.be.an ( 'array' ) 42 | expect ( result ).to.have.length (1) 43 | expect ( result[0] ).to.be.equal ( 'Peter and Ivan are working together.') 44 | }) // it Draw: one placeholder 45 | 46 | 47 | 48 | it ( 'Draw: Multiple placeholders', () => { 49 | const 50 | template = { 51 | tpl : [ '{{name}}', ' and ', '{{name}}', ' product ', '{{what}}' ] 52 | , placeholders: { name: [0,2], what:[4] } 53 | , spaces: {} 54 | } 55 | , data = [ { name: 'Johnson', what: 'list'} ] 56 | ; 57 | 58 | const result = processOps.draw ( {template, data} ) 59 | expect ( result ).to.be.an ( 'array' ) 60 | expect ( result ).to.have.length (1) 61 | expect ( result[0] ).to.be.equal ( 'Johnson and Johnson product list') 62 | }) // it Draw: Multiple placeholders 63 | 64 | 65 | 66 | it ( 'Draw: data > placeholders', () => { 67 | const 68 | template = { 69 | tpl : [ 'Happy Birthday to my friend ', '{{name}}', '!'] 70 | , placeholders: { name: [1] } 71 | , spaces: {} 72 | } 73 | , data = [ { name: 'Peter', age: '43'} ] 74 | ; 75 | 76 | const result = processOps.draw ( {template, data} ) 77 | expect ( result ).to.have.length (1) 78 | expect ( result[0] ).to.be.equal ( 'Happy Birthday to my friend Peter!') 79 | }) // it Draw: data > placeholders 80 | 81 | 82 | 83 | it ( 'Draw: missField strategy "_hide"', () => { 84 | const 85 | template = { 86 | tpl : [ 'Hey ', '{{name}}', '!'] 87 | , placeholders: { name: [1] } 88 | , spaces: {} 89 | } 90 | , data = [ { myName: 'Peter', age: '43'} ] 91 | ; 92 | 93 | const result = processOps.draw ( { template, data, missField:'_hide' }) 94 | expect ( result ).to.have.length (1) 95 | expect ( result[0] ).to.be.equal ( 'Hey !') 96 | }) // it Draw: missField strategy _hide 97 | 98 | 99 | 100 | it ( 'Draw: missField strategy "_position"', () => { 101 | const 102 | template = { 103 | tpl : [ 'Hey ', '{{name}}', '!'] 104 | , placeholders: { name: [1] } 105 | , spaces: {} 106 | } 107 | , data = [ { myName: 'Peter', age: '43'} ] 108 | ; 109 | 110 | const result = processOps.draw ( { template, data, missField: '_position'} ) 111 | expect ( result ).to.have.length (1) 112 | expect ( result[0] ).to.be.equal ( 'Hey name!') 113 | }) // it Draw: missField strategy _position 114 | 115 | 116 | 117 | it ( 'Draw: missField error strategy ', () => { 118 | const 119 | template = { 120 | tpl : [ 'Hey ', '{{name}}', '!'] 121 | , placeholders: { name: [1] } 122 | , spaces: {} 123 | } 124 | , data = [ { myName: 'Peter', age: '43'} ] 125 | ; 126 | 127 | const result = processOps.draw ( { template, data, missField:'{error: No data}'} ) 128 | expect ( result ).to.have.length (1) 129 | expect ( result[0] ).to.be.equal ( 'Hey {error: No data}!') 130 | }) // it Draw: missField error strategy 131 | 132 | 133 | 134 | it ( 'Draw: missData strategy "_hide"', () => { 135 | const 136 | template = { 137 | tpl : [ 'Hey ', '{{name}}', '!'] 138 | , placeholders: { name: [1] } 139 | , spaces : {} 140 | } 141 | , data = [ 142 | { myName: 'Peter', age: '43'} 143 | , { name: 'Ivan', age: '41'} 144 | , { name: 'Ivo', age: '19'} 145 | ] 146 | ; 147 | 148 | const result = processOps.draw ( { template, data, missData: '_hide'} ) 149 | expect ( result ).to.have.length (2) 150 | expect ( result[0] ).to.be.equal ( 'Hey Ivan!') 151 | expect ( result[1] ).to.be.equal ( 'Hey Ivo!' ) 152 | }) // it Draw: missData strategy "hide" 153 | 154 | 155 | 156 | it ( 'Draw: missData error strategy', () => { 157 | const 158 | template = { 159 | tpl : [ 'Hey ', '{{name}}', '!'] 160 | , placeholders: { name: [1] } 161 | , spaces : {} 162 | } 163 | , data = [ 164 | { myName: 'Peter', age: '43'} 165 | , { name: 'Ivan', age: '41'} 166 | , { name: 'Ivo', age: '19'} 167 | ] 168 | ; 169 | 170 | const result = processOps.draw ({ template, data, missData: 'Error: Missing data' }) 171 | expect ( result ).to.have.length (3) 172 | expect ( result[0] ).to.be.equal ( 'Error: Missing data') 173 | expect ( result[1] ).to.be.equal ( 'Hey Ivan!') 174 | expect ( result[2] ).to.be.equal ( 'Hey Ivo!' ) 175 | }) // it Draw: missData error strategy 176 | 177 | 178 | 179 | it ( 'Alter Template', () => { 180 | const 181 | step = { do: 'alterTemplate', data: { 'name':'user', 'age': 'alt'} } 182 | , sourcePlaceholders = { name : 1, fixed: 'will stay' } 183 | ; 184 | const result = processOps.alterTemplate ( step, sourcePlaceholders ) 185 | expect ( sourcePlaceholders ).to.have.property ( 'name' ) 186 | expect ( sourcePlaceholders ).to.not.have.property ( 'user' ) 187 | expect ( sourcePlaceholders ).to.not.have.property ( 'age' ) 188 | expect ( sourcePlaceholders ).to.not.have.property ( 'alt' ) 189 | 190 | expect ( result ).to.have.property ( 'user' ) 191 | expect ( result ).to.not.have.property ( 'name' ) 192 | expect ( result ).to.not.have.property ( 'age' ) 193 | expect ( result ).to.not.have.property ( 'alt' ) 194 | }) // it alter template 195 | 196 | 197 | 198 | it ( 'Block', () => { 199 | const 200 | data = [ 201 | 'Hey Ivan!' 202 | ,'Hey Ivo!' 203 | ] 204 | , space = ' ' 205 | ; 206 | const result = processOps.block ( data, space ) 207 | expect ( result ).to.be.an ('array') 208 | expect ( result ).to.have.length (1) 209 | expect ( result[0] ).to.be.equal ( 'Hey Ivan! Hey Ivo!') 210 | }) // it Block 211 | 212 | 213 | 214 | it ( 'Set', () => { 215 | const 216 | data = [ 217 | 'Hey Ivan!' 218 | ,'Hey Ivo!' 219 | ] 220 | , step = { do:'set', as: 'text' } 221 | ; 222 | 223 | const result = processOps.set ( step, data ) 224 | expect ( result ).to.be.an ( 'array' ) 225 | expect ( result ).to.have.length (2) 226 | expect ( result[0] ).to.be.an ('object') 227 | expect ( result[0] ).to.have.property('text') 228 | expect ( result[0]['text'] ).to.be.equal ('Hey Ivan!') 229 | }) // it Set 230 | 231 | 232 | 233 | it ( 'Alter', () => { 234 | const 235 | step = { do: 'alter', data: {'user':'text'} } 236 | , data = [ 237 | { user: 'Peter', age: 43 } 238 | , { user: 'Ivo', age: 19 } 239 | ] 240 | 241 | const result = processOps.alter ( step, data ) 242 | expect ( result ).to.be.an('array') 243 | expect ( result ).to.have.length (2) 244 | expect ( result[0] ).to.have.property('text') 245 | expect ( result[0]['text'] ).to.be.equal ( 'Peter' ) 246 | expect ( result[0] ).to.have.property('age') 247 | expect ( result[0] ).to.not.have.property('user') 248 | }) // it alter 249 | 250 | 251 | 252 | it ( 'Alter with selection "all"', () => { 253 | const 254 | step = { do: 'alter', select:'all', data: {'user':'text'} } 255 | , data = [ 256 | { user: 'Peter', age: 43 } 257 | , { user: 'Ivo', age: 19 } 258 | ] 259 | 260 | const result = processOps.alter ( step, data ) 261 | expect ( result ).to.be.an('array') 262 | expect ( result ).to.have.length (2) 263 | expect ( result[0] ).to.have.property('text') 264 | expect ( result[0]['text'] ).to.be.equal ( 'Peter' ) 265 | expect ( result[0] ).to.have.property('age') 266 | expect ( result[0] ).to.not.have.property('user') 267 | }) // it alter with selection "all" 268 | 269 | 270 | 271 | it ( 'Alter with selection "first"', () => { 272 | const 273 | step = { do: 'alter', select:'first', data: {'user':'text'} } 274 | , data = [ 275 | { user: 'Peter', age: 43 } 276 | , { user: 'Ivo', age: 19 } 277 | ] 278 | 279 | const result = processOps.alter ( step, data ) 280 | expect ( result ).to.be.an('array') 281 | expect ( result ).to.have.length (2) 282 | expect ( result[0] ).to.have.property('text') 283 | expect ( result[0]['text'] ).to.be.equal ( 'Peter' ) 284 | expect ( result[0] ).to.have.property('age') 285 | expect ( result[0] ).to.not.have.property('user') 286 | expect ( result[1] ).to.have.property ('user') 287 | expect ( result[1] ).to.not.have.property ('text') 288 | }) // it alter with selection first 289 | 290 | 291 | 292 | it ( 'Alter with selection "last"', () => { 293 | const 294 | step = { do: 'alter', select:'last', data: {'user':'text'} } 295 | , data = [ 296 | { user: 'Peter', age: 43 } 297 | , { user: 'Ivo', age: 19 } 298 | ] 299 | 300 | const result = processOps.alter ( step, data ) 301 | expect ( result ).to.be.an('array') 302 | expect ( result ).to.have.length (2) 303 | expect ( result[1] ).to.have.property('text') 304 | expect ( result[1]['text'] ).to.be.equal ( 'Ivo' ) 305 | expect ( result[1] ).to.have.property('age') 306 | expect ( result[1] ).to.not.have.property('user') 307 | expect ( result[0] ).to.have.property ('user') 308 | expect ( result[0] ).to.not.have.property ('text') 309 | }) // it alter with selection last 310 | 311 | 312 | 313 | it ( 'Alter with selection -1', () => { 314 | const 315 | step = { do: 'alter', select:-1, data: {'user':'text'} } 316 | , data = [ 317 | { user: 'Peter', age: 43 } 318 | , { user: 'Ivo', age: 19 } 319 | ] 320 | 321 | const result = processOps.alter ( step, data ) 322 | expect ( result ).to.be.an('array') 323 | expect ( result ).to.have.length (2) 324 | expect ( result[0] ).to.have.property('text') 325 | expect ( result[0]['text'] ).to.be.equal ( 'Peter' ) 326 | expect ( result[0] ).to.have.property('age') 327 | expect ( result[0] ).to.not.have.property('user') 328 | expect ( result[1] ).to.have.property ('user') 329 | expect ( result[1] ).to.not.have.property ('text') 330 | }) // it alter with selection -1 331 | 332 | 333 | 334 | it ( 'Alter with selection 1', () => { 335 | const 336 | step = { do: 'alter', select:1, data: {'user':'text'} } 337 | , data = [ 338 | { user: 'Peter', age: 43 } 339 | , { user: 'Ivo', age: 19 } 340 | ] 341 | ; 342 | 343 | const result = processOps.alter ( step, data ) 344 | expect ( result ).to.be.an('array') 345 | expect ( result ).to.have.length (2) 346 | expect ( result[0] ).to.have.property('text') 347 | expect ( result[0]['text'] ).to.be.equal ( 'Peter' ) 348 | expect ( result[0] ).to.have.property('age') 349 | expect ( result[0] ).to.not.have.property('user') 350 | expect ( result[1] ).to.have.property ('user') 351 | expect ( result[1] ).to.not.have.property ('text') 352 | }) // it alter with selection 1 353 | 354 | 355 | 356 | it ( 'Add', () => { 357 | const 358 | step = { do: 'add', select:['first',2], data: {'className': 'simple'} } 359 | , data = [ 360 | { user: 'Peter' , age: 43, className: 'test' } 361 | , { user: 'Stefan', age: 42, className: ['test','second'] } 362 | , { user: 'Ivo' , age: 19, className: 'test' } 363 | ] 364 | ; 365 | const result = processOps.add ( step, data ); 366 | expect ( result ).to.have.length (3) 367 | expect ( result[0]['className'] ).to.be.an ( 'string' ) 368 | expect ( result[0]['className'] ).to.be.equal ( 'test simple' ) 369 | expect ( result[1]['className'] ).to.be.an ( 'string' ) 370 | expect ( result[1]['className'] ).to.be.equal ( 'test second simple' ) 371 | expect ( result[2]['className'] ).to.be.a ( 'string' ) 372 | expect ( result[2]['className'] ).to.be.equal ( 'test' ) 373 | }) // it add 374 | 375 | 376 | 377 | it ( 'Copy', () => { 378 | const 379 | step = { do: 'copy', select:['first','last'], data: {'className': 'alt'} } 380 | , data = [ 381 | { user: 'Peter', age: 43, className: ['test','first'] } 382 | , { user: 'Ivo', age: 19, className: 'test' } 383 | , { user: 'Ivan', age: 39, className: 'test' } 384 | ] 385 | ; 386 | 387 | const result = processOps.copy ( step, data ) 388 | expect ( result ).to.have.length (3) 389 | expect ( result[0] ).to.have.property ( 'alt' ) 390 | expect ( result[0]['alt']).to.be.an ( 'array' ) 391 | expect ( result[0]['alt']).to.includes ( 'test' ) 392 | expect ( result[0]['alt']).to.includes ( 'first' ) 393 | expect ( result[1] ).to.not.have.property ( 'alt') 394 | expect ( result[2] ).to.have.property ( 'alt') 395 | expect ( result[2]['alt'] ).to.be.a ('string') 396 | expect ( result[2]['alt'] ).to.be.equal ('test') 397 | }) // it copy 398 | 399 | 400 | 401 | it ( 'Remove', () => { 402 | const 403 | step = { do: 'remove', select:['first','last'], keys: ['className', 'age', 'alt'] } 404 | , data = [ 405 | { user: 'Peter', age: 43, className: ['test','first'] } 406 | , { user: 'Ivo', age: 19, className: 'test' } 407 | , { user: 'Ivan', age: 39, className: 'test' } 408 | ] 409 | ; 410 | 411 | const result = processOps.remove ( step, data ) 412 | expect ( result ).to.have.length (3) 413 | expect ( result[0] ).to.not.have.property ( 'alt' ) 414 | expect ( result[0] ).to.not.have.property ( 'className' ) 415 | expect ( result[0] ).to.not.have.property ( 'age' ) 416 | expect ( result[1] ).to.have.property ( 'className') 417 | expect ( result[2] ).to.not.have.property ( 'alt') 418 | expect ( result[2] ).to.not.have.property ( 'age') 419 | expect ( result[2] ).to.not.have.property ( 'className') 420 | }) // it remove 421 | 422 | 423 | 424 | it ( 'Hook. Function defined', () => { 425 | const data = [{ user: 'Peter', age:43}]; 426 | const fn = data => [{ user: 'Ivo', age:21}]; 427 | 428 | const result = processOps.hook ( data, fn ) 429 | expect ( result[0].user ).to.be.equal ('Ivo') 430 | expect ( result[0].age ).to.be.equal (21) 431 | }) // it hook 432 | 433 | 434 | 435 | it ( 'Hook. Use step-operation to modify hook data', () => { 436 | // * Get more details in 'Hook Modifiers' chapter 437 | const 438 | data = [{ user: 'Peter', age: 43 }] 439 | , fn = (data, modify) => modify ( data, { do:'alter', data: {'user':'name'}} ) 440 | ; 441 | 442 | const result = processOps.hook ( data, fn ) 443 | expect ( result[0] ).to.not.have.property ( 'user' ) 444 | expect ( result[0].name ).to.be.equal ( 'Peter' ) 445 | expect ( result[0].age ).to.be.equal (43) 446 | }) // it hook 447 | 448 | 449 | 450 | it ( 'Hook. Function is not defined', () => { 451 | const 452 | data = [{ user: 'Peter', age:43}] 453 | , hook = {} 454 | ; 455 | 456 | 457 | const result = processOps.hook ( data, hook.test ) 458 | expect ( result).is.an ( 'array' ) 459 | expect ( result ).has.length(1) 460 | expect ( result[0] ).has.property ( 'user' ) 461 | expect ( result[0] ).has.property ( 'age' ) 462 | expect ( result[0].user ).is.equal ( 'Peter' ) 463 | }) // it hook, no function 464 | 465 | 466 | it ( 'Calculate "attr"', () => { 467 | const 468 | createAttributes = processOps._createAttributes 469 | , attributes = [ 'id', 'name', 'href', 'src', 'value', 'data-test', 'alt', 'role', 'class' ] 470 | , altAttributes = [ 'id', 'data', 'class'] 471 | , data = { 472 | 'href' : 'test.com/anypage.html' 473 | , 'text' : 'Link to nowhere' 474 | , 'className' : 'fashion' 475 | , 'data-test' : 'yo' 476 | , 'data-again': 'hey' 477 | , 'id' : 'top' 478 | , 'name' : 'Ivan' 479 | } 480 | ; 481 | 482 | const result = createAttributes ( data, attributes ); 483 | expect ( result ).to.be.a ( 'string' ) 484 | expect ( result ).to.not.include ( 'text' ) 485 | expect ( result ).to.include ( 'data-test' ) 486 | expect ( result ).to.not.include ( 'data-again' ) 487 | 488 | const altResult = createAttributes ( data, altAttributes ); 489 | expect ( altResult ).to.be.a ( 'string' ) 490 | expect ( altResult ).to.not.include ( 'text' ) 491 | expect ( altResult ).to.include ( 'data-test' ) 492 | expect ( altResult ).to.include ( 'data-again' ) 493 | expect ( altResult ).to.include ( 'class="fashion"' ) 494 | 495 | const 496 | placeId = result.indexOf('id') 497 | , placeHref = result.indexOf('href="') 498 | , placeClass = result.indexOf ('class="fashion"') 499 | ; 500 | 501 | expect ( placeClass, "Class attribute is missing" ).to.not.be.equal(-1) 502 | expect ( null, 'Attribute "id" should be first' ).to.satisfies ( () => placeId < placeHref ) 503 | expect ( null, 'Attribute "href" should be before "class"').to.satisfies ( () => placeHref < placeClass ) 504 | }) // it calculate attr 505 | 506 | }) // describe -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | Code Assembly Line 5 | - started at 23 september 2017 6 | */ 7 | 8 | const 9 | templateTools = require ( './template-tools' ) 10 | , processTools = require ( './process-tools' ) 11 | , tools   = require ( './general-tools' ) 12 | , showError = require ( './errors' ) 13 | ; 14 | 15 | const str2intTemplate = templateTools.str2intTemplate; 16 | const interpretTemplate = templateTools.load_interpretTemplate ( str2intTemplate ); 17 | 18 | 19 | // Default config object applied to every codeAssembly object. 20 | const codeAssemblyConfig = { 21 | overwriteTemplates : false 22 | , overwriteProcesses : false 23 | , overwriteData : false 24 | , htmlAttributes : [ 'id', 'name', 'href', 'src', 'value', 'data', 'alt', 'role', 'class' ] 25 | } // config 26 | 27 | 28 | 29 | function codeAssembly ( cfg ) { 30 | this.templates = {} 31 | this.processes = {} 32 | this.data = {} 33 | this.config = {} 34 | 35 | Object.keys(codeAssemblyConfig).forEach ( k => this.config[k] = codeAssemblyConfig[k] ) 36 | if ( cfg ) Object.keys(cfg).forEach ( k => this.config[k] = cfg[k] ) 37 | } // codeAssembly func. 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | const help = { 58 | 59 | _isWritingForbidden ( engine , type, name ) { 60 | let configProperty; 61 | if ( type == 'templates' ) configProperty = 'overwriteTemplates' 62 | else if ( type == 'processes' ) configProperty = 'overwriteProcesses' 63 | else configProperty = 'overwriteData' 64 | const entryExists = engine[type].hasOwnProperty ( name ); // 'true' if template/process/data with this name already exists 65 | const entryForbidden = entryExists && !engine.config[configProperty] 66 | return entryForbidden 67 | } // _isWritingForbidden func. 68 | 69 | 70 | 71 | , _normalizeExternalData ( data ) { 72 | let result; 73 | if ( !(data instanceof Array )) result = [data] 74 | else { 75 | result = data.map ( el => { 76 | if ( typeof el == 'object' ) return help._flatten(el) 77 | else return el 78 | }) 79 | } // else no array 80 | return result 81 | } // _normalizeExternalData func. 82 | 83 | 84 | 85 | , _validateProcess ( engine, processName ) { 86 | // * Find if process exists and no errors in it. Find if all templates needed are available. 87 | let errors = []; 88 | const 89 | processExists = engine.processes.hasOwnProperty ( processName ) 90 | , intProcess = engine.processes[processName] 91 | ; 92 | 93 | if ( processExists && intProcess['errors'] ) errors = errors.concat( intProcess['errors']) 94 | 95 | if ( !processExists ) errors.push ( showError('processNotExists',processName) ) 96 | else { 97 | intProcess['arguments'] 98 | .reduce ( (listTemplates, step) => { 99 | if ( step.do == 'draw' && step.tpl ) listTemplates.push ( step.tpl ) 100 | return listTemplates 101 | },[]) 102 | .forEach ( name => { 103 | const tpl = engine.templates[name]; 104 | if ( tpl && tpl['errors'] ) errors = errors.concat ( tpl['errors'] ) 105 | if ( !tpl ) errors.push ( showError('templateNotExists',name) ) 106 | }) 107 | } 108 | return errors 109 | } // _validateProcess func. 110 | 111 | 112 | 113 | , _validateProcessLib ( ext ) { // (JSON) -> { processName : intProcess } | false 114 | // * Find if JSON is valid, find if every process is an array. 115 | try { 116 | const 117 | list = JSON.parse ( ext ) 118 | , processNames = Object.keys(list) 119 | ; 120 | return processNames.reduce ( (res,name) => { 121 | if ( list[name] instanceof Array ) res[name] = [].concat(list[name]) 122 | return res 123 | },{}) 124 | } // try 125 | catch ( e ) { 126 | return false 127 | } 128 | } // _validateProcessLib func. 129 | 130 | 131 | 132 | , _extractLib ( tpl, libRequst ) { // (tpl:inTemplates[], libRequest:string[]) -> ExternalTemplate 133 | // * Creates new ExternalTemplate by removing libName from template name 134 | const tplNames = Object.keys ( tpl ).filter ( name => name.includes('/') ); 135 | 136 | return libRequst.reduce ( (res, libItem ) => { 137 | tplNames 138 | .filter ( name => name.indexOf(libItem) == 0 ) 139 | .forEach ( name => { 140 | const 141 | sliceIndex = name.indexOf('/') 142 | , prop = name.slice ( sliceIndex+1 ) 143 | ; 144 | 145 | res [prop] = tpl[name].tpl.join('') 146 | }) 147 | return res 148 | },{}) 149 | } // _extractLib func. 150 | 151 | 152 | 153 | , _flatten ( data, res , keyIn ) { 154 | let result = res || {}; 155 | 156 | keyIn = keyIn || '' 157 | 158 | for ( let key in data ) { 159 | const value = data[key] 160 | let newKey = key 161 | if (keyIn) newKey = `${keyIn}/${key}` 162 | if ( typeof value == 'function' ) continue // Data can't contain functions 163 | if ( help._isPrimitive(value) ) result[newKey] = value 164 | else return help._flatten ( value, result, newKey ) 165 | } 166 | return result 167 | } // _flatten func. -- Help 168 | 169 | 170 | 171 | , _isPrimitive ( value ) { 172 | return ( typeof value === 'object' ) ? false : true; 173 | } // isPrimitive func. -- Help 174 | 175 | } // help lib 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | const lib_Template = { 197 | 198 | insert ( extTemplate ) { // (extTemplate: ExternalTemplate) -> engine 199 | let 200 | me = this 201 | , templateNames = Object.keys( extTemplate ) 202 | ; 203 | 204 | templateNames.forEach ( name => { 205 | if ( help._isWritingForbidden(me,'templates',name) ) { 206 | console.error ( showError('overwriteTemplate') ) 207 | return 208 | } 209 | me.templates [ name ] = interpretTemplate ( extTemplate[name] ) 210 | }) 211 | return me 212 | } // insert func. -- Template 213 | 214 | 215 | 216 | , insertLib ( extLib, libName ) { // ( extLib: ExternalTemplate, name: string ) -> engine 217 | let 218 | me = this 219 | , simpleTemplates = Object.keys ( extLib ) 220 | ; 221 | 222 | simpleTemplates.forEach ( extName => { 223 | const 224 | newTpl = {} 225 | , name = `${libName}/${extName}` 226 | ; 227 | if ( help._isWritingForbidden(me,'templates',name) ) { 228 | console.error ( showError('overwriteTemplate') ) 229 | return 230 | } 231 | newTpl[name] = extLib[extName] 232 | lib_Template.insert.call ( me, newTpl ) 233 | }) 234 | return me 235 | } // insertLib func. -- Template 236 | 237 | 238 | 239 | , rename ( ops ) { // ({oldName:newName}) -> engine 240 | // * Change template names 241 | const 242 | me = this 243 | , list = Object.keys (ops) 244 | , doOverwrite = me.config.overwriteTemplates 245 | ; 246 | 247 | list.forEach ( key => { 248 | if ( !me.templates[key] ) return 249 | const 250 | newKey = ops[key] 251 | , keyExists = (me.templates[newKey]) ? true : false 252 | ; 253 | 254 | if ( keyExists && !doOverwrite ){ 255 | console.error ( showError('overwriteTemplate') ) 256 | return 257 | } 258 | else { 259 | me.templates[newKey] = me.templates[key] 260 | delete me.templates[key] 261 | } 262 | }) 263 | return me 264 | } // rename func. -- Template 265 | 266 | 267 | 268 | , remove ( tplName ) { // ( tplName:string|string[]) -> engine 269 | const me = this; 270 | let listDelete; 271 | 272 | if ( tplName instanceof Array ) listDelete = tplName 273 | else { 274 | const t = Object.keys ( arguments ); 275 | listDelete = t.map ( k => arguments[k] ) 276 | } 277 | 278 | listDelete.forEach ( item => delete me.templates[item]) 279 | return me 280 | } // remove func. -- Template 281 | 282 | 283 | 284 | , get ( tplName ) { // (tplName: string|string[]) -> ExternalTemplate 285 | const 286 | me = this 287 | , tpl = me.templates 288 | ; 289 | let tplRequst; 290 | 291 | if ( tplName instanceof Array ) tplRequst = tplName 292 | else { 293 | const t = Object.keys ( arguments ); 294 | tplRequst = t.map ( k => arguments[k] ) 295 | } 296 | 297 | return tplRequst.reduce ( (res, item ) => { 298 | if ( tpl[item] ) res[item] = tpl[item].tpl.join('') 299 | else res[item] = '' 300 | return res 301 | },{}) 302 | 303 | } // get func. -- Template 304 | 305 | 306 | 307 | , getLib ( libName ) { // (libName:string|string[]) -> ExternalTemplate 308 | const me = this; 309 | let libRequst; 310 | 311 | if ( libName instanceof Array ) libRequst = libName 312 | else { 313 | const t = Object.keys ( arguments ); 314 | libRequst = t.map ( k => arguments[k] ) 315 | } 316 | 317 | return help._extractLib ( me.templates, libRequst ) 318 | } // getLib func. -- Template 319 | 320 | 321 | 322 | , getPlaceholders ( templateName ) { // (templateName:string) -> placeholderNames:string[]. 323 | const tpl = this.templates [ templateName ]; 324 | 325 | if ( tpl ) return Object.keys ( tpl.placeholders ) 326 | else return [] 327 | } // getPlaceholders func. -- Template 328 | 329 | } // lib_Template lib 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | const lib_Process = { 351 | 352 | insert ( ext, name ) { // (ext: extProcess, name: string) -> engine 353 | const me = this; 354 | 355 | if ( help._isWritingForbidden(me,'processes',name) ) { 356 | console.error ( showError('overwriteProcess',name) ) 357 | return 358 | } 359 | 360 | me.processes[name] = processTools.interpret ( ext ) 361 | return me 362 | } // insert func. -- Process 363 | 364 | 365 | 366 | , insertLib ( extLib, libName ) { // ( processLib: JSON, name: string ) -> engine 367 | let 368 | me = this 369 | , listOfProcesses = help._validateProcessLib ( extLib ) 370 | , processNames = Object.keys ( listOfProcesses ) 371 | ; 372 | if ( listOfProcesses ) { 373 | processNames.forEach ( extName => { 374 | const name = ( libName ) ? `${libName}/${extName}` : extName; 375 | lib_Process.insert.call ( me, listOfProcesses[extName], name ) 376 | }) 377 | } 378 | return me 379 | } // insertLib func. -- Process 380 | 381 | 382 | 383 | , mix ( mixList, newProcessName ) { // ( mixList:string[], processName:string ) -> engine 384 | // * Set new process as combination of existing processes 385 | const 386 | me = this 387 | , processes = me.processes 388 | ; 389 | 390 | let mix = {}; // new process container 391 | 392 | if ( help._isWritingForbidden(me,'processes',newProcessName) ) { 393 | console.error ( showError('overwriteProcess', newProcessName) ) 394 | return 395 | } 396 | 397 | mix.steps = [] 398 | mix.arguments =[] 399 | mix.hooks = [] 400 | 401 | mixList.forEach ( requestedName => { 402 | if ( !processes[requestedName] ) return 403 | const 404 | source = processes[requestedName] 405 | , hookNames = source.hooks.map ( name => `${requestedName}/${name}` ) 406 | , newArguments = source.arguments.map ( arg => { 407 | if ( arg.do != 'hook' ) return arg 408 | return { do: 'hook', name: `${requestedName}/${arg.name}` } 409 | }) 410 | ; 411 | 412 | mix['steps'] = mix['steps'].concat ( source['steps'] ) 413 | mix['arguments'] = mix['arguments'].concat ( newArguments ) 414 | mix['hooks'] = mix['hooks'].concat ( hookNames ) 415 | }) 416 | me.processes[newProcessName] = mix 417 | return me 418 | } // mix func. -- Process 419 | 420 | 421 | 422 | , getLib ( name ) { 423 | // * Extract process library as JSON 424 | const 425 | me = this 426 | , allKeys = Object.keys ( me.processes ) 427 | , takeEverything = (name) ? false : true 428 | ; 429 | const result = allKeys.reduce ( (res,key) => { 430 | if ( !takeEverything ) { 431 | if ( key.includes(name) ) { 432 | const t = key.split('/'); 433 | t.shift() 434 | const newKey = t.join('/'); 435 | res[newKey] = me.processes[key] 436 | } 437 | } 438 | else res[key] = me.processes[key] 439 | return res 440 | }, {}) 441 | return JSON.stringify ( result ) 442 | } // getLib func. -- Process 443 | 444 | 445 | 446 | , rename ( ops ) { // ({oldName:newName}) -> engine 447 | // * Change process names 448 | const 449 | me = this 450 | , list = Object.keys (ops) 451 | , doOverwrite = me.config.overwriteProcesses 452 | ; 453 | 454 | list.forEach ( key => { 455 | if ( !me.processes[key] ) return 456 | const 457 | newKey = ops[key] 458 | , keyAlreadyDefined = (me.processes[newKey]) ? true : false 459 | ; 460 | 461 | if ( keyAlreadyDefined && !doOverwrite ) { 462 | console.error ( showError('overwriteProcess', newKey) ) 463 | return 464 | } 465 | else { 466 | me.processes[newKey] = me.processes[key] 467 | delete me.processes[key] 468 | } 469 | }) 470 | return me 471 | } // rename func. -- Process 472 | 473 | 474 | 475 | , remove ( processName ) { // ( processName:string|string[]) -> engine 476 | const me = this; 477 | let listDelete; 478 | 479 | if ( processName instanceof Array ) listDelete = processName 480 | else { 481 | const t = Object.keys ( arguments ); 482 | listDelete = t.map ( k => arguments[k] ) 483 | } 484 | 485 | listDelete.forEach ( item => delete me.processes[item]) 486 | return me 487 | } // remove func. -- Process 488 | 489 | 490 | 491 | , getHooks ( processName ) { // (processName) -> { hookName : undefined } 492 | const recordExists = this.processes.hasOwnProperty ( processName ); 493 | if ( recordExists ) { 494 | const hooks = this.processes[ processName ].hooks; 495 | return hooks.reduce ( (res, name) => { 496 | res[name] = undefined 497 | return res 498 | },{}) 499 | } 500 | else return {} 501 | } // getHooks func. -- Process 502 | 503 | 504 | 505 | , run ( processList, data, hooks ) { 506 | if ( !(processList instanceof Array )) processList = [processList] 507 | const 508 | internalData = help._normalizeExternalData(data) 509 | , error = processList.reduce ( (res, processName ) => { 510 | const errorUpdate = help._validateProcess(this, processName); 511 | return res.concat ( errorUpdate ) 512 | },[]) 513 | ; 514 | 515 | if ( error.length == 0 ) { 516 | let current = internalData; 517 | processList.forEach ( processName => { 518 | current = processTools.run.call ( this, this.processes[processName], current, hooks ) 519 | }) 520 | return current 521 | } 522 | else return error 523 | } // run func. -- Process 524 | 525 | } // lib_Process lib 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | const lib_Data = { 547 | insert ( data ) { // ({}) -> engine 548 | const 549 | me = this 550 | , flatData = help._flatten ( data ) 551 | , dataNames = Object.keys( flatData ) 552 | ; 553 | 554 | dataNames.forEach ( name => { 555 | if ( help._isWritingForbidden(me,'data',name) ) { 556 | console.error ( showError('overwriteData', name) ) 557 | return 558 | } 559 | me.data [ name ] = flatData[name] 560 | }) 561 | return me 562 | } // insert func. -- Data 563 | 564 | 565 | 566 | , insertLib ( data, libName ) { // ({}, string) -> engine 567 | const 568 | me = this 569 | , flatData = help._flatten ( data ) 570 | , dataNames = Object.keys ( flatData ) 571 | ; 572 | 573 | dataNames 574 | .forEach ( name => { 575 | const newKey = `${libName}/${name}` 576 | if ( help._isWritingForbidden ( me, 'data', newKey ) ) { 577 | console.error ( showError('overwriteData',newKey) ) 578 | return 579 | } 580 | me.data [ newKey ] = flatData[name] 581 | }) 582 | return me 583 | } // insertLib func. -- Data 584 | 585 | 586 | 587 | , rename ( ops ) { // ({oldName:newName}) -> engine 588 | // * Change data-record names 589 | const 590 | me = this 591 | , list = Object.keys (ops) 592 | , doOverwrite = me.config.overwriteData 593 | ; 594 | 595 | list.forEach ( key => { 596 | if ( !me.data[key] ) return 597 | const 598 | newKey = ops[key] 599 | , keyAlreadyDefined = (me.data[newKey]) ? true : false 600 | ; 601 | 602 | if ( keyAlreadyDefined && !doOverwrite ) { 603 | console.error ( showError('overwriteData', newKey) ) 604 | return 605 | } 606 | else { 607 | me.data[newKey] = me.data[key] 608 | delete me.data[key] 609 | } 610 | }) 611 | return me 612 | } // rename func --- Data 613 | 614 | 615 | 616 | , remove ( dataName ) { // ( dataName:string|string[]) -> engine 617 | const me = this; 618 | let listDelete; 619 | 620 | if ( dataName instanceof Array ) listDelete = dataName 621 | else { 622 | const t = Object.keys ( arguments ); 623 | listDelete = t.map ( k => arguments[k] ) 624 | } 625 | 626 | listDelete.forEach ( item => delete me.data[item]) 627 | return me 628 | } // remove func. -- Data 629 | 630 | 631 | 632 | , getBlock ( blockName ) { // ( string ) -> string 633 | const me = this; 634 | let blockRequst = []; 635 | if ( blockName instanceof Array ) blockRequst = blockName 636 | else { 637 | const t = Object.keys ( arguments ); 638 | blockRequst = t.map ( k => arguments[k] ) 639 | } 640 | return blockRequst.reduce ( (res, name) => { 641 | const snippet = ( me.data[`block/${name}`] ) ? me.data[`block/${name}`] : ''; 642 | res += snippet 643 | return res 644 | }, '') 645 | } // getBlock func. -- Data 646 | } // lib_Data 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | // * Code-Assembly-Line API 668 | codeAssembly.prototype = { 669 | tools : tools // Usefull template and process related functions (external) 670 | 671 | // Template I/O Operations 672 | , insertTemplate : lib_Template.insert // Insert templates; 673 | , insertTemplateLib : lib_Template.insertLib // Insert templates as a template-library; 674 | , getTemplate : lib_Template.get // Export templates; 675 | , getTemplateLib : lib_Template.getLib // Export templates from template-library; 676 | , getPlaceholders : lib_Template.getPlaceholders // Return placeholders per template; 677 | 678 | // Template Manipulation 679 | , renameTemplate : lib_Template.rename // Change name of template/templates; 680 | , removeTemplate : lib_Template.remove // Remove template/templates; 681 | 682 | // Processes 683 | , insertProcess : lib_Process.insert // Insert new process; 684 | , insertProcessLib : lib_Process.insertLib // Insert list of processes with a single operation. JSON required; 685 | , mixProcess : lib_Process.mix // Set new process as combination of existing processes; 686 | , getProcessLib : lib_Process.getLib // Export processes from process-library as JSON; 687 | , getHooks : lib_Process.getHooks // Provide information about hooks available 688 | , run : lib_Process.run // Execute process/processes 689 | 690 | // Process Manipulation 691 | , renameProcess : lib_Process.rename // Renames a process 692 | , removeProcess : lib_Process.remove // Remove process/processes 693 | 694 | // Data I/O 695 | , insertData : lib_Data.insert // Insert data. Save data. Word 'blocks' 696 | , insertDataLib : lib_Data.insertLib // Insert set of data 697 | , getBlock : lib_Data.getBlock // Obtain rendered code snippets 698 | , getBlocks : lib_Data.getBlock 699 | 700 | // Data Manipulation 701 | , renameData : lib_Data.rename // Change name of data record 702 | , removeData : lib_Data.remove // Remove data record from template engine 703 | } // codeAssembly prototype 704 | 705 | 706 | 707 | module.exports = codeAssembly; 708 | 709 | 710 | -------------------------------------------------------------------------------- /test/12_run.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const 4 | chai = require ('chai') 5 | , expect = chai.expect 6 | , CodeAssemblyLine = require ( '../src/index' ) 7 | , showError = require ( '../src/errors' ) 8 | ; 9 | 10 | 11 | 12 | describe ( 'Run', () => { 13 | 14 | 15 | 16 | it ( 'Process has error', () => { 17 | const 18 | tplEngine = new CodeAssemblyLine() 19 | , fakeProcess = [{tpl:'fake'}] 20 | ; 21 | 22 | tplEngine.insertProcess ( fakeProcess, 'fake') 23 | const result = tplEngine.run ( 'fake' ) 24 | 25 | expect ( result ).to.be.an('array') 26 | expect ( result[0] ).to.be.equal( showError('missingOperation') ) 27 | }) // it Process has error 28 | 29 | 30 | 31 | it ( 'Template has error', () => { 32 | const 33 | tplEngine = new CodeAssemblyLine() 34 | , brokenTpl = { 'break' : 'Something}} {{broken'} 35 | , drawBreak = [ { do:'draw', tpl:'break'} ] 36 | ; 37 | 38 | tplEngine.insertTemplate ( brokenTpl ) 39 | tplEngine.insertProcess ( drawBreak, 'drawBreak' ) 40 | 41 | const result = tplEngine.run ( 'drawBreak' ); 42 | expect ( result ).to.be.an('array') 43 | expect ( result[0] ).to.be.equal ( showError('brokenTemplate') ) 44 | }) // it Template has error 45 | 46 | 47 | 48 | it ( 'Draw. Valid operation', () => { 49 | const 50 | tplEngine = new CodeAssemblyLine() 51 | , templateLib = { random: 'Some text with {{place}}.' } 52 | , processData = [ 53 | { do: 'draw', tpl: 'random' } 54 | , { do: 'block' } 55 | ] 56 | , renderData = [{ place: 'test string'}] 57 | ; 58 | 59 | tplEngine.insertProcess ( processData, 'test') 60 | tplEngine.insertTemplate ( templateLib ) 61 | 62 | const result = tplEngine.run ( 'test', renderData ) 63 | 64 | expect ( result ).to.be.an ('array') 65 | expect ( result[0] ).to.be.equal ( 'Some text with test string.' ) 66 | }) // it Draw. Valid operation 67 | 68 | 69 | 70 | it ( 'Draw. Enrich data', () => { 71 | const 72 | tplEngine = new CodeAssemblyLine() 73 | , simple = 'Some text with {{place}}.' 74 | , complex = '{{place}}: {{text}}' 75 | , processTest = [ 76 | { do: 'draw', tpl: 'simple', as: 'text' } 77 | , { do: 'draw', tpl: 'complex' } 78 | , { do: 'block', name: 'result' } 79 | ] 80 | , renderData = [{ place: 'test string'}] 81 | ; 82 | 83 | tplEngine.insertProcess ( processTest, 'test') 84 | tplEngine.insertTemplate ( {simple, complex} ) 85 | 86 | const result = tplEngine.run ( 'test', renderData ) 87 | 88 | expect ( result ).to.be.an ('array') 89 | expect ( result[0] ).to.be.equal ( 'test string: Some text with test string.' ) 90 | }) // it Draw. Enrich data 91 | 92 | 93 | 94 | 95 | it ( 'Draw templates with _attr', () => { 96 | const 97 | tplEngine = new CodeAssemblyLine() 98 | , templateLib = { 99 | link: '{{text}}' 100 | } 101 | , processData = [ 102 | { do: 'draw', tpl: 'link' } 103 | , { do: 'block' } 104 | ] 105 | , renderData = [ 106 | { text: 'Test link', href: '#link', className:'hello'} 107 | ] 108 | ; 109 | 110 | tplEngine.insertProcess ( processData, 'test') 111 | tplEngine.insertTemplate ( templateLib ) 112 | 113 | const result = tplEngine.run ( 'test', renderData ) 114 | 115 | expect ( result ).to.be.an ('array') 116 | expect ( result[0] ).to.be.equal ( 'Test link' ) 117 | }) // it Draw templates with _attr 118 | 119 | 120 | 121 | it ( 'Draw with missField: string', () => { 122 | const 123 | tplEngine = new CodeAssemblyLine() 124 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 125 | , processData = [ 126 | { do: 'draw', tpl: 'random', missField: 'ERROR!' } 127 | ] 128 | , renderData = [ 129 | { place: 'test string'} 130 | , { place: 'ala-bala' } 131 | , { place: 'pasion', more: 'Great to be here' } 132 | ] 133 | ; 134 | 135 | tplEngine.insertProcess ( processData, 'test') 136 | tplEngine.insertTemplate ( templateLib ) 137 | 138 | const result = tplEngine.run ( 'test', renderData ) 139 | 140 | expect ( result ).to.be.an ('array') 141 | expect ( result[0] ).to.be.equal ( 'Some text with test string.ERROR!' ) 142 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 143 | }) // it Draw with missField string. 144 | 145 | 146 | 147 | it ( 'Draw with missField: _hide', () => { 148 | const 149 | tplEngine = new CodeAssemblyLine() 150 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 151 | , processData = [ 152 | { do: 'draw', tpl: 'random', missField:'_hide' } 153 | ] 154 | , renderData = [ 155 | { place: 'test string'} 156 | , { place: 'ala-bala' } 157 | , { place: 'pasion', more: 'Great to be here' } 158 | ] 159 | ; 160 | 161 | tplEngine.insertProcess ( processData, 'test') 162 | tplEngine.insertTemplate ( templateLib ) 163 | 164 | const result = tplEngine.run ( 'test', renderData ) 165 | 166 | expect ( result ).to.be.an ('array') 167 | expect ( result[0] ).to.be.equal ( 'Some text with test string.' ) 168 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 169 | }) // it Draw with missField _hide. 170 | 171 | 172 | 173 | it ( 'Draw with missField: _fn', () => { 174 | const 175 | tplEngine = new CodeAssemblyLine() 176 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 177 | , processData = [ 178 | { do: 'draw', tpl: 'random', missField:'_fn', hook: 'ole' } 179 | ] 180 | , renderData = [ 181 | { place: 'test string'} 182 | , { place: 'ala-bala' } 183 | , { place: 'pasion', more: 'Great to be here' } 184 | ] 185 | ; 186 | 187 | tplEngine.insertProcess ( processData, 'test') 188 | tplEngine.insertTemplate ( templateLib ) 189 | 190 | const result = tplEngine.run ( 'test', renderData , { ole: (x) => `Error: Missing data - ${x}` } ) 191 | 192 | expect ( result ).to.be.an ('array') 193 | expect ( result[0] ).to.be.equal ( 'Some text with test string.Error: Missing data - more' ) 194 | expect ( result[1] ).to.be.equal ( 'Some text with ala-bala.Error: Missing data - more' ) 195 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 196 | }) // it Draw with missField: _fn. 197 | 198 | 199 | 200 | it ( 'Draw with missField: _fn but missing hook', () => { 201 | const 202 | tplEngine = new CodeAssemblyLine() 203 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 204 | , processData = [ 205 | { do: 'draw', tpl: 'random', missField:'_fn', hook: 'ole' } 206 | ] 207 | , renderData = [ 208 | { place: 'test string'} 209 | , { place: 'ala-bala' } 210 | , { place: 'pasion', more: 'Great to be here' } 211 | ] 212 | ; 213 | 214 | tplEngine.insertProcess ( processData, 'test') 215 | tplEngine.insertTemplate ( templateLib ) 216 | 217 | const result = tplEngine.run ( 'test', renderData , { oled: (x) => `Error: Missing data - ${x}` } ) 218 | 219 | expect ( result ).to.be.an ('array') 220 | expect ( result[0] ).to.be.equal ( 'Some text with test string.' ) 221 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 222 | }) // it Draw with missField: _fn but missing hook. 223 | 224 | 225 | 226 | it ( 'Draw with missField: _fn but no hook object', () => { 227 | const 228 | tplEngine = new CodeAssemblyLine() 229 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 230 | , processData = [ 231 | { do: 'draw', tpl: 'random', missField:'_fn', hook: 'ole' } 232 | ] 233 | , renderData = [ 234 | { place: 'test string'} 235 | , { place: 'ala-bala' } 236 | , { place: 'pasion', more: 'Great to be here' } 237 | ] 238 | ; 239 | 240 | tplEngine.insertProcess ( processData, 'test') 241 | tplEngine.insertTemplate ( templateLib ) 242 | 243 | const result = tplEngine.run ( 'test', renderData ) 244 | 245 | expect ( result ).to.be.an ('array') 246 | expect ( result[0] ).to.be.equal ( 'Some text with test string.' ) 247 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 248 | }) // it Draw with missField: _fn but no hook object. 249 | 250 | 251 | 252 | it ( 'Draw with missData: _fn.', () => { 253 | const 254 | tplEngine = new CodeAssemblyLine() 255 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 256 | , processData = [ 257 | { do: 'draw', tpl: 'random', missData:'_fn', hook: 'ole' } 258 | ] 259 | , renderData = [ 260 | { place: 'test string'} 261 | , { place: 'ala-bala' } 262 | , { place: 'pasion', more: 'Great to be here' } 263 | ] 264 | ; 265 | 266 | tplEngine.insertProcess ( processData, 'test') 267 | tplEngine.insertTemplate ( templateLib ) 268 | 269 | const result = tplEngine.run ( 'test', renderData , { ole: (x) => `Error: Missing data - ${x.join(',')}` } ) 270 | 271 | expect ( result ).to.be.an ('array') 272 | expect ( result[0] ).to.be.equal ( 'Error: Missing data - more' ) 273 | expect ( result[1] ).to.be.equal ( 'Error: Missing data - more' ) 274 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 275 | }) // it Draw with missData: _fn. 276 | 277 | 278 | 279 | it ( 'Draw with missData: _fn. but no hook function', () => { 280 | const 281 | tplEngine = new CodeAssemblyLine() 282 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 283 | , processData = [ 284 | { do: 'draw', tpl: 'random', missData:'_fn', hook: 'ole' } 285 | ] 286 | , renderData = [ 287 | { place: 'test string'} 288 | , { place: 'ala-bala' } 289 | , { place: 'pasion', more: 'Great to be here' } 290 | ] 291 | ; 292 | 293 | tplEngine.insertProcess ( processData, 'test') 294 | tplEngine.insertTemplate ( templateLib ) 295 | 296 | const result = tplEngine.run ( 'test', renderData , { oled: (x) => `Error: Missing data - ${x.join(',')}` } ) 297 | 298 | expect ( result ).to.be.an ('array') 299 | expect ( result ).to.have.length (1) 300 | expect ( result[0] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 301 | }) // it Draw with missData: _fn but no hook function. 302 | 303 | 304 | 305 | it ( 'Draw with missData: _fn. but no hooks', () => { 306 | const 307 | tplEngine = new CodeAssemblyLine() 308 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 309 | , processData = [ 310 | { do: 'draw', tpl: 'random', missData:'_fn', hook: 'ole' } 311 | ] 312 | , renderData = [ 313 | { place: 'test string'} 314 | , { place: 'ala-bala' } 315 | , { place: 'pasion', more: 'Great to be here' } 316 | ] 317 | ; 318 | 319 | tplEngine.insertProcess ( processData, 'test') 320 | tplEngine.insertTemplate ( templateLib ) 321 | 322 | const result = tplEngine.run ( 'test', renderData ) 323 | 324 | expect ( result ).to.be.an ('array') 325 | expect ( result ).to.have.length (1) 326 | expect ( result[0] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 327 | }) // it Draw with missData: _fn but no hooks. 328 | 329 | 330 | 331 | it ( 'Draw with missData: _hide', () => { 332 | const 333 | tplEngine = new CodeAssemblyLine() 334 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 335 | , processData = [ 336 | { do: 'draw', tpl: 'random', missData:'_hide' } 337 | ] 338 | , renderData = [ 339 | { place: 'test string'} 340 | , { place: 'ala-bala' } 341 | , { place: 'pasion', more: 'Great to be here' } 342 | ] 343 | ; 344 | 345 | tplEngine.insertProcess ( processData, 'test') 346 | tplEngine.insertTemplate ( templateLib ) 347 | 348 | const result = tplEngine.run ( 'test', renderData ) 349 | 350 | expect ( result ).to.be.an ('array') 351 | expect ( result ).to.have.length (1) 352 | expect ( result[0] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 353 | }) // it Draw with missData: _hide. 354 | 355 | 356 | 357 | it ( 'Draw with missData: string', () => { 358 | const 359 | tplEngine = new CodeAssemblyLine() 360 | , templateLib = { random: 'Some text with {{place}}.{{more}}' } 361 | , processData = [ 362 | { do: 'draw', tpl: 'random', missData:'ERROR' } 363 | ] 364 | , renderData = [ 365 | { place: 'test string'} 366 | , { place: 'ala-bala' } 367 | , { place: 'pasion', more: 'Great to be here' } 368 | ] 369 | ; 370 | 371 | tplEngine.insertProcess ( processData, 'test') 372 | tplEngine.insertTemplate ( templateLib ) 373 | 374 | const result = tplEngine.run ( 'test', renderData ) 375 | 376 | expect ( result ).to.be.an ('array') 377 | expect ( result ).to.have.length (3) 378 | expect ( result[0] ).to.be.equal ( 'ERROR' ) 379 | expect ( result[1] ).to.be.equal ( 'ERROR' ) 380 | expect ( result[2] ).to.be.equal ( 'Some text with pasion.Great to be here' ) 381 | }) // it Draw with missData: string. 382 | 383 | 384 | 385 | it ( 'Draw with a non existing template.', () => { 386 | const 387 | tplEngine = new CodeAssemblyLine() 388 | , tplName = 'random' 389 | , processData = [ { do: 'draw', tpl: tplName } ] 390 | , renderData = [{ place: 'test string'}] 391 | ; 392 | 393 | tplEngine.insertProcess ( processData, 'test') 394 | const result = tplEngine.run ( 'test', renderData ) 395 | 396 | expect ( result ).to.be.an ('array') 397 | expect ( result[0] ).to.be.equal ( `Error: Template "${tplName}" is not available` ) 398 | }) // it Draw with a non existing template. 399 | 400 | 401 | 402 | it ( 'Alter template', () => { 403 | const 404 | tplEngine = new CodeAssemblyLine () 405 | , tpl = { test: 'Find {{who}}!' } 406 | , data = { name: 'Peter' } 407 | , processData = [ 408 | { do: 'alterTemplate', tpl:'test', data: { 'who':'name'} } 409 | , { do: 'draw', tpl: 'test' } 410 | ] 411 | ; 412 | 413 | tplEngine.insertTemplate ( tpl ) 414 | tplEngine.insertProcess ( processData, 'change' ) 415 | 416 | const result = tplEngine.run ( 'change', data ) 417 | expect ( result[0] ).to.be.equal ( 'Find Peter!' ) 418 | }) // it alter template 419 | 420 | 421 | 422 | it ( 'Write Block', () => { 423 | const 424 | tplEngine = new CodeAssemblyLine () 425 | , tpl = { test: 'Find {{who}}!' } 426 | , blockName = 'finding' 427 | , data = [ 428 | { name: 'Peter' } 429 | , { name: 'Ivan' } 430 | ] 431 | , processData = [ 432 | { do: 'alterTemplate', tpl:'test', data: { 'who':'name'} } 433 | , { do: 'draw', tpl: 'test' } 434 | , { do: 'block', name: blockName } 435 | ] 436 | ; 437 | 438 | tplEngine.insertTemplate ( tpl ) 439 | tplEngine.insertProcess ( processData, 'change' ) 440 | 441 | const result = tplEngine.run ( 'change', data ); 442 | 443 | expect ( tplEngine.data ).to.have.property ( `block/${blockName}` ) 444 | }) // it write block 445 | 446 | 447 | 448 | it ( 'Can not overwrite Block', () => { 449 | const 450 | tplEngine = new CodeAssemblyLine () 451 | , tpl = { test: 'Find {{who}}!' } 452 | , blockName = 'finding' 453 | , data = [ 454 | { name: 'Peter' } 455 | , { name: 'Ivan' } 456 | ] 457 | , altData = [ { name: 'Joseph'} ] 458 | , processData = [ 459 | { do: 'alterTemplate', tpl:'test', data: { 'who':'name'} } 460 | , { do: 'draw', tpl: 'test' } 461 | , { do: 'block', name: blockName } 462 | ] 463 | , badProcess = [ { do: 'block', name: 'bad' } ] 464 | ; 465 | 466 | tplEngine.insertTemplate ( tpl ) 467 | tplEngine.insertProcess ( processData, 'processData' ) 468 | tplEngine.insertProcess ( badProcess, 'badProcess' ) 469 | 470 | tplEngine.run ( 'badProcess', data ) 471 | tplEngine.run ( 'processData', data ) 472 | tplEngine.run ( 'processData', altData ) 473 | 474 | const D = tplEngine.data; 475 | expect ( D ).to.have.property ( `block/${blockName}` ) 476 | expect ( D[`block/${blockName}`] ).to.be.equal ( 'Find Peter!Find Ivan!' ) 477 | expect ( D ).to.not.have.property ( 'bad' ) 478 | }) // it can not overwrite block 479 | 480 | 481 | 482 | it ( 'Save Template', () => { 483 | const 484 | tplEngine = new CodeAssemblyLine () 485 | , tpl = { test: 'Find {{who}}!' } 486 | , blockName = 'finding' 487 | , processData = [ 488 | { do: 'draw', tpl: 'test' } 489 | , { do: 'save', as:'template', name: blockName } 490 | ] 491 | ; 492 | 493 | tplEngine.insertTemplate ( tpl ) 494 | tplEngine.insertProcess ( processData, 'change' ) 495 | 496 | tplEngine.run ( 'change', [{name:'Peter'},{name:'Ivan'}] ); 497 | 498 | const templates = tplEngine.templates; 499 | expect ( templates ).to.have.property ( blockName ) 500 | const test = templates[blockName]['tpl'].join(''); 501 | expect ( test ).to.be.equal ( tpl.test ) 502 | }) // it save template 503 | 504 | 505 | 506 | it ( 'Save Process, save Block', () => { 507 | const 508 | tplEngine = new CodeAssemblyLine () 509 | , testTPL = 'Find {{who}}!' 510 | , renderBlock = `[ 511 | { "do": "draw", "tpl": "{{tpl}}" } 512 | , { "do": "save", "as": "block", "name": "{{name}}" } 513 | ]` 514 | , setBlock = [ 515 | { do: 'draw', tpl: 'renderBlock' } 516 | , { do: 'save', as: 'process', name: 'build/block' } 517 | ] 518 | ; 519 | 520 | tplEngine.insertTemplate ({ 521 | renderBlock 522 | , test : testTPL 523 | }) 524 | tplEngine.insertProcess ( setBlock, 'setBlock' ) 525 | 526 | tplEngine.run ( 'setBlock', { name: 'first', tpl:'test' } ) 527 | tplEngine.run ( 'build/block', { who: 'Peter' }) 528 | 529 | const processes = tplEngine.processes; 530 | expect ( processes ).to.have.property ( 'setBlock' ) 531 | expect ( processes ).to.have.property ( 'build/block' ) 532 | expect ( tplEngine.data ).to.have.property ( 'block/first' ) 533 | }) // it save process, save Block 534 | 535 | 536 | 537 | it ( 'Try to save as a process invalid JSON', () => { 538 | const 539 | tplEngine = new CodeAssemblyLine () 540 | , testTPL = 'Find {{who}}!' 541 | , renderBlock = `{ 542 | 'name': "{{name}}" 543 | }` 544 | , setBlock = [ 545 | { do: 'draw', tpl: 'renderBlock' } 546 | , { do: 'save', as: 'process', name: 'result' } 547 | ] 548 | ; 549 | 550 | tplEngine.insertTemplate ({ 551 | renderBlock 552 | , test : testTPL 553 | }) 554 | tplEngine.insertProcess ( setBlock, 'setBlock' ) 555 | 556 | tplEngine.run ( 'setBlock', { name: 'first', tpl:'test' } ) 557 | tplEngine.run ( 'build/block', { who: 'Peter' }) 558 | 559 | const processes = tplEngine.processes; 560 | expect ( processes ).to.not.have.property ( 'result' ) 561 | }) // it Try to save as a process invalid JSON 562 | 563 | 564 | 565 | it ( 'Save as Data', () => { 566 | const 567 | tplEngine = new CodeAssemblyLine () 568 | , testTPL = 'Find {{who}}!' 569 | , findWho = '{{findWho}}' 570 | , setData = [ 571 | { do: 'draw', tpl: 'test' } 572 | , { do: 'save', as: 'data', name: 'findWho' } 573 | ] 574 | , renderMyBlock = [ 575 | { do: 'draw', tpl: 'findWho' } 576 | , { do: 'save', as: 'block', name: 'myBlock' } 577 | ] 578 | ; 579 | 580 | tplEngine.insertTemplate ({ test : testTPL, findWho }) 581 | tplEngine.insertProcess ( setData, 'setData' ) 582 | tplEngine.insertProcess ( renderMyBlock, 'renderMyBlock' ) 583 | 584 | tplEngine.run ( 'setData', { who: 'Peter' } ) 585 | tplEngine.run ( 'renderMyBlock' ) 586 | 587 | const result = tplEngine.data; 588 | expect ( result ).to.have.property ( 'findWho' ) 589 | expect ( result ).to.have.property ( 'block/myBlock' ) 590 | expect ( result['findWho']).to.be.equal ( result['block/myBlock']) 591 | }) // it save as Data 592 | 593 | 594 | 595 | it ( 'Save as non existing item', () => { 596 | const 597 | tplEngine = new CodeAssemblyLine () 598 | , testTPL = 'Find {{who}}!' 599 | , renderMyBlock = [ 600 | { do: 'draw', tpl: 'test' } 601 | , { do: 'save', as: 'memo', name: 'myBlock' } 602 | ] 603 | ; 604 | 605 | tplEngine.insertTemplate ({ test : testTPL }) 606 | tplEngine.insertProcess ( renderMyBlock, 'renderMyBlock' ) 607 | 608 | const result = tplEngine.run ( 'renderMyBlock', { who: 'Peter' } ) 609 | 610 | expect ( result[0] ).to.be.equal ( 'Find Peter!') 611 | expect ( tplEngine ).to.not.have.property ( 'memo' ) 612 | expect ( tplEngine.data ).to.not.have.property ( 'memo' ) 613 | expect ( tplEngine.templates ).to.not.have.property ( 'memo' ) 614 | expect ( tplEngine.processes ).to.not.have.property ( 'memo' ) 615 | }) // it save as non existing item 616 | 617 | 618 | 619 | it ( 'Set', () => { 620 | const 621 | tplEngine = new CodeAssemblyLine() 622 | , tpl = { test: 'Find {{who}}!' } 623 | , data = [ 'Peter', 'Ivan' ] 624 | , processData = [ 625 | { do: 'set', as: 'who'} 626 | , { do: 'draw', tpl: 'test' } 627 | ] 628 | ; 629 | tplEngine.insertTemplate ( tpl ) 630 | tplEngine.insertProcess ( processData, 'findList' ) 631 | 632 | const result = tplEngine.run ( 'findList', data ) 633 | 634 | expect ( result ).to.be.an('array') 635 | expect ( result ).to.have.length (2) 636 | expect ( result[0] ).to.be.equal ( 'Find Peter!' ) 637 | expect ( result[1] ).to.be.equal ( 'Find Ivan!' ) 638 | }) // it set 639 | 640 | 641 | 642 | it ( 'Set with high depth objects', () => { 643 | const 644 | tplEngine = new CodeAssemblyLine() 645 | , tpl = { test: '{{who}} has a {{profile/eyes}} eyes!' } 646 | , data = [ 647 | { who: 'Peter', profile: { age: 43, eyes: 'blue' } } 648 | , { who: 'Ivan', profile: { age: 42, eyes: 'brown' } } 649 | ] 650 | , processData = [ 651 | { do: 'draw', tpl: 'test' } 652 | ] 653 | ; 654 | tplEngine.insertTemplate ( tpl ) 655 | tplEngine.insertProcess ( processData, 'findList' ) 656 | 657 | const result = tplEngine.run ( 'findList', data ) 658 | 659 | expect ( result ).to.be.an('array') 660 | expect ( result ).to.have.length (2) 661 | expect ( result[0] ).to.be.equal ( 'Peter has a blue eyes!' ) 662 | expect ( result[1] ).to.be.equal ( 'Ivan has a brown eyes!' ) 663 | }) // it set with high depth objects 664 | 665 | 666 | 667 | it ( 'List of processes', () => { 668 | const 669 | tplEngine = new CodeAssemblyLine() 670 | , tpl = { test: 'Find {{who}}!' } 671 | , data = [ 'Peter', 'Ivan' ] 672 | , processSetData = [ { do: 'set', as: 'who' }] 673 | , processDraw = [ { do: 'draw', tpl: 'test' }] 674 | ; 675 | 676 | tplEngine.insertTemplate ( tpl ) 677 | tplEngine.insertProcess( processSetData, 'setData') 678 | tplEngine.insertProcess( processDraw, 'draw' ) 679 | const result = tplEngine.run ( ['setData','draw'], data ) 680 | 681 | expect ( result ).to.be.an('array') 682 | expect ( result ).to.have.length (2) 683 | expect ( result[0] ).to.be.equal ( 'Find Peter!' ) 684 | expect ( result[1] ).to.be.equal ( 'Find Ivan!' ) 685 | }) // it list of processes 686 | 687 | 688 | 689 | it ( 'Consume "data" for missing fields', () => { 690 | const 691 | tplEngine = new CodeAssemblyLine() 692 | , tpl = { test : 'Find {{who}}! Address: {{location}}' } 693 | , data = [ 694 | { who: 'Peter', 'location' : 'Bulgaria,Sofia' } 695 | , { who: 'Ivan' } 696 | ] 697 | , processDraw = [{ do: 'draw', tpl: 'test' }] 698 | , defaultLocation = 'Location unknown' 699 | ; 700 | 701 | tplEngine.insertTemplate ( tpl ) 702 | tplEngine.insertProcess ( processDraw, 'draw' ) 703 | tplEngine.insertData ({'location': defaultLocation }) 704 | 705 | const result = tplEngine.run ( 'draw', data ) 706 | expect ( result[1] ).to.be.equal ( `Find Ivan! Address: ${defaultLocation}` ) 707 | }) // it consume "data" for missing fields 708 | 709 | 710 | it ( 'Hook', () => { 711 | const 712 | tplEngine = new CodeAssemblyLine ({ overwriteData: true}) 713 | , simple = 'Just simple text, {{name}}!' 714 | , processWithHook = [ 715 | { do: 'draw', tpl: 'simple' } 716 | , { do: 'hook', name: 'afterSimple' } 717 | , { do: 'block', name: 'result' } 718 | ] 719 | ; 720 | 721 | const hookFn = function ( ) { return 'ala-bala' }; 722 | const hookAlt = function ( ) { return ['brum-brum']}; 723 | 724 | tplEngine.insertProcess ( processWithHook, 'withHook' ) 725 | tplEngine.insertTemplate ( {simple} ) 726 | 727 | const hookObject = tplEngine.getHooks ( 'withHook' ); 728 | hookObject['afterSimple'] = hookFn 729 | tplEngine.run ( 'withHook', {name:'Johny'}, hookObject ) 730 | 731 | const D = tplEngine.data; 732 | expect ( D ).to.have.property ( 'block/result' ) 733 | expect ( D['block/result'] ).to.be.equal ('ala-bala') 734 | 735 | hookObject['afterSimple'] = hookAlt 736 | tplEngine.run ( 'withHook', {name:'Johny'}, hookObject ) 737 | expect ( D['block/result'] ).to.be.equal ('brum-brum') 738 | }) // it hook 739 | 740 | 741 | 742 | it ( 'Draw templates with _count placeholder', () => { 743 | const 744 | tplEngine = new CodeAssemblyLine() 745 | , userName = '{{_count}}. User: {{name}},{{_count}}' 746 | , data = ['Peter', 'Ivan', 'Ivo', 'Stefan' ] 747 | , processUserList = [ 748 | { do: 'set', as: 'name' } 749 | , { do: 'draw', tpl:'userName'} 750 | ] 751 | ; 752 | 753 | tplEngine.insertTemplate ( {userName}) 754 | tplEngine.insertProcess ( processUserList, 'userList' ) 755 | 756 | const result = tplEngine.run ( 'userList', data ); 757 | 758 | expect ( result ).to.be.an ('array') 759 | expect ( result[0] ).to.be.equal ( '1. User: Peter,1' ) 760 | expect ( result[1] ).to.be.equal ( '2. User: Ivan,2' ) 761 | expect ( result[2] ).to.be.equal ( '3. User: Ivo,3' ) 762 | expect ( result[3] ).to.be.equal ( '4. User: Stefan,4' ) 763 | }) // it Draw templates with _count placeholder 764 | 765 | 766 | 767 | it ( 'Whitespaces with external data', () => { 768 | const 769 | tplEngine = new CodeAssemblyLine() 770 | , simple = '{{_count~~}}{{~~combine~~}}{{~~name}}' 771 | , data = ['Peter', 'Ivan', 'Ivo', 'Stefan' ] 772 | , renderSimple = [ 773 | { do: 'set', as: 'name' } 774 | , { do: 'draw', tpl:'simple', as: 'combine', missField:'_hide' } 775 | , { do: 'draw', tpl:'simple'} 776 | ] 777 | ; 778 | 779 | tplEngine.insertTemplate ({simple}) 780 | tplEngine.insertProcess ( renderSimple, 'renderSimple' ) 781 | 782 | const result = tplEngine.run ( 'renderSimple', data ); 783 | 784 | expect ( result[0] ).to.be.equal ( '1 1 Peter Peter' ) 785 | expect ( result[1] ).to.be.equal ( '2 2 Ivan Ivan' ) 786 | expect ( result[2] ).to.be.equal ( '3 3 Ivo Ivo' ) 787 | expect ( result[3] ).to.be.equal ( '4 4 Stefan Stefan' ) 788 | }) // it whitespaces with external data 789 | 790 | 791 | 792 | it ( 'Whitespaces with system placeholders', () => { 793 | const 794 | tplEngine = new CodeAssemblyLine() 795 | , simple = '{{text}}{{~~_count}}' 796 | , data = [ 797 | { text:'Peter', id:'first'} 798 | , { text:'Ivan', id: 'second'} 799 | , { text:'Stefan'} 800 | ] 801 | , renderSimple = [ 802 | { do: 'draw', tpl:'simple'} 803 | ] 804 | ; 805 | 806 | tplEngine.insertTemplate ({simple}) 807 | tplEngine.insertProcess ( renderSimple, 'renderSimple' ) 808 | 809 | const result = tplEngine.run ( 'renderSimple', data ); 810 | 811 | expect ( result[0] ).to.be.equal ( '
Peter 1
' ) 812 | expect ( result[1] ).to.be.equal ( '
Ivan 2
' ) 813 | expect ( result[2] ).to.be.equal ( '
Stefan 3
' ) 814 | }) // it Whitespaces with system placeholders 815 | 816 | 817 | 818 | it ( 'Whitespaces with data', () => { 819 | const 820 | tplEngine = new CodeAssemblyLine() 821 | , simple = '{{name}}:{{~~info~~}}.' 822 | , data = [ 823 | { name:'Peter', info: 'General info'} 824 | , { name:'Ivan'} 825 | , { name:'Stefan'} 826 | ] 827 | , info = 'Missing information' 828 | , renderSimple = [ 829 | { do: 'draw', tpl:'simple'} 830 | ] 831 | ; 832 | 833 | tplEngine.insertTemplate ({simple}) 834 | tplEngine.insertData ({info}) 835 | tplEngine.insertProcess ( renderSimple, 'renderSimple' ) 836 | 837 | const result = tplEngine.run ( 'renderSimple', data ); 838 | 839 | expect ( result[0] ).to.be.equal ( 'Peter: General info .' ) 840 | expect ( result[1] ).to.be.equal ( 'Ivan: Missing information .' ) 841 | expect ( result[2] ).to.be.equal ( 'Stefan: Missing information .' ) 842 | }) // it Whitespaces with data 843 | 844 | 845 | 846 | }) // describe Run --------------------------------------------------------------------------------