├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── benchmark.js ├── bower.json ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .nyc_output 4 | coverage 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .nyc_output 4 | coverage 5 | test.js 6 | benchmark.js 7 | index-old.js 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Sebastian Raff (https://hobbyquaker.github.io) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mqtt-wildcard 2 | 3 | [![NPM version](https://badge.fury.io/js/mqtt-wildcard.svg)](http://badge.fury.io/js/mqtt-wildcard) 4 | [![Dependency Status](https://img.shields.io/gemnasium/hobbyquaker/mqtt-wildcard.svg?maxAge=2592000)](https://gemnasium.com/github.com/hobbyquaker/mqtt-wildcard) 5 | [![Build Status](https://travis-ci.org/hobbyquaker/mqtt-wildcard.svg?branch=master)](https://travis-ci.org/hobbyquaker/mqtt-wildcard) 6 | [![Coverage Status](https://coveralls.io/repos/github/hobbyquaker/mqtt-wildcard/badge.svg?branch=master)](https://coveralls.io/github/hobbyquaker/mqtt-wildcard?branch=master) 7 | [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) 8 | [![License][mit-badge]][mit-url] 9 | 10 | > Match a MQTT Topic against Wildcards 11 | 12 | 13 | ## Install and Usage 14 | 15 | ``` 16 | $ npm install mqtt-wildcard 17 | ``` 18 | 19 | ```javascript 20 | var mqttWildcard = require('mqtt-wildcard'); 21 | ``` 22 | 23 | Alternatively you can use bower to install mqtt-wildcard, AMD is also supported. 24 | 25 | 26 | ## API 27 | 28 | _array|null_ **mqttWildcard** **(**_string_ **topic,** _string_ **wildcard)** 29 | 30 | Returns `null` if not matched, otherwise an array containing the wildcards contents will be returned. 31 | 32 | Examples: 33 | ```javascript 34 | mqttWildcard('test/foo/bar', 'test/foo/bar'); // [] 35 | mqttWildcard('test/foo/bar', 'test/+/bar'); // ['foo'] 36 | mqttWildcard('test/foo/bar', 'test/#'); // ['foo/bar'] 37 | mqttWildcard('test/foo/bar/baz', 'test/+/#'); // ['foo', 'bar/baz'] 38 | mqttWildcard('test/foo/bar/baz', 'test/+/+/baz'); // ['foo', 'bar'] 39 | 40 | mqttWildcard('test', 'test/#'); // [] 41 | mqttWildcard('test/', 'test/#'); // [''] 42 | 43 | mqttWildcard('test/foo/bar', 'test/+'); // null 44 | mqttWildcard('test/foo/bar', 'test/nope/bar'); // null 45 | ``` 46 | 47 | 48 | ## License 49 | 50 | MIT (c) 2017 [Sebastian Raff](https://github.com/hobbyquaker) 51 | 52 | [mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat 53 | [mit-url]: LICENSE 54 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Benchmark = require('benchmark'); 4 | 5 | const mw = require('./index.js'); 6 | const mqttMatch = require('mqtt-match'); 7 | const mqttPattern = require("mqtt-pattern").exec; 8 | 9 | const wins = { 10 | 'mqtt-wildcard': 0, 11 | 'mqtt-match': 0, 12 | 'mqtt-pattern': 0 13 | }; 14 | 15 | function bench(desc, pattern, topic) { 16 | const suite = new Benchmark.Suite; 17 | suite.add('mqtt-wildcard - ' + desc, function () { 18 | mw(topic, pattern); 19 | }).add('mqtt-pattern - ' + desc, function () { 20 | mqttPattern(pattern, topic); 21 | }).add('mqtt-match - ' + desc, function () { 22 | mqttMatch(pattern, topic); 23 | }).on('cycle', function (event) { 24 | console.log(String(event.target)); 25 | }).on('complete', function () { 26 | const id = this.filter('fastest').map('name')[0].split(' ')[0]; 27 | console.log(' => Fastest is ' + id + '\n'); 28 | wins[id] += 1; 29 | }).run({'async': false}); 30 | } 31 | 32 | bench('match equal depth 4', 'test/test/test/test', 'test/test/test/test'); 33 | bench('match equal depth 8', 'test/test/test/test/test/test/test/test', 'test/test/test/test/test/test/test/test'); 34 | bench( 35 | 'match equal depth 16', 36 | 'test/test/test/test/test/test/test/test/test/test/test/test/test/test/test/test', 37 | 'test/test/test/test/test/test/test/test/test/test/test/test/test/test/test/test' 38 | ); 39 | 40 | bench('match + 4 1', 'test/test/test/+', 'test/test/test/test'); 41 | bench('match + 4 2', 'test/test/+/test', 'test/test/test/test'); 42 | bench('match + 4 3', 'test/test/+/+', 'test/test/test/test'); 43 | bench('match + 4 4', 'test/+/test/test', 'test/test/test/test'); 44 | bench('match + 4 5', 'test/+/test/+', 'test/test/test/test'); 45 | bench('match + 4 6', 'test/+/+/test', 'test/test/test/test'); 46 | bench('match + 4 7', 'test/+/+/+', 'test/test/test/test'); 47 | bench('match + 4 8', '+/test/test/test', 'test/test/test/test'); 48 | bench('match + 4 9', '+/test/test/+', 'test/test/test/test'); 49 | bench('match + 4 10', '+/test/+/test', 'test/test/test/test'); 50 | bench('match + 4 11', '+/test/+/+', 'test/test/test/test'); 51 | bench('match + 4 12', '+/+/test/test', 'test/test/test/test'); 52 | bench('match + 4 13', '+/+/test/+', 'test/test/test/test'); 53 | bench('match + 4 14', '+/+/+/test', 'test/test/test/test'); 54 | bench('match + 4 15', '+/+/+/+', 'test/test/test/test'); 55 | 56 | bench('match # 4 1', '#', 'test/test/test/test'); 57 | bench('match # 4 2', 'test/#', 'test/test/test/test'); 58 | bench('match # 4 3', 'test/test/#', 'test/test/test/test'); 59 | bench('match # 4 4', 'test/test/test/#', 'test/test/test/test'); 60 | 61 | bench('match +# 4 2', 'test/test/+/#', 'test/test/test/test'); 62 | bench('match +# 4 4', 'test/+/test/#', 'test/test/test/test'); 63 | bench('match +# 4 6', 'test/+/+/#', 'test/test/test/test'); 64 | bench('match +# 4 8', '+/test/test/#', 'test/test/test/test'); 65 | bench('match +# 4 10', '+/test/+/#', 'test/test/test/test'); 66 | bench('match +# 4 12', '+/+/test/#', 'test/test/test/test'); 67 | bench('match +# 4 14', '+/+/+/#', 'test/test/test/test'); 68 | 69 | bench('mismatch + 4 1', 'test/test/muh/+', 'test/test/test/test'); 70 | bench('mismatch + 4 4', 'test/+/test/muh', 'test/test/test/test'); 71 | bench('mismatch + 4 5', 'test/+/muh/+', 'test/test/test/test'); 72 | bench('mismatch + 4 6', 'test/+/+/muh', 'test/test/test/test'); 73 | bench('mismatch + 4 2', 'test/muh/+/test', 'test/test/test/test'); 74 | bench('mismatch + 4 3', 'test/muh/+/+', 'test/test/test/test'); 75 | bench('mismatch + 4 7', 'muh/+/+/+', 'test/test/test/test'); 76 | 77 | bench('mismatch +# 4 4', 'test/+/muh/#', 'test/test/test/test'); 78 | bench('mismatch +# 4 8', '+/test/muh/#', 'test/test/test/test'); 79 | bench('mismatch +# 4 12', '+/+/muh/#', 'test/test/test/test'); 80 | bench('mismatch +# 4 2', 'test/muh/+/#', 'test/test/test/test'); 81 | bench('mismatch +# 4 10', '+/muh/+/#', 'test/test/test/test'); 82 | bench('mismatch +# 4 6', 'muh/+/+/#', 'test/test/test/test'); 83 | 84 | console.dir(wins); 85 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-wildcard", 3 | "description": "Match a MQTT Topic against Wildcards", 4 | "main": "index.js", 5 | "authors": [ 6 | "Sebastian Raff " 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "mqtt", 11 | "topic", 12 | "wildcard", 13 | "pattern", 14 | "match" 15 | ], 16 | "homepage": "https://github.com/hobbyquaker/mqtt-wildcard", 17 | "ignore": [ 18 | "**/.*", 19 | ".nyc-output", 20 | "coverage", 21 | "node_modules", 22 | "bower_components", 23 | "test.js", 24 | "benchmark.js", 25 | "index-old.js", 26 | "npm-debug.log", 27 | ".travis.yml" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* global define, window */ 2 | (function () { 3 | 'use strict'; 4 | function mqttWildcard(topic, wildcard) { 5 | if (topic === wildcard) { 6 | return []; 7 | } else if (wildcard === '#') { 8 | return [topic]; 9 | } 10 | 11 | var res = []; 12 | 13 | var t = String(topic).split('/'); 14 | var w = String(wildcard).split('/'); 15 | 16 | var i = 0; 17 | for (var lt = t.length; i < lt; i++) { 18 | if (w[i] === '+') { 19 | res.push(t[i]); 20 | } else if (w[i] === '#') { 21 | res.push(t.slice(i).join('/')); 22 | return res; 23 | } else if (w[i] !== t[i]) { 24 | return null; 25 | } 26 | } 27 | 28 | if (w[i] === '#') { 29 | i += 1; 30 | } 31 | 32 | return (i === w.length) ? res : null; 33 | } 34 | 35 | /* istanbul ignore next */ 36 | if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { 37 | module.exports = mqttWildcard; 38 | } else if (typeof define === 'function' && define.amd) { 39 | define([], function () { 40 | return mqttWildcard(); 41 | }); 42 | } else { 43 | window.mqttWildcard = mqttWildcard; 44 | } 45 | })(); 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtt-wildcard", 3 | "version": "3.0.9", 4 | "description": "Match a MQTT Topic against Wildcards", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "camo-purge ; xo && nyc mocha ./test.js && nyc report --reporter=text-lcov | coveralls --force", 8 | "testonly": "mocha ./test.js", 9 | "lint": "xo", 10 | "lintfix": "xo --fix", 11 | "benchmark": "./benchmark.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/hobbyquaker/mqtt-wildcard.git" 16 | }, 17 | "keywords": [ 18 | "mqtt", 19 | "topic", 20 | "wildcard", 21 | "pattern", 22 | "match" 23 | ], 24 | "author": "Sebastian Raff ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/hobbyquaker/mqtt-wildcard/issues" 28 | }, 29 | "homepage": "https://github.com/hobbyquaker/mqtt-wildcard#readme", 30 | "devDependencies": { 31 | "benchmark": "latest", 32 | "camo-purge": "latest", 33 | "coveralls": "latest", 34 | "mocha": "latest", 35 | "mqtt-match": "^1.0.3", 36 | "mqtt-pattern": "^1.2.0", 37 | "nyc": "latest", 38 | "should": "latest", 39 | "xo": "latest" 40 | }, 41 | "xo": { 42 | "space": 4, 43 | "ignore": [ 44 | "test.js", 45 | "benchmark.js" 46 | ], 47 | "esnext": false 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const mw = require('./index.js'); 2 | 3 | const should = require('should'); 4 | 5 | should.Assertion.add('arrayEqual', function (other) { 6 | this.params = { operator: 'to be have same items' }; 7 | 8 | this.obj.forEach(function (item, index) { 9 | //both arrays should at least contain the same items 10 | other[index].should.equal(item); 11 | }); 12 | // both arrays need to have the same number of items 13 | this.obj.length.should.be.equal(other.length); 14 | }); 15 | 16 | 17 | describe('trivial matching', function () { 18 | it('should return the correct array when topic equals wildcard', function () { 19 | mw('test/123', 'test/123').should.arrayEqual([]); 20 | }); 21 | }); 22 | 23 | describe('mismatching', function () { 24 | it('should return null', function () { 25 | should(mw('test/test/test', 'test/test')).equal(null); 26 | }); 27 | it('should return null', function () { 28 | should(mw('test/test/test/test', 'test/test')).equal(null); 29 | }); 30 | it('should return null', function () { 31 | should(mw('test/test', 'test/test/test')).equal(null); 32 | }); 33 | it('should return null', function () { 34 | should(mw('test/test', 'test/test/test/test')).equal(null); 35 | }); 36 | }); 37 | 38 | describe('wildcard # matching', function () { 39 | it('should return the correct array when wildcard is #', function () { 40 | mw('test', '#').should.arrayEqual(['test']); 41 | }); 42 | it('should return the correct array when wildcard is #', function () { 43 | mw('test/test', '#').should.arrayEqual(['test/test']); 44 | }); 45 | it('should return the correct array when wildcard is #', function () { 46 | mw('test/test/test', '#').should.arrayEqual(['test/test/test']); 47 | }); 48 | it('should return the correct array', function () { 49 | mw('test/test', 'test/#').should.arrayEqual(['test']); 50 | }); 51 | it('should return the correct array', function () { 52 | mw('test/test/test', 'test/#').should.arrayEqual(['test/test']); 53 | }); 54 | it('should return the correct array', function () { 55 | mw('test/test/test', 'test/test/#').should.arrayEqual(['test']); 56 | }); 57 | it('should return the correct array', function () { 58 | mw('/', '/#').should.arrayEqual(['']); 59 | }); 60 | it('should return the correct array', function () { 61 | mw('/test', '/#').should.arrayEqual(['test']); 62 | }); 63 | it('should return the correct array', function () { 64 | mw('/test/', '/#').should.arrayEqual(['test/']); 65 | }); 66 | it('should return the correct array', function () { 67 | mw('/test/test', '/#').should.arrayEqual(['test/test']); 68 | }); 69 | it('should return the correct array', function () { 70 | mw('test/', 'test/#').should.arrayEqual(['']); 71 | }); 72 | it('should return correct array', function () { 73 | mw('test', 'test/#').should.arrayEqual([]); 74 | }); 75 | it('should return correct array', function () { 76 | mw('test/test', 'test/test/#').should.arrayEqual([]); 77 | }); 78 | }); 79 | 80 | describe('wildcard # mismatching', function () { 81 | it('should return null', function () { 82 | should(mw('test', '/#')).equal(null); 83 | }); 84 | it('should return null', function () { 85 | should(mw('test/test', 'muh/#')).equal(null); 86 | }); 87 | it('should return null', function () { 88 | should(mw('', 'muh/#')).equal(null); 89 | }); 90 | }); 91 | 92 | describe('wildcard + matching', function () { 93 | it('should return the correct array', function () { 94 | mw('test', '+').should.arrayEqual(['test']); 95 | }); 96 | it('should return the correct array', function () { 97 | mw('test/', 'test/+').should.arrayEqual(['']); 98 | }); 99 | it('should return the correct array', function () { 100 | mw('test/test', 'test/+').should.arrayEqual(['test']); 101 | }); 102 | it('should return the correct array', function () { 103 | mw('test/test/test', 'test/+/+').should.arrayEqual(['test', 'test']); 104 | }); 105 | it('should return the correct array', function () { 106 | mw('test/test/test', 'test/+/test').should.arrayEqual(['test']); 107 | }); 108 | }); 109 | 110 | describe('wildcard + mismatching', function () { 111 | it('should return null', function () { 112 | should(mw('test', '/+')).equal(null); 113 | }); 114 | it('should return null', function () { 115 | should(mw('test', 'test/+')).equal(null); 116 | }); 117 | it('should return null', function () { 118 | should(mw('test/test', 'test/test/+')).equal(null); 119 | }); 120 | }); 121 | 122 | describe('wildcard +/# matching', function () { 123 | it('should return the correct array', function () { 124 | mw('test/test', '+/#').should.arrayEqual(['test', 'test']); 125 | }); 126 | it('should return the correct array', function () { 127 | mw('test/test/', '+/test/#').should.arrayEqual(['test', '']); 128 | }); 129 | it('should return the correct array', function () { 130 | mw('test/test/', 'test/+/#').should.arrayEqual(['test', '']); 131 | }); 132 | it('should return the correct array', function () { 133 | mw('test/test/test', '+/test/#').should.arrayEqual(['test', 'test']); 134 | }); 135 | it('should return the correct array', function () { 136 | mw('test/test/test', 'test/+/#').should.arrayEqual(['test', 'test']); 137 | }); 138 | it('should return the correct array', function () { 139 | mw('test/test/test', '+/+/#').should.arrayEqual(['test', 'test', 'test']); 140 | }); 141 | it('should return the correct array', function () { 142 | mw('test/test/test/test', 'test/+/+/#').should.arrayEqual(['test', 'test', 'test']); 143 | }); 144 | it('should return the correct array', function () { 145 | mw('test', '+/#').should.arrayEqual(['test']); 146 | }); 147 | it('should return the correct array', function () { 148 | mw('test/test', 'test/+/#').should.arrayEqual(['test']); 149 | }); 150 | it('should return the correct array', function () { 151 | mw('test/test/test', 'test/+/test/#').should.arrayEqual(['test']); 152 | }); 153 | }); 154 | 155 | describe('wildcard +/# mismatching', function () { 156 | it('should return null', function () { 157 | should(mw('test/foo/test', '+/test/#')).equal(null); 158 | }); 159 | it('should return null', function () { 160 | should(mw('foo/test/test', 'test/+/#')).equal(null); 161 | }); 162 | it('should return null', function () { 163 | should(mw('foo/test/test/test', 'test/+/+/#')).equal(null); 164 | }); 165 | }); 166 | 167 | describe('examples', function () { 168 | it('should return the correct array', function () { 169 | should(mw('test/foo/bar', 'test/+/bar')).arrayEqual(['foo']); 170 | }); 171 | it('should return the correct array', function () { 172 | should(mw('test/foo/bar', 'test/#')).arrayEqual(['foo/bar']); 173 | }); 174 | it('should return the correct array', function () { 175 | should(mw('test/foo/bar/baz', 'test/+/#')).arrayEqual(['foo', 'bar/baz']); 176 | }); 177 | it('should return the correct array', function () { 178 | should(mw('test/foo/bar/baz', 'test/+/+/baz')).arrayEqual(['foo', 'bar']); 179 | }); 180 | it('should return the correct array', function () { 181 | should(mw('test', 'test/#')).arrayEqual([]); 182 | }); 183 | it('should return the correct array', function () { 184 | should(mw('test/', 'test/#')).arrayEqual(['']); 185 | }); 186 | it('should return the correct array', function () { 187 | should(mw('test/foo/bar/baz', 'test/+/+/baz/#')).arrayEqual(['foo', 'bar']); 188 | }); 189 | it('should return null', function () { 190 | should(mw('test/foo/bar', 'test/+')).equal(null); 191 | }); 192 | it('should return null', function () { 193 | should(mw('test/foo/bar', 'test/nope/bar')).equal(null); 194 | }); 195 | }); 196 | --------------------------------------------------------------------------------