├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.ts ├── package.json ├── test └── sem-dsl.spec.ts ├── tools └── cleanup.ts └── tsconfig.json /.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 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | typings 30 | dist 31 | .vscode 32 | 33 | demo/dist 34 | demo/vendor 35 | dist 36 | dist-test 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | 4 | sudo: false 5 | 6 | install: true 7 | 8 | os: 9 | - linux 10 | 11 | before_script: 12 | - npm install 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Minko Gechev 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 | [![Build Status](https://travis-ci.org/mgechev/semver-dsl.svg?branch=master)](https://travis-ci.org/mgechev/semver-dsl) 2 | 3 | # SemVer DSL 4 | 5 | A simple internal DSL which allows you to invoke different functionality depending on version match. Used in codelyzer for keeping the code compatible across different versions of the Angular compiler. 6 | 7 | # Demo 8 | 9 | ```bash 10 | $ npm i semver-dsl --save 11 | ``` 12 | 13 | ```ts 14 | import {SemVerDSL} from 'semver-dsl'; 15 | 16 | const base = () => {}; 17 | const elseIf1 = () => {}; 18 | const elseIf2 = () => {}; 19 | const else = () => console.log('I will be invoked!'); 20 | 21 | SemVerDSL('3.0.0') 22 | .gt('3.2.1', base) 23 | .elseIf.gt('3.0.1', elseIf1) 24 | .elseIf.between('3.0.1', '3.1.8', elseIf2) 25 | .else(else); 26 | ``` 27 | 28 | In the example above will be invoked `else`. 29 | 30 | # API 31 | 32 | - `SemDSL(version: string)` - factory which accepts a version and returns an object. 33 | - `gte(version: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` and `else` properties. 34 | - `lte(version: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` and `else` properties. 35 | - `gt(version: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` and `else` properties. 36 | - `lt(version: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` and `else` properties. 37 | - `eq(version: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` and `else` properties. 38 | - `neq(version: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` and `else` properties. 39 | - `between(v1: string, v2: string, callback?: Function): ISemContextualDSL` - returns an object with `elseIf` properties. 40 | - `elseIf` - returns an object of type `ISemVerDSL` bound to the previous predicate. 41 | - `else` - invokes given callback if all of the previous conditions have failed. 42 | 43 | ```ts 44 | export interface ISemVerDSL { 45 | gte(version: string, callback: Function): ISemContextualDSL; 46 | lte(version: string, callback: Function): ISemContextualDSL; 47 | gt(version: string, callback: Function): ISemContextualDSL; 48 | lt(version: string, callback: Function): ISemContextualDSL; 49 | eq(version: string, callback: Function): ISemContextualDSL; 50 | neq(version: string, callback: Function): ISemContextualDSL; 51 | between(v1: string, v2: string, callback: Function): ISemContextualDSL; 52 | } 53 | ``` 54 | 55 | ```ts 56 | export interface ISemVerContextBoundDSL { 57 | elseIf: ISemVerDSL; 58 | else(callback: Function): void; 59 | } 60 | ``` 61 | 62 | # License 63 | 64 | MIT 65 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import * as semver from 'semver'; 2 | 3 | export interface ISemVerDSL { 4 | gte(version: string, callback: Function): ISemVerContextBoundDSL; 5 | lte(version: string, callback: Function): ISemVerContextBoundDSL; 6 | gt(version: string, callback: Function): ISemVerContextBoundDSL; 7 | lt(version: string, callback: Function): ISemVerContextBoundDSL; 8 | eq(version: string, callback: Function): ISemVerContextBoundDSL; 9 | neq(version: string, callback: Function): ISemVerContextBoundDSL; 10 | between(v1: string, v2: string, callback: Function): ISemVerContextBoundDSL; 11 | } 12 | 13 | export interface ISemVerContextBoundDSL { 14 | elseIf: ISemVerDSL; 15 | else(callback: Function): void; 16 | } 17 | 18 | export const SemVerDSL = (target: string, lastPredicate = () => true) => { 19 | 20 | function createBoundContext(lastPredicate: () => boolean): ISemVerContextBoundDSL { 21 | return Object.create({}, { 22 | else: { 23 | value(callback: Function) { 24 | if (!lastPredicate()) callback(); 25 | } 26 | }, 27 | elseIf: { 28 | get() { 29 | return SemVerDSL(target, () => !lastPredicate()) 30 | } 31 | } 32 | }); 33 | }; 34 | 35 | const dsl = { 36 | 37 | gte(version: string, callback: Function): ISemVerContextBoundDSL { 38 | const predicate = () => semver.gte(target, version) && lastPredicate(); 39 | if (predicate()) callback(); 40 | return createBoundContext(predicate); 41 | }, 42 | 43 | lte(version: string, callback: Function): ISemVerContextBoundDSL { 44 | const predicate = () => semver.lte(target, version) && lastPredicate(); 45 | if (predicate()) callback(); 46 | return createBoundContext(predicate); 47 | }, 48 | 49 | gt(version: string, callback: Function): ISemVerContextBoundDSL { 50 | const predicate = () => semver.gt(target, version) && lastPredicate(); 51 | if (predicate()) callback(); 52 | return createBoundContext(predicate); 53 | }, 54 | 55 | lt(version: string, callback: Function): ISemVerContextBoundDSL { 56 | const predicate = () => semver.lt(target, version) && lastPredicate(); 57 | if (predicate()) callback(); 58 | return createBoundContext(predicate); 59 | }, 60 | 61 | eq(version: string, callback: Function): ISemVerContextBoundDSL { 62 | const predicate = () => semver.eq(target, version) && lastPredicate(); 63 | if (predicate()) callback(); 64 | return createBoundContext(predicate); 65 | }, 66 | 67 | neq(version: string, callback: Function): ISemVerContextBoundDSL { 68 | const predicate = () => semver.neq(target, version) && lastPredicate(); 69 | if (predicate()) callback(); 70 | return createBoundContext(predicate); 71 | }, 72 | 73 | between(v1: string, v2: string, callback: Function): ISemVerContextBoundDSL { 74 | const predicate = () => semver.gte(target, v1) && semver.lte(target, v2) && lastPredicate(); 75 | if (predicate()) callback(); 76 | return createBoundContext(predicate); 77 | } 78 | }; 79 | return dsl; 80 | }; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "semver-dsl", 3 | "version": "1.0.1", 4 | "description": "Tiny internal DSL which allows invocation of different functionality depending on SemVer match.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "build": "tsc && cp package.json dist", 9 | "release": "npm run build && ts-node tools/cleanup.ts && rimraf dist/test && cp README.md dist", 10 | "test": "tsc && jest" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mgechev/semver-dsl.git" 15 | }, 16 | "keywords": [ 17 | "semver", 18 | "dsl" 19 | ], 20 | "author": "Minko Gechev ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/mgechev/semver-dsl/issues" 24 | }, 25 | "homepage": "https://github.com/mgechev/semver-dsl#readme", 26 | "devDependencies": { 27 | "@types/jest": "^18.1.1", 28 | "@types/node": "^7.0.5", 29 | "@types/semver": "^5.3.30", 30 | "jest": "^18.1.0", 31 | "ts-node": "^2.1.0", 32 | "typescript": "^2.1.6" 33 | }, 34 | "dependencies": { 35 | "semver": "^5.3.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/sem-dsl.spec.ts: -------------------------------------------------------------------------------- 1 | import { SemVerDSL } from '../index'; 2 | 3 | describe('SemDSL', () => { 4 | 5 | describe('basic operators', () => { 6 | 7 | it('should has a factory which returns the context', () => { 8 | expect(SemVerDSL).not.toBeFalsy(); 9 | expect(SemVerDSL('2.0.0')).not.toBeFalsy(); 10 | }); 11 | 12 | it('should invoke function when "eq" condition matched', () => { 13 | const target = { 14 | foo() {} 15 | }; 16 | const spy = spyOn(target, 'foo'); 17 | SemVerDSL('2.0.0').eq('2.0.0', target.foo); 18 | expect(target.foo).toHaveBeenCalled(); 19 | }); 20 | 21 | it('should invoke function when "gt" condition matched', () => { 22 | const target = { 23 | foo() {} 24 | }; 25 | const spy = spyOn(target, 'foo'); 26 | SemVerDSL('2.0.0').gt('1.9.9', target.foo); 27 | expect(target.foo).toHaveBeenCalled(); 28 | }); 29 | 30 | it('should invoke function when "lt" condition matched', () => { 31 | const target = { 32 | foo() {} 33 | }; 34 | const spy = spyOn(target, 'foo'); 35 | SemVerDSL('2.0.0').lt('2.9.9', target.foo); 36 | expect(target.foo).toHaveBeenCalled(); 37 | }); 38 | 39 | it('should invoke function when "lte" condition matched', () => { 40 | const target = { 41 | foo() {}, 42 | bar() {} 43 | }; 44 | spyOn(target, 'foo'); 45 | spyOn(target, 'bar'); 46 | SemVerDSL('2.0.0').lte('2.0.0', target.foo); 47 | SemVerDSL('2.0.0').lte('3.0.0', target.bar); 48 | expect(target.foo).toHaveBeenCalled(); 49 | expect(target.bar).toHaveBeenCalled(); 50 | }); 51 | 52 | it('should invoke function when "gte" condition matched', () => { 53 | const target = { 54 | foo() {}, 55 | bar() {} 56 | }; 57 | spyOn(target, 'foo'); 58 | spyOn(target, 'bar'); 59 | SemVerDSL('2.0.0').gte('2.0.0', target.foo); 60 | SemVerDSL('2.0.0').gte('1.0.0', target.bar); 61 | expect(target.foo).toHaveBeenCalled(); 62 | expect(target.bar).toHaveBeenCalled(); 63 | }); 64 | 65 | it('should invoke function when "between" condition matched', () => { 66 | const target = { 67 | foo() {}, 68 | bar() {} 69 | }; 70 | spyOn(target, 'foo'); 71 | spyOn(target, 'bar'); 72 | SemVerDSL('2.0.0').between('1.9.0', '2.2.0', target.foo); 73 | SemVerDSL('3.0.0-beta.4').between('1.9.0', '3.0.0', target.bar); 74 | expect(target.foo).toHaveBeenCalled(); 75 | expect(target.bar).toHaveBeenCalled(); 76 | }); 77 | 78 | }); 79 | 80 | describe('else and elseIf', () => { 81 | it('should invoke "else" in case the base condition don\'t match', () => { 82 | const target = { 83 | else() {}, 84 | base() {} 85 | }; 86 | spyOn(target, 'else'); 87 | spyOn(target, 'base'); 88 | SemVerDSL('3.0.0') 89 | .between('1.9.0', '2.2.0', target.base) 90 | .else(target.else); 91 | expect(target.base).not.toHaveBeenCalled(); 92 | expect(target.else).toHaveBeenCalled(); 93 | }); 94 | 95 | it('should invoke "elseIf" in case base does\'t match', () => { 96 | const target = { 97 | elseIf() {}, 98 | else() {}, 99 | base() {} 100 | }; 101 | spyOn(target, 'elseIf'); 102 | spyOn(target, 'else'); 103 | spyOn(target, 'base'); 104 | SemVerDSL('3.0.0') 105 | .gt('3.2.1', target.base) 106 | .elseIf.lt('3.0.1', target.elseIf) 107 | .else(target.else); 108 | expect(target.elseIf).toHaveBeenCalled(); 109 | expect(target.base).not.toHaveBeenCalled(); 110 | expect(target.else).not.toHaveBeenCalled(); 111 | }); 112 | 113 | it('should invoke proper "elseIf" from the chain', () => { 114 | const target = { 115 | elseIf1() {}, 116 | elseIf2() {}, 117 | else() {}, 118 | base() {} 119 | }; 120 | spyOn(target, 'elseIf1'); 121 | spyOn(target, 'elseIf2'); 122 | spyOn(target, 'else'); 123 | spyOn(target, 'base'); 124 | SemVerDSL('3.0.0') 125 | .gt('3.2.1', target.base) 126 | .elseIf.gt('3.0.1', target.elseIf1) 127 | .elseIf.lt('3.0.1', target.elseIf2) 128 | .else(target.else); 129 | expect(target.elseIf2).toHaveBeenCalled(); 130 | expect(target.elseIf1).not.toHaveBeenCalled(); 131 | expect(target.base).not.toHaveBeenCalled(); 132 | expect(target.else).not.toHaveBeenCalled(); 133 | }); 134 | 135 | it('should invoke proper "elseIf" from the chain', () => { 136 | const target = { 137 | elseIf1() {}, 138 | elseIf2() {}, 139 | else() {}, 140 | base() {} 141 | }; 142 | spyOn(target, 'elseIf1'); 143 | spyOn(target, 'elseIf2'); 144 | spyOn(target, 'else'); 145 | spyOn(target, 'base'); 146 | SemVerDSL('3.0.0') 147 | .gt('3.2.1', target.base) 148 | .elseIf.gt('3.0.1', target.elseIf1) 149 | .elseIf.between('3.0.1', '3.1.8', target.elseIf2) 150 | .else(target.else); 151 | expect(target.elseIf2).not.toHaveBeenCalled(); 152 | expect(target.elseIf1).not.toHaveBeenCalled(); 153 | expect(target.base).not.toHaveBeenCalled(); 154 | expect(target.else).toHaveBeenCalled(); 155 | }); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /tools/cleanup.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync, readFileSync } from 'fs'; 2 | 3 | const packageJson = JSON.parse(readFileSync('./dist/package.json').toString()); 4 | delete packageJson.devDependencies; 5 | delete packageJson.scripts; 6 | writeFileSync('./dist/package.json', JSON.stringify(packageJson, null, 2)); 7 | 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "stripInternal": true, 9 | "declaration": true, 10 | "outDir": "./dist", 11 | "lib": ["es2015", "dom"] 12 | }, 13 | "files": [ 14 | "index.ts", 15 | "test/sem-dsl.spec.ts" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------