├── test ├── .gitignore ├── fixture.tpl ├── fixture-data2.tpl ├── fixture-data.tpl ├── fixture-data3.tpl └── test.js ├── .gitignore ├── package.json ├── index.js └── README.md /test/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /test/fixture.tpl: -------------------------------------------------------------------------------- 1 |

foo

-------------------------------------------------------------------------------- /test/fixture-data2.tpl: -------------------------------------------------------------------------------- 1 |

foo

2 | <%= name %> 3 | <%= color %> -------------------------------------------------------------------------------- /test/fixture-data.tpl: -------------------------------------------------------------------------------- 1 |

foo

2 | <%= data.name %> 3 | <%= data.color %> -------------------------------------------------------------------------------- /test/fixture-data3.tpl: -------------------------------------------------------------------------------- 1 |

foo

2 | <%= it.name %> 3 | <%= it.color %> -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-multifile", 3 | "version": "0.2.2", 4 | "description": "generate collection files from json files and template file", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/icai/gulp-multifile.git" 12 | }, 13 | "keywords": [ 14 | "generate", 15 | "collection", 16 | "json", 17 | "template", 18 | "file" 19 | ], 20 | "author": "Terry Cai", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/icai/gulp-multifile/issues" 24 | }, 25 | "homepage": "https://github.com/icai/gulp-multifile#readme", 26 | "dependencies": { 27 | "gulp-util": "^3.0.7", 28 | "lodash.template": "^4.2.5", 29 | "through2": "^2.0.1" 30 | }, 31 | "devDependencies": { 32 | "mocha": "*" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var through = require("through2"); 7 | var gutil = require('gulp-util'); 8 | var template = require('lodash.template'); 9 | 10 | var isType = function(obj, str){ 11 | return Object.prototype.toString.call(obj).toLowerCase().slice(8,-1) === str; 12 | } 13 | 14 | 15 | var multifile = function(options) { 16 | var PLUGIN_NAME = 'gulp-multifile'; 17 | var templatePath = options.template; 18 | var rename = options.rename || function(pa, data, dataFile) { 19 | throw new gutil.PluginError(PLUGIN_NAME, PLUGIN_NAME + ': Invalid rename'); 20 | } 21 | var filter = options.filter || null; 22 | var filterResult; 23 | if (!templatePath) { 24 | throw new gutil.PluginError(PLUGIN_NAME, PLUGIN_NAME + ': Invalid template'); 25 | } 26 | 27 | var fileTpl = fs.readFileSync(templatePath, "utf8"); 28 | 29 | var tmpl; 30 | 31 | if(isType(options.engine, 'function')){ 32 | /** 33 | * [tmpl description] 34 | * @type {Function} compiled source method 35 | */ 36 | tmpl = options.engine(fileTpl); 37 | } else { 38 | var settings = {}; 39 | if(options.varname !== undefined){ 40 | settings['variable'] = options.varname; 41 | } 42 | if(options.variable !== undefined){ 43 | settings['variable'] = options.variable; 44 | } 45 | if(settings['variable'] === undefined){ 46 | settings['variable'] = "data"; 47 | } 48 | if(options.escape) 49 | settings['escape'] = options.escape; 50 | if(options.evaluate) 51 | settings['evaluate'] = options.evaluate; 52 | if(options.interpolate) 53 | settings['interpolate'] = options.interpolate; 54 | tmpl = template(fileTpl, settings); 55 | 56 | } 57 | 58 | 59 | return through.obj(function(file, enc, cb) { 60 | if (file.isNull()) { 61 | cb(null, file); 62 | return; 63 | } 64 | if (file.isStream()) { 65 | cb(new gutil.PluginError(PLUGIN_NAME, 'Streaming not supported')); 66 | return; 67 | } 68 | file = new gutil.File(file); 69 | var fileContents = JSON.parse(file.contents.toString('utf8')); 70 | 71 | if(isType(fileContents, 'object')){ 72 | fileContents = [fileContents]; 73 | } 74 | 75 | fileContents.forEach(function(item, index) { 76 | if(filter){ 77 | filterResult = filter.call(null, item, file); 78 | if(filterResult === false) 79 | return false; 80 | } 81 | 82 | var data = rename.call(null, {}, item, file); 83 | var tmpldata = item; 84 | if(options.extdata){ 85 | tmpldata['extdata'] = options.extdata; 86 | } 87 | var cwd = process.cwd(); 88 | var base = path.join(cwd, '/'); 89 | var generateFile = new gutil.File({ 90 | cwd: cwd, 91 | base: base, 92 | path: path.join(base, path.join(data.dirname, data.basename + data.extname)), 93 | contents: new Buffer(tmpl(tmpldata)), 94 | }); 95 | this.push(generateFile); 96 | }.bind(this)); 97 | 98 | cb(); 99 | }) 100 | } 101 | 102 | 103 | module.exports = multifile; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gulp-multifile 2 | 3 | generate collection files from json files and template file. 4 | 5 | 6 | [![NPM](https://nodei.co/npm/gulp-multifile.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/gulp-multifile/) 7 | 8 | 9 | 10 | ## Getting Started 11 | 12 | 13 | you may install this plugin with this command: 14 | 15 | ```shell 16 | npm install gulp-multifile --save-dev 17 | ``` 18 | 19 | ## Setup 20 | 21 | Once the plugin has been installed, it may be enabled inside your gulpfile with this line of JavaScript: 22 | 23 | 24 | 25 | ```js 26 | var multifile = require('gulp-multifile'); 27 | 28 | 29 | gulp.task('gen:scss', function() { 30 | gulp.src('src/data/*.json') 31 | .pipe(multifile({ 32 | template: "src/template/sass.tpl", 33 | rename: function(paths, data, dataFile) { 34 | paths.dirname = path.basename(dataFile.basename, '.json') 35 | paths.basename = data.name; 36 | paths.extname = ".scss"; 37 | return paths; 38 | } 39 | })) 40 | .pipe(gulp.dest('src/sass')); 41 | 42 | // src/data/file1.json => `{ name: 'file1-0'}` 43 | // output file => src/sass/file1/file1-0.scss 44 | 45 | }); 46 | 47 | 48 | ``` 49 | 50 | 51 | ## Options 52 | 53 | 54 | **Plugin options** are: 55 | 56 | | Property | Necessary | Type | Plugin default value | 57 | | -------------------- | --------- | ---------- | ----------------------------------- | 58 | | template | yes | `String` | undefined | 59 | | rename | yes | `Function` | undefined | 60 | | [filter] | no | `Function` | null | 61 | | [extdata] | no | 'Object' | 'undefined' | 62 | | [varname / variable] | no | `String` | 'data', _.templateSettings.variable | 63 | | [escape] | no | 'RegExp' | _.templateSettings.escape | 64 | | [evaluate] | no | 'RegExp' | _.templateSettings.evaluate | 65 | | [interpolate] | no | 'RegExp' | _.templateSettings.interpolate | 66 | | [engine] | no | 'Function' | undefined | 67 | 68 | 69 | More detailed explanation is below. 70 | 71 | #### options.template 72 | 73 | Type: `String` 74 | 75 | Default value: `undefined` 76 | 77 | The template file for collection generating 78 | 79 | 80 | 81 | #### options.rename 82 | 83 | Type: `Function` 84 | 85 | Default value: `undefined` 86 | 87 | Can define rename function to output your file which you should return `Object` including keys `dirname`, `basename`, `extname` in order to make file relative path. 88 | 89 | ```js 90 | rename: function(paths, data, dataFile) { 91 | paths.dirname = path.basename(dataFile.basename, '.json') 92 | paths.basename = data.name; 93 | paths.extname = ".scss"; 94 | return paths; 95 | // return the path object for building file relative path 96 | } 97 | 98 | ``` 99 | 100 | 101 | 102 | 103 | #### options.filter 104 | 105 | Type: `Function` 106 | 107 | Default value: `undefined` 108 | 109 | Can define filter function to skin the item data render which you should `return false`. 110 | 111 | 112 | ```js 113 | filter: function(data, file){ 114 | // data is json model data 115 | // file is json file 116 | 117 | // you should return value here 118 | } 119 | 120 | ``` 121 | 122 | #### options.extdata 123 | 124 | Type: `Object` 125 | 126 | Default value: `undefined` 127 | 128 | 129 | extend the json data, this `extdata` inject to `data.extdata`, if it given. 130 | 131 | 132 | 133 | #### options.varname / options.variable 134 | 135 | Type: `String` 136 | 137 | Default value: `data` 138 | 139 | 140 | lodash templateSettings `_.templateSettings.variable` 141 | 142 | Used to reference the data object in the template text. 143 | 144 | 145 | #### options.escape 146 | 147 | Type: `RegExp` 148 | 149 | Default value: `_.templateSettings.escape` 150 | 151 | 152 | lodash templateSettings `_.templateSettings.escape` 153 | 154 | Used to detect `data` property values to be HTML-escaped. 155 | 156 | 157 | #### options.evaluate 158 | 159 | Type: `RegExp` 160 | 161 | Default value: `_.templateSettings.evaluate` 162 | 163 | lodash templateSettings `_.templateSettings.evaluate` 164 | 165 | Used to detect code to be evaluated. 166 | 167 | 168 | #### options.interpolate 169 | 170 | Type: `RegExp` 171 | 172 | Default value: `_.templateSettings.interpolate` 173 | 174 | 175 | lodash templateSettings `_.templateSettings.interpolate` 176 | 177 | Used to detect `data` property values to inject. 178 | 179 | 180 | #### options.engine 181 | 182 | Type: `Function` 183 | 184 | Default value: `undefined` 185 | 186 | return {Function} compiled source method 187 | 188 | Used to replace lodash template, if the template engine privided. 189 | 190 | ```js 191 | 192 | engine: function(templatefile){ 193 | return doT.template(templatefile); 194 | } 195 | 196 | ``` 197 | 198 | ## Note 199 | 200 | #### Json File 201 | the json pass from gulp.src , which their format as following is correct: 202 | 203 | 204 | **Array** 205 | 206 | `[{ 'name': 'I am name'},{ 'name': 'name also'}]` 207 | 208 | generate two file, which this json is a collection file. 209 | 210 | **Object** 211 | 212 | `{ 'name' : 'model'}` 213 | 214 | generate one file, which this json is a model file. 215 | 216 | 217 | #### Template Engine 218 | 219 | we use `lodash.template` to do this, you can use `engine` parameter to replace it. 220 | 221 | 222 | 223 | 224 | 225 | ## Demo 226 | see the [cozhihu](https://github.com/icai/cozhihu) project or unit testing. 227 | 228 | 229 | ## Contributing 230 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Gulp](http://gulpjs.com/). 231 | 232 | 233 | 234 | ## License 235 | Copyright (c) 2017 Terry Cai. Licensed under the MIT license. 236 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var fs = require('fs'); 4 | var gutil = require('gulp-util'); 5 | var multifile = require('../'); 6 | 7 | 8 | describe('variable parameter', function() { 9 | it('should compile template variable: "" to target content', function (cb) { 10 | var stream = multifile({ 11 | template: 'test/fixture-data2.tpl', 12 | variable: "", 13 | rename: function(path){ 14 | return { 15 | dirname: '', 16 | basename: 'fixture', 17 | extname: '.html' 18 | } 19 | } 20 | }); 21 | stream.once('data', function (file) { 22 | assert.equal(file.relative, 'fixture.html'); 23 | assert.equal(file.contents.toString(), '

