Repository: char0n/ramda-adjunct (js)

Issue:
padCharsEnd
**Is your feature request related to a problem? Please describe.**

This function pads the current string with a given string (repeated, if needed) so that the resulting string reaches a given length. The padding is applied from the end of the current string.

**Describe the solution you'd like**

```js
RA.padCharsEnd('-', 3, 'a'); // => 'a--'
```

The function is of arity=3 and is autocurried.

**Additional context**

The functions should use [String.prototype.padEnd()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd) if available. If not polyfill like [this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd#Polyfill) should be provided to compensate.

[This PR](https://github.com/char0n/ramda-adjunct/pull/1067) should serve as a model template of PR for `padCharsEnd` function. 


Pull request description:
feat(string): add padCharsEnd
Closes #1104


Patch:
diff --git a/package-lock.json b/package-lock.json
index 4d4d060768..413b2efdb7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6719,9 +6719,9 @@
       }
     },
     "glob": {
-      "version": "7.1.4",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
-      "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+      "version": "7.1.5",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz",
+      "integrity": "sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ==",
       "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
diff --git a/package.json b/package.json
index f5cb42c716..73bbd026f4 100644
--- a/package.json
+++ b/package.json
@@ -155,7 +155,7 @@
     "fantasy-land": "4.0.0",
     "fantasy-laws": "=1.2.0",
     "folktale": "=2.3.2",
-    "glob": "7.1.4",
+    "glob": "7.1.5",
     "husky": "3.0.9",
     "istanbul": "=0.4.5",
     "jsdoc": "3.6.3",
diff --git a/src/index.js b/src/index.js
index 6c210e0b67..a4a4f5b15d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -196,5 +196,6 @@ export { default as replaceAll } from './replaceAll';
 export { default as escapeRegExp } from './escapeRegExp';
 export { default as trimStart } from './trimStart';
 export { default as trimLeft } from './trimStart'; // alias of trimStart
+export { default as padCharsEnd } from './padCharsEnd';
 // Types
 export { default as Identity } from './fantasy-land/Identity';
diff --git a/src/internal/polyfills/String.padCharsEnd.js b/src/internal/polyfills/String.padCharsEnd.js
new file mode 100644
index 0000000000..824a0b83d5
--- /dev/null
+++ b/src/internal/polyfills/String.padCharsEnd.js
@@ -0,0 +1,25 @@
+import isFunction from '../../isFunction';
+import repeat from './String.repeat';
+
+const padCharsEndPolyfill = (padString, targetLength, value) => {
+  // eslint-disable-next-line no-bitwise
+  let finalLength = targetLength >> 0;
+  let finalPadString = String(
+    typeof padString !== 'undefined' ? padString : ' '
+  );
+
+  if (value.length > finalLength) {
+    return String(value);
+  }
+  finalLength -= value.length;
+  if (finalLength > finalPadString.length) {
+    const remainingLength = finalLength / finalPadString.length;
+    finalPadString += isFunction(String.prototype.repeat)
+      ? finalPadString.repeat(remainingLength)
+      : repeat(finalPadString, remainingLength);
+  }
+
+  return String(value) + finalPadString.slice(0, finalLength);
+};
+
+export default padCharsEndPolyfill;
diff --git a/src/padCharsEnd.js b/src/padCharsEnd.js
new file mode 100644
index 0000000000..797e4ec83b
--- /dev/null
+++ b/src/padCharsEnd.js
@@ -0,0 +1,37 @@
+import { curry, invoker, flip } from 'ramda';
+
+import polyfill from './internal/polyfills/String.padCharsEnd';
+import isFunction from './isFunction';
+
+export const padCharsEndStrPolyfill = curry(polyfill);
+
+export const padCharsEndStrInvoker = flip(invoker(2, 'padEnd'));
+
+/**
+ * The function pads the current string with a given string
+ * (repeated, if needed) so that the resulting string reaches a given length.
+ * The padding is applied from the end of the current string.
+ *
+ * @func padCharsEnd
+ * @memberOf RA
+ * @since {@link https://char0n.github.io/ramda-adjunct/2.22.0|v2.22.0}
+ * @category String
+ * @sig String -> Number -> String -> String
+ * @param {string} padString The string to pad the current string with
+ * @param {number} targetLength The length of the resulting string once
+ * the current string has been padded
+ * @param {string} value String value to be padded
+ * @return {string} A new string of the specified length with the pad string
+ * applied at the end of the current string
+ * @see {@link RA.padEnd|padEnd}, {@link RA.padCharsStart|padCharsStart}, {@link RA.padStart|padStart}
+ * @example
+ *
+ * RA.padCharsEnd('-', 3, 'a'); // => 'a--'
+ * RA.padCharsEnd('foo', 10, 'abc'); // => 'abcfoofoof'
+ * RA.padCharsEnd('123456', 6, 'abc'); // => 'abc123'
+ */
+const padCharsEnd = isFunction(String.prototype.padEnd)
+  ? padCharsEndStrInvoker
+  : padCharsEndStrPolyfill;
+
+export default padCharsEnd;
diff --git a/types/index.d.ts b/types/index.d.ts
index 990271a80c..1d6c7429e3 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -1253,6 +1253,15 @@ declare namespace RamdaAdjunct {
          */
         trimStart(value: string): string;
         trimLeft(value: string): string;
+
+        /*
+         * The function pads the current string with a given string
+         * (repeated, if needed) so that the resulting string reaches a given length.
+         * The padding is applied from the end of the current string.
+         */
+        padCharsEnd(padString: string, targetLength: number, value: string): string;
+        padCharsEnd(padString: string, targetLength: number): (value: string) => string;
+        padCharsEnd(padString: string): (targetLength: number, value: string) => string;
     }
 }
 


