├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '5' 5 | - '4' 6 | - '0.12' 7 | - '0.10' 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Thomas Watson Steen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # patterns 2 | 3 | Match a string against a list of patterns. 4 | 5 | [![build status](https://secure.travis-ci.org/watson/patterns.png)](http://travis-ci.org/watson/patterns) 6 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) 7 | 8 | *The name of this module was previosuly `match-patterns`, but [Pavel 9 | Lang](https://github.com/langpavel) have been generous to give me the 10 | `patterns` name on NPM. If you are looking for the previous module it 11 | have been renamed 12 | [design-patterns](https://www.npmjs.com/package/design-patterns).* 13 | 14 | ## Installation 15 | 16 | ``` 17 | npm install patterns 18 | ``` 19 | 20 | ## Example usage 21 | 22 | Add patterns using the `.add()` function: 23 | 24 | ```js 25 | var patterns = require('patterns')() 26 | 27 | patterns.add('mathias', 'foo') // a pattern can be a string 28 | patterns.add(/(tom|thomas)/, 'bar') // or a RegExp object 29 | patterns.add('anders', 'baz') 30 | 31 | var match = patterns.match('thomas') 32 | 33 | if (match) console.log(match.value) // outputs 'bar' 34 | ``` 35 | 36 | The module can also be seeded with an array of patterns if you don't 37 | care about a 2nd argument that you can supply to the `.add()` function: 38 | 39 | ```js 40 | var patterns = require('patterns')([ 41 | /foo/, 42 | /bar/, 43 | /baz/ 44 | ]) 45 | 46 | if (patterns.match('foobar')) { 47 | console.log('success!') 48 | } 49 | ``` 50 | 51 | ## API 52 | 53 | ### `patterns.add(pattern[, value])` 54 | 55 | Arguments: 56 | 57 | - `pattern` - The pattern as either a string or a RegExp object 58 | - `value` (optional) - Returned as part of the match object when calling 59 | the `.match()` function. Can be of any type 60 | 61 | If the `pattern` is a string it will be matched using the 62 | [murl](https://github.com/mafintosh/murl) module. If the `pattern` is a 63 | RegExp object it will be matched as is. 64 | 65 | ### `patterns.match(target)` 66 | 67 | Arguments: 68 | 69 | - `target` - The string that should be matched against each pattern 70 | 71 | Runs through each pattern in order and returns a match object for the 72 | first match. If no pattern matches `null` is returned. 73 | 74 | ### `patterns.matchAll(target)` 75 | 76 | Arguments 77 | 78 | - `target` - The string that should be matched against each pattern 79 | 80 | Runs through each pattern in order and returns an array of match objects. 81 | If no pattern matches, an empty array is returned. 82 | 83 | #### Example match object 84 | 85 | ```js 86 | { 87 | pattern: '/Users/{name}', // the matched pattern (1st argument to `.add()` function) 88 | target: '/Users/watson', // the target string 89 | params: { // the named parameters from the pattern 90 | name: 'watson' 91 | }, 92 | value: value, // the value (2nd argument to `.add()` function) 93 | next: function () {...} // function to skip this match and continue 94 | } 95 | ``` 96 | 97 | Note that if the pattern is a RegExp object, `params` will be the result 98 | of the native `.match()` function (an array). 99 | 100 | #### The next-function 101 | 102 | The `match.next` function can be used to skip the found match and 103 | continue matching the string against the patterns: 104 | 105 | ```js 106 | patterns.add(/foo/, 1) 107 | patterns.add(/baz/, 2) 108 | patterns.add(/bar/, 3) 109 | 110 | var values = [] 111 | 112 | var match = patterns.match('foobar') 113 | while (match) { 114 | values.push(match.value) 115 | match = match.next() 116 | } 117 | 118 | console.log(values) // [1, 3] 119 | ``` 120 | 121 | ## Example: A complete HTTP router 122 | 123 | In this example the patterns module is used as a simple but powerful 124 | HTTP route matcher: 125 | 126 | ```js 127 | var http = require('http') 128 | var url = require('url') 129 | var patterns = require('patterns')() 130 | 131 | patterns.add('GET /foo', fn1) 132 | patterns.add('GET /foo/{id}', fn2) 133 | patterns.add('POST /foo/{id}', fn3) 134 | 135 | http.createServer(function (req, res) { 136 | var path = url.parse(req.url).pathname 137 | var match = patterns.match(req.method + ' ' + path) 138 | 139 | if (!match) { 140 | res.writeHead(404) 141 | res.end() 142 | return 143 | } 144 | 145 | var fn = match.value // expects the value to be a function 146 | req.params = match.params 147 | 148 | fn(req, res) 149 | }).listen(8080) 150 | ``` 151 | 152 | ## License 153 | 154 | MIT 155 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var murl = require('murl') 4 | 5 | var Patterns = module.exports = function (patterns) { 6 | if (!(this instanceof Patterns)) return new Patterns(patterns) 7 | 8 | this._patterns = (patterns || []).map(function (ptn) { 9 | return [compile(ptn), ptn, undefined] 10 | }) 11 | } 12 | 13 | var compile = function (ptn) { 14 | if (typeof ptn === 'string') return murl(ptn) 15 | return function (target) { 16 | return target.match(ptn) 17 | } 18 | } 19 | 20 | Patterns.prototype.add = function (ptn, val) { 21 | this._patterns.push([compile(ptn), ptn, val]) 22 | } 23 | 24 | Patterns.prototype.match = function (target, index) { 25 | var self = this 26 | var next = function () { 27 | return self.match(target, index + 1) 28 | } 29 | var ptn, match 30 | if (!index) index = 0 31 | for (var l = this._patterns.length; index < l; index++) { 32 | ptn = this._patterns[index] 33 | match = ptn[0](target) 34 | if (match) { 35 | return { 36 | target: target, 37 | pattern: ptn[1], 38 | params: match, 39 | value: ptn[2], 40 | next: next 41 | } 42 | } 43 | } 44 | return null 45 | } 46 | 47 | Patterns.prototype.matchAll = function (target) { 48 | var matches = [] 49 | var ptn, match 50 | for (var i = 0; i < this._patterns.length; i++) { 51 | ptn = this._patterns[i] 52 | match = ptn[0](target) 53 | if (match) { 54 | matches.push({ 55 | target: target, 56 | pattern: ptn[1], 57 | params: match, 58 | value: ptn[2] 59 | }) 60 | } 61 | } 62 | 63 | return matches 64 | } 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "patterns", 3 | "version": "1.0.3", 4 | "description": "Match a string against a list of patterns", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "standard && node test.js" 8 | }, 9 | "keywords": [ 10 | "match", 11 | "matcher", 12 | "pattern", 13 | "regex", 14 | "regexp", 15 | "route", 16 | "router" 17 | ], 18 | "author": "Thomas Watson Steen ", 19 | "license": "MIT", 20 | "dependencies": { 21 | "murl": "^0.4.1" 22 | }, 23 | "devDependencies": { 24 | "standard": "^8.3.0", 25 | "tape": "^4.6.2" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/watson/patterns.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/watson/patterns/issues" 33 | }, 34 | "homepage": "https://github.com/watson/patterns", 35 | "coordinates": [ 36 | 55.6875127, 37 | 12.595903 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var murl = require('murl') 5 | var Patterns = require('./') 6 | 7 | test('no patterns', function (t) { 8 | var p = Patterns() 9 | var match = p.match('foo') 10 | t.equal(match, null) 11 | t.end() 12 | }) 13 | 14 | test('string patterns', function (t) { 15 | var p = Patterns() 16 | p.add('foo', 1) 17 | p.add('bar', 2) 18 | p.add('baz', 3) 19 | var match = p.match('bar') 20 | t.equal(match.target, 'bar') 21 | t.equal(match.pattern, 'bar') 22 | t.deepEqual(match.params, murl('bar')('bar')) 23 | t.equal(match.value, 2) 24 | t.ok(typeof match.next, 'function') 25 | t.end() 26 | }) 27 | 28 | test('murl patterns', function (t) { 29 | var p = Patterns() 30 | p.add('/{hello}/{world}?') 31 | var match = p.match('/foo/bar') 32 | t.deepEqual(match.params, { hello: 'foo', world: 'bar' }) 33 | match = p.match('/foo') 34 | t.deepEqual(match.params, { hello: 'foo', world: undefined }) 35 | t.end() 36 | }) 37 | 38 | test('RegExp patterns', function (t) { 39 | var p = Patterns() 40 | p.add(/foo/, 1) 41 | p.add(/bar/, 2) 42 | p.add(/baz/, 3) 43 | var match = p.match('bar') 44 | t.equal(match.target, 'bar') 45 | t.deepEqual(match.pattern, /bar/) 46 | t.deepEqual(match.params, 'bar'.match(/bar/)) 47 | t.equal(match.value, 2) 48 | t.ok(typeof match.next, 'function') 49 | t.end() 50 | }) 51 | 52 | test('next function', function (t) { 53 | var p = Patterns() 54 | p.add(/foo/, 1) 55 | p.add(/bar/, 2) 56 | p.add(/baz/, 3) 57 | var match = p.match('foobar') 58 | t.equal(match.value, 1) 59 | match = match.next() 60 | t.equal(match.value, 2) 61 | match = match.next() 62 | t.equal(match, null) 63 | t.end() 64 | }) 65 | 66 | test('last next function', function (t) { 67 | var p = Patterns() 68 | p.add(/foo/, 1) 69 | var match = p.match('foo') 70 | t.equal(match.value, 1) 71 | match = match.next() 72 | t.equal(match, null) 73 | t.end() 74 | }) 75 | 76 | test('matchAll', function (t) { 77 | var p = Patterns() 78 | p.add(/foo/, 1) 79 | p.add(/foo/, 2) 80 | p.add(/foo/, 3) 81 | var matches = p.matchAll('foo') 82 | t.equal(matches[0].value, 1) 83 | t.equal(matches[1].value, 2) 84 | t.equal(matches[2].value, 3) 85 | t.end() 86 | }) 87 | 88 | --------------------------------------------------------------------------------