├── .gitignore
├── .istanbul.yml
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── git_hooks
└── pre-commit
├── lib
├── constraintInterpreter.js
├── errors.js
├── escaper.js
├── functionalHelpers.js
├── grouper.js
├── quantifier.js
├── regularity.js
├── specialIdentifiers.js
└── wordUtils.js
├── package.json
└── spec
├── regularity_spec.js
└── support
└── jasmine.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # For posting to coveralls.io from your local machine.
4 | # This file contains the 'repo_token', which MUST NOT
5 | # be made public! Hence, tell Git to ignore it.
6 | .coveralls.yml
7 |
8 |
9 | # This is where istanbul drops its coverage report (when run locally)
10 | coverage
11 |
--------------------------------------------------------------------------------
/.istanbul.yml:
--------------------------------------------------------------------------------
1 | instrumentation:
2 | # Prevent tests from being instrumented so as to not skew coverage results
3 | excludes: ["**/spec/**"]
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 | # this is the default (see http://docs.travis-ci.com/user/languages/javascript-with-nodejs/#Travis-CI-uses-npm)
6 | # install: "npm install"
7 |
8 |
9 | # this is the default (see http://docs.travis-ci.com/user/languages/javascript-with-nodejs/#Default-Test-Script)
10 | # script: "npm test"
11 |
12 |
13 | # run 'istanbul' and post coverage results to coveralls.io
14 | after_script: "npm run post-to-coveralls-io"
15 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | grunt.initConfig({
4 | jshint: {
5 | all: ["Gruntfile.js", "lib/**/*.js", "spec/**/*.js"],
6 | options: {
7 | /* See http://jshint.com/docs/options */
8 | node: true,
9 | jasmine: true
10 | }
11 | }
12 | });
13 |
14 |
15 | grunt.loadNpmTasks('grunt-contrib-jshint');
16 |
17 | grunt.registerTask('default', ['jshint']);
18 | };
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2015 Fernando Martínez
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # regularity — regular expressions for humans
2 |
3 | [![Build Status][travis-image]][travis-url]
4 | [![Coverage Status][coveralls-image]][coveralls-url]
5 | [![Code Climate][codeclimate-image]][codeclimate-url]
6 |
7 | [![NPM][nodeico-image]][nodeico-url]
8 |
9 |
10 |
11 |
12 | **regularity** is a friendly regular expression builder
13 | for [Node](https://nodejs.org).
14 | It is a JavaScript port
15 | of the very fine [`regularity` Ruby gem](https://rubygems.org/gems/regularity).
16 |
17 |
18 | Regular expressions are a powerful way
19 | of matching patterns against text,
20 | but too often they are _write once, read never_.
21 | After all, who wants to try and decipher
22 |
23 | ```javascript
24 | /^(?:[0-9]){3}-(?:[A-Za-z]){2}(?:#)?(?:a|b)(?:a){2,4}\$$/i
25 | ```
26 |
27 | when you could express it as
28 |
29 | ```javascript
30 | var Regularity = require('regularity');
31 | var regularity = new Regularity();
32 |
33 | var myRegexp = regularity
34 | .startWith(3, 'digits')
35 | .then('-')
36 | .then(2, 'letters')
37 | .maybe('#')
38 | .oneOf('a', 'b')
39 | .between([2, 4], 'a')
40 | .endWith('$')
41 | .insensitive()
42 | .done();
43 | ```
44 |
45 | While taking up a bit more space,
46 | regular expressions created using **regularity**
47 | are much more readable than their cryptic counterparts.
48 | But they are still native regular expressions!
49 |
50 |
51 |
52 | ## Installation
53 |
54 | To get **regularity**,
55 | you need to have [npm](https://www.npmjs.com/) installed.
56 | It should have been installed
57 | along with [Node](https://nodejs.org).
58 | Once you have it, just run
59 |
60 | ```
61 | $ npm install regularity
62 | ```
63 |
64 | in your terminal,
65 | and it should promptly download
66 | into the current folder.
67 |
68 |
69 |
70 | ## Usage
71 |
72 | When you [require](https://nodejs.org/api/modules.html#modules_modules) **regularity**,
73 | all you get is a constructor function.
74 | To start building a regular expression,
75 | you should instantiate an object using `new`,
76 | as demonstrated above,
77 | and then call
78 | any of the methods it provides
79 | with the proper arguments.
80 |
81 | When you are done building the regular expression,
82 | tell **regularity** so by calling [`done`](#done).
83 | This call will return a native [`RegExp`][regexp-mdn]
84 | implementing the pattern which you described
85 | using the methods of the **regularity** object,
86 | which you can then use
87 | for your own purposes.
88 |
89 | Notice that you should probably use
90 | one new **regularity** instance
91 | for each regular expression
92 | that you want to build.
93 | If you keep calling methods
94 | on an existing **regularity** instance,
95 | you will be reusing
96 | the declarations you made on that object before.
97 |
98 |
99 |
100 | ## Documentation
101 |
102 | **regularity** instances expose a set of methods
103 | (the _[DSL](https://en.wikipedia.org/wiki/Domain-specific_language) methods_)
104 | which allow you to declaratively build
105 | the regular expression you want.
106 | They all return `this`,
107 | so they are chainable.
108 | Notice that the order in which you call these methods
109 | determines the order in which the pattern is assembled.
110 |
111 | All _DSL methods_ accept at least
112 | one of the following signatures:
113 | either an _unnumbered constraint_,
114 | which is expected to be a single string,
115 | such as `then('xyz')`,
116 | or a _numbered constraint_,
117 | composed by a count and a pattern,
118 | such as `atLeast(2, 'ab')`.
119 |
120 | In addition, the following _special identifers_
121 | are supported as a shorthand
122 | for some common patterns:
123 |
124 | ```javascript
125 | 'digit' : '[0-9]'
126 | 'lowercase' : '[a-z]'
127 | 'uppercase' : '[A-Z]'
128 | 'letter' : '[A-Za-z]'
129 | 'alphanumeric' : '[A-Za-z0-9]'
130 | 'whitespace' : '\s'
131 | 'space' : ' '
132 | 'tab' : '\t'
133 | ```
134 |
135 | _Special identifiers_ may be pluralized,
136 | and **regularity** will still understand them.
137 | This allows you
138 | to write more meaningful declarations,
139 | because `then(2, 'letters')` works
140 | in addition to `then(1, 'letter')`.
141 |
142 |
143 | The following is a more detailed explanation
144 | of all the _DSL methods_ and their signatures.
145 | Should you have any doubts,
146 | please refer to the [spec](./spec/regularity_spec.js),
147 | where you can find examples
148 | of all the supported use cases.
149 |
150 | Bear in mind that, in what follows,
151 | `pattern` stands for any string,
152 | which might or might not be
153 | any of the _special identifiers_,
154 | and which might include characters
155 | which need escaping (you don't need
156 | to escape them yourself, as **regularity**
157 | will take care of that),
158 | and `n` stands for any positive integer
159 | (that is, any integer
160 | greater than or equal to `1`).
161 | Where `n` is optional
162 | (denoted by `[n,]` in the signature),
163 | passing `1` as `n`
164 | is equivalent to not passing `n` at all.
165 |
166 | - [**`startWith([n,] pattern)`**](#startWith):
167 | Require that `pattern` occur
168 | exactly `n` times
169 | at the beginning of the input.
170 | This method may be called only once.
171 |
172 | - [**`append([n,] pattern)`**](#append):
173 | Require that `pattern` occur
174 | exactly `n` times
175 | after what has been declared so far
176 | and before anything that is declared afterwards.
177 |
178 | - [**`then([n,] pattern)`**](#then):
179 | This is just an alias for [**`append`**](#append).
180 |
181 | - [**`endWith([n,] pattern)`**](#endWith):
182 | Require that `pattern` occur
183 | exactly `n` times
184 | at the end of the input.
185 | This method may be called only once.
186 |
187 |
188 | - [**`maybe(pattern)`**](#maybe):
189 | Require that `pattern` occur
190 | either one or zero times.
191 |
192 | - [**`oneOf(firstPattern[, secondPattern[, ...]])`**](#oneOf):
193 | Require that at least
194 | one of the passed `pattern`s occur.
195 |
196 | - [**`between(range, pattern)`**](#between):
197 | Require that `pattern` occur
198 | a number of consecutive times
199 | between `range[0]` and `range[1]`, both included.
200 | `range` is expected to be an array
201 | containing two positive integers.
202 |
203 | - [**`zeroOrMore(pattern)`**](#zeroOrMore):
204 | Require that `pattern` occur
205 | any number of consecutive times,
206 | including zero times.
207 |
208 | - [**`oneOrMore(pattern)`**](#oneOrMore):
209 | Require that `pattern` occur
210 | consecutively at least once.
211 |
212 | - [**`atLeast(n, pattern)`**](#atLeast):
213 | Require that `pattern` occur
214 | consecutively at least `n` times.
215 | Typically, here `n` should be greater than `1`
216 | (if you want it to be exactly `1`, you should use [**`oneOrMore`**](#oneOrMore)).
217 |
218 | - [**`atMost(n, pattern)`**](#atMost):
219 | Require that `pattern` occur
220 | consecutively at most `n` times.
221 | Typically, here `n` should be greater than `1`
222 | (if you want it to be exactly `1`, you should use [**`maybe`**](#maybe)).
223 |
224 |
225 |
226 | Besides the _DSL methods_, **regularity** instances
227 | also expose the following methods:
228 |
229 | - [**`insensitive()`**](#insensitive):
230 | Specify that the regular expression
231 | mustn't distinguish
232 | between uppercacase and lowercase letters.
233 |
234 | - [**`global()`**](#global):
235 | Specify that the regular expression
236 | must match against all possible matches in the string
237 | (instead of matching just the first,
238 | which is the default behaviour).
239 |
240 | - [**`multiline()`**](#multiline):
241 | Specify that the string
242 | against which the [`RegExp`][regexp-mdn] is to be matched
243 | may span multiple lines.
244 |
245 | - [**`done()`**](#done):
246 | Return the native [`RegExp`][regexp-mdn] object
247 | representing the pattern which you described
248 | by means of the previous calls
249 | on that **regularity** instance.
250 |
251 | - [**`regexp()`**](#regexp):
252 | This is just an alias for [**`done`**](#done).
253 |
254 |
255 |
256 | ## Credits
257 |
258 | Original idea and [Ruby](https://rubygems.org/gems/regularity)
259 | [implementation](https://github.com/andrewberls/regularity)
260 | are by [Andrew Berls](https://github.com/andrewberls/).
261 |
262 | If you are unsure about the [`RegExp`][regexp-mdn]
263 | you just built, [`regulex`](https://jex.im/regulex)
264 | is a great tool which will draw you
265 | a fancy _railroad diagram_ of it.
266 |
267 |
268 | ## License
269 |
270 | This project is licensed under the
271 | [MIT License](http://opensource.org/licenses/MIT).
272 | For more details, see the [`LICENSE`](./LICENSE) file
273 | at the root of the repository.
274 |
275 |
276 |
277 | [travis-image]: https://travis-ci.org/angelsanz/regularity.svg?branch=master
278 | [travis-url]: https://travis-ci.org/angelsanz/regularity
279 | [coveralls-image]: https://coveralls.io/repos/angelsanz/regularity/badge.svg?branch=master
280 | [coveralls-url]: https://coveralls.io/r/angelsanz/regularity?branch=master
281 | [codeclimate-image]: https://codeclimate.com/github/angelsanz/regularity/badges/gpa.svg
282 | [codeclimate-url]: https://codeclimate.com/github/angelsanz/regularity
283 | [nodeico-image]: https://nodei.co/npm/regularity.png?downloads=true&stars=true
284 | [nodeico-url]: https://nodei.co/npm/regularity/
285 |
286 | [regexp-mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
287 |
--------------------------------------------------------------------------------
/git_hooks/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | git stash -q --keep-index
4 |
5 | npm test
6 | TEST_RESULT="$?"
7 |
8 | git stash pop -q
9 |
10 | exit $TEST_RESULT
11 |
--------------------------------------------------------------------------------
/lib/constraintInterpreter.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var quantify = require('./quantifier');
4 | var _escapeRegExp = require('./escaper').escapeRegExp;
5 | var _translate = require('./specialIdentifiers').translate;
6 |
7 |
8 | var interpret = function(userArguments) {
9 | return constraintBuilderFromArgCount[userArguments.length].apply(null, userArguments);
10 | };
11 |
12 |
13 | var _buildNumberedConstraint = function(count, type) {
14 | return quantify.exactly(_buildUnnumberedConstraint(type), count);
15 | };
16 |
17 | var _buildUnnumberedConstraint = function(pattern) {
18 | return _translate(_escapeRegExp(pattern));
19 | };
20 |
21 | var constraintBuilderFromArgCount = {
22 | 1: _buildUnnumberedConstraint,
23 | 2: _buildNumberedConstraint
24 | };
25 |
26 | module.exports = {
27 | interpret: interpret
28 | };
29 |
--------------------------------------------------------------------------------
/lib/errors.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | MethodCalledMoreThanOnce: function(methodName) {
5 | return {
6 | name: 'MethodCalledMoreThanOnce',
7 | message: (methodName + ' must only be called once')
8 | };
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/lib/escaper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var charactersWhichNeedToBeEscaped = /[.*+?^${}()|[\]\\]/g;
4 |
5 |
6 | var escapeRegExp = function(string) {
7 | return string.replace(charactersWhichNeedToBeEscaped, '\\$&');
8 | };
9 |
10 | module.exports = {
11 | escapeRegExp: escapeRegExp
12 | };
13 |
--------------------------------------------------------------------------------
/lib/functionalHelpers.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var errors = require('./errors');
4 |
5 | var onceOrThrow = function(func) {
6 | var called = false;
7 |
8 | return function() {
9 | if (called) {
10 | throw errors.MethodCalledMoreThanOnce(func.name);
11 | }
12 |
13 |
14 | called = true;
15 |
16 | return func.apply(this, arguments);
17 | };
18 | };
19 |
20 |
21 | module.exports = {
22 | onceOrThrow: onceOrThrow
23 | };
24 |
--------------------------------------------------------------------------------
/lib/grouper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var nonCapturing = function(pattern) {
4 | return ('(?:' + pattern + ')');
5 | };
6 |
7 | var oneOf = function(choices) {
8 | return (nonCapturing(choices.join('|')));
9 | };
10 |
11 | var atBeginning = function(pattern) {
12 | return ('^' + pattern);
13 | };
14 |
15 | var atEnd = function(pattern) {
16 | return (pattern + '$');
17 | };
18 |
19 |
20 | module.exports = {
21 | nonCapturing: nonCapturing,
22 | oneOf: oneOf,
23 |
24 | atBeginning: atBeginning,
25 | atEnd: atEnd
26 | };
27 |
--------------------------------------------------------------------------------
/lib/quantifier.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var group = require('./grouper');
4 |
5 | var zeroOrOne = function(pattern) {
6 | return (group.nonCapturing(pattern) + '?');
7 | };
8 |
9 | var zeroOrMore = function(pattern) {
10 | return (group.nonCapturing(pattern) + '*');
11 | };
12 |
13 | var oneOrMore = function(pattern) {
14 | return (group.nonCapturing(pattern) + '+');
15 | };
16 |
17 | var inRange = function(pattern, beginning, end) {
18 | return (group.nonCapturing(pattern) + _makeRangeDelimiter(beginning, end));
19 | };
20 |
21 | var _makeRangeDelimiter = function(beginning, end) {
22 | return ('{' + (beginning || '0') + ',' + (end || '') + '}');
23 | };
24 |
25 | var exactly = function(pattern, times) {
26 | return ((times === 1) ?
27 | pattern :
28 | (group.nonCapturing(pattern) + '{' + times + '}'));
29 | };
30 |
31 | module.exports = {
32 | zeroOrOne: zeroOrOne,
33 | zeroOrMore:zeroOrMore,
34 | oneOrMore: oneOrMore,
35 | inRange: inRange,
36 | exactly: exactly
37 | };
38 |
--------------------------------------------------------------------------------
/lib/regularity.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _ = require('./functionalHelpers');
4 | var group = require('./grouper');
5 | var quantify = require('./quantifier');
6 |
7 | var _translate = require('./specialIdentifiers').translate;
8 | var _interpret = require('./constraintInterpreter').interpret;
9 | var _escapeRegExp = require('./escaper').escapeRegExp;
10 |
11 |
12 |
13 | var Regularity = function() {
14 |
15 | var _regexpSource = '',
16 | _beginning = '',
17 | _end = '',
18 | _flags = '';
19 |
20 |
21 |
22 | var _appendPatternChunk = function(patternChunk) {
23 | _regexpSource += patternChunk;
24 | };
25 |
26 | var _enableFlag = function(flagIdentifier) {
27 | _flags += flagIdentifier;
28 | };
29 |
30 | var _setBeginning = function(pattern) {
31 | _beginning = group.atBeginning(pattern);
32 | };
33 |
34 | var _setEnd = function(pattern) {
35 | _end = group.atEnd(pattern);
36 | };
37 |
38 |
39 |
40 |
41 | this.startWith = _.onceOrThrow(function startWith() {
42 | _setBeginning(_interpret(arguments));
43 |
44 | return this;
45 | });
46 |
47 | this.append = function() {
48 | _appendPatternChunk(_interpret(arguments));
49 |
50 | return this;
51 | };
52 | this.then = this.append;
53 |
54 | this.endWith = _.onceOrThrow(function endWith() {
55 | _setEnd(_interpret(arguments));
56 |
57 | return this;
58 | });
59 |
60 | this.maybe = function() {
61 | _appendPatternChunk(quantify.zeroOrOne(_interpret(arguments)));
62 |
63 | return this;
64 | };
65 |
66 | this.oneOf = function() {
67 | var choices = Array.prototype.slice.call(arguments)
68 | .map(_escapeRegExp)
69 | .map(_translate);
70 |
71 | _appendPatternChunk(group.oneOf(choices));
72 |
73 | return this;
74 | };
75 |
76 | this.between = function(range, pattern) {
77 | _appendPatternChunk(quantify.inRange(_interpret([pattern]), range[0], range[1]));
78 |
79 | return this;
80 | };
81 |
82 | this.zeroOrMore = function() {
83 | _appendPatternChunk(quantify.zeroOrMore(_interpret(arguments)));
84 |
85 | return this;
86 | };
87 |
88 | this.oneOrMore = function() {
89 | _appendPatternChunk(quantify.oneOrMore(_interpret(arguments)));
90 |
91 | return this;
92 | };
93 |
94 | this.atLeast = function(times, pattern) {
95 | return this.between([times, null], pattern);
96 | };
97 |
98 | this.atMost = function(times, pattern) {
99 | return this.between([null, times], pattern);
100 | };
101 |
102 | this.insensitive = _.onceOrThrow(function insensitive() {
103 | _enableFlag('i');
104 |
105 | return this;
106 | });
107 |
108 | this.global = _.onceOrThrow(function global() {
109 | _enableFlag('g');
110 |
111 | return this;
112 | });
113 |
114 | this.multiline = _.onceOrThrow(function multiline() {
115 | _enableFlag('m');
116 |
117 | return this;
118 | });
119 |
120 | this.done = function() {
121 | return (new RegExp(_beginning + _regexpSource + _end, _flags));
122 | };
123 | this.regexp = this.done;
124 | };
125 |
126 | module.exports = exports = Regularity;
127 |
--------------------------------------------------------------------------------
/lib/specialIdentifiers.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var word = require('./wordUtils');
4 |
5 |
6 | var _specialIdentifiers = {
7 | 'digit' : '[0-9]',
8 | 'lowercase' : '[a-z]',
9 | 'uppercase' : '[A-Z]',
10 | 'letter' : '[A-Za-z]',
11 | 'alphanumeric': '[A-Za-z0-9]',
12 | 'whitespace' : '\\s',
13 | 'space' : ' ',
14 | 'tab' : '\\t'
15 | };
16 |
17 | var translate = function(pattern) {
18 | return (_specialIdentifiers[word.singularize(pattern)] || pattern);
19 | };
20 |
21 |
22 | module.exports = {
23 | translate: translate
24 | };
25 |
--------------------------------------------------------------------------------
/lib/wordUtils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var singularize = function(word) {
4 | return ((word[word.length - 1] === 's') ?
5 | word.substring(0, word.length - 1) :
6 | word);
7 | };
8 |
9 |
10 | module.exports = {
11 | singularize: singularize
12 | };
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "regularity",
3 | "version": "0.5.1",
4 | "description": "regular expressions for humans",
5 | "main": "lib/regularity.js",
6 | "author": "Fernando Martinez de la Cueva (http://oinak.com/)",
7 | "contributors": [
8 | "Elías Alonso (http://redradix.com/)",
9 | "Laura Paredes Viera (http://wearepeople.io)",
10 | "Alejandra Goicoechea (http://wearepeople.io)",
11 | "Ángel Sanz (https://github.com/angelsanz)"
12 | ],
13 | "thanks": "Andrew Berls for the original idea and the Regularity Ruby gem (https://rubygems.org/gems/regularity)",
14 | "homepage": "https://github.com/oinak/regularity",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/oinak/regularity.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/oinak/regularity/issues"
21 | },
22 | "license": "MIT",
23 | "keywords": [
24 | "regular",
25 | "expression",
26 | "regex",
27 | "regexp",
28 | "dsl"
29 | ],
30 | "scripts": {
31 | "test": "jasmine",
32 | "istanbul-local": "istanbul cover jasmine",
33 | "post-to-coveralls-io": "istanbul cover jasmine && cat ./coverage/lcov.info | coveralls && rm -rf ./coverage"
34 | },
35 | "devDependencies": {
36 | "coveralls": "^2.11.2",
37 | "grunt": "^0.4.5",
38 | "grunt-contrib-jshint": "^0.11.2",
39 | "istanbul": "^0.3.14",
40 | "jasmine": "^2.1.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/spec/regularity_spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var Regularity = require('../lib/regularity.js');
4 | var errors = require('../lib/errors');
5 |
6 | describe("Regularity", function() {
7 | var regularity;
8 |
9 | beforeEach(function() {
10 | regularity = new Regularity();
11 | });
12 |
13 | it("is an object constructor", function() {
14 | expect(typeof Regularity).toBe('function');
15 | expect(typeof regularity).toBe('object');
16 | });
17 |
18 | describe("escapes regexp special characters", function() {
19 | var charactersToBeEscaped = ['*', '.', '?', '^', '+',
20 | '$', '|', '(', ')', '[',
21 | ']', '{', '}'];
22 |
23 | charactersToBeEscaped.forEach(function testCharacterIsEscaped(character) {
24 | it("escapes '" + character + "'", function() {
25 | var currentRegexp = regularity.append(character).done();
26 |
27 | expect(currentRegexp.source).toBe("\\" + character);
28 | });
29 | });
30 | });
31 |
32 | describe("#startWith requires that the passed pattern occur exactly at the beginning of the input", function() {
33 | var regexp;
34 |
35 | describe("unnumbered", function() {
36 | it("single character", function() {
37 | regexp = regularity.startWith('a').done();
38 | expect(regexp).toEqual(/^a/);
39 | });
40 |
41 | it("multiple characters", function() {
42 | regexp = regularity.startWith('abc').done();
43 | expect(regexp).toEqual(/^abc/);
44 | });
45 | });
46 |
47 | describe("numbered", function() {
48 | it("special identifiers", function() {
49 | regexp = regularity.startWith(4, 'digits').done();
50 | expect(regexp).toEqual(/^(?:[0-9]){4}/);
51 | });
52 |
53 | it("one occurrence of one character", function() {
54 | regexp = regularity.startWith(1, 'p').done();
55 | expect(regexp).toEqual(/^p/);
56 | });
57 |
58 | it("more than one occurence of one character", function() {
59 | regexp = regularity.startWith(6, 'p').done();
60 | expect(regexp).toEqual(/^(?:p){6}/);
61 | });
62 |
63 | it("one occurence of several characters", function() {
64 | regexp = regularity.startWith(1, 'hey').done();
65 | expect(regexp).toEqual(/^hey/);
66 | });
67 |
68 | it("more than one occurence of several characters", function() {
69 | regexp = regularity.startWith(5, 'hey').done();
70 | expect(regexp).toEqual(/^(?:hey){5}/);
71 | });
72 | });
73 |
74 | it("can only be called once", function() {
75 | expect(function() {
76 | regularity.startWith('a').startWith('b');
77 | }).toThrow(errors.MethodCalledMoreThanOnce('startWith'));
78 | });
79 | });
80 |
81 | describe("#endWith requires that the passed pattern occur exactly at the end of the input", function() {
82 | var regexp;
83 |
84 | describe("unnumbered", function() {
85 | it("single character", function() {
86 | regexp = regularity.endWith('a').done();
87 | expect(regexp).toEqual(/a$/);
88 | });
89 |
90 | it("multiple characters", function() {
91 | regexp = regularity.endWith('abc').done();
92 | expect(regexp).toEqual(/abc$/);
93 | });
94 | });
95 |
96 | describe("numbered", function() {
97 | it("numbered special identifier", function() {
98 | regexp = regularity.endWith(4, 'alphanumeric').done();
99 | expect(regexp).toEqual(/(?:[A-Za-z0-9]){4}$/);
100 | });
101 |
102 | it("one occurrence of one character", function() {
103 | regexp = regularity.endWith(1, 'p').done();
104 | expect(regexp).toEqual(/p$/);
105 | });
106 |
107 | it("more than one occurence of one character", function() {
108 | regexp = regularity.endWith(6, 'p').done();
109 | expect(regexp).toEqual(/(?:p){6}$/);
110 | });
111 |
112 | it("one occurence of several characters", function() {
113 | regexp = regularity.endWith(1, 'hey').done();
114 | expect(regexp).toEqual(/hey$/);
115 | });
116 |
117 | it("more than one occurence of several characters", function() {
118 | regexp = regularity.endWith(5, 'hey').done();
119 | expect(regexp).toEqual(/(?:hey){5}$/);
120 | });
121 | });
122 |
123 | it("can only be called once", function() {
124 | expect(function() {
125 | regularity.endWith('y').endWith('z');
126 | }).toThrow(errors.MethodCalledMoreThanOnce('endWith'));
127 | });
128 | });
129 |
130 | describe("#maybe requires that the passed pattern occur either one or zero times", function() {
131 | var regexp;
132 |
133 | it("special identifier", function() {
134 | regexp = regularity.maybe('letter').done();
135 | expect(regexp).toEqual(/(?:[A-Za-z])?/);
136 | });
137 |
138 | it("single character", function() {
139 | regexp = regularity.maybe('a').done();
140 | expect(regexp).toEqual(/(?:a)?/);
141 | });
142 |
143 | it("multiple characters", function() {
144 | regexp = regularity.maybe('abc').done();
145 | expect(regexp).toEqual(/(?:abc)?/);
146 | });
147 | });
148 |
149 | describe("#oneOf requires that at least one of the passed patterns occur", function() {
150 | var regexp;
151 |
152 | describe("special identifiers", function() {
153 | it("digit or tab", function() {
154 | regexp = regularity.oneOf('digit', 'tab').done();
155 | expect(regexp).toEqual(/(?:[0-9]|\t)/);
156 | });
157 |
158 | it("uppercase or whitespace", function() {
159 | regexp = regularity.oneOf('uppercase', 'whitespace').done();
160 | expect(regexp).toEqual(/(?:[A-Z]|\s)/);
161 | });
162 |
163 | it("letter or space", function() {
164 | regexp = regularity.oneOf('letter', 'space').done();
165 | expect(regexp).toEqual(/(?:[A-Za-z]| )/);
166 | });
167 | });
168 |
169 |
170 | it("one argument, one character", function() {
171 | regexp = regularity.oneOf('a').done();
172 | expect(regexp).toEqual(/(?:a)/);
173 | });
174 |
175 | it("one argument, more than one character", function() {
176 | regexp = regularity.oneOf('bc').done();
177 | expect(regexp).toEqual(/(?:bc)/);
178 | });
179 |
180 | it("multiple arguments, one character each", function() {
181 | regexp = regularity.oneOf('a', 'b', 'c').done();
182 | expect(regexp).toEqual(/(?:a|b|c)/);
183 | });
184 |
185 | it("multiple arguments, some more than one character", function() {
186 | regexp = regularity.oneOf('a', 'bc', 'def', 'gh').done();
187 | expect(regexp).toEqual(/(?:a|bc|def|gh)/);
188 | });
189 | });
190 |
191 | describe("#between requires that the passed pattern occur a number of consecutive times within the specified interval", function() {
192 | var regexp;
193 |
194 | describe("special identifiers", function() {
195 | it("digits", function() {
196 | regexp = regularity.between([3, 5], 'digits').done();
197 | expect(regexp).toEqual(/(?:[0-9]){3,5}/);
198 | });
199 |
200 | it("whitespace", function() {
201 | regexp = regularity.between([2, 6], 'whitespaces').done();
202 | expect(regexp).toEqual(/(?:\s){2,6}/);
203 | });
204 |
205 | it("lowercase", function() {
206 | regexp = regularity.between([4, 8], 'lowercases').done();
207 | expect(regexp).toEqual(/(?:[a-z]){4,8}/);
208 | });
209 | });
210 |
211 | it("one character", function() {
212 | regexp = regularity.between([2, 4], 'a').done();
213 | expect(regexp).toEqual(/(?:a){2,4}/);
214 | });
215 |
216 | it("more than one character", function() {
217 | regexp = regularity.between([2, 4], 'abc').done();
218 | expect(regexp).toEqual(/(?:abc){2,4}/);
219 | });
220 |
221 |
222 |
223 | it("throws a native error when the lower bound is greater than the upper bound", function() {
224 | expect(function() {
225 | var regexp = regularity.between([5, 3], 'k').done();
226 | }).toThrowError(SyntaxError);
227 | });
228 | });
229 |
230 | describe("#append requires that the passed pattern occur after what has been declared so far (and before whatever is declared afterwards), as many times as specified (or one, by default)", function() {
231 | var regexp;
232 |
233 | describe("unnumbered", function() {
234 | it("one character", function() {
235 | regexp = regularity.append('a').done();
236 | expect(regexp).toEqual(/a/);
237 | });
238 |
239 | it("more than one character", function() {
240 | regexp = regularity.append('abc').done();
241 | expect(regexp).toEqual(/abc/);
242 | });
243 | });
244 |
245 | describe("numbered", function() {
246 | it("one time, one character", function() {
247 | regexp = regularity.append(1, 'a').done();
248 | expect(regexp).toEqual(/a/);
249 | });
250 |
251 | it("more than one time, one character", function() {
252 | regexp = regularity.append(3, 'a').done();
253 | expect(regexp).toEqual(/(?:a){3}/);
254 | });
255 |
256 | it("one time, more than one character", function() {
257 | regexp = regularity.append(1, 'abc').done();
258 | expect(regexp).toEqual(/abc/);
259 | });
260 |
261 | it("more than one time, more than one character", function() {
262 | regexp = regularity.append(5, 'abc').done();
263 | expect(regexp).toEqual(/(?:abc){5}/);
264 | });
265 | });
266 |
267 | describe("special identifiers", function() {
268 | it("letters", function() {
269 | regexp = regularity.append(3, 'letters').done();
270 | expect(regexp).toEqual(/(?:[A-Za-z]){3}/);
271 | });
272 |
273 | it("uppercase", function() {
274 | regexp = regularity.append(1, 'uppercase').done();
275 | expect(regexp).toEqual(/[A-Z]/);
276 | });
277 | });
278 | });
279 |
280 | it("#then is just an alias for #append", function() {
281 | expect(regularity.then).toBe(regularity.append);
282 | });
283 |
284 | describe("#zeroOrMore requires that the passed pattern occur consecutively any number of consecutive times, including zero", function() {
285 | var regexp;
286 |
287 | describe("special identifiers", function() {
288 | it("lowercase", function() {
289 | regexp = regularity.zeroOrMore('lowercases').done();
290 | expect(regexp).toEqual(/(?:[a-z])*/);
291 | });
292 | });
293 |
294 | it("one character", function() {
295 | regexp = regularity.zeroOrMore('a').done();
296 | expect(regexp).toEqual(/(?:a)*/);
297 | });
298 |
299 | it("more than one character", function() {
300 | regexp = regularity.zeroOrMore('abc').done();
301 | expect(regexp).toEqual(/(?:abc)*/);
302 | });
303 | });
304 |
305 | describe("#oneOrMore requires that the passed pattern occur consecutively at least once", function() {
306 | var regexp;
307 |
308 | describe("special identifiers", function() {
309 | it("digits", function() {
310 | regexp = regularity.oneOrMore('digits').done();
311 | expect(regexp).toEqual(/(?:[0-9])+/);
312 | });
313 | });
314 |
315 | it("one character", function() {
316 | regexp = regularity.oneOrMore('a').done();
317 | expect(regexp).toEqual(/(?:a)+/);
318 | });
319 |
320 | it("more than one character", function() {
321 | regexp = regularity.oneOrMore('abc').done();
322 | expect(regexp).toEqual(/(?:abc)+/);
323 | });
324 | });
325 |
326 | describe("#atLeast requires that the passed pattern occur consecutively at least the specified number of times", function() {
327 | var regexp;
328 |
329 | describe("special identifiers", function() {
330 | it("tabs", function() {
331 | regexp = regularity.atLeast(4, 'tabs').done();
332 | expect(regexp).toEqual(/(?:\t){4,}/);
333 | });
334 | });
335 |
336 | it("one character", function() {
337 | regexp = regularity.atLeast(3, 'a').done();
338 | expect(regexp).toEqual(/(?:a){3,}/);
339 | });
340 |
341 | it("more than one character", function() {
342 | regexp = regularity.atLeast(5, 'abc').done();
343 | expect(regexp).toEqual(/(?:abc){5,}/);
344 | });
345 | });
346 |
347 | describe("#atMost requires that the passed pattern occur consecutively at most the specified number of times", function() {
348 | var regexp;
349 |
350 | describe("special identifiers", function() {
351 | it("spaces", function() {
352 | regexp = regularity.atMost(8, 'spaces').done();
353 | expect(regexp).toEqual(/(?: ){0,8}/);
354 | });
355 | });
356 |
357 | it("one character", function() {
358 | regexp = regularity.atMost(3, 'a').done();
359 | expect(regexp).toEqual(/(?:a){0,3}/);
360 | });
361 |
362 | it("more than one character", function() {
363 | regexp = regularity.atMost(5, 'abc').done();
364 | expect(regexp).toEqual(/(?:abc){0,5}/);
365 | });
366 | });
367 |
368 | describe("#insensitive specifies that the matching must be done case-insensitively", function() {
369 | beforeEach(function() {
370 | regularity.insensitive();
371 | });
372 |
373 | it("sets the 'insensitive' native flag", function() {
374 | var regexp = regularity.done();
375 | expect(regexp.ignoreCase).toBe(true);
376 | });
377 |
378 | it("can only be called once", function() {
379 | expect(function() {
380 | regularity.insensitive();
381 | }).toThrow(errors.MethodCalledMoreThanOnce('insensitive'));
382 | });
383 | });
384 |
385 | describe("#global specifies that the matching must be performed as many times as necessary to identify all matches", function() {
386 | beforeEach(function() {
387 | regularity.global();
388 | });
389 |
390 | it("sets the 'global' native flag", function() {
391 | var regexp = regularity.done();
392 | expect(regexp.global).toBe(true);
393 | });
394 |
395 | it("can only be called once", function() {
396 | expect(function() {
397 | regularity.global();
398 | }).toThrow(errors.MethodCalledMoreThanOnce('global'));
399 | });
400 | });
401 |
402 | describe("#multiline specifies that the input may span multiple lines", function() {
403 | beforeEach(function() {
404 | regularity.multiline();
405 | });
406 |
407 | it("sets the 'multiline' native flag", function() {
408 | var regexp = regularity.done();
409 | expect(regexp.multiline).toBe(true);
410 | });
411 |
412 | it("can only be called once", function() {
413 | expect(function() {
414 | regularity.multiline();
415 | }).toThrow(errors.MethodCalledMoreThanOnce('multiline'));
416 | });
417 | });
418 |
419 | describe("#done", function() {
420 | it("returns a RegExp instance", function() {
421 | expect(regularity.done() instanceof RegExp).toBe(true);
422 | });
423 |
424 | it("returns an empty regexp by default", function() {
425 | expect(regularity.done()).toEqual(new RegExp());
426 | });
427 | });
428 |
429 | it("#regexp is just an alias for #done", function() {
430 | expect(regularity.regexp).toBe(regularity.done);
431 | });
432 | });
433 |
--------------------------------------------------------------------------------
/spec/support/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "spec",
3 | "spec_files": [
4 | "**/*[sS]pec.js"
5 | ],
6 | "helpers": [
7 | "helpers/**/*.js"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------