Test patch:
diff --git a/test/padCharsEnd.js b/test/padCharsEnd.js
new file mode 100644
index 0000000000..952aa84cb0
--- /dev/null
+++ b/test/padCharsEnd.js
@@ -0,0 +1,213 @@
+import * as RA from '../src';
+import {
+  padCharsEndStrInvoker,
+  padCharsEndStrPolyfill,
+} from '../src/padCharsEnd';
+import eq from './shared/eq';
+
+describe('padCharsEnd', function() {
+  specify('should pad string with given padString', function() {
+    eq(RA.padCharsEnd('-', 3, 'a'), 'a--');
+  });
+
+  context('given targetLength as 0', function() {
+    specify('should return original string', function() {
+      eq(RA.padCharsEnd('$', 0, 'abc'), 'abc');
+    });
+  });
+
+  context('given targetLength less than string length', function() {
+    specify('should return original string', function() {
+      eq(RA.padCharsEnd('$', 1, 'abc'), 'abc');
+    });
+  });
+
+  context('given targetLength less than equal to padString length', function() {
+    specify('should trim padString', function() {
+      eq(RA.padCharsEnd('123456', 6, 'abc'), 'abc123');
+      eq(RA.padCharsEnd('123456', 5, 'abc'), 'abc12');
+    });
+  });
+
+  context('given targetLength supplied as float', function() {
+    specify('should convert targetLength to integer', function() {
+      eq(RA.padCharsEnd('$', 7.5, 'abc'), 'abc$$$$');
+    });
+  });
+
+  context(
+    'given targetLength is a value coercible to valid integer number',
+    function() {
+      specify('should return padded string', function() {
+        eq(RA.padCharsEnd('*', '5', 'abc'), 'abc**');
+      });
+    }
+  );
+
+  context('given targeLength is a negative number', function() {
+    specify('should return original string', function() {
+      eq(RA.padCharsEnd('$', -10, 'abc'), 'abc');
+    });
+  });
+
+  context('given padString set to undefined', function() {
+    specify('should return string padded with spaces', function() {
+      eq(RA.padCharsEnd(undefined, 5, '200'), '200  ');
+    });
+  });
+
+  context('given null padString', function() {
+    specify('should convert null to string type', function() {
+      eq(RA.padCharsEnd(null, 5, 'abc'), 'abcnu');
+    });
+  });
+
+  specify('should be curried', function() {
+    eq(RA.padCharsEnd('*', 5, 'abc'), 'abc**');
+    eq(RA.padCharsEnd('*', 5)('abc'), 'abc**');
+    eq(RA.padCharsEnd('*')(5, 'abc'), 'abc**');
+    eq(RA.padCharsEnd('*')(5)('abc'), 'abc**');
+  });
+
+  context('padCharsEndStrInvoker', function() {
+    beforeEach(function() {
+      if (RA.isNotFunction(String.prototype.padEnd)) {
+        this.skip();
+      }
+    });
+
+    specify('should pad string with given padString', function() {
+      eq(padCharsEndStrInvoker('-', 3, 'a'), 'a--');
+    });
+
+    context('given targetLength as 0', function() {
+      specify('should return original string', function() {
+        eq(padCharsEndStrInvoker('$', 0, 'abc'), 'abc');
+      });
+    });
+
+    context('given targetLength less than string length', function() {
+      specify('should return original string', function() {
+        eq(padCharsEndStrInvoker('$', 1, 'abc'), 'abc');
+      });
+    });
+
+    context(
+      'given targetLength less than equal to padString length',
+      function() {
+        specify('should trim padString', function() {
+          eq(padCharsEndStrInvoker('123456', 6, 'abc'), 'abc123');
+          eq(padCharsEndStrInvoker('123456', 5, 'abc'), 'abc12');
+        });
+      }
+    );
+
+    context('given targetLength supplied as float', function() {
+      specify('should convert targetLength to integer', function() {
+        eq(padCharsEndStrInvoker('$', 7.5, 'abc'), 'abc$$$$');
+      });
+    });
+
+    context(
+      'given targetLength is a value coercible to valid integer number',
+      function() {
+        specify('should return padded string', function() {
+          eq(padCharsEndStrInvoker('*', '5', 'abc'), 'abc**');
+        });
+      }
+    );
+
+    context('given targeLength is a negative number', function() {
+      specify('should return original string', function() {
+        eq(padCharsEndStrInvoker('$', -10, 'abc'), 'abc');
+      });
+    });
+
+    context('given padString set to undefined', function() {
+      specify('should return string padded with spaces', function() {
+        eq(padCharsEndStrInvoker(undefined, 5, '200'), '200  ');
+      });
+    });
+
+    context('given null padString', function() {
+      specify('should convert null to string type', function() {
+        eq(padCharsEndStrInvoker(null, 5, 'abc'), 'abcnu');
+      });
+    });
+
+    specify('should be curried', function() {
+      eq(padCharsEndStrInvoker('*', 5, 'abc'), 'abc**');
+      eq(padCharsEndStrInvoker('*', 5)('abc'), 'abc**');
+      eq(padCharsEndStrInvoker('*')(5, 'abc'), 'abc**');
+      eq(padCharsEndStrInvoker('*')(5)('abc'), 'abc**');
+    });
+  });
+
+  context('padCharsEndStrPolyfill', function() {
+    specify('should pad string with given padString', function() {
+      eq(padCharsEndStrPolyfill('-', 3, 'a'), 'a--');
+    });
+
+    context('given targetLength as 0', function() {
+      specify('should return original string', function() {
+        eq(padCharsEndStrPolyfill('$', 0, 'abc'), 'abc');
+      });
+    });
+
+    context('given targetLength less than string length', function() {
+      specify('should return original string', function() {
+        eq(padCharsEndStrPolyfill('$', 1, 'abc'), 'abc');
+      });
+    });
+
+    context(
+      'given targetLength less than equal to padString length',
+      function() {
+        specify('should trim padString', function() {
+          eq(padCharsEndStrPolyfill('123456', 6, 'abc'), 'abc123');
+          eq(padCharsEndStrPolyfill('123456', 5, 'abc'), 'abc12');
+        });
+      }
+    );
+
+    context('given targetLength supplied as float', function() {
+      specify('should convert targetLength to integer', function() {
+        eq(padCharsEndStrPolyfill('$', 7.5, 'abc'), 'abc$$$$');
+      });
+    });
+
+    context(
+      'given targetLength is a value coercible to valid integer number',
+      function() {
+        specify('should return padded string', function() {
+          eq(padCharsEndStrPolyfill('*', '5', 'abc'), 'abc**');
+        });
+      }
+    );
+
+    context('given targeLength is a negative number', function() {
+      specify('should return original string', function() {
+        eq(padCharsEndStrPolyfill('$', -10, 'abc'), 'abc');
+      });
+    });
+
+    context('given padString set to undefined', function() {
+      specify('should return string padded with spaces', function() {
+        eq(padCharsEndStrPolyfill(undefined, 5, '200'), '200  ');
+      });
+    });
+
+    context('given null padString', function() {
+      specify('should convert null to string type', function() {
+        eq(padCharsEndStrPolyfill(null, 5, 'abc'), 'abcnu');
+      });
+    });
+
+    specify('should be curried', function() {
+      eq(padCharsEndStrPolyfill('*', 5, 'abc'), 'abc**');
+      eq(padCharsEndStrPolyfill('*', 5)('abc'), 'abc**');
+      eq(padCharsEndStrPolyfill('*')(5, 'abc'), 'abc**');
+      eq(padCharsEndStrPolyfill('*')(5)('abc'), 'abc**');
+    });
+  });
+});
diff --git a/types/test/padCharsEnd.ts b/types/test/padCharsEnd.ts
new file mode 100644
index 0000000000..c38a42dba7
--- /dev/null
+++ b/types/test/padCharsEnd.ts
@@ -0,0 +1,10 @@
+import * as RA from 'ramda-adjunct';
+
+RA.padCharsEnd('-', 3, 'a'); // $ExpectType string
+RA.padCharsEnd('$', 5)('abc'); // $ExpectType string
+RA.padCharsEnd('*')(10, 'hello'); // $ExpectType string
+
+RA.padCharsEnd(1, 2, '3'); // $ExpectError
+RA.padCharsEnd('#', '10', 'afsd'); // $ExpectError
+RA.padCharsEnd(undefined, '12', 54); // $ExpectError
+RA.padCharsEnd(null, 12, 'hello'); // $ExpectError