foo

\nname1\ncat'); 24 | }); 25 | stream.on('end', cb); 26 | 27 | stream.write(new gutil.File({ 28 | path: 'fixture.json', 29 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 30 | })); 31 | stream.end(); 32 | }); 33 | it('should compile template variable: "it" to target content', function (cb) { 34 | var stream = multifile({ 35 | template: 'test/fixture-data3.tpl', 36 | variable: "it", 37 | rename: function(path){ 38 | return { 39 | dirname: '', 40 | basename: 'fixture', 41 | extname: '.html' 42 | } 43 | } 44 | }); 45 | stream.once('data', function (file) { 46 | assert.equal(file.relative, 'fixture.html'); 47 | assert.equal(file.contents.toString(), '

foo

\nname1\ncat'); 48 | }); 49 | stream.on('end', cb); 50 | 51 | stream.write(new gutil.File({ 52 | path: 'fixture.json', 53 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 54 | })); 55 | stream.end(); 56 | }); 57 | 58 | it('should compile template varname: "it" to target content', function (cb) { 59 | var stream = multifile({ 60 | template: 'test/fixture-data3.tpl', 61 | varname: "it", 62 | rename: function(path){ 63 | return { 64 | dirname: '', 65 | basename: 'fixture', 66 | extname: '.html' 67 | } 68 | } 69 | }); 70 | stream.once('data', function (file) { 71 | assert.equal(file.relative, 'fixture.html'); 72 | assert.equal(file.contents.toString(), '

