diff --git a/.eslintrc b/.eslintrc index 2052e44..1d0a4a1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,8 +1,5 @@ { - "ecmaFeatures": { - "modules": true - }, - "env": { + "env": { "node": true, "es6": true, "mocha": true diff --git a/.gitignore b/.gitignore index 89bcf39..740d966 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules coverage .vscode +package-lock.json diff --git a/.travis.yml b/.travis.yml index 138cf6d..ec02ede 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,15 @@ language: node_js node_js: - - "6" + - 8 + - 10 + - 12 cache: - node_modules install: - npm install - - npm install -g eslint - npm install -g codecov script: - - eslint . + - npm run lint - istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec - codecov diff --git a/README.md b/README.md index 4e67cfb..e57f4b1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ query .get() // /[a-z]{3}[0-9]*$/g ``` -Required Node 6.0+ for the ES6 support, Or you can use [Babel](http://babeljs.io/) to support Node below 6.0. +Required Node 8.0+ for the ES6 support, Or you can use [Babel](http://babeljs.io/) to support Node below 6.0. Using [Webpack](http://webpack.github.io) and [babel-loader](https://github.com/babel/babel-loader) to pack it if want to use in browsers. diff --git a/lib/Builder.js b/lib/Builder.js index e441900..96398ad 100644 --- a/lib/Builder.js +++ b/lib/Builder.js @@ -39,16 +39,31 @@ const simpleMapper = { 'type': METHOD_TYPE_CHARACTER, 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS }, + 'backslash': { + 'add': '\\\\', + 'type': METHOD_TYPE_CHARACTER, + 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS + }, 'tab': { 'add': '\\t', 'type': METHOD_TYPE_CHARACTER, 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS }, + 'verticalTab': { + 'add': '\\v', + 'type': METHOD_TYPE_CHARACTER, + 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS + }, 'newLine': { 'add': '\\n', 'type': METHOD_TYPE_CHARACTER, 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS }, + 'carriageReturn': { + 'add': '\\r', + 'type': METHOD_TYPE_CHARACTER, + 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS + }, 'whitespace': { 'add': '\\s', 'type': METHOD_TYPE_CHARACTER, @@ -68,7 +83,18 @@ const simpleMapper = { 'add': '\\W', 'type': METHOD_TYPE_CHARACTER, 'allowed': METHOD_TYPES_ALLOWED_FOR_CHARACTERS + }, + 'word': { + 'add': '\\b', + 'type': METHOD_TYPE_CHARACTER, + 'allowed': METHOD_TYPE_BEGIN + }, + 'nonWord': { + 'add': '\\B', + 'type': METHOD_TYPE_CHARACTER, + 'allowed': METHOD_TYPE_BEGIN } + } class Builder { @@ -140,6 +166,21 @@ class Builder { return this.add(`[${result}]`) } + /** + * Literally match a character that is not one of these characters. + * + * @param {string} chars + * @return {Builder} + */ + noneOf(chars) { + this._validateAndAddMethodType(METHOD_TYPE_CHARACTER, METHOD_TYPES_ALLOWED_FOR_CHARACTERS) + + let result = chars.split('').map((character) => this.escape(character)).join('') + result = result.replace('-', '\\-').replace(']', '\\]') + + return this.add(`[^${result}]`) + } + /** * Literally match all of these characters in that order. * @@ -166,6 +207,17 @@ class Builder { return this.add(`[${min}-${max}]`) } + /** + * Match any non-digit character (in given span). Default will be any character not between 0 and 9. + * + * @return {Builder} + */ + noDigit() { + this._validateAndAddMethodType(METHOD_TYPE_CHARACTER, METHOD_TYPES_ALLOWED_FOR_CHARACTERS) + + return this.add('[^0-9]') + } + /** * Match any uppercase letter (between A to Z). * @@ -194,10 +246,10 @@ class Builder { /**********************************************************/ /** - * Match any of these condition. + * Match any of these conditions. * * @param {Closure|Builder|string} conditions Anonymous function with its Builder as first parameter. - * @return {Builer} + * @return {Builder} */ anyOf(conditions) { this._validateAndAddMethodType(METHOD_TYPE_GROUP, METHOD_TYPES_ALLOWED_FOR_CHARACTERS) @@ -423,10 +475,18 @@ class Builder { return this._addFromMapper('any') } + backslash() { + return this._addFromMapper('backslash') + } + tab() { return this._addFromMapper('tab') } + verticalTab() { + return this._addFromMapper('verticalTab') + } + newLine() { return this._addFromMapper('newLine') } @@ -447,6 +507,14 @@ class Builder { return this._addFromMapper('noCharacter') } + word() { + return this._addFromMapper('word') + } + + nonWord() { + return this._addFromMapper('nonWord') + } + /**********************************************************/ /* INTERNAL METHODS */ /**********************************************************/ @@ -693,6 +761,9 @@ class Builder { _mapCaptureIndexToName(result) { const names = this._captureNames + // No match + if (!result) {return null} + return Array.prototype.reduce.call(result.slice(1), (result, current, index) => { if (names[index]) { result[names[index]] = current || '' diff --git a/lib/Language/Helpers/buildQuery.js b/lib/Language/Helpers/buildQuery.js index 8c1dabc..d1225d1 100644 --- a/lib/Language/Helpers/buildQuery.js +++ b/lib/Language/Helpers/buildQuery.js @@ -42,9 +42,13 @@ function buildQuery(query, builder = new Builder()) { // Now, append that method to the builder object. method.setParameters(parameters).callMethodOn(builder) } catch (e) { - if (Array.isArray(parameters[0])) { + const lastIndex = parameters.length - 1 + if (Array.isArray(parameters[lastIndex])) { + if (lastIndex !== 0) { + method.setParameters(parameters.slice(0, lastIndex)) + } method.callMethodOn(builder) - builder.and(buildQuery(parameters[0], new NonCapture())) + builder.and(buildQuery(parameters[lastIndex], new NonCapture())) } else { throw new SyntaxException(`Invalid parameter given for ${method.origin}`) } diff --git a/lib/Language/Helpers/methodMatch.js b/lib/Language/Helpers/methodMatch.js index 27a82f9..7f2b62d 100644 --- a/lib/Language/Helpers/methodMatch.js +++ b/lib/Language/Helpers/methodMatch.js @@ -13,11 +13,14 @@ const SyntaxException = require('../../Exceptions/Syntax') // Unimplemented: all lazy, single line, unicode, first match const mapper = { 'any character': { 'class': SimpleMethod, 'method': 'anyCharacter' }, + 'backslash': { 'class': SimpleMethod, 'method': 'backslash' }, 'no character': { 'class': SimpleMethod, 'method': 'noCharacter' }, 'multi line': { 'class': SimpleMethod, 'method': 'multiLine' }, 'case insensitive': { 'class': SimpleMethod, 'method': 'caseInsensitive' }, 'starts with': { 'class': SimpleMethod, 'method': 'startsWith' }, + 'start with': { 'class': SimpleMethod, 'method': 'startsWith' }, 'begin with': { 'class': SimpleMethod, 'method': 'startsWith' }, + 'begins with': { 'class': SimpleMethod, 'method': 'startsWith' }, 'must end': { 'class': SimpleMethod, 'method': 'mustEnd' }, 'once or more': { 'class': SimpleMethod, 'method': 'onceOrMore' }, 'never or more': { 'class': SimpleMethod, 'method': 'neverOrMore' }, @@ -25,17 +28,26 @@ const mapper = { 'whitespace': { 'class': SimpleMethod, 'method': 'whitespace' }, 'no whitespace': { 'class': SimpleMethod, 'method': 'noWhitespace' }, 'anything': { 'class': SimpleMethod, 'method': 'any' }, - 'tab': { 'class': SimpleMethod, 'method': 'atb' }, + 'tab': { 'class': SimpleMethod, 'method': 'tab' }, + 'vertical tab': { 'class': SimpleMethod, 'method': 'verticalTab' }, 'digit': { 'class': SimpleMethod, 'method': 'digit' }, + 'no digit': { 'class': SimpleMethod, 'method': 'noDigit' }, + 'nondigit': { 'class': SimpleMethod, 'method': 'noDigit' }, 'number': { 'class': SimpleMethod, 'method': 'digit' }, 'letter': { 'class': SimpleMethod, 'method': 'letter' }, 'uppercase': { 'class': SimpleMethod, 'method': 'uppercaseLetter' }, 'once': { 'class': SimpleMethod, 'method': 'once' }, 'twice': { 'class': SimpleMethod, 'method': 'twice' }, + 'word': { 'class': SimpleMethod, 'method': 'word' }, + 'no word': { 'class': SimpleMethod, 'method': 'nonWord' }, + 'nonword': { 'class': SimpleMethod, 'method': 'nonWord' }, + 'carriage return': { 'class': SimpleMethod, 'method': 'carriageReturn' }, + 'carriagereturn': { 'class': SimpleMethod, 'method': 'carriageReturn' }, 'literally': { 'class': DefaultMethod, 'method': 'literally' }, 'either of': { 'class': DefaultMethod, 'method': 'anyOf' }, 'any of': { 'class': DefaultMethod, 'method': 'anyOf' }, + 'none of': { 'class': DefaultMethod, 'method': 'noneOf' }, 'if followed by': { 'class': DefaultMethod, 'method': 'ifFollowedBy' }, 'if not followed by': { 'class': DefaultMethod, 'method': 'ifNotFollowedBy' }, 'optional': { 'class': DefaultMethod, 'method': 'optional' }, diff --git a/package.json b/package.json index 33816d7..cbac627 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "srl", - "version": "0.2.1", + "version": "0.2.3", "description": "Simple Regex Language", "main": "lib/SRL.js", "scripts": { "test": "mocha", + "lint": "eslint lib --fix", "coverage": "istanbul cover _mocha" }, "repository": { @@ -30,6 +31,7 @@ }, "homepage": "https://simple-regex.com", "devDependencies": { + "eslint": "^6.8.0", "istanbul": "^0.4.5", "mocha": "^3.0.2", "mocha-lcov-reporter": "^1.2.0" diff --git a/test/issue-11-test.js b/test/issue-11-test.js new file mode 100644 index 0000000..68bf352 --- /dev/null +++ b/test/issue-11-test.js @@ -0,0 +1,19 @@ +'use strict' + +const assert = require('assert') +const SRL = require('../') + +describe('Fix issue 11', () => { + it('Numerical quantifies & non-capturing group', () => { + const query = new SRL('digit, exactly 5 times, (letter, twice) optional') + assert.ok(query.isMatching('12345')) + assert.ok(query.isMatching('12345aa')) + }) + + it('Complicated case', () => { + const query = new SRL('begin with, digit, exactly 5 times, ( literally \'-\', digit, exactly 4 times ), optional, must end') + assert.ok(query.isMatching('12345-1234')) + }) +}) + + diff --git a/test/issue-17-test.js b/test/issue-17-test.js new file mode 100644 index 0000000..2a30be1 --- /dev/null +++ b/test/issue-17-test.js @@ -0,0 +1,16 @@ +'use strict' + +const assert = require('assert') +const SRL = require('../') + +describe('Fix issue 17', () => { + it('Capture group name assignment fails', () => { + assert.doesNotThrow(() => { + const query = new SRL('capture (literally "TEST") as test') + const match = query.getMatch('WORD NOT HERE') + assert.equal(match, null) + }, TypeError) + }) +}) + + diff --git a/test/parseParentheses-test.js b/test/parseParentheses-test.js index 89e2024..7f9782a 100644 --- a/test/parseParentheses-test.js +++ b/test/parseParentheses-test.js @@ -18,8 +18,8 @@ describe('ParseParentheses Test', () => { assert.deepEqual(parseParentheses('foo (0)'), [ 'foo', [ '0' ] ]) assert.deepEqual( - parseParentheses('foo (bar (nested)) baz'), - [ 'foo', [ 'bar', [ 'nested' ] ], 'baz' ] + parseParentheses('foo (bar (nested)) baz'), + [ 'foo', [ 'bar', [ 'nested' ] ], 'baz' ] ) assert.deepEqual( diff --git a/test/rules b/test/rules index 91a988c..be17002 160000 --- a/test/rules +++ b/test/rules @@ -1 +1 @@ -Subproject commit 91a988cf30e81e67a6869a799bbb082e95a67d60 +Subproject commit be17002c2403999ff418dfd78343307bb7efc3ff