Repository: sanctuary-js/sanctuary (js)

Issue:
stripPrefix and stripSuffix
PureScript provides [`stripPrefix`][1] and [`stripSuffix`][2]:

```purescript
stripPrefix :: Pattern -> String -> Maybe String
stripSuffix :: Pattern -> String -> Maybe String
```

I believe Sanctuary should do so too. Today I found myself wanting `stripSuffix` (in Haskell), so I now see the utility. :)


[1]: https://pursuit.purescript.org/packages/purescript-strings/2.1.0/docs/Data.String#v:stripPrefix
[2]: https://pursuit.purescript.org/packages/purescript-strings/2.1.0/docs/Data.String#v:stripSuffix


Pull request description:
string: add S.stripPrefix and S.stripSuffix
Closes #329

When reviewing, please pay particularly close attention to the `stripSuffix` implementation. Negative indexes impinge on my reasoning ability, but in striking a balance between readability and efficiency I decided not to use `Math.max(0, s.length - suffix.length)` to avoid them.


Patch:
diff --git a/index.js b/index.js
index d252df7..adebd8d 100644
--- a/index.js
+++ b/index.js
@@ -3995,6 +3995,50 @@
   }
   S.trim = def('trim', {}, [$.String, $.String], trim);
 
+  //# stripPrefix :: String -> String -> Maybe String
+  //.
+  //. Returns Just the portion of the given string (the second argument) left
+  //. after removing the given prefix (the first argument) if the string starts
+  //. with the prefix; Nothing otherwise.
+  //.
+  //. See also [`stripSuffix`](#stripSuffix).
+  //.
+  //. ```javascript
+  //. > S.stripPrefix('https://', 'https://sanctuary.js.org')
+  //. Just('sanctuary.js.org')
+  //.
+  //. > S.stripPrefix('https://', 'http://sanctuary.js.org')
+  //. Nothing
+  //. ```
+  function stripPrefix(prefix, s) {
+    var idx = prefix.length;
+    return s.slice(0, idx) === prefix ? Just(s.slice(idx)) : Nothing;
+  }
+  S.stripPrefix =
+  def('stripPrefix', {}, [$.String, $.String, $Maybe($.String)], stripPrefix);
+
+  //# stripSuffix :: String -> String -> Maybe String
+  //.
+  //. Returns Just the portion of the given string (the second argument) left
+  //. after removing the given suffix (the first argument) if the string ends
+  //. with the suffix; Nothing otherwise.
+  //.
+  //. See also [`stripPrefix`](#stripPrefix).
+  //.
+  //. ```javascript
+  //. > S.stripSuffix('.md', 'README.md')
+  //. Just('README')
+  //.
+  //. > S.stripSuffix('.md', 'README')
+  //. Nothing
+  //. ```
+  function stripSuffix(suffix, s) {
+    var idx = s.length - suffix.length;  // value may be negative
+    return s.slice(idx) === suffix ? Just(s.slice(0, idx)) : Nothing;
+  }
+  S.stripSuffix =
+  def('stripSuffix', {}, [$.String, $.String, $Maybe($.String)], stripSuffix);
+
   //# words :: String -> Array String
   //.
   //. Takes a string and returns the array of words the string contains


Test patch:
diff --git a/test/stripPrefix.js b/test/stripPrefix.js
new file mode 100644
index 0000000..eccd1dc
--- /dev/null
+++ b/test/stripPrefix.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('stripPrefix', function() {
+
+  eq(typeof S.stripPrefix, 'function');
+  eq(S.stripPrefix.length, 2);
+  eq(S.stripPrefix.toString(), 'stripPrefix :: String -> String -> Maybe String');
+
+  eq(S.stripPrefix('', ''), S.Just(''));
+  eq(S.stripPrefix('', 'abc'), S.Just('abc'));
+  eq(S.stripPrefix('a', ''), S.Nothing);
+  eq(S.stripPrefix('a', 'abc'), S.Just('bc'));
+  eq(S.stripPrefix('a', '[abc]'), S.Nothing);
+  eq(S.stripPrefix('aaa', 'a'), S.Nothing);
+  eq(S.stripPrefix('https://', 'https://sanctuary.js.org'), S.Just('sanctuary.js.org'));
+  eq(S.stripPrefix('https://', 'http://sanctuary.js.org'), S.Nothing);
+
+});
diff --git a/test/stripSuffix.js b/test/stripSuffix.js
new file mode 100644
index 0000000..b3d1dbd
--- /dev/null
+++ b/test/stripSuffix.js
@@ -0,0 +1,23 @@
+'use strict';
+
+var S = require('..');
+
+var eq = require('./internal/eq');
+
+
+test('stripSuffix', function() {
+
+  eq(typeof S.stripSuffix, 'function');
+  eq(S.stripSuffix.length, 2);
+  eq(S.stripSuffix.toString(), 'stripSuffix :: String -> String -> Maybe String');
+
+  eq(S.stripSuffix('', ''), S.Just(''));
+  eq(S.stripSuffix('', 'xyz'), S.Just('xyz'));
+  eq(S.stripSuffix('z', ''), S.Nothing);
+  eq(S.stripSuffix('z', 'xyz'), S.Just('xy'));
+  eq(S.stripSuffix('z', '[xyz]'), S.Nothing);
+  eq(S.stripSuffix('zzz', 'z'), S.Nothing);
+  eq(S.stripSuffix('.md', 'README.md'), S.Just('README'));
+  eq(S.stripSuffix('.md', 'README'), S.Nothing);
+
+});