foo

\nname1\ncat'); 73 | }); 74 | stream.on('end', cb); 75 | 76 | stream.write(new gutil.File({ 77 | path: 'fixture.json', 78 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 79 | })); 80 | stream.end(); 81 | }); 82 | 83 | 84 | }) 85 | 86 | 87 | 88 | describe('template and rename parameter', function() { 89 | it('should compile template to target content', function (cb) { 90 | var stream = multifile({ 91 | template: 'test/fixture.tpl', 92 | rename: function(path){ 93 | return { 94 | dirname: '', 95 | basename: 'fixture', 96 | extname: '.html' 97 | } 98 | } 99 | }); 100 | stream.once('data', function (file) { 101 | assert.equal(file.relative, 'fixture.html'); 102 | assert.equal(file.contents.toString(), '

foo

'); 103 | }); 104 | stream.on('end', cb); 105 | 106 | stream.write(new gutil.File({ 107 | path: 'fixture.json', 108 | contents: new Buffer('{ "name": "name1"}') 109 | })); 110 | stream.end(); 111 | }); 112 | 113 | it('should json model data rended file match target content', function (cb) { 114 | var stream = multifile({ 115 | template: 'test/fixture-data.tpl', 116 | rename: function(path){ 117 | return { 118 | dirname: '', 119 | basename: 'fixture', 120 | extname: '.html' 121 | } 122 | } 123 | }); 124 | stream.once('data', function (file) { 125 | assert.equal(file.relative, 'fixture.html'); 126 | assert.equal(file.contents.toString(), '

