├── .gitattributes ├── examples ├── sync-without-use.js ├── sync.js ├── async-without-use.js ├── async-string.js ├── async-array.js ├── inherit.js ├── stream.js └── instantiate.js ├── .jshintrc ├── .gitignore ├── package.json ├── test ├── fixtures │ └── LICENSE-MIT └── test.js ├── LICENSE ├── iterators.js ├── .verb.md ├── index.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | *.* text eol=lf 3 | *.css text eol=lf 4 | *.html text eol=lf 5 | *.js text eol=lf 6 | *.json text eol=lf 7 | *.less text eol=lf 8 | *.md text eol=lf 9 | *.yml text eol=lf 10 | 11 | *.jpg binary 12 | *.gif binary 13 | *.png binary 14 | *.jpeg binary -------------------------------------------------------------------------------- /examples/sync-without-use.js: -------------------------------------------------------------------------------- 1 | var Plugins = require('..'); 2 | var plugins = new Plugins(); 3 | 4 | var a = function(val) { 5 | return val + 'a'; 6 | }; 7 | var b = function(val) { 8 | return val + 'b'; 9 | }; 10 | var c = function(val) { 11 | return val + 'c'; 12 | }; 13 | 14 | console.log(plugins.run('alphabet-', [a, b, c])); 15 | //=> 'alphabet-abc' -------------------------------------------------------------------------------- /examples/sync.js: -------------------------------------------------------------------------------- 1 | var Plugins = require('..'); 2 | var plugins = new Plugins(); 3 | 4 | plugins 5 | .use(function (str) { 6 | return str + 'a'; 7 | }) 8 | .use(function (str) { 9 | return str + 'b'; 10 | }) 11 | .use(function (str) { 12 | return str + 'c'; 13 | }); 14 | 15 | console.log(plugins.run('alphabet-')); 16 | //=> 'alphabet-abc' -------------------------------------------------------------------------------- /examples/async-without-use.js: -------------------------------------------------------------------------------- 1 | var Plugins = require('..'); 2 | var plugins = new Plugins(); 3 | 4 | var a = function (str, next) { 5 | next(null, str + 'a'); 6 | }; 7 | var b = function (str, next) { 8 | next(null, str + 'b'); 9 | }; 10 | var c = function (str, next) { 11 | next(null, str + 'c'); 12 | }; 13 | 14 | plugins.run('alphabet-', [a, b, c], function (err, str) { 15 | console.log(str); //=> 'alphabet-abc' 16 | }); -------------------------------------------------------------------------------- /examples/async-string.js: -------------------------------------------------------------------------------- 1 | var Plugins = require('..'); 2 | var plugins = new Plugins(); 3 | 4 | plugins 5 | .use(function (val, next) { 6 | next(null, val += 'a'); 7 | }) 8 | .use(function (val, next) { 9 | next(null, val += 'b'); 10 | }) 11 | .use(function (val, next) { 12 | next(null, val += 'c'); 13 | }); 14 | 15 | plugins.run('alphabet-', function (err, res) { 16 | console.log(res); //=> 'alphabet-abc' 17 | }); -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "boss": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "eqnull": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noarg": true, 11 | "node": true, 12 | "sub": true, 13 | "undef": true, 14 | "unused": true, 15 | "globals": { 16 | "define": true, 17 | "before": true, 18 | "after": true, 19 | "describe": true, 20 | "it": true 21 | } 22 | } -------------------------------------------------------------------------------- /examples/async-array.js: -------------------------------------------------------------------------------- 1 | var Plugins = require('..'); 2 | var plugins = new Plugins(); 3 | 4 | plugins 5 | .use(function (val, next) { 6 | next(null, val.concat('a')); 7 | }) 8 | .use(function (val, next) { 9 | next(null, val.concat('b')); 10 | }) 11 | .use(function (val, next) { 12 | next(null, val.concat('c')); 13 | }); 14 | 15 | plugins.run(['alphabet-'], function (err, res) { 16 | console.log(res); //=> 'alphabet-abc' 17 | }); -------------------------------------------------------------------------------- /examples/inherit.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var Plugins = require('..'); 3 | var plugins = new Plugins(); 4 | 5 | /** 6 | * Create your app and inherit `Plugins` 7 | */ 8 | 9 | function App() { 10 | Plugins.call(this); 11 | this.tasks = {}; 12 | } 13 | util.inherits(App, Plugins); 14 | 15 | /** 16 | * Useage 17 | */ 18 | 19 | var app = new App(); 20 | 21 | app.use(function (str) { 22 | return str + 'a'; 23 | }) 24 | .use(function (str) { 25 | return str + 'b'; 26 | }) 27 | .use(function (str) { 28 | return str + 'c'; 29 | }); 30 | 31 | console.log(app.run('alphabet-')); 32 | //=> 'alphabet-abc' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.csv 3 | *.dat 4 | *.diff 5 | *.err 6 | *.gz 7 | *.log 8 | *.orig 9 | *.out 10 | *.pid 11 | *.rej 12 | *.seed 13 | *.swo 14 | *.swp 15 | *.vi 16 | *.yo-rc.json 17 | *.zip 18 | *~ 19 | .ruby-version 20 | lib-cov 21 | 22 | # OS or Editor folders 23 | *.esproj 24 | *.sublime-project 25 | *.sublime-workspace 26 | ._* 27 | .cache 28 | .DS_Store 29 | .idea 30 | .project 31 | .settings 32 | .tmproj 33 | nbproject 34 | Thumbs.db 35 | 36 | # Komodo 37 | *.komodoproject 38 | .komodotools 39 | 40 | # grunt-html-validation 41 | validation-status.json 42 | validation-report.json 43 | 44 | # Vendor packages 45 | node_modules 46 | bower_components 47 | vendor 48 | 49 | # General folders and files to ignore 50 | _gh_pages 51 | tmp 52 | temp 53 | TODO.md -------------------------------------------------------------------------------- /examples/stream.js: -------------------------------------------------------------------------------- 1 | var es = require('event-stream'); 2 | var Plugins = require('..'); 3 | var plugins = new Plugins(); 4 | 5 | plugins 6 | .use(es.through(function (str) { 7 | this.emit('data', str + 'a'); 8 | })) 9 | .use(es.through(function (str) { 10 | this.emit('data', str + 'b'); 11 | })) 12 | .use(es.through(function (str) { 13 | this.emit('data', str + 'c'); 14 | })); 15 | 16 | var input = es.through(); 17 | var output = es.through(function (str) { 18 | console.log(str); 19 | this.emit('data', str); 20 | }, function () { 21 | this.emit('end'); 22 | }); 23 | 24 | output.on('end', function (err) { 25 | if (err) console.log(err); 26 | }); 27 | 28 | input 29 | .pipe(plugins.pipeline()) 30 | .pipe(output); 31 | 32 | input.write('alphabet-'); 33 | input.end(); -------------------------------------------------------------------------------- /examples/instantiate.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var Plugins = require('..'); 3 | 4 | /** 5 | * Create your app and inherit `Plugins` 6 | */ 7 | 8 | function App() { 9 | this.plugins = new Plugins(); 10 | this.tasks = {}; 11 | } 12 | util.inherits(App, Plugins); 13 | 14 | App.prototype.use = function() { 15 | return this.plugins.use.apply(this.plugins, arguments); 16 | }; 17 | 18 | App.prototype.run = function() { 19 | return this.plugins.run.apply(this.plugins, arguments); 20 | }; 21 | 22 | /** 23 | * Useage 24 | */ 25 | 26 | var app = new App(); 27 | 28 | app.use(function (str) { 29 | return str + 'a'; 30 | }) 31 | .use(function (str) { 32 | return str + 'b'; 33 | }) 34 | .use(function (str) { 35 | return str + 'c'; 36 | }); 37 | 38 | console.log(app.run('alphabet-')); 39 | //=> 'alphabet-abc' -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugins", 3 | "description": "Run a value through a plugin stack.", 4 | "version": "0.4.2", 5 | "homepage": "https://github.com/jonschlinkert/plugins", 6 | "author": "Jon Schlinkert (https://github.com/jonschlinkert)", 7 | "repository": "jonschlinkert/plugins", 8 | "bugs": { 9 | "url": "https://github.com/jonschlinkert/plugins/issues" 10 | }, 11 | "license": "MIT", 12 | "files": [ 13 | "index.js", 14 | "iterators.js" 15 | ], 16 | "main": "index.js", 17 | "engines": { 18 | "node": ">=0.10.0" 19 | }, 20 | "scripts": { 21 | "test": "mocha" 22 | }, 23 | "devDependencies": { 24 | "fs-utils": "^0.6.0", 25 | "mocha": "^2.2.5", 26 | "should": "^7.0.4" 27 | }, 28 | "dependencies": { 29 | "async-array-reduce": "^0.1.0", 30 | "event-stream": "^3.3.1" 31 | }, 32 | "keywords": [ 33 | "extension", 34 | "extensions", 35 | "helper", 36 | "helpers", 37 | "middleware", 38 | "use", 39 | "plugin", 40 | "fn", 41 | "fns", 42 | "plugins" 43 | ], 44 | "verb": { 45 | "related": { 46 | "list": "async-array-reduce" 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /test/fixtures/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Jon Schlinkert, contributors. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014, 2015 Jon Schlinkert. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /iterators.js: -------------------------------------------------------------------------------- 1 | var reduce = require('async-array-reduce'); 2 | var es = require('event-stream'); 3 | 4 | /** 5 | * Expose `iterators` 6 | */ 7 | 8 | var iterators = module.exports; 9 | 10 | /** 11 | * Async iterator 12 | */ 13 | 14 | iterators.async = function asyncIterator(val) { 15 | var args = [].slice.call(arguments); 16 | var fns = this.fns; 17 | var cb = args.pop(); 18 | 19 | // if the second arg is an array, 20 | // assume it's a plugin array 21 | if (Array.isArray(args[1])) { 22 | fns = fns.concat(args.pop()); 23 | } 24 | 25 | var isArray = Array.isArray(args[0]); 26 | var self = this, i = 0; 27 | 28 | return reduce(fns, args, function (acc, fn, next) { 29 | acc = arrayify(acc); 30 | if (isArray && i > 0) acc = [acc]; 31 | i++; 32 | fn.apply(self, acc.concat(next)); 33 | }.bind(this), cb); 34 | }; 35 | 36 | /** 37 | * Sync iterator 38 | */ 39 | 40 | iterators.sync = function syncIterator() { 41 | var args = [].slice.call(arguments); 42 | var fns = this.fns; 43 | 44 | if (Array.isArray(args[1])) { 45 | fns = fns.concat(args.pop()); 46 | } 47 | 48 | return fns.reduce(function (acc, fn) { 49 | return fn.apply(this, arrayify(acc)); 50 | }, args); 51 | }; 52 | 53 | /** 54 | * Stream iterator 55 | * TODO: this needs to be updated to work like an iterator. 56 | * currently it works like a plugin itself. 57 | */ 58 | 59 | iterators.stream = function streamIterator() { 60 | var args = [].slice.call(arguments); 61 | var fns = this.fns; 62 | 63 | if (Array.isArray(args[0])) { 64 | fns = fns.concat(args.unshift()); 65 | } 66 | 67 | var len = fns.length, i = -1; 68 | var pipeline = []; 69 | 70 | while (++i < len) { 71 | var fn = fns[i]; 72 | // if stream, push into pipeline 73 | if (isStream(fn)) { 74 | pipeline.push(fn); 75 | continue; 76 | } 77 | // otherwise, call the function and pass in the args 78 | // expect a stream to be returned to push onto the pipeline 79 | try { 80 | pipeline.push(fn.apply(this, args)); 81 | } catch (err) { 82 | throw err; 83 | } 84 | } 85 | return es.pipe.apply(es, pipeline); 86 | }; 87 | 88 | /** 89 | * Utilities 90 | */ 91 | 92 | function isStream(obj) { 93 | return typeof obj === 'object' && obj.pipe && isFunction(obj.pipe); 94 | } 95 | 96 | function isFunction(val) { 97 | return typeof val === 'function'; 98 | } 99 | 100 | function arrayify(val) { 101 | return Array.isArray(val) ? val : [val]; 102 | } 103 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | # {%= name %} {%= badge("fury") %} 2 | 3 | > {%= description %} 4 | 5 | ## Install 6 | {%= include("install-npm", {save: true}) %} 7 | 8 | See [the examples](examples/). 9 | 10 | ## Table of contents 11 | 12 | 13 | ## Docs 14 | 15 | See [the examples](examples/). 16 | 17 | ## Creating plugins 18 | 19 | > A plugin can take any arguments and **must return a function**. 20 | 21 | ### sync 22 | 23 | Plugins just return a value. 24 | 25 | **Example:** 26 | 27 | ```js 28 | var plugins = new Plugins(); 29 | 30 | plugins 31 | .use(function (str) { 32 | return str + 'a'; 33 | }) 34 | .use(function (str) { 35 | return str + 'b'; 36 | }) 37 | .use(function (str) { 38 | return str + 'c'; 39 | }); 40 | 41 | console.log(plugins.run('alphabet-')); 42 | //=> 'alphabet-abc' 43 | ``` 44 | 45 | ### async 46 | 47 | Pass `next` as the last argument to run plugins asynchronously. 48 | 49 | **Example:** 50 | 51 | ```js 52 | var plugins = new Plugins(); 53 | 54 | plugins 55 | .use(function (str, next) { 56 | next(null, str + 'a'); 57 | }) 58 | .use(function (str, next) { 59 | next(null, str + 'b'); 60 | }) 61 | .use(function (str, next) { 62 | next(null, str + 'c'); 63 | }); 64 | 65 | plugins.run('alphabet-', function (err, str) { 66 | console.log(str); //=> 'alphabet-abc' 67 | }); 68 | ``` 69 | 70 | ### Directly run plugins 71 | 72 | To run plugins **without** `.use()`, pass an array of functions as a section argument to `.run()`. 73 | 74 | **sync example:** 75 | 76 | ```js 77 | var plugins = new Plugins(); 78 | 79 | var a = function(val) { 80 | return val + 'a'; 81 | }; 82 | var b = function(val) { 83 | return val + 'b'; 84 | }; 85 | var c = function(val) { 86 | return val + 'c'; 87 | }; 88 | 89 | console.log(plugins.run('alphabet-', [a, b, c])); 90 | //=> 'alphabet-abc' 91 | ``` 92 | 93 | **async example:** 94 | 95 | ```js 96 | var plugins = new Plugins(); 97 | 98 | var a = function (str, next) { 99 | next(null, str + 'a'); 100 | }; 101 | var b = function (str, next) { 102 | next(null, str + 'b'); 103 | }; 104 | var c = function (str, next) { 105 | next(null, str + 'c'); 106 | }; 107 | 108 | plugins.run('alphabet-', [a, b, c], function (err, str) { 109 | console.log(str); //=> 'alphabet-abc' 110 | }); 111 | ``` 112 | 113 | ## API 114 | 115 | See [the examples](examples/). 116 | 117 | {%= apidocs("index.js") %} 118 | 119 | ## Related projects 120 | {%= related(verb.related.list, {remove: name}) %} 121 | 122 | ## Running tests 123 | {%= include("tests") %} 124 | 125 | ## Contributing 126 | {%= include("contributing") %} 127 | 128 | ## Author 129 | {%= include("author") %} 130 | 131 | ## License 132 | {%= copyright() %} 133 | {%= license() %} 134 | 135 | *** 136 | 137 | {%= include("footer") %} 138 | {%= reflinks(["verb"]) %} 139 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * plugins 3 | * 4 | * Copyright (c) 2014 Jon Schlinkert, contributors. 5 | * Licensed under the MIT License 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var iterators = require('./iterators'); 11 | 12 | /** 13 | * Initialize `Plugins` 14 | * 15 | * ```js 16 | * var Plugins = require('plugins'); 17 | * var plugins = new Plugins(); 18 | * ``` 19 | * @constructor 20 | * @api public 21 | */ 22 | 23 | function Plugins(options) { 24 | if (!(this instanceof Plugins)) { 25 | return new Plugins(options); 26 | } 27 | this.iterators = {}; 28 | this.fns = []; 29 | this.init(); 30 | } 31 | 32 | /** 33 | * Register default iterators 34 | */ 35 | 36 | Plugins.prototype.init = function() { 37 | this.iterator('async', iterators.async.bind(this)); 38 | this.iterator('stream', iterators.stream.bind(this)); 39 | this.iterator('sync', iterators.sync.bind(this)); 40 | }; 41 | 42 | /** 43 | * Add a plugin `fn` to the `plugins` stack. 44 | * 45 | * ```js 46 | * plugins 47 | * .use(foo) 48 | * .use(bar) 49 | * .use(baz) 50 | * ``` 51 | * 52 | * @param {Function} `fn` Plugin function to add to the `plugins` stack. 53 | * @return {Object} `Plugins` to enable chaining. 54 | * @api public 55 | */ 56 | 57 | Plugins.prototype.use = function (fn) { 58 | this.fns.push(fn); 59 | return this; 60 | }; 61 | 62 | /** 63 | * Call each `fn` in the `plugins` stack 64 | * to iterate over `val`. 65 | * 66 | * ```js 67 | * plugins.run(value) 68 | * ``` 69 | * @param {Array|Object|String} `val` The value to iterate over. 70 | * @api public 71 | */ 72 | 73 | Plugins.prototype.run = function () { 74 | var last = arguments[arguments.length - 1]; 75 | var type = isFunction(last) ? 'async' : 'sync'; 76 | return this.iterators[type].apply(this, arguments); 77 | }; 78 | 79 | /** 80 | * Register an iterator `fn` by its `type`. 81 | * 82 | * @param {String} `type` The iterator type. 83 | * @param {Function} `fn` Iterator function 84 | * @api public 85 | */ 86 | 87 | Plugins.prototype.iterator = function(type, fn) { 88 | this.iterators[type] = fn; 89 | return this; 90 | }; 91 | 92 | /** 93 | * Add each plugin to a pipeline to be used with streams. 94 | * Plugins must either be a stream or a function that returns a stream. 95 | * 96 | * ```js 97 | * var pipeline = plugins.pipeline(plugin()); 98 | * ``` 99 | * @param {Array|Object|String} `val` The value to iterate over. 100 | * @api public 101 | */ 102 | 103 | Plugins.prototype.pipeline = function() { 104 | return this.iterators.stream.apply(this, arguments); 105 | }; 106 | 107 | /** 108 | * Utilities 109 | */ 110 | 111 | function isFunction(val) { 112 | return typeof val === 'function'; 113 | } 114 | 115 | /** 116 | * Expose `Plugins` 117 | */ 118 | 119 | module.exports = Plugins; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plugins [![NPM version](https://badge.fury.io/js/plugins.svg)](http://badge.fury.io/js/plugins) 2 | 3 | > Run a value through a plugin stack. 4 | 5 | ## Install 6 | 7 | Install with [npm](https://www.npmjs.com/) 8 | 9 | ```sh 10 | $ npm i plugins --save 11 | ``` 12 | 13 | See [the examples](examples/). 14 | 15 | ## Table of contents 16 | 17 | 18 | 19 | * [Docs](#docs) 20 | * [Creating plugins](#creating-plugins) 21 | - [sync](#sync) 22 | - [async](#async) 23 | - [Directly run plugins](#directly-run-plugins) 24 | * [API](#api) 25 | * [Related projects](#related-projects) 26 | * [Running tests](#running-tests) 27 | * [Contributing](#contributing) 28 | * [Author](#author) 29 | * [License](#license) 30 | 31 | _(Table of contents generated by [verb])_ 32 | 33 | 34 | 35 | ## Docs 36 | 37 | See [the examples](examples/). 38 | 39 | ## Creating plugins 40 | 41 | > A plugin can take any arguments and **must return a function**. 42 | 43 | ### sync 44 | 45 | Plugins just return a value. 46 | 47 | **Example:** 48 | 49 | ```js 50 | var plugins = new Plugins(); 51 | 52 | plugins 53 | .use(function (str) { 54 | return str + 'a'; 55 | }) 56 | .use(function (str) { 57 | return str + 'b'; 58 | }) 59 | .use(function (str) { 60 | return str + 'c'; 61 | }); 62 | 63 | console.log(plugins.run('alphabet-')); 64 | //=> 'alphabet-abc' 65 | ``` 66 | 67 | ### async 68 | 69 | Pass `next` as the last argument to run plugins asynchronously. 70 | 71 | **Example:** 72 | 73 | ```js 74 | var plugins = new Plugins(); 75 | 76 | plugins 77 | .use(function (str, next) { 78 | next(null, str + 'a'); 79 | }) 80 | .use(function (str, next) { 81 | next(null, str + 'b'); 82 | }) 83 | .use(function (str, next) { 84 | next(null, str + 'c'); 85 | }); 86 | 87 | plugins.run('alphabet-', function (err, str) { 88 | console.log(str); //=> 'alphabet-abc' 89 | }); 90 | ``` 91 | 92 | ### Directly run plugins 93 | 94 | To run plugins **without** `.use()`, pass an array of functions as a section argument to `.run()`. 95 | 96 | **sync example:** 97 | 98 | ```js 99 | var plugins = new Plugins(); 100 | 101 | var a = function(val) { 102 | return val + 'a'; 103 | }; 104 | var b = function(val) { 105 | return val + 'b'; 106 | }; 107 | var c = function(val) { 108 | return val + 'c'; 109 | }; 110 | 111 | console.log(plugins.run('alphabet-', [a, b, c])); 112 | //=> 'alphabet-abc' 113 | ``` 114 | 115 | **async example:** 116 | 117 | ```js 118 | var plugins = new Plugins(); 119 | 120 | var a = function (str, next) { 121 | next(null, str + 'a'); 122 | }; 123 | var b = function (str, next) { 124 | next(null, str + 'b'); 125 | }; 126 | var c = function (str, next) { 127 | next(null, str + 'c'); 128 | }; 129 | 130 | plugins.run('alphabet-', [a, b, c], function (err, str) { 131 | console.log(str); //=> 'alphabet-abc' 132 | }); 133 | ``` 134 | 135 | ## API 136 | 137 | See [the examples](examples/). 138 | 139 | ### [Plugins](index.js#L23) 140 | 141 | Initialize `Plugins` 142 | 143 | **Example** 144 | 145 | ```js 146 | var Plugins = require('plugins'); 147 | var plugins = new Plugins(); 148 | ``` 149 | 150 | ### [.use](index.js#L57) 151 | 152 | Add a plugin `fn` to the `plugins` stack. 153 | 154 | **Params** 155 | 156 | * `fn` **{Function}**: Plugin function to add to the `plugins` stack. 157 | * `returns` **{Object}** `Plugins`: to enable chaining. 158 | 159 | **Example** 160 | 161 | ```js 162 | plugins 163 | .use(foo) 164 | .use(bar) 165 | .use(baz) 166 | ``` 167 | 168 | ### [.run](index.js#L73) 169 | 170 | Call each `fn` in the `plugins` stack to iterate over `val`. 171 | 172 | **Params** 173 | 174 | * `val` **{Array|Object|String}**: The value to iterate over. 175 | 176 | **Example** 177 | 178 | ```js 179 | plugins.run(value) 180 | ``` 181 | 182 | ### [.iterator](index.js#L87) 183 | 184 | Register an iterator `fn` by its `type`. 185 | 186 | **Params** 187 | 188 | * `type` **{String}**: The iterator type. 189 | * `fn` **{Function}**: Iterator function 190 | 191 | ### [.pipeline](index.js#L103) 192 | 193 | Add each plugin to a pipeline to be used with streams. Plugins must either be a stream or a function that returns a stream. 194 | 195 | **Params** 196 | 197 | * `val` **{Array|Object|String}**: The value to iterate over. 198 | 199 | **Example** 200 | 201 | ```js 202 | var pipeline = plugins.pipeline(plugin()); 203 | ``` 204 | 205 | ## Related projects 206 | 207 | [async-array-reduce](https://github.com/jonschlinkert/async-array-reduce): Async reduce. 208 | 209 | ## Running tests 210 | 211 | Install dev dependencies: 212 | 213 | ```sh 214 | $ npm i -d && npm test 215 | ``` 216 | 217 | ## Contributing 218 | 219 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/plugins/issues/new) 220 | 221 | ## Author 222 | 223 | **Jon Schlinkert** 224 | 225 | + [github/jonschlinkert](https://github.com/jonschlinkert) 226 | + [twitter/jonschlinkert](http://twitter.com/jonschlinkert) 227 | 228 | ## License 229 | 230 | Copyright © 2015 Jon Schlinkert 231 | Released under the MIT license. 232 | 233 | *** 234 | 235 | _This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on August 14, 2015._ 236 | 237 | [verb]: https://github.com/assemble/verb -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * plugins 3 | * 4 | * Copyright (c) 2014 Jon Schlinkert, contributors. 5 | * Licensed under the MIT License 6 | */ 7 | 8 | /* deps: mocha */ 9 | var es = require('event-stream'); 10 | var file = require('fs-utils'); 11 | var should = require('should'); 12 | var Plugins = require('..'); 13 | 14 | function fixture(filename) { 15 | return file.readFileSync('test/fixtures/' + filename); 16 | } 17 | 18 | function actual(filename, content) { 19 | return file.writeFileSync('test/actual/' + filename, content); 20 | } 21 | 22 | describe('plugins:', function () { 23 | it('should run a plugin and return the result:', function () { 24 | var plugins = new Plugins(); 25 | 26 | plugins.use(function(val) { 27 | return 'abc-' + val; 28 | }); 29 | 30 | plugins.run('xyz').should.equal('abc-xyz') 31 | }); 32 | 33 | 34 | it('should run a plugin with options:', function () { 35 | var plugins = new Plugins(); 36 | 37 | var abc = function (opts) { 38 | return function(val) { 39 | return 'abc' + opts + val; 40 | } 41 | }; 42 | 43 | plugins.use(abc('|')); 44 | plugins.run('xyz').should.equal('abc|xyz') 45 | }); 46 | 47 | 48 | it('should run a stack of plugins passed directly to `.run()`:', function () { 49 | var plugins = new Plugins(); 50 | 51 | var a = function(val) { 52 | return val + 'a'; 53 | }; 54 | var b = function(val) { 55 | return val + 'b'; 56 | }; 57 | var c = function(val) { 58 | return val + 'c'; 59 | }; 60 | 61 | plugins.run('alphabet-', [a, b, c]).should.equal('alphabet-abc'); 62 | }); 63 | }); 64 | 65 | 66 | describe('plugins.run() async:', function () { 67 | it('should run all of the plugins in a stack on an object:', function (done) { 68 | var plugins = new Plugins(); 69 | 70 | plugins 71 | .use(function (val, next) { 72 | val.a = val.a || 'a' 73 | next(null, val); 74 | }) 75 | .use(function (val, next) { 76 | val.a = val.a + 'b' 77 | next(null, val); 78 | }) 79 | .use(function (val, next) { 80 | val.a = val.a + 'c' 81 | next(null, val); 82 | }); 83 | 84 | plugins.run({a: ''}, function (err, val) { 85 | val.should.eql({a: 'abc'}); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('should run all of the plugins in a stack on a string:', function (done) { 91 | var plugins = new Plugins(); 92 | plugins 93 | .use(function (a, next) { 94 | next(null, a + 'a'); 95 | }) 96 | .use(function (a, next) { 97 | next(null, a + 'b'); 98 | }) 99 | .use(function (a, next) { 100 | next(null, a + 'c'); 101 | }); 102 | 103 | plugins.run('alphabet-', function (err, str) { 104 | str.should.eql('alphabet-abc'); 105 | done(); 106 | }); 107 | }); 108 | 109 | it('should run all of the plugins in a stack on an array:', function (done) { 110 | var plugins = new Plugins(); 111 | 112 | plugins 113 | .use(function (arr, next) { 114 | next(null, arr.concat('a')); 115 | }) 116 | .use(function (arr, next) { 117 | next(null, arr.concat('b')); 118 | }) 119 | .use(function (arr, next) { 120 | next(null, arr.concat('c')); 121 | }); 122 | 123 | plugins.run([], function (err, arr) { 124 | arr.should.eql(['a', 'b', 'c']); 125 | done(); 126 | }); 127 | }); 128 | 129 | it('should run the string through each plugin in the stack:', function (done) { 130 | var plugins = new Plugins(); 131 | 132 | var foo = function(options) { 133 | return function(str, next) { 134 | var re = /[\r\n]/; 135 | next(null, str.split(re).map(function (line, i) { 136 | return '\naaa' + line + 'bbb'; 137 | }).join('')); 138 | }; 139 | }; 140 | 141 | plugins 142 | .use(foo({a: 'b'})) 143 | .use(function (str, next) { 144 | next(null, str + 'ccc'); 145 | }) 146 | .use(function (str, next) { 147 | next(null, str + 'ddd'); 148 | }); 149 | 150 | plugins.run(fixture('LICENSE-MIT'), function (err, str) { 151 | /bbbcccddd$/.test(str).should.equal(true); 152 | done(); 153 | }); 154 | }); 155 | 156 | it('should run a stack of plugins passed directly to `.run()`:', function (done) { 157 | var plugins = new Plugins(); 158 | 159 | var a = function(val, next) { 160 | next(null, val + 'a'); 161 | }; 162 | var b = function(val, next) { 163 | next(null, val + 'b'); 164 | }; 165 | var c = function(val, next) { 166 | next(null, val + 'c'); 167 | }; 168 | 169 | plugins.run('alphabet-', [a, b, c], function (err, val) { 170 | val.should.equal('alphabet-abc'); 171 | done(); 172 | }); 173 | }); 174 | 175 | it('should run a stack of plugins passed directly to `.run()`:', function (done) { 176 | var plugins = new Plugins(); 177 | 178 | var a = function(val, next) { 179 | next(null, val + 'a'); 180 | }; 181 | var b = function(val, next) { 182 | next(null, val + 'b'); 183 | }; 184 | var c = function(val, next) { 185 | next(null, val + 'c'); 186 | }; 187 | 188 | plugins.run('alphabet-', [a], function (err, val) { 189 | val.should.equal('alphabet-a'); 190 | }); 191 | plugins.run('alphabet-', [b], function (err, val) { 192 | val.should.equal('alphabet-b'); 193 | }); 194 | plugins.run('alphabet-', [c], function (err, val) { 195 | val.should.equal('alphabet-c'); 196 | done(); 197 | }); 198 | }); 199 | }); 200 | 201 | 202 | describe('plugins.run() sync:', function () { 203 | it('should run all of the plugins in a stack synchronously:', function () { 204 | var plugins = new Plugins(); 205 | 206 | plugins 207 | .use(function (str) { 208 | return str + 'a'; 209 | }) 210 | .use(function (str) { 211 | return str + 'b'; 212 | }) 213 | .use(function (str) { 214 | return str + 'c'; 215 | }); 216 | 217 | plugins.run('alphabet-').should.equal('alphabet-abc'); 218 | }); 219 | 220 | describe('when a string is passed to plugins.run():', function () { 221 | it('should run the string through each plugin in the stack:', function () { 222 | var plugins = new Plugins(); 223 | 224 | var foo = function(options) { 225 | return function(str) { 226 | var re = /[\r\n]/; 227 | return str.split(re).map(function (line, i) { 228 | return '\naaa' + line + 'bbb'; 229 | }).join(''); 230 | }; 231 | }; 232 | 233 | plugins 234 | .use(foo({a: 'b'})) 235 | .use(function (str) { 236 | return str + 'ccc'; 237 | }) 238 | .use(function (str) { 239 | return str + 'ddd'; 240 | }); 241 | 242 | var str = plugins.run(fixture('LICENSE-MIT')); 243 | /bbbcccddd$/.test(str).should.equal(true); 244 | }); 245 | }); 246 | }); 247 | 248 | describe('plugins.pipeline():', function () { 249 | it('should run a stack of streams:', function (done) { 250 | var plugins = new Plugins(); 251 | plugins 252 | .use(es.through(function (str) { 253 | this.emit('data', str + 'a'); 254 | })) 255 | .use(es.through(function (str) { 256 | this.emit('data', str + 'b'); 257 | })) 258 | .use(es.through(function (str) { 259 | this.emit('data', str + 'c'); 260 | })); 261 | 262 | var input = es.through(); 263 | var output = es.through(function (str) { 264 | str.should.equal('alphabet-abc'); 265 | this.emit('data', str); 266 | }, function () { 267 | this.emit('end'); 268 | }); 269 | 270 | output.on('end', done); 271 | input.pipe(plugins.pipeline()).pipe(output); 272 | 273 | input.write('alphabet-'); 274 | input.end(); 275 | }); 276 | 277 | it('should run a stack of functions that return streams:', function (done) { 278 | var plugins = new Plugins(); 279 | function append (suffix) { 280 | return function (options) { 281 | return es.through(function (str) { 282 | this.emit('data', str + options.delims[0] + suffix + options.delims[1]); 283 | }); 284 | }; 285 | } 286 | 287 | plugins 288 | .use(append('a')) 289 | .use(append('b')) 290 | .use(append('c')); 291 | 292 | var input = es.through(); 293 | var output = es.through(function (str) { 294 | str.should.equal('alphabet- [a] [b] [c]'); 295 | this.emit('data', str); 296 | }, function () { 297 | this.emit('end'); 298 | }); 299 | 300 | output.on('end', done); 301 | 302 | var options = {delims: [' [', ']']}; 303 | 304 | input.pipe(plugins.pipeline(options)).pipe(output); 305 | input.write('alphabet-'); 306 | input.end(); 307 | }); 308 | }); 309 | 310 | 311 | 312 | describe('when a plugin is passed with options:', function () { 313 | it('should run the function and return the result:', function () { 314 | var plugins = new Plugins(); 315 | 316 | var src = function (options) { 317 | return function(filepath) { 318 | var year = new RegExp((new Date).getUTCFullYear()); 319 | var str = fixture(filepath); 320 | return str.replace(year, options.year || ''); 321 | } 322 | }; 323 | 324 | plugins.use(src({year: 'Stardate 3000'})) 325 | 326 | var str = plugins.run('LICENSE-MIT'); 327 | /Stardate/.test(str).should.be.true; 328 | }); 329 | }); 330 | 331 | 332 | describe('when a plugin is passed a file path:', function () { 333 | it('should read the file with the first plugin, then run the string through the rest of the stack:', function () { 334 | var plugins = new Plugins(); 335 | 336 | var src = function (filepath, options) { 337 | return function() { 338 | return options.prepend + file.readFileSync(filepath); 339 | }; 340 | }; 341 | 342 | var append = function (footer) { 343 | return function(str) { 344 | return str + footer; 345 | }; 346 | }; 347 | 348 | var dest = function (filepath) { 349 | return function(str) { 350 | return actual(filepath, str); 351 | }; 352 | }; 353 | 354 | plugins 355 | .use(src('LICENSE', {prepend: 'banner'}), {local: 'options'}) 356 | .use(append('footer.', {footer: 'opts'}), {a: 'b'}) 357 | .use(dest('footer.md')); 358 | 359 | plugins.run({global: 'options'}, {c: 'd'}, {e: 'f'}); 360 | file.exists('test/actual/footer.md').should.be.true; 361 | file.del('test/actual/footer.md'); 362 | }); 363 | }); 364 | 365 | --------------------------------------------------------------------------------