├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .npmignore ├── LICENSE.txt ├── README.md ├── dist ├── gex.d.ts ├── gex.js ├── gex.js.map └── gex.min.js ├── gex.ts ├── package.json ├── test ├── gex.test.js ├── jester-tests.js ├── jester-web.js ├── jester.html └── jester.js └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | tmp 2 | dist 3 | test-web 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'eslint:recommended', 3 | env: { 4 | node: true, 5 | es6: true, 6 | es2020: true, 7 | }, 8 | "parserOptions": { 9 | "ecmaVersion": 10 10 | }, 11 | rules: { 12 | 'no-console': 0, 13 | 'no-unused-vars': [2,{args:'none'}] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macos-latest] 19 | node-version: [10.x, 12.x, 14.x, 16.x, 17.x, lts/*] 20 | 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - run: npm install 30 | - run: npm run build --if-present 31 | - run: npm test 32 | 33 | - name: Coveralls 34 | uses: coverallsapp/github-action@master 35 | with: 36 | github-token: ${{ secrets.GITHUB_TOKEN }} 37 | path-to-lcov: ./test/lcov.info 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | test/db 4 | *.old 5 | *.bak 6 | node_modules 7 | README.html 8 | *.off 9 | *-off 10 | .DS_Store 11 | npm-debug.log 12 | coverage 13 | 14 | package-lock.json 15 | yarn.lock 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.off 3 | *-off 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2020 Richard Rodger 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 | # gex 2 | 3 | [![npm version](https://badge.fury.io/js/gex.svg)](https://badge.fury.io/js/gex) 4 | [![Build](https://github.com/rjrodger/gex/workflows/build/badge.svg)](https://github.com/rjrodger/gex/actions?query=workflow%3Abuild) 5 | [![Coverage Status](https://coveralls.io/repos/github/rjrodger/gex/badge.svg?branch=main)](https://coveralls.io/github/rjrodger/gex?branch=main) 6 | [![DeepScan grade](https://deepscan.io/api/teams/5016/projects/13588/branches/232094/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=5016&pid=13588&bid=232094) 7 | [![Maintainability](https://api.codeclimate.com/v1/badges/5def990719578771abb3/maintainability)](https://codeclimate.com/github/rjrodger/gex/maintainability) 8 | [![Dependency Status](https://david-dm.org/rjrodger/gex.svg)](https://david-dm.org/rjrodger/gex) 9 | 10 | 11 | 12 | ## Glob expressions for JavaScript 13 | 14 | *"When regular expressions are just too hard!"* 15 | 16 | Match glob expressions using * and ? against any JavaScript data type. 17 | The character * means match anything of any length, the character ? means match exactly one of any character, 18 | and all other characters match themselves. 19 | 20 | const { Gex } = require('gex') 21 | 22 | Gex('a*').on( 'abc' ) // returns 'abc' 23 | Gex('a*c').on( 'abbbc' ) // returns 'abbbc' 24 | Gex('a?c').on( 'abc' ) // returns 'abc' 25 | 26 | You can also match against objects and arrays: 27 | 28 | Gex('a*').on( ['ab','zz','ac'] ) // returns ['ab','ac'] 29 | Gex('a*').on( {ab:1,zz:2,ac:3} ) // returns {ab:1,ac:3} 30 | 31 | And also match against multiple globs: 32 | 33 | Gex(['a*','b*']).on( 'bx' ) // returns 'bx' 34 | Gex(['a*','b*']).on( ['ax','zz','bx'] ) // returns ['ax','bx'] 35 | 36 | 37 | One of the most useful things you can do with this library is quick 38 | assertions in unit tests. For example if your objects contain dates, 39 | randomly generated unique identifiers, or other data irrelevant for 40 | testing, `Gex` can help you ignore them when you use `JSON.stringify`: 41 | 42 | var entity = {created: new Date().getTime(), name:'foo' } 43 | assert.ok( Gex('{"created":*,"name":"foo"}').on( JSON.stringify(entity) ) ) 44 | 45 | If you need to use globbing on files, here's how apply a glob to a list of files in a folder: 46 | 47 | var fs = require('fs') 48 | fs.readdir('.',function(err,files){ 49 | var pngs = Gex('*.png').on(files) 50 | }) 51 | 52 | And that's it! 53 | 54 | 55 | ## Installation 56 | 57 | npm install gex 58 | 59 | And in your code: 60 | 61 | const { Gex } = require('gex') 62 | 63 | Or clone the git repository: 64 | git clone git://github.com/rjrodger/gex.git 65 | 66 | 67 | This library depends on the excellent underscore module: [underscore](https://github.com/documentcloud/underscore) 68 | 69 | 70 | ## Usage 71 | 72 | The `Gex` object is a function that takes a single argument, the glob 73 | expression. This returns a `Gex` object that has only one function 74 | itself: `on`. The `on` function accepts any JavaScript data type, and operates as follows: 75 | 76 | * strings, numbers, booleans, dates, regexes: converted to string form for matching, returned as themselves 77 | * arrays: return a new array with all the elements that matched. Elements are not modified, but are converted to strings for matching. Does not recurse into elements. 78 | * objects: return a new object with with all the property *names* that matched. Values are copied by reference. 79 | * null, NAN, undefined: never match anything 80 | 81 | ## Support 82 | 83 | If you're using this library, feel free to contact me on twitter if you have any questions! :) [@rjrodger](http://twitter.com/rjrodger) 84 | 85 | This module works on both Node.js and browsers. 86 | 87 | 88 | 89 | ## License 90 | Copyright (c) 2010-2020, Richard Rodger and other contributors. 91 | Licensed under [MIT][]. 92 | 93 | [MIT]: ./LICENSE 94 | 95 | 96 | -------------------------------------------------------------------------------- /dist/gex.d.ts: -------------------------------------------------------------------------------- 1 | declare class Gexer { 2 | gexmap: { 3 | [key: string]: RegExp; 4 | }; 5 | desc: string; 6 | constructor(gexspec: string | string[]); 7 | dodgy(obj: any): boolean; 8 | clean(gexexp: any): string; 9 | match(str: any): boolean; 10 | on(obj: any): any; 11 | esc(gexexp: any): string; 12 | escregexp(restr: string): string; 13 | re(gs: string): RegExp | any; 14 | toString(): string; 15 | inspect(): string; 16 | } 17 | declare function Gex(gexspec: string | string[]): Gexer; 18 | export default Gex; 19 | export { Gex }; 20 | -------------------------------------------------------------------------------- /dist/gex.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* Copyright (c) 2011-2020 Richard Rodger, MIT License */ 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | exports.Gex = void 0; 5 | class Gexer { 6 | constructor(gexspec) { 7 | this.desc = ''; 8 | this.gexmap = {}; 9 | if (null != gexspec) { 10 | let gexstrs = Array.isArray(gexspec) ? gexspec : [gexspec]; 11 | gexstrs.forEach((str) => { 12 | this.gexmap[str] = this.re(this.clean(str)); 13 | }); 14 | } 15 | } 16 | dodgy(obj) { 17 | return null == obj || Number.isNaN(obj); 18 | } 19 | clean(gexexp) { 20 | let gexstr = '' + gexexp; 21 | return this.dodgy(gexexp) ? '' : gexstr; 22 | } 23 | match(str) { 24 | str = '' + str; 25 | let hasmatch = false; 26 | let gexstrs = Object.keys(this.gexmap); 27 | for (let i = 0; i < gexstrs.length && !hasmatch; i++) { 28 | hasmatch = !!this.gexmap[gexstrs[i]].exec(str); 29 | } 30 | return hasmatch; 31 | } 32 | on(obj) { 33 | if (null == obj) { 34 | return null; 35 | } 36 | let typeof_obj = typeof obj; 37 | if ('string' === typeof_obj || 38 | 'number' === typeof_obj || 39 | 'boolean' === typeof_obj || 40 | obj instanceof Date || 41 | obj instanceof RegExp) { 42 | return this.match(obj) ? obj : null; 43 | } 44 | else if (Array.isArray(obj)) { 45 | let out = []; 46 | for (let i = 0; i < obj.length; i++) { 47 | if (!this.dodgy(obj[i]) && this.match(obj[i])) { 48 | out.push(obj[i]); 49 | } 50 | } 51 | return out; 52 | } 53 | else { 54 | let outobj = {}; 55 | for (let p in obj) { 56 | if (Object.prototype.hasOwnProperty.call(obj, p)) { 57 | if (this.match(p)) { 58 | outobj[p] = obj[p]; 59 | } 60 | } 61 | } 62 | return outobj; 63 | } 64 | } 65 | esc(gexexp) { 66 | let gexstr = this.clean(gexexp); 67 | gexstr = gexstr.replace(/\*/g, '**'); 68 | gexstr = gexstr.replace(/\?/g, '*?'); 69 | return gexstr; 70 | } 71 | escregexp(restr) { 72 | return restr ? ('' + restr).replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') : ''; 73 | } 74 | re(gs) { 75 | if ('' === gs || gs) { 76 | gs = this.escregexp(gs); 77 | // use [\s\S] instead of . to match newlines 78 | gs = gs.replace(/\\\*/g, '[\\s\\S]*'); 79 | gs = gs.replace(/\\\?/g, '[\\s\\S]'); 80 | // escapes ** and *? 81 | gs = gs.replace(/\[\\s\\S\]\*\[\\s\\S\]\*/g, '\\*'); 82 | gs = gs.replace(/\[\\s\\S\]\*\[\\s\\S\]/g, '\\?'); 83 | gs = '^' + gs + '$'; 84 | return new RegExp(gs); 85 | } 86 | else { 87 | let gexstrs = Object.keys(this.gexmap); 88 | return 1 == gexstrs.length ? this.gexmap[gexstrs[0]] : { ...this.gexmap }; 89 | } 90 | } 91 | toString() { 92 | let d = this.desc; 93 | return '' != d ? d : (this.desc = 'Gex[' + Object.keys(this.gexmap) + ']'); 94 | } 95 | inspect() { 96 | return this.toString(); 97 | } 98 | } 99 | function Gex(gexspec) { 100 | return new Gexer(gexspec); 101 | } 102 | exports.Gex = Gex; 103 | if ('undefined' !== typeof module) { 104 | module.exports = Gex; 105 | module.exports.Gex = Gex; 106 | } 107 | exports.default = Gex; 108 | //# sourceMappingURL=gex.js.map -------------------------------------------------------------------------------- /dist/gex.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"gex.js","sourceRoot":"","sources":["../gex.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/gex.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).Gex=e()}}((function(){var e={exports:{}};Object.defineProperty(e.exports,"__esModule",{value:!0}),e.exports.Gex=void 0;class t{constructor(e){this.desc="",this.gexmap={},null!=e&&(Array.isArray(e)?e:[e]).forEach(e=>{this.gexmap[e]=this.re(this.clean(e))})}dodgy(e){return null==e||Number.isNaN(e)}clean(e){let t=""+e;return this.dodgy(e)?"":t}match(e){e=""+e;let t=!1,r=Object.keys(this.gexmap);for(let s=0;s { 14 | this.gexmap[str] = this.re(this.clean(str)) 15 | }) 16 | } 17 | } 18 | 19 | dodgy(obj: any) { 20 | return null == obj || Number.isNaN(obj) 21 | } 22 | 23 | clean(gexexp: any) { 24 | let gexstr = '' + gexexp 25 | return this.dodgy(gexexp) ? '' : gexstr 26 | } 27 | 28 | match(str: any) { 29 | str = '' + str 30 | let hasmatch = false 31 | let gexstrs = Object.keys(this.gexmap) 32 | 33 | for (let i = 0; i < gexstrs.length && !hasmatch; i++) { 34 | hasmatch = !!this.gexmap[gexstrs[i]].exec(str) 35 | } 36 | return hasmatch 37 | } 38 | 39 | on(obj: any) { 40 | if (null == obj) { 41 | return null 42 | } 43 | 44 | let typeof_obj = typeof obj 45 | if ( 46 | 'string' === typeof_obj || 47 | 'number' === typeof_obj || 48 | 'boolean' === typeof_obj || 49 | obj instanceof Date || 50 | obj instanceof RegExp 51 | ) { 52 | return this.match(obj) ? obj : null 53 | } else if (Array.isArray(obj)) { 54 | let out = [] 55 | for (let i = 0; i < obj.length; i++) { 56 | if (!this.dodgy(obj[i]) && this.match(obj[i])) { 57 | out.push(obj[i]) 58 | } 59 | } 60 | return out 61 | } else { 62 | let outobj: any = {} 63 | for (let p in obj) { 64 | if (Object.prototype.hasOwnProperty.call(obj, p)) { 65 | if (this.match(p)) { 66 | outobj[p] = obj[p] 67 | } 68 | } 69 | } 70 | return outobj 71 | } 72 | } 73 | 74 | esc(gexexp: any) { 75 | let gexstr = this.clean(gexexp) 76 | gexstr = gexstr.replace(/\*/g, '**') 77 | gexstr = gexstr.replace(/\?/g, '*?') 78 | return gexstr 79 | } 80 | 81 | escregexp(restr: string) { 82 | return restr ? ('' + restr).replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') : '' 83 | } 84 | 85 | re(gs: string): RegExp | any { 86 | if ('' === gs || gs) { 87 | gs = this.escregexp(gs) 88 | 89 | // use [\s\S] instead of . to match newlines 90 | gs = gs.replace(/\\\*/g, '[\\s\\S]*') 91 | gs = gs.replace(/\\\?/g, '[\\s\\S]') 92 | 93 | // escapes ** and *? 94 | gs = gs.replace(/\[\\s\\S\]\*\[\\s\\S\]\*/g, '\\*') 95 | gs = gs.replace(/\[\\s\\S\]\*\[\\s\\S\]/g, '\\?') 96 | 97 | gs = '^' + gs + '$' 98 | 99 | return new RegExp(gs) 100 | } else { 101 | let gexstrs = Object.keys(this.gexmap) 102 | return 1 == gexstrs.length ? this.gexmap[gexstrs[0]] : { ...this.gexmap } 103 | } 104 | } 105 | 106 | toString() { 107 | let d = this.desc 108 | return '' != d ? d : (this.desc = 'Gex[' + Object.keys(this.gexmap) + ']') 109 | } 110 | 111 | inspect() { 112 | return this.toString() 113 | } 114 | } 115 | 116 | function Gex(gexspec: string | string[]): Gexer { 117 | return new Gexer(gexspec) 118 | } 119 | 120 | if ('undefined' !== typeof module) { 121 | module.exports = Gex 122 | module.exports.Gex = Gex 123 | } 124 | 125 | export default Gex 126 | 127 | export { Gex } 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gex", 3 | "version": "4.0.1", 4 | "main": "dist/gex.js", 5 | "browser": "dist/gex.min.js", 6 | "types": "dist/gex.d.ts", 7 | "description": "Glob expressions for JavaScript", 8 | "scripts": { 9 | "test": "jest --coverage", 10 | "test-pure": "jest --coverage --config jest.config.pure.js", 11 | "test-some": "jest -t", 12 | "test-some-pure": "jest --config jest.config.pure.js -t", 13 | "test-watch": "jest --coverage --watchAll", 14 | "test-web": "npm run build-web && browserify -i util -o test/jester-web.js -e test/jester.js -im && open test/jester.html", 15 | "watch": "tsc -w -d", 16 | "build": "tsc -d", 17 | "build-web": "npm run build && cp dist/gex.js dist/gex.min.js && browserify -i util -o dist/gex.min.js -e dist/gex.js -s Gex -im -p tinyify", 18 | "prettier": "prettier --write --no-semi --single-quote gex.ts test/*.js", 19 | "clean": "rm -rf node_modules yarn.lock package-lock.json", 20 | "reset": "npm run clean && npm i && npm run build && npm test", 21 | "repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"`; echo TAG: v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag v$REPO_VERSION && git push --tags;", 22 | "repo-publish": "npm run clean && npm i && npm run repo-publish-quick", 23 | "repo-publish-quick": "npm run prettier && npm run build && npm run test && npm run test-web && npm run repo-tag && npm publish --registry https://registry.npmjs.org " 24 | }, 25 | "keywords": [ 26 | "glob", 27 | "star", 28 | "question", 29 | "mark", 30 | "expression", 31 | "regular" 32 | ], 33 | "homepage": "https://github.com/rjrodger/gex", 34 | "author": "Richard Rodger (http://richardrodger.com/)", 35 | "license": "MIT", 36 | "dependencies": {}, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/rjrodger/gex.git" 40 | }, 41 | "files": [ 42 | "README.md", 43 | "LICENSE.txt", 44 | "gex.ts", 45 | "dist" 46 | ], 47 | "devDependencies": { 48 | "@types/jest": "^28.1.2", 49 | "@types/node": "^18.0.0", 50 | "browserify": "^17.0.0", 51 | "esbuild": "^0.14.45", 52 | "esbuild-jest": "^0.5.0", 53 | "jest": "^28.1.1", 54 | "prettier": "^2.7.1", 55 | "tinyify": "^3.0.0", 56 | "ts-jest": "^28.0.5", 57 | "typescript": "^4.7.4" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/gex.test.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2022 Richard Rodger, MIT License */ 2 | 3 | const GexRoot = require('..') 4 | const { Gex } = require('..') 5 | 6 | function s(obj) { 7 | return JSON.stringify(obj) 8 | } 9 | 10 | describe('Gex', function () { 11 | test('happy', () => { 12 | var ab = Gex('ab') 13 | expect(ab.on('ab')).toEqual('ab') 14 | expect(ab.on('a')).toEqual(null) 15 | expect(ab.on('b')).toEqual(null) 16 | expect(ab.on('ba')).toEqual(null) 17 | expect(ab.on('abc')).toEqual(null) 18 | expect(ab.on('cab')).toEqual(null) 19 | expect(ab.on('cabc')).toEqual(null) 20 | 21 | var a_b = Gex('a*b') 22 | expect(a_b.on('acb')).toEqual('acb') 23 | expect(a_b.on('adb')).toEqual('adb') 24 | expect(a_b.on('aab')).toEqual('aab') 25 | expect(a_b.on('abb')).toEqual('abb') 26 | 27 | expect(a_b.on('aaa')).toEqual(null) 28 | expect(a_b.on('bbb')).toEqual(null) 29 | expect(a_b.on('bca')).toEqual(null) 30 | expect(a_b.on('ba')).toEqual(null) 31 | expect(a_b.on('ac')).toEqual(null) 32 | expect(a_b.on('a')).toEqual(null) 33 | 34 | var a$b = Gex('a?b') 35 | expect(a$b.on('acb')).toEqual('acb') 36 | expect(a$b.on('adb')).toEqual('adb') 37 | expect(a$b.on('aab')).toEqual('aab') 38 | expect(a$b.on('abb')).toEqual('abb') 39 | 40 | expect(a$b.on('aaa')).toEqual(null) 41 | expect(a$b.on('bbb')).toEqual(null) 42 | expect(a$b.on('bca')).toEqual(null) 43 | expect(a$b.on('ba')).toEqual(null) 44 | expect(a$b.on('ac')).toEqual(null) 45 | expect(a$b.on('a')).toEqual(null) 46 | 47 | expect(a_b.on('accb')).toEqual('accb') 48 | expect(a_b.on('acccb')).toEqual('acccb') 49 | expect(a_b.on('aaab')).toEqual('aaab') 50 | expect(a_b.on('aabb')).toEqual('aabb') 51 | expect(a_b.on('abbb')).toEqual('abbb') 52 | }) 53 | 54 | test('module-root', () => { 55 | var ab = GexRoot('ab') 56 | expect(ab.on('ab')).toEqual('ab') 57 | }) 58 | 59 | // test('module-default', () => { 60 | // var ab = GexDefault('ab') 61 | // expect(ab.on('ab')).toEqual('ab') 62 | // }) 63 | 64 | test('arrays', () => { 65 | var a_ = Gex('a*') // maybe: to deep equal 66 | expect(s(a_.on(['ab', 'ac']))).toEqual(s(['ab', 'ac'])) 67 | expect(s(a_.on(['ab', 'dd', 'ac']))).toEqual(s(['ab', 'ac'])) 68 | expect(s(a_.on(['ab', 'dd', 'ee']))).toEqual(s(['ab'])) 69 | expect(s(a_.on(['ff', 'dd', 'ee']))).toEqual(s([])) 70 | expect(s(a_.on([]))).toEqual(s([])) 71 | expect(s(a_.on([null]))).toEqual(s([])) 72 | expect(s(a_.on(['ab', null, 'dd', undefined, 'ee', NaN]))).toEqual( 73 | s(['ab']) 74 | ) 75 | }) 76 | 77 | test('objects', () => { 78 | var foo_ = Gex('foo*') 79 | expect(s(foo_.on({ foo: 1 }))).toEqual(s({ foo: 1 })) 80 | expect(s(foo_.on({ foo: 1, doo: 2 }))).toEqual(s({ foo: 1 })) 81 | expect(s(foo_.on({ foo: 1, doo: 2, food: 3 }))).toEqual( 82 | s({ foo: 1, food: 3 }) 83 | ) 84 | 85 | var o0 = { food: 3 } 86 | var o1 = Object.create(o0) 87 | o1.foo = 1 88 | o1.doo = 2 89 | expect(s(foo_.on(o1))).toEqual(s({ foo: 1 })) 90 | }) 91 | 92 | test('object without prototype', () => { 93 | var obj = Object.create(null) 94 | obj.foo = 'bar' 95 | expect(s({ foo: 'bar' })).toEqual(s(Gex('foo').on(obj))) 96 | }) 97 | 98 | test('dodgy', () => { 99 | expect(Gex().on('aaa')).toEqual(null) 100 | expect(Gex(null).on('aaa')).toEqual(null) 101 | expect(Gex(NaN).on('aaa')).toEqual(null) 102 | expect(Gex(undefined).on('aaa')).toEqual(null) 103 | 104 | var g = Gex('g') 105 | expect(g.on()).toEqual(null) 106 | expect(g.on(null)).toEqual(null) 107 | expect(g.on(NaN)).toEqual(null) 108 | expect(g.on(undefined)).toEqual(null) 109 | 110 | expect(g.on(true)).toEqual(null) 111 | expect(g.on(false)).toEqual(null) 112 | expect(g.on(new Date())).toEqual(null) 113 | expect(g.on(/x/)).toEqual(null) 114 | 115 | expect(g.on('')).toEqual(null) 116 | }) 117 | 118 | test('escapes', () => { 119 | var g = Gex('a**b') 120 | expect(g.toString()).toEqual('Gex[a**b]') 121 | expect(g.re().toString()).toEqual('/^a\\*b$/') 122 | expect(g.on('a*b')).toEqual('a*b') 123 | expect(g.on('a**b')).toEqual(null) // not a literal 'a*b' 124 | 125 | g = Gex('a*?b') 126 | expect(g.toString()).toEqual('Gex[a*?b]') 127 | expect(g.re().toString()).toEqual('/^a\\?b$/') 128 | expect(g.on('a?b')).toEqual('a?b') 129 | expect(g.on('a*?b')).toEqual(null) // not a literal 'a?b' 130 | 131 | expect(g.esc('')).toEqual('') 132 | expect(g.esc('*')).toEqual('**') 133 | expect(g.esc('?')).toEqual('*?') 134 | expect(g.esc('a*')).toEqual('a**') 135 | expect(g.esc('a?')).toEqual('a*?') 136 | expect(g.esc('a*b*c')).toEqual('a**b**c') 137 | expect(g.esc('a?b?c')).toEqual('a*?b*?c') 138 | }) 139 | 140 | test('newlines', () => { 141 | var g = Gex('a*b') 142 | expect('/^a[\\s\\S]*b$/').toEqual('' + g.re()) 143 | expect(g.on('a\nb')).toEqual('a\nb') 144 | }) 145 | 146 | test('zero', () => { 147 | expect(Gex('0').on('0')).toEqual('0') 148 | expect(Gex('0*').on('0')).toEqual('0') 149 | expect(Gex('*0').on('0')).toEqual('0') 150 | expect(Gex('*0*').on('0')).toEqual('0') 151 | 152 | expect(Gex(['0']).on('0')).toEqual('0') 153 | expect(Gex(['0*']).on('0')).toEqual('0') 154 | expect(Gex(['*0']).on('0')).toEqual('0') 155 | expect(Gex(['*0*']).on('0')).toEqual('0') 156 | 157 | expect(Gex(1).on('1')).toEqual('1') 158 | expect(Gex(100).on('100')).toEqual('100') 159 | expect(Gex(0).on('0')).toEqual('0') 160 | }) 161 | 162 | test('multi', () => { 163 | var g = Gex(['a', 'b']) 164 | expect(g.on('a')).toEqual('a') 165 | expect(g.on('b')).toEqual('b') 166 | expect(s(g.re())).toEqual('{"a":{},"b":{}}') 167 | 168 | g = Gex(['a*', 'b']) 169 | expect(g.on('ax')).toEqual('ax') 170 | expect(g.on('b')).toEqual('b') 171 | expect(s(g.re())).toEqual('{"a*":{},"b":{}}') 172 | 173 | expect(Gex(['a*', 'b*']).on('bx')).toEqual('bx') 174 | expect(Gex(['a*', 'b*']).on(['ax', 'zz', 'bx']).toString()).toEqual('ax,bx') 175 | }) 176 | 177 | test('inspect', () => { 178 | var g = Gex('a*') 179 | expect(g.toString()).toEqual('Gex[a*]') 180 | expect(g.inspect()).toEqual('Gex[a*]') 181 | 182 | g = Gex(['a*', '*b']) 183 | expect(g.inspect()).toEqual('Gex[a*,*b]') 184 | }) 185 | 186 | test('funky', () => { 187 | expect(Gex('').on('a')).toEqual(null) 188 | expect(Gex().on('a')).toEqual(null) 189 | expect(Gex(null).on('a')).toEqual(null) 190 | expect(Gex(undefined).on('a')).toEqual(null) 191 | expect(Gex(NaN).on('a')).toEqual(null) 192 | expect(Gex(/a/).on('a')).toEqual(null) 193 | }) 194 | }) 195 | -------------------------------------------------------------------------------- /test/jester-tests.js: -------------------------------------------------------------------------------- 1 | require('./gex.test.js') 2 | -------------------------------------------------------------------------------- /test/jester-web.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i{this.gexmap[e]=this.re(this.clean(e))})}dodgy(e){return null==e||Number.isNaN(e)}clean(e){let t=""+e;return this.dodgy(e)?"":t}match(e){e=""+e;let t=!1,r=Object.keys(this.gexmap);for(let s=0;s { 17 | var ab = Gex('ab') 18 | expect(ab.on('ab')).toEqual('ab') 19 | expect(ab.on('a')).toEqual(null) 20 | expect(ab.on('b')).toEqual(null) 21 | expect(ab.on('ba')).toEqual(null) 22 | expect(ab.on('abc')).toEqual(null) 23 | expect(ab.on('cab')).toEqual(null) 24 | expect(ab.on('cabc')).toEqual(null) 25 | 26 | var a_b = Gex('a*b') 27 | expect(a_b.on('acb')).toEqual('acb') 28 | expect(a_b.on('adb')).toEqual('adb') 29 | expect(a_b.on('aab')).toEqual('aab') 30 | expect(a_b.on('abb')).toEqual('abb') 31 | 32 | expect(a_b.on('aaa')).toEqual(null) 33 | expect(a_b.on('bbb')).toEqual(null) 34 | expect(a_b.on('bca')).toEqual(null) 35 | expect(a_b.on('ba')).toEqual(null) 36 | expect(a_b.on('ac')).toEqual(null) 37 | expect(a_b.on('a')).toEqual(null) 38 | 39 | var a$b = Gex('a?b') 40 | expect(a$b.on('acb')).toEqual('acb') 41 | expect(a$b.on('adb')).toEqual('adb') 42 | expect(a$b.on('aab')).toEqual('aab') 43 | expect(a$b.on('abb')).toEqual('abb') 44 | 45 | expect(a$b.on('aaa')).toEqual(null) 46 | expect(a$b.on('bbb')).toEqual(null) 47 | expect(a$b.on('bca')).toEqual(null) 48 | expect(a$b.on('ba')).toEqual(null) 49 | expect(a$b.on('ac')).toEqual(null) 50 | expect(a$b.on('a')).toEqual(null) 51 | 52 | expect(a_b.on('accb')).toEqual('accb') 53 | expect(a_b.on('acccb')).toEqual('acccb') 54 | expect(a_b.on('aaab')).toEqual('aaab') 55 | expect(a_b.on('aabb')).toEqual('aabb') 56 | expect(a_b.on('abbb')).toEqual('abbb') 57 | }) 58 | 59 | test('module-root', () => { 60 | var ab = GexRoot('ab') 61 | expect(ab.on('ab')).toEqual('ab') 62 | }) 63 | 64 | // test('module-default', () => { 65 | // var ab = GexDefault('ab') 66 | // expect(ab.on('ab')).toEqual('ab') 67 | // }) 68 | 69 | test('arrays', () => { 70 | var a_ = Gex('a*') // maybe: to deep equal 71 | expect(s(a_.on(['ab', 'ac']))).toEqual(s(['ab', 'ac'])) 72 | expect(s(a_.on(['ab', 'dd', 'ac']))).toEqual(s(['ab', 'ac'])) 73 | expect(s(a_.on(['ab', 'dd', 'ee']))).toEqual(s(['ab'])) 74 | expect(s(a_.on(['ff', 'dd', 'ee']))).toEqual(s([])) 75 | expect(s(a_.on([]))).toEqual(s([])) 76 | expect(s(a_.on([null]))).toEqual(s([])) 77 | expect(s(a_.on(['ab', null, 'dd', undefined, 'ee', NaN]))).toEqual( 78 | s(['ab']) 79 | ) 80 | }) 81 | 82 | test('objects', () => { 83 | var foo_ = Gex('foo*') 84 | expect(s(foo_.on({ foo: 1 }))).toEqual(s({ foo: 1 })) 85 | expect(s(foo_.on({ foo: 1, doo: 2 }))).toEqual(s({ foo: 1 })) 86 | expect(s(foo_.on({ foo: 1, doo: 2, food: 3 }))).toEqual( 87 | s({ foo: 1, food: 3 }) 88 | ) 89 | 90 | var o0 = { food: 3 } 91 | var o1 = Object.create(o0) 92 | o1.foo = 1 93 | o1.doo = 2 94 | expect(s(foo_.on(o1))).toEqual(s({ foo: 1 })) 95 | }) 96 | 97 | test('object without prototype', () => { 98 | var obj = Object.create(null) 99 | obj.foo = 'bar' 100 | expect(s({ foo: 'bar' })).toEqual(s(Gex('foo').on(obj))) 101 | }) 102 | 103 | test('dodgy', () => { 104 | expect(Gex().on('aaa')).toEqual(null) 105 | expect(Gex(null).on('aaa')).toEqual(null) 106 | expect(Gex(NaN).on('aaa')).toEqual(null) 107 | expect(Gex(undefined).on('aaa')).toEqual(null) 108 | 109 | var g = Gex('g') 110 | expect(g.on()).toEqual(null) 111 | expect(g.on(null)).toEqual(null) 112 | expect(g.on(NaN)).toEqual(null) 113 | expect(g.on(undefined)).toEqual(null) 114 | 115 | expect(g.on(true)).toEqual(null) 116 | expect(g.on(false)).toEqual(null) 117 | expect(g.on(new Date())).toEqual(null) 118 | expect(g.on(/x/)).toEqual(null) 119 | 120 | expect(g.on('')).toEqual(null) 121 | }) 122 | 123 | test('escapes', () => { 124 | var g = Gex('a**b') 125 | expect(g.toString()).toEqual('Gex[a**b]') 126 | expect(g.re().toString()).toEqual('/^a\\*b$/') 127 | expect(g.on('a*b')).toEqual('a*b') 128 | expect(g.on('a**b')).toEqual(null) // not a literal 'a*b' 129 | 130 | g = Gex('a*?b') 131 | expect(g.toString()).toEqual('Gex[a*?b]') 132 | expect(g.re().toString()).toEqual('/^a\\?b$/') 133 | expect(g.on('a?b')).toEqual('a?b') 134 | expect(g.on('a*?b')).toEqual(null) // not a literal 'a?b' 135 | 136 | expect(g.esc('')).toEqual('') 137 | expect(g.esc('*')).toEqual('**') 138 | expect(g.esc('?')).toEqual('*?') 139 | expect(g.esc('a*')).toEqual('a**') 140 | expect(g.esc('a?')).toEqual('a*?') 141 | expect(g.esc('a*b*c')).toEqual('a**b**c') 142 | expect(g.esc('a?b?c')).toEqual('a*?b*?c') 143 | }) 144 | 145 | test('newlines', () => { 146 | var g = Gex('a*b') 147 | expect('/^a[\\s\\S]*b$/').toEqual('' + g.re()) 148 | expect(g.on('a\nb')).toEqual('a\nb') 149 | }) 150 | 151 | test('zero', () => { 152 | expect(Gex('0').on('0')).toEqual('0') 153 | expect(Gex('0*').on('0')).toEqual('0') 154 | expect(Gex('*0').on('0')).toEqual('0') 155 | expect(Gex('*0*').on('0')).toEqual('0') 156 | 157 | expect(Gex(['0']).on('0')).toEqual('0') 158 | expect(Gex(['0*']).on('0')).toEqual('0') 159 | expect(Gex(['*0']).on('0')).toEqual('0') 160 | expect(Gex(['*0*']).on('0')).toEqual('0') 161 | 162 | expect(Gex(1).on('1')).toEqual('1') 163 | expect(Gex(100).on('100')).toEqual('100') 164 | expect(Gex(0).on('0')).toEqual('0') 165 | }) 166 | 167 | test('multi', () => { 168 | var g = Gex(['a', 'b']) 169 | expect(g.on('a')).toEqual('a') 170 | expect(g.on('b')).toEqual('b') 171 | expect(s(g.re())).toEqual('{"a":{},"b":{}}') 172 | 173 | g = Gex(['a*', 'b']) 174 | expect(g.on('ax')).toEqual('ax') 175 | expect(g.on('b')).toEqual('b') 176 | expect(s(g.re())).toEqual('{"a*":{},"b":{}}') 177 | 178 | expect(Gex(['a*', 'b*']).on('bx')).toEqual('bx') 179 | expect(Gex(['a*', 'b*']).on(['ax', 'zz', 'bx']).toString()).toEqual('ax,bx') 180 | }) 181 | 182 | test('inspect', () => { 183 | var g = Gex('a*') 184 | expect(g.toString()).toEqual('Gex[a*]') 185 | expect(g.inspect()).toEqual('Gex[a*]') 186 | 187 | g = Gex(['a*', '*b']) 188 | expect(g.inspect()).toEqual('Gex[a*,*b]') 189 | }) 190 | 191 | test('funky', () => { 192 | expect(Gex('').on('a')).toEqual(null) 193 | expect(Gex().on('a')).toEqual(null) 194 | expect(Gex(null).on('a')).toEqual(null) 195 | expect(Gex(undefined).on('a')).toEqual(null) 196 | expect(Gex(NaN).on('a')).toEqual(null) 197 | expect(Gex(/a/).on('a')).toEqual(null) 198 | }) 199 | }) 200 | 201 | },{"..":1}],3:[function(require,module,exports){ 202 | require('./gex.test.js') 203 | 204 | },{"./gex.test.js":2}],4:[function(require,module,exports){ 205 | // Run: npm run test-web 206 | 207 | // A quick and dirty abomination to partially run the unit tests inside an 208 | // actual browser by simulating some of the Jest API. 209 | 210 | const Jester = (window.Jester = { 211 | exclude: [], 212 | state: { 213 | describe: {}, 214 | unit: {}, 215 | fail: {}, 216 | }, 217 | }) 218 | 219 | // Ensure keys are sorted when JSONified. 220 | function stringify(o) { 221 | if (null === o) return 'null' 222 | if ('symbol' === typeof o) return String(o) 223 | if ('object' !== typeof o) return '' + o 224 | return JSON.stringify( 225 | Object.keys(o) 226 | .sort() 227 | .reduce((a, k) => ((a[k] = o[k]), a), {}), 228 | stringify 229 | ) // Recusively! 230 | } 231 | 232 | function print(s) { 233 | let test = document.getElementById('test') 234 | test.innerHTML = test.innerHTML + s + '
' 235 | } 236 | 237 | window.describe = function (name, tests) { 238 | Jester.state.describe = { name } 239 | tests() 240 | } 241 | window.test = function (name, unit) { 242 | if (Jester.exclude.includes(name)) return 243 | 244 | try { 245 | Jester.state.unit = { name } 246 | unit() 247 | // console.log('PASS:', name) 248 | print('PASS: ' + name) 249 | } catch (e) { 250 | console.log(e) 251 | print('FAIL: ' + name) 252 | print(e.message + '
' + e.stack + '
') 253 | } 254 | } 255 | window.expect = function (sval) { 256 | function pass(cval, ok) { 257 | // console.log('pass',cval,ok) 258 | if (!ok) { 259 | let state = Jester.state 260 | state.fail.found = sval 261 | state.fail.expected = cval 262 | let err = new Error( 263 | 'FAIL: ' + state.describe.name + ' ' + state.unit.name 264 | ) 265 | throw err 266 | } 267 | } 268 | 269 | function passEqualJSON(cval) { 270 | let sjson = stringify(sval) 271 | let cjson = stringify(cval) 272 | 273 | let ok = sjson === cjson 274 | pass(cval, ok) 275 | } 276 | 277 | return { 278 | toEqual: (cval) => { 279 | passEqualJSON(cval) 280 | }, 281 | toBeTruthy: (cval) => pass(cval, !!cval), 282 | toBeFalsy: (cval) => pass(cval, !cval), 283 | toBeDefined: (cval) => pass(cval, undefined !== sval), 284 | toBeUndefined: (cval) => pass(cval, undefined === sval), 285 | toMatch: (cval) => pass(cval, sval.match(cval)), 286 | toThrow: (cval) => { 287 | try { 288 | sval() 289 | pass(cval, false) 290 | } catch (e) { 291 | pass(cval, true) 292 | } 293 | }, 294 | toMatchObject: (cval) => { 295 | passEqualJSON(cval) 296 | }, 297 | } 298 | } 299 | 300 | require('./jester-tests.js') 301 | 302 | },{"./jester-tests.js":3}]},{},[4]); 303 | -------------------------------------------------------------------------------- /test/jester.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | -------------------------------------------------------------------------------- /test/jester.js: -------------------------------------------------------------------------------- 1 | // Run: npm run test-web 2 | 3 | // A quick and dirty abomination to partially run the unit tests inside an 4 | // actual browser by simulating some of the Jest API. 5 | 6 | const Jester = (window.Jester = { 7 | exclude: [], 8 | state: { 9 | describe: {}, 10 | unit: {}, 11 | fail: {}, 12 | }, 13 | }) 14 | 15 | // Ensure keys are sorted when JSONified. 16 | function stringify(o) { 17 | if (null === o) return 'null' 18 | if ('symbol' === typeof o) return String(o) 19 | if ('object' !== typeof o) return '' + o 20 | return JSON.stringify( 21 | Object.keys(o) 22 | .sort() 23 | .reduce((a, k) => ((a[k] = o[k]), a), {}), 24 | stringify 25 | ) // Recusively! 26 | } 27 | 28 | function print(s) { 29 | let test = document.getElementById('test') 30 | test.innerHTML = test.innerHTML + s + '
' 31 | } 32 | 33 | window.describe = function (name, tests) { 34 | Jester.state.describe = { name } 35 | tests() 36 | } 37 | window.test = function (name, unit) { 38 | if (Jester.exclude.includes(name)) return 39 | 40 | try { 41 | Jester.state.unit = { name } 42 | unit() 43 | // console.log('PASS:', name) 44 | print('PASS: ' + name) 45 | } catch (e) { 46 | console.log(e) 47 | print('FAIL: ' + name) 48 | print(e.message + '
' + e.stack + '
') 49 | } 50 | } 51 | window.expect = function (sval) { 52 | function pass(cval, ok) { 53 | // console.log('pass',cval,ok) 54 | if (!ok) { 55 | let state = Jester.state 56 | state.fail.found = sval 57 | state.fail.expected = cval 58 | let err = new Error( 59 | 'FAIL: ' + state.describe.name + ' ' + state.unit.name 60 | ) 61 | throw err 62 | } 63 | } 64 | 65 | function passEqualJSON(cval) { 66 | let sjson = stringify(sval) 67 | let cjson = stringify(cval) 68 | 69 | let ok = sjson === cjson 70 | pass(cval, ok) 71 | } 72 | 73 | return { 74 | toEqual: (cval) => { 75 | passEqualJSON(cval) 76 | }, 77 | toBeTruthy: (cval) => pass(cval, !!cval), 78 | toBeFalsy: (cval) => pass(cval, !cval), 79 | toBeDefined: (cval) => pass(cval, undefined !== sval), 80 | toBeUndefined: (cval) => pass(cval, undefined === sval), 81 | toMatch: (cval) => pass(cval, sval.match(cval)), 82 | toThrow: (cval) => { 83 | try { 84 | sval() 85 | pass(cval, false) 86 | } catch (e) { 87 | pass(cval, true) 88 | } 89 | }, 90 | toMatchObject: (cval) => { 91 | passEqualJSON(cval) 92 | }, 93 | } 94 | } 95 | 96 | require('./jester-tests.js') 97 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "ES2019", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "noEmitOnError": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "strictPropertyInitialization": true, 11 | "esModuleInterop": true, 12 | "resolveJsonModule": true, 13 | }, 14 | "exclude": [ 15 | "dist", 16 | "node_modules" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------