Repository: sanctuary-js/sanctuary
Pull request #693 (2020-08-12 16:36:03)

regexp: add S.replace
Supersedes #686

This pull request adds the following function:

```haskell
replace :: (Array (Maybe String) -> String) -> RegExp -> String -> String
```

In <https://github.com/sanctuary-js/sanctuary/pull/686#discussion_r466296798>, @Avaq made a strong case for naming this more general function `replace`. @Avaq also noted that the specialized function, `replaceWith`, is trivially derivable from `replace`. For this reason I decided not to include `replaceWith` in this pull request; it could easily be added in the future if `replace (K ('...'))` proves irksome.

I have opened sanctuary-js/sanctuary-site#90 to provide several examples of `replace` in use.



Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2ee728d..b45eb01 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -28,7 +28,7 @@ jobs:
               nvm exec $1 npm test
             }
             case $CIRCLE_NODE_INDEX in
-              0) test_with_version 8 ;;
-              1) test_with_version 10 ;;
-              2) npm install && npm test ;;
+              0) test_with_version 10 ;;
+              1) npm install && npm test ;;
+              2) test_with_version 14 ;;
             esac
diff --git a/index.js b/index.js
index fe5a288..0a699bb 100644
--- a/index.js
+++ b/index.js
@@ -4403,6 +4403,53 @@
     impl: matchAll
   };
 
+  //# replace :: (Array (Maybe String) -> String) -> RegExp -> String -> String
+  //.
+  //. Replaces occurrences of the given pattern within the given string
+  //. in accordance with the given replacement function, which receives an
+  //. array of captured values. Replaces all occurrences of the pattern if
+  //. its `g` flag is set; just the first occurrence otherwise.
+  //.
+  //. ```javascript
+  //. > S.replace (([$1]) => S.maybe ('') (S.toUpper) ($1)) (/(\w)/) ('foo')
+  //. 'Foo'
+  //.
+  //. > S.replace (([$1]) => S.maybe ('') (S.toUpper) ($1)) (/(\w)/g) ('foo')
+  //. 'FOO'
+  //.
+  //. > S.replace (S.show) (/(foo)(bar)?/) ('<>')
+  //. '<>'
+  //.
+  //. > S.replace (S.show) (/(foo)(bar)?/) ('<foo>')
+  //. '<[Just ("foo"), Nothing]>'
+  //.
+  //. > S.replace (S.show) (/(foo)(bar)?/) ('<foobar>')
+  //. '<[Just ("foo"), Just ("bar")]>'
+  //. ```
+  function replace(substitute) {
+    return function(pattern) {
+      return function(text) {
+        return text.replace (pattern, function() {
+          var groups = [];
+          var group, idx = 1;
+          //  eslint-disable-next-line no-plusplus
+          while (typeof (group = arguments[idx++]) !== 'number') {
+            groups.push (group == null ? Nothing : Just (group));
+          }
+          return substitute (groups);
+        });
+      };
+    };
+  }
+  _.replace = {
+    consts: {},
+    types: [$.Fn ($.Array ($.Maybe ($.String))) ($.String),
+            $.RegExp,
+            $.String,
+            $.String],
+    impl: replace
+  };
+
   //. ### String
 
   //# toUpper :: String -> String


Test patch:
diff --git a/test/.eslintrc.json b/test/.eslintrc.json
index 35afa62..cb24959 100644
--- a/test/.eslintrc.json
+++ b/test/.eslintrc.json
@@ -1,9 +1,11 @@
 {
   "root": true,
   "extends": ["../node_modules/sanctuary-style/eslint-es6.json"],
+  "parserOptions": {"ecmaVersion": 2018},
   "env": {"node": true},
   "globals": {"suite": false, "test": false},
   "rules": {
+    "comma-dangle": ["error", {"arrays": "always-multiline", "objects": "always-multiline", "functions": "never"}],
     "max-len": ["off"]
   }
 }
diff --git a/test/replace.js b/test/replace.js
new file mode 100644
index 0000000..f0d26ce
--- /dev/null
+++ b/test/replace.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const S = require ('..');
+
+const eq = require ('./internal/eq');
+
+
+test ('replace', () => {
+
+  eq (S.show (S.replace)) ('replace :: (Array (Maybe String) -> String) -> RegExp -> String -> String');
+
+  eq (S.replace (([$1]) => S.maybe ('') (S.toUpper) ($1)) (/(\w)/) ('foo')) ('Foo');
+  eq (S.replace (([$1]) => S.maybe ('') (S.toUpper) ($1)) (/(\w)/g) ('foo')) ('FOO');
+  eq (S.replace (S.show) (/(foo)(bar)?/) ('<>')) ('<>');
+  eq (S.replace (S.show) (/(foo)(bar)?/) ('<foo>')) ('<[Just ("foo"), Nothing]>');
+  eq (S.replace (S.show) (/(foo)(bar)?/) ('<foobar>')) ('<[Just ("foo"), Just ("bar")]>');
+  eq (S.replace (S.show) (/@(?<username>[-\w]+)/) ('@sanctuary-js')) ('[Just ("sanctuary-js")]');
+
+});