diff --git a/.gitignore b/.gitignore index f06dfb9..5380e3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ -.DS_Store -node_modules/ -.vscode -.idea -.project -.settings -npm-debug.log -Desktop.ini -Thumbs.db +.DS_Store +node_modules/ +.vscode +.idea +.project +.settings +npm-debug.log +Desktop.ini +Thumbs.db package-lock.json \ No newline at end of file diff --git a/package.json b/package.json index 70dc09b..bd55c78 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,13 @@ "test": "mocha -R spec src/**/*.spec.js" }, "dependencies": { - "through2": "^2.0.1", - "gulp-util": "^3.0.0", + "ansi-colors": "^1.0.1", "escape-string-regexp": "^1.0.5", "event-stream": "^3.1.0", + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2", + "through2": "^2.0.1", + "vinyl": "^2.1.0" "strip-bom-buf": "^1.0.0" }, "devDependencies": { diff --git a/src/index.js b/src/index.js index 81e30ae..c493bfa 100644 --- a/src/index.js +++ b/src/index.js @@ -1,266 +1,271 @@ -'use strict'; -var through = require('through2'); -var gutil = require('gulp-util'); -var path = require('path'); -var fs = require('fs'); -var stripBomBuf = require('strip-bom-buf'); -var escapeStringRegexp = require('escape-string-regexp'); -var magenta = gutil.colors.magenta; -var cyan = gutil.colors.cyan; -var red = gutil.colors.red; - -/** - * Constants - */ -var PLUGIN_NAME = 'gulp-inject-partials'; -var DEFAULT_START = ''; -var DEFAULT_END = ''; -var FILE_PATH_REGEX = "((\/|\\.\/)?((\\.\\.\/)+)?((\\w|\\-)(\\.(\\w|\\-))?)+((\/((\\w|\\-)(\\.(\\w|\\-))?)+)+)?)"; -var PATH_REGEX = /\\\{\\\{path\\\}\\\}/; // ugly I know -var LEADING_WHITESPACE_REGEXP = /^\s*/; - -module.exports = function(opt) { - opt = opt || {}; - - opt.start = defaults(opt, 'start', DEFAULT_START); - opt.end = defaults(opt, 'end', DEFAULT_END); - opt.removeTags = bool(opt, 'removeTags', false); - opt.quiet = bool(opt, 'quiet', false); - opt.prefix = defaults(opt, 'prefix', ''); - opt.ignoreError = bool(opt, 'ignoreError', false); - - /** - * Handle injection of files - * @param target - * @param encoding - * @param cb - * @returns {*} - */ - function handleStream(target, encoding, cb){ - if (target.isNull()) { - return cb(null, target); - } - - if (target.isStream()) { - return cb(error(target.path + ': Streams not supported for target templates!')); - } - - try { - var tagsRegExp = getRegExpTags(opt, null); - target.contents = processContent(target, opt, tagsRegExp, [target.path]); - this.push(target); - return cb(); - } catch(err) { - this.emit('error', err); - return cb(); - } - } - - return through.obj(handleStream); -}; - -/** - * Parse content and create new template - * with all injections made - * - * @param {Object} target - * @param {Object} opt - * @param {Object} tagsRegExp - * @param {Array} listOfFiles - * @returns {Buffer} - */ -function processContent(target, opt, tagsRegExp, listOfFiles){ - var targetContent = String(target.contents); - var targetPath = target.path; - var files = extractFilePaths(targetContent, targetPath, opt, tagsRegExp); - // recursively process files - files.forEach(function(fileData){ - if (listOfFiles.indexOf(fileData.file.path) !== -1) { - throw error("Circular definition found. File: " + fileData.file.path + " referenced in a child file."); - } - listOfFiles.push(fileData.file.path); - var content = processContent(fileData.file, opt, tagsRegExp, listOfFiles); - listOfFiles.pop(); - - targetContent = inject(targetContent, String(content), opt, fileData.tags); - }); - if (listOfFiles.length === 1 && !opt.quiet && files.length) { - log(cyan(files.length) + ' partials injected into ' + magenta(targetPath) + '.'); - } - return new Buffer(targetContent); -} - -/** - * Inject tags into target content between given - * start and end tags - * - * @param {String} targetContent - * @param {String} sourceContent - * @param {Object} opt - * @param {Object} tagsRegExp - * @returns {String} - */ -function inject(targetContent, sourceContent, opt, tagsRegExp){ - var startTag = tagsRegExp.start; - var endTag = tagsRegExp.end; - var startMatch; - var endMatch; - - while ((startMatch = startTag.exec(targetContent)) !== null) { - // Take care of content length change - endTag.lastIndex = startTag.lastIndex; - endMatch = endTag.exec(targetContent); - if (!endMatch) { - throw error('Missing end tag for start tag: ' + startMatch[0]); - } - var toInject = [sourceContent]; - // content part before start tag - var newContents = targetContent.slice(0, startMatch.index); - - if (opt.removeTags) { - // Take care of content length change - startTag.lastIndex -= startMatch[0].length; - } else { - // + partial body + - toInject.unshift(startMatch[0]); - toInject.push(endMatch[0]); - } - var previousInnerContent = targetContent.substring(startTag.lastIndex, endMatch.index); - var indent = getLeadingWhitespace(previousInnerContent); - // add new content - newContents += toInject.join(indent); - // append rest of target file - newContents += targetContent.slice(endTag.lastIndex); - // replace old content with new - targetContent = newContents; - } - startTag.lastIndex = 0; - endTag.lastIndex = 0; - return targetContent; -} - -/** - * Prepare regular expressions for parsing template - * Replace {{path}} with regular expression for matching relative path - * or with exact file path - * - * @param {Object} opt - * @param {String} fileUrl - * @returns {Object} - */ -function getRegExpTags(opt, fileUrl) { - function parseTag(tag, replacement) { - return new RegExp(escapeStringRegexp(tag).replace(PATH_REGEX, replacement || FILE_PATH_REGEX), 'g'); - } - - if (fileUrl) { - return { - start: parseTag(opt.start, fileUrl), - end: parseTag(opt.end, fileUrl) - } - } - return { - start: parseTag(opt.start), - startex: parseTag(opt.start, "(.+)"), - end: parseTag(opt.end) - } -} - -/** - * Parse content and get all partials to be injected - * @param {String} content - * @param {String} targetPath - * @param {Object} opt - * @param {Object} tagsRegExp - * @returns {Array} - */ -function extractFilePaths(content, targetPath, opt, tagsRegExp) { - var files = []; - var tagMatches; - - // get all start matches - tagMatches = content.match(tagsRegExp.start); - if (tagMatches) { - tagMatches.forEach(function(tagMatch){ - var fileUrl = tagsRegExp.startex.exec(tagMatch)[1]; - var filePath = setFullPath(targetPath, opt.prefix + fileUrl); - try { - var fileContent = stripBomBuf(fs.readFileSync(filePath)); - files.push({ - file: new gutil.File({ - path: filePath, - cwd: __dirname, - base: path.resolve(__dirname, 'expected', path.dirname(filePath)), - contents: fileContent - }), - tags: getRegExpTags(opt, fileUrl) - }); - } catch (e) { - if (opt.ignoreError) { - log(red(filePath + ' not found.')); - } else { - throw error(filePath + ' not found.'); - } - } - // reset the regex - tagsRegExp.startex.lastIndex = 0; - }); - } - return files; -} - -///////////////////////////////////// -// HELPER FUNCTIONS -///////////////////////////////////// -/** - * @param str - * @returns {*} - */ -function getLeadingWhitespace(str) { - return str.match(LEADING_WHITESPACE_REGEXP)[0]; -} - -/** - * @param options - * @param prop - * @param defaultValue - * @returns {*} - */ -function defaults(options, prop, defaultValue) { - return options[prop] || defaultValue; -} - -/** - * @param options - * @param prop - * @param defaultVal - * @returns {boolean} - */ -function bool(options, prop, defaultVal) { - return typeof options[prop] === 'undefined' ? defaultVal : Boolean(options[prop]); -} - -/** - * @param message - * @returns {*} - */ -function error(message) { - return new gutil.PluginError(PLUGIN_NAME, message); -} - -/** - * @param message - */ -function log(message) { - gutil.log(magenta(PLUGIN_NAME), message); -} - -/** - * @param targetPath - * @param file - */ -function setFullPath(targetPath, file) { - var base = path.dirname(targetPath); - - return path.resolve(base, file); -} +'use strict'; + +var through = require('through2'); +var PluginError = require('plugin-error'); +var fancyLog = require('fancy-log'); +var colors = require('ansi-colors'); +var File = require('vinyl'); +var path = require('path'); +var fs = require('fs'); +var stripBomBuf = require('strip-bom-buf'); +var escapeStringRegexp = require('escape-string-regexp'); +var magenta = colors.magenta; +var cyan = colors.cyan; +var red = colors.red; + + +/** + * Constants + */ +var PLUGIN_NAME = 'gulp-inject-partials'; +var DEFAULT_START = ''; +var DEFAULT_END = ''; +var FILE_PATH_REGEX = "((\/|\\.\/)?((\\.\\.\/)+)?((\\w|\\-)(\\.(\\w|\\-))?)+((\/((\\w|\\-)(\\.(\\w|\\-))?)+)+)?)"; +var PATH_REGEX = /\\\{\\\{path\\\}\\\}/; // ugly I know +var LEADING_WHITESPACE_REGEXP = /^\s*/; + +module.exports = function(opt) { + opt = opt || {}; + + opt.start = defaults(opt, 'start', DEFAULT_START); + opt.end = defaults(opt, 'end', DEFAULT_END); + opt.removeTags = bool(opt, 'removeTags', false); + opt.quiet = bool(opt, 'quiet', false); + opt.prefix = defaults(opt, 'prefix', ''); + opt.ignoreError = bool(opt, 'ignoreError', false); + + /** + * Handle injection of files + * @param target + * @param encoding + * @param cb + * @returns {*} + */ + function handleStream(target, encoding, cb){ + if (target.isNull()) { + return cb(null, target); + } + + if (target.isStream()) { + return cb(error(target.path + ': Streams not supported for target templates!')); + } + + try { + var tagsRegExp = getRegExpTags(opt, null); + target.contents = processContent(target, opt, tagsRegExp, [target.path]); + this.push(target); + return cb(); + } catch(err) { + this.emit('error', err); + return cb(); + } + } + + return through.obj(handleStream); +}; + +/** + * Parse content and create new template + * with all injections made + * + * @param {Object} target + * @param {Object} opt + * @param {Object} tagsRegExp + * @param {Array} listOfFiles + * @returns {Buffer} + */ +function processContent(target, opt, tagsRegExp, listOfFiles){ + var targetContent = String(target.contents); + var targetPath = target.path; + var files = extractFilePaths(targetContent, targetPath, opt, tagsRegExp); + // recursively process files + files.forEach(function(fileData){ + if (listOfFiles.indexOf(fileData.file.path) !== -1) { + throw error("Circular definition found. File: " + fileData.file.path + " referenced in a child file."); + } + listOfFiles.push(fileData.file.path); + var content = processContent(fileData.file, opt, tagsRegExp, listOfFiles); + listOfFiles.pop(); + + targetContent = inject(targetContent, String(content), opt, fileData.tags); + }); + if (listOfFiles.length === 1 && !opt.quiet && files.length) { + log(cyan(files.length) + ' partials injected into ' + magenta(targetPath) + '.'); + } + return new Buffer(targetContent); +} + +/** + * Inject tags into target content between given + * start and end tags + * + * @param {String} targetContent + * @param {String} sourceContent + * @param {Object} opt + * @param {Object} tagsRegExp + * @returns {String} + */ +function inject(targetContent, sourceContent, opt, tagsRegExp){ + var startTag = tagsRegExp.start; + var endTag = tagsRegExp.end; + var startMatch; + var endMatch; + + while ((startMatch = startTag.exec(targetContent)) !== null) { + // Take care of content length change + endTag.lastIndex = startTag.lastIndex; + endMatch = endTag.exec(targetContent); + if (!endMatch) { + throw error('Missing end tag for start tag: ' + startMatch[0]); + } + var toInject = [sourceContent]; + // content part before start tag + var newContents = targetContent.slice(0, startMatch.index); + + if (opt.removeTags) { + // Take care of content length change + startTag.lastIndex -= startMatch[0].length; + } else { + // + partial body + + toInject.unshift(startMatch[0]); + toInject.push(endMatch[0]); + } + var previousInnerContent = targetContent.substring(startTag.lastIndex, endMatch.index); + var indent = getLeadingWhitespace(previousInnerContent); + // add new content + newContents += toInject.join(indent); + // append rest of target file + newContents += targetContent.slice(endTag.lastIndex); + // replace old content with new + targetContent = newContents; + } + startTag.lastIndex = 0; + endTag.lastIndex = 0; + return targetContent; +} + +/** + * Prepare regular expressions for parsing template + * Replace {{path}} with regular expression for matching relative path + * or with exact file path + * + * @param {Object} opt + * @param {String} fileUrl + * @returns {Object} + */ +function getRegExpTags(opt, fileUrl) { + function parseTag(tag, replacement) { + return new RegExp(escapeStringRegexp(tag).replace(PATH_REGEX, replacement || FILE_PATH_REGEX), 'g'); + } + + if (fileUrl) { + return { + start: parseTag(opt.start, fileUrl), + end: parseTag(opt.end, fileUrl) + } + } + return { + start: parseTag(opt.start), + startex: parseTag(opt.start, "(.+)"), + end: parseTag(opt.end) + } +} + +/** + * Parse content and get all partials to be injected + * @param {String} content + * @param {String} targetPath + * @param {Object} opt + * @param {Object} tagsRegExp + * @returns {Array} + */ +function extractFilePaths(content, targetPath, opt, tagsRegExp) { + var files = []; + var tagMatches; + + // get all start matches + tagMatches = content.match(tagsRegExp.start); + if (tagMatches) { + tagMatches.forEach(function(tagMatch){ + var fileUrl = tagsRegExp.startex.exec(tagMatch)[1]; + var filePath = setFullPath(targetPath, opt.prefix + fileUrl); + try { + var fileContent = stripBomBuf(fs.readFileSync(filePath)); + files.push({ + file: new File({ + path: filePath, + cwd: __dirname, + base: path.resolve(__dirname, 'expected', path.dirname(filePath)), + contents: fileContent + }), + tags: getRegExpTags(opt, fileUrl) + }); + } catch (e) { + if (opt.ignoreError) { + log(red(filePath + ' not found.')); + } else { + throw error(filePath + ' not found.'); + } + } + // reset the regex + tagsRegExp.startex.lastIndex = 0; + }); + } + return files; +} + +///////////////////////////////////// +// HELPER FUNCTIONS +///////////////////////////////////// +/** + * @param str + * @returns {*} + */ +function getLeadingWhitespace(str) { + return str.match(LEADING_WHITESPACE_REGEXP)[0]; +} + +/** + * @param options + * @param prop + * @param defaultValue + * @returns {*} + */ +function defaults(options, prop, defaultValue) { + return options[prop] || defaultValue; +} + +/** + * @param options + * @param prop + * @param defaultVal + * @returns {boolean} + */ +function bool(options, prop, defaultVal) { + return typeof options[prop] === 'undefined' ? defaultVal : Boolean(options[prop]); +} + +/** + * @param message + * @returns {*} + */ +function error(message) { + return new PluginError(PLUGIN_NAME, message); +} + +/** + * @param message + */ +function log(message) { + fancyLog(magenta(PLUGIN_NAME), message); +} + +/** + * @param targetPath + * @param file + */ +function setFullPath(targetPath, file) { + var base = path.dirname(targetPath); + + return path.resolve(base, file); +} diff --git a/src/index.spec.js b/src/index.spec.js index 334fe13..23b5bf9 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -1,175 +1,181 @@ -'use strict'; - -var fs = require('fs'); -var injectPartials = require('../.'); -var gutil = require('gulp-util'); -var es = require('event-stream'); -var should = require('should'); -var path = require('path'); - -describe('gulp-inject-partials', function(){ - var log; - var logOutput = []; - - beforeEach(function () { - log = gutil.log; - logOutput = []; - gutil.log = function () { - logOutput.push(arguments); - }; - }); - - afterEach(function () { - gutil.log = log; - }); - - it('should inject single partial', function (done){ - var stream = src(['template1.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template1.html'], done); - }); - it('should inject two partials', function (done){ - var stream = src(['template2.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template2.html'], done); - }); - it('should inject partials and remove tags', function (done){ - var stream = src(['template2.html'], {read: true}) - .pipe(injectPartials({removeTags: true})); - - streamShouldContain(stream, ['template22.html'], done); - }); - it('should inject nested partials', function(done){ - var stream = src(['template3.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template3.html'], done); - }); - it('should inject single partial with custom tags', function (done){ - var stream = src(['template4.html'], {read: true}) - .pipe(injectPartials({ - start: "<##:{{path}}>", - end: "" - })); - - streamShouldContain(stream, ['template4.html'], done); - }); - it('should throw exception if partial is not found', function (done){ - var stream = src(['template5.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template4.html'], done, /not found/); - }); - it('should throw exception if end tag is missing', function (done){ - var stream = src(['template6.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template4.html'], done, /Missing end tag for start tag/); - }); - it('should throw exception if circular partials found', function (done){ - var stream = src(['template7.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template4.html'], done, /Circular definition found/); - }); - it('should remove any content between tags during the injection', function(done){ - var stream = src(['template8.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['template3.html'], done); - }); - it('should not produce log output if quiet option is set', function (done) { - var stream = src(['template1.html'], {read: true}) - .pipe(injectPartials({quiet: true})); - - stream.on('data', function () {}); - - stream.on('end', function () { - logOutput.should.have.length(0); - done(); - }); - }); - it('should produce log output if quiet option is not set', function (done) { - var stream = src(['template1.html'], {read: true}) - .pipe(injectPartials({quiet: false})); - - stream.on('data', function () {}); - - stream.on('end', function () { - logOutput.should.have.length(1); - done(); - }); - }); - it('should use prefix for loading partials from common path', function (done){ - var stream = src(['template9.html'], {read: true}) - .pipe(injectPartials({prefix: 'partials/'})); - - streamShouldContain(stream, ['template9.html'], done); - }); - it('should inject single partial with UTF-8 BOM', function (done){ - var stream = src(['templateBOM.html'], {read: true}) - .pipe(injectPartials()); - - streamShouldContain(stream, ['templateBOM.html'], done); - }); -}); - -// helpers -function src(files, opt) { - opt = opt || {}; - var stream = es.readArray(files.map(function (file) { - return fixture(file, opt.read); - })); - return stream; -} - -// get expected file -function expectedFile(file) { - var filepath = path.resolve(__dirname, 'expected', file); - return new gutil.File({ - path: filepath, - cwd: __dirname, - base: path.resolve(__dirname, 'expected', path.dirname(file)), - contents: fs.readFileSync(filepath) - }); -} - -// get fixture -function fixture(file, read) { - var filepath = path.resolve(__dirname, 'fixtures', file); - return new gutil.File({ - path: filepath, - cwd: __dirname, - base: path.resolve(__dirname, 'fixtures', path.dirname(file)), - contents: read ? fs.readFileSync(filepath) : null - }); -} - -function streamShouldContain(stream, files, done, errRegexp) { - var received = 0; - - stream.on('error', function (err) { - err.message.should.match(errRegexp); - done(); - }); - - var contents = files.map(function (file) { - return String(expectedFile(file).contents); - }); - stream.on('data', function (newFile) { - should.exist(newFile); - should.exist(newFile.contents); - - if (contents.length === 1) { - String(newFile.contents).should.equal(contents[0]); - } else { - contents.should.containEql(String(newFile.contents)); - } - - if (++received === files.length) { - done(); - } - }); -} \ No newline at end of file +'use strict'; + +//2018-01-02 sc mod: gulp-util deprecation +//2018-01-02 sc mod: require fancy-log to replace gutil.log +//2018-01-02 sc mod: require vinyl to replace gutil.File + +var fs = require('fs'); +var injectPartials = require('../.'); +//var gutil = require('gulp-util'); +var fancyLog =require('fancy-log'); +var File = require('vinyl'); +var es = require('event-stream'); +var should = require('should'); +var path = require('path'); + +describe('gulp-inject-partials', function(){ + var log; + var logOutput = []; + + beforeEach(function () { + log = fancyLog.log; + logOutput = []; + fancyLog.log = function () { + logOutput.push(arguments); + }; + }); + + afterEach(function () { + fancyLog.log = log; + }); + + it('should inject single partial', function (done){ + var stream = src(['template1.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template1.html'], done); + }); + it('should inject two partials', function (done){ + var stream = src(['template2.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template2.html'], done); + }); + it('should inject partials and remove tags', function (done){ + var stream = src(['template2.html'], {read: true}) + .pipe(injectPartials({removeTags: true})); + + streamShouldContain(stream, ['template22.html'], done); + }); + it('should inject nested partials', function(done){ + var stream = src(['template3.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template3.html'], done); + }); + it('should inject single partial with custom tags', function (done){ + var stream = src(['template4.html'], {read: true}) + .pipe(injectPartials({ + start: "<##:{{path}}>", + end: "" + })); + + streamShouldContain(stream, ['template4.html'], done); + }); + it('should throw exception if partial is not found', function (done){ + var stream = src(['template5.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template4.html'], done, /not found/); + }); + it('should throw exception if end tag is missing', function (done){ + var stream = src(['template6.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template4.html'], done, /Missing end tag for start tag/); + }); + it('should throw exception if circular partials found', function (done){ + var stream = src(['template7.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template4.html'], done, /Circular definition found/); + }); + it('should remove any content between tags during the injection', function(done){ + var stream = src(['template8.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['template3.html'], done); + }); + it('should not produce log output if quiet option is set', function (done) { + var stream = src(['template1.html'], {read: true}) + .pipe(injectPartials({quiet: true})); + + stream.on('data', function () {}); + + stream.on('end', function () { + logOutput.should.have.length(0); + done(); + }); + }); + it('should produce log output if quiet option is not set', function (done) { + var stream = src(['template1.html'], {read: true}) + .pipe(injectPartials({quiet: false})); + + stream.on('data', function () {}); + + stream.on('end', function () { + logOutput.should.have.length(1); + done(); + }); + }); + it('should use prefix for loading partials from common path', function (done){ + var stream = src(['template9.html'], {read: true}) + .pipe(injectPartials({prefix: 'partials/'})); + + streamShouldContain(stream, ['template9.html'], done); + }); + it('should inject single partial with UTF-8 BOM', function (done){ + var stream = src(['templateBOM.html'], {read: true}) + .pipe(injectPartials()); + + streamShouldContain(stream, ['templateBOM.html'], done); + }); +}); + +// helpers +function src(files, opt) { + opt = opt || {}; + var stream = es.readArray(files.map(function (file) { + return fixture(file, opt.read); + })); + return stream; +} + +// get expected file +function expectedFile(file) { + var filepath = path.resolve(__dirname, 'expected', file); + return new File({ + path: filepath, + cwd: __dirname, + base: path.resolve(__dirname, 'expected', path.dirname(file)), + contents: fs.readFileSync(filepath) + }); +} + +// get fixture +function fixture(file, read) { + var filepath = path.resolve(__dirname, 'fixtures', file); + return new File({ + path: filepath, + cwd: __dirname, + base: path.resolve(__dirname, 'fixtures', path.dirname(file)), + contents: read ? fs.readFileSync(filepath) : null + }); +} + +function streamShouldContain(stream, files, done, errRegexp) { + var received = 0; + + stream.on('error', function (err) { + err.message.should.match(errRegexp); + done(); + }); + + var contents = files.map(function (file) { + return String(expectedFile(file).contents); + }); + stream.on('data', function (newFile) { + should.exist(newFile); + should.exist(newFile.contents); + + if (contents.length === 1) { + String(newFile.contents).should.equal(contents[0]); + } else { + contents.should.containEql(String(newFile.contents)); + } + + if (++received === files.length) { + done(); + } + }); +}