foo

\nname1\ncat'); 127 | }); 128 | stream.on('end', cb); 129 | 130 | stream.write(new gutil.File({ 131 | path: 'fixture.json', 132 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 133 | })); 134 | stream.end(); 135 | }); 136 | 137 | it('should json collection data render files match target files', function (cb) { 138 | var stream = multifile({ 139 | template: 'test/fixture-data.tpl', 140 | rename: function(path, data, json){ 141 | return { 142 | dirname: 'dir', 143 | basename: 'fixture' + data.name, 144 | extname: '.html' 145 | } 146 | } 147 | }); 148 | stream.once('data', function (file) { 149 | assert.equal(file.relative, 'dir/fixturename1.html'); 150 | assert.equal(file.contents.toString(), '

foo

\nname1\ncat'); 151 | 152 | stream.once('data', function (file) { 153 | assert.equal(file.relative, 'dir/fixturename2.html'); 154 | assert.equal(file.contents.toString(), '

foo

\nname2\ndog'); 155 | }); 156 | }); 157 | stream.on('end', cb); 158 | stream.write(new gutil.File({ 159 | path: 'fixture.json', 160 | contents: new Buffer('[{ "name": "name1", "color": "cat"}, { "name": "name2", "color": "dog"}]') 161 | })); 162 | stream.end(); 163 | }); 164 | }); 165 | 166 | 167 | 168 | describe('filter parameter', function() { 169 | 170 | it('should json model data filter render file match target content', function (cb) { 171 | var stream = multifile({ 172 | template: 'test/fixture-data.tpl', 173 | rename: function(path){ 174 | return { 175 | dirname: '', 176 | basename: 'fixture', 177 | extname: '.html' 178 | } 179 | }, 180 | filter: function(data, json){ 181 | return false 182 | } 183 | }); 184 | var fileContent = ""; 185 | stream.once('data', function (file) { 186 | fileContent += file.contents.toString(); 187 | 188 | }); 189 | stream.on('end', function(){ 190 | assert.equal(fileContent, ""); 191 | cb() 192 | }); 193 | 194 | stream.write(new gutil.File({ 195 | path: 'fixture.json', 196 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 197 | })); 198 | stream.end(); 199 | }); 200 | 201 | it('should json collection data filter render files match target files', function (cb) { 202 | var stream = multifile({ 203 | template: 'test/fixture-data.tpl', 204 | rename: function(path, data, json){ 205 | return { 206 | dirname: 'dir', 207 | basename: 'fixture' + data.name, 208 | extname: '.html' 209 | } 210 | }, 211 | filter: function(data, json){ 212 | return !(data.name == 'name1'); 213 | 214 | } 215 | }); 216 | 217 | var fileContent = ""; 218 | stream.once('data', function (file) { 219 | fileContent += file.contents.toString(); 220 | stream.once('data', function (file) { 221 | fileContent += file.contents.toString(); 222 | assert.fail(file.contents.toString(), '

