Repository: plaid/sanctuary (js)

Issue:
function for creating RegExp values
Similar to [`regex`][1] from PureScript's Data.String.Regex module.


[1]: https://github.com/purescript/purescript-strings/blob/v0.7.0/docs/Data/String/Regex.md#regex-1


Pull request description:
regexp: add S.regex
Closes #97

I'm not thrilled about naming this function `regex` when the type's name is RegEx<strong>p</strong>, but `regexp` is inconsistent in its capitalization and `regExp` is unbearably ugly. `regex` seems the best of the three options.

Having dropped the "p" elsewhere, I'm happy to name the pseudotype RegexFlags (as PureScript does).


Patch:
diff --git a/index.js b/index.js
index 70eb53f..409b07d 100644
--- a/index.js
+++ b/index.js
@@ -81,6 +81,19 @@
 //. The List pseudotype represents non-Function values with numeric `length`
 //. properties greater than or equal to zero, such as `[1, 2, 3]` and `'foo'`.
 //.
+//. ### RegexFlags pseudotype
+//.
+//. The RegexFlags pseudotype represents the set of canonical [RegExp][] flags:
+//.
+//.   - `''`
+//.   - `'g'`
+//.   - `'i'`
+//.   - `'m'`
+//.   - `'gi'`
+//.   - `'gm'`
+//.   - `'im'`
+//.   - `'gim'`
+//.
 //. ### Type representatives
 //.
 //. What is the type of `Number`? One answer is `a -> Number`, since it's a
@@ -151,6 +164,7 @@
   var Accessible = /* istanbul ignore next */ function Accessible() {};
   var Integer = /* istanbul ignore next */ function Integer() {};
   var List = /* istanbul ignore next */ function List() {};
+  var RegexFlags = /* istanbul ignore next */ function RegexFlags() {};
   var TypeRep = /* istanbul ignore next */ function TypeRep() {};
   var a = {name: 'a'};
   var b = {name: 'b'};
@@ -166,6 +180,8 @@
                x >= MIN_SAFE_INTEGER && x <= MAX_SAFE_INTEGER;
       case List:
         return !_is(Function, x) && _is(Number, x.length) && x.length >= 0;
+      case RegexFlags:
+        return R.test(/^g?i?m?$/, x);
       case TypeRep:
         return _is(Function, x);
       default:
@@ -1826,6 +1842,19 @@
 
   //. ### RegExp
 
+  //# regex :: RegexFlags -> String -> RegExp
+  //.
+  //. Takes a [RegexFlags](#regexflags-pseudotype) and a pattern, and returns
+  //. a RegExp.
+  //.
+  //. ```javascript
+  //. > S.regex('g', ':\\d+:')
+  //. /:\d+:/g
+  //. ```
+  S.regex = def('regex', [RegexFlags, String], function(flags, source) {
+    return new RegExp(source, flags);
+  });
+
   //# match :: RegExp -> String -> Maybe [Maybe String]
   //.
   //. Takes a pattern and a string, and returns Just a list of matches
@@ -1915,6 +1944,7 @@
 //. [R.is]:         http://ramdajs.com/docs/#is
 //. [R.map]:        http://ramdajs.com/docs/#map
 //. [Ramda]:        http://ramdajs.com/
+//. [RegExp]:       https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
 //. [Semigroup]:    https://github.com/fantasyland/fantasy-land#semigroup
 //. [parseInt]:     https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt
 //. [primitives]:   https://developer.mozilla.org/en-US/docs/Glossary/Primitive


Test patch:
diff --git a/test/index.js b/test/index.js
index c9f95e8..8faea06 100644
--- a/test/index.js
+++ b/test/index.js
@@ -2739,6 +2739,53 @@ describe('parse', function() {
 
 describe('regexp', function() {
 
+  describe('regex', function() {
+
+    it('is a binary function', function() {
+      eq(typeof S.regex, 'function');
+      eq(S.regex.length, 2);
+    });
+
+    it('type checks its arguments', function() {
+      assert.throws(function() { S.regex('y'); },
+                    errorEq(TypeError,
+                            '‘regex’ requires a value of type RegexFlags ' +
+                            'as its first argument; received "y"'));
+
+      assert.throws(function() { S.regex('G'); },
+                    errorEq(TypeError,
+                            '‘regex’ requires a value of type RegexFlags ' +
+                            'as its first argument; received "G"'));
+
+      assert.throws(function() { S.regex('ig'); },
+                    errorEq(TypeError,
+                            '‘regex’ requires a value of type RegexFlags ' +
+                            'as its first argument; received "ig"'));
+
+      assert.throws(function() { S.regex('', /(?:)/); },
+                    errorEq(TypeError,
+                            '‘regex’ requires a value of type String ' +
+                            'as its second argument; received /(?:)/'));
+    });
+
+    it('returns a RegExp', function() {
+      eq(S.regex('', '\\d'), /\d/);
+      eq(S.regex('g', '\\d'), /\d/g);
+      eq(S.regex('i', '\\d'), /\d/i);
+      eq(S.regex('m', '\\d'), /\d/m);
+      eq(S.regex('gi', '\\d'), /\d/gi);
+      eq(S.regex('gm', '\\d'), /\d/gm);
+      eq(S.regex('im', '\\d'), /\d/im);
+      eq(S.regex('gim', '\\d'), /\d/gim);
+    });
+
+    it('is curried', function() {
+      eq(S.regex('').length, 1);
+      eq(S.regex('')('\\d'), /\d/);
+    });
+
+  });
+
   describe('match', function() {
 
     it('is a binary function', function() {