foo

\nname2\ndog' , "catch exception file", '=='); 223 | }); 224 | }); 225 | stream.on('end', function(){ 226 | assert.equal(fileContent, '

foo

\nname2\ndog'); 227 | cb(); 228 | }); 229 | stream.write(new gutil.File({ 230 | path: 'fixture.json', 231 | contents: new Buffer('[{ "name": "name1", "color": "cat"}, { "name": "name2", "color": "dog"}]') 232 | })); 233 | stream.end(); 234 | }); 235 | }); 236 | 237 | 238 | describe('engine parameter', function() { 239 | 240 | it('should engine prividor render file match json content', function (cb) { 241 | var stream = multifile({ 242 | template: 'test/fixture-data.tpl', 243 | rename: function(path){ 244 | return { 245 | dirname: '', 246 | basename: 'fixture', 247 | extname: '.html' 248 | } 249 | }, 250 | engine: function(templatefile){ 251 | return function(data){ 252 | return JSON.stringify(data); 253 | } 254 | 255 | } 256 | }); 257 | 258 | stream.once('data', function (file) { 259 | var json = JSON.parse(file.contents.toString()); 260 | assert.equal(file.relative, 'fixture.html'); 261 | assert.equal(json.name, "name1"); 262 | assert.equal(json.color, "cat"); 263 | }); 264 | stream.on('end', cb); 265 | 266 | stream.write(new gutil.File({ 267 | path: 'fixture.json', 268 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 269 | })); 270 | stream.end(); 271 | }); 272 | 273 | 274 | it('should engine prividor render file match template content', function (cb) { 275 | var stream = multifile({ 276 | template: 'test/fixture-data.tpl', 277 | rename: function(path){ 278 | return { 279 | dirname: '', 280 | basename: 'fixture', 281 | extname: '.html' 282 | } 283 | }, 284 | engine: function(templatefile){ 285 | return function(data){ 286 | return templatefile; 287 | } 288 | 289 | } 290 | }); 291 | 292 | stream.once('data', function (file) { 293 | assert.equal(file.relative, 'fixture.html'); 294 | assert.equal(file.contents.toString(), fs.readFileSync('test/fixture-data.tpl', "utf8")); 295 | }); 296 | stream.on('end', cb); 297 | 298 | stream.write(new gutil.File({ 299 | path: 'fixture.json', 300 | contents: new Buffer('{ "name": "name1", "color": "cat"}') 301 | })); 302 | stream.end(); 303 | }); 304 | 305 | }); 306 | --------------------------------------------------------------------------------