Repository: plexis-js/plexis (js)

Issue:
Feature: `tail / rest / last / pop`
Extracts the last length characters from the input text.

```javascript
  tail(); // => ''
  tail('Hello'); // => 'o'
  tail('Hello', 2); // => 'lo'
  tail('Hello', 100); // => 'Hello'
```

**Aliases**

```javascript
import tail from '@plexis/tail';
import {tail, rest, last, pop} from 'plexis';
```


Pull request description:
feat: tail / rest / last / pop #102
Extracts the last length characters from the input text
closes #102 

Patch:
diff --git a/docs/pages/api.md b/docs/pages/api.md
index a709fe8..a9d68cc 100644
--- a/docs/pages/api.md
+++ b/docs/pages/api.md
@@ -6,6 +6,7 @@
 - [distance](/packages/distance)
 - [endsWith](/packages/endsWith)
 - [escapeHTML](/packages/escapeHTML)
+- [head](/packages/head)
 - [isAlpha](/packages/isAlpha)
 - [isAlphaNumeric](/packages/isAlphaNumeric)
 - [isLowerCase](/packages/isLowerCase)
@@ -30,3 +31,4 @@
 - [withoutDiacritics](/packages/withoutDiacritics)
 - [withoutHTML](/packages/withoutHTML)
 - [withoutIndent](/packages/withoutIndent)
+- [tail](/packages/tail)
diff --git a/packages/head/README.md b/packages/head/README.md
new file mode 100644
index 0000000..ce11647
--- /dev/null
+++ b/packages/head/README.md
@@ -0,0 +1,24 @@
+## `head`
+
+Extracts the first length characters from the input text.
+
+`npm i @plexis/head`
+
+**Usage**
+
+```javascript
+import head from '@plexis/head';
+
+head(); // => ''
+head('Hello'); // => 'H'
+head('Hello', 2); // => 'He'
+head('Hello', 100); // => 'Hello'
+```
+
+**Aliases**
+
+```javascript
+import {head} from 'plexis';
+import {first} from 'plexis';
+import {popFirst} from 'plexis';
+```
diff --git a/packages/head/package.json b/packages/head/package.json
new file mode 100644
index 0000000..61a67f0
--- /dev/null
+++ b/packages/head/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "@plexis/head",
+  "version": "0.0.21",
+  "main": "dist/index.js",
+  "module": "dist/index.esm.js",
+  "jsnext:main": "./src/index.js",
+  "files": [
+    "dist",
+    "src"
+  ],
+  "license": "MIT",
+  "keywords": [
+    "utility",
+    "string",
+    "parse",
+    "format",
+    "validate"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/plexis-js/plexis.git"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}
diff --git a/packages/head/src/index.js b/packages/head/src/index.js
new file mode 100644
index 0000000..e4715e9
--- /dev/null
+++ b/packages/head/src/index.js
@@ -0,0 +1,23 @@
+/**
+ * @description Extracts the first length characters from the input text.
+ * @param {String} text
+ * @param {Number} length
+ * @example
+ * head(); // => ''
+ * head('Hello'); // => 'H'
+ * head('Hello', 2); // => 'He'
+ * head('Hello', 100); // => 'Hello'
+ */
+const head = (text = '', length = 1) => {
+  if (typeof text !== 'string') {
+    throw new Error('Input text must be of type string');
+  }
+
+  if (isNaN(length) || typeof length !== 'number') {
+    throw new Error('Length characters must be of type number');
+  }
+
+  return text.substr(0, length);
+};
+
+export default head;
diff --git a/packages/plexis/package.json b/packages/plexis/package.json
index ddccfac..5496a29 100644
--- a/packages/plexis/package.json
+++ b/packages/plexis/package.json
@@ -18,6 +18,7 @@
     "@plexis/distance": "^0.0.21",
     "@plexis/ends-with": "^0.0.21",
     "@plexis/escape-html": "^0.0.21",
+    "@plexis/head": "^0.0.21",
     "@plexis/is-alpha": "^0.0.21",
     "@plexis/is-alphanumeric": "^0.0.21",
     "@plexis/is-lowercase": "^0.0.21",
@@ -40,7 +41,8 @@
     "@plexis/when": "^0.0.21",
     "@plexis/without-diacritics": "^0.0.21",
     "@plexis/without-html": "^0.0.21",
-    "@plexis/without-indent": "^0.0.21"
+    "@plexis/without-indent": "^0.0.21",
+    "@plexis/tail": "^0.0.21"
   },
   "keywords": [
     "utility",
diff --git a/packages/plexis/src/index.js b/packages/plexis/src/index.js
index dfdcfbe..eb29f01 100644
--- a/packages/plexis/src/index.js
+++ b/packages/plexis/src/index.js
@@ -11,6 +11,7 @@ export {
 } from '@plexis/distance';
 export {default as endsWith} from '@plexis/ends-with';
 export {default as escapeHTML} from '@plexis/escape-html';
+export {default as head, default as first, default as popFirst} from '@plexis/head';
 export {default as isAlpha} from '@plexis/is-alpha';
 export {default as isLower, default as isLowerCase} from '@plexis/is-lowercase';
 export {default as isNum, default as isNumeric} from '@plexis/is-numeric';
@@ -59,3 +60,4 @@ export {
   default as isAlphaDigit
 } from '@plexis/is-alphanumeric';
 export {default as withoutHTML, default as removeHTML} from '@plexis/without-html';
+export {default as tail, default as rest, default as last, default as pop} from '@plexis/tail';
diff --git a/packages/tail/README.md b/packages/tail/README.md
new file mode 100644
index 0000000..80c8a8d
--- /dev/null
+++ b/packages/tail/README.md
@@ -0,0 +1,24 @@
+## `head`
+
+Extracts the last length characters from the input text
+
+`npm i @plexis/tail`
+
+**Usage**
+
+```javascript
+import tail from '@plexis/tail';
+tail(); // => ''
+tail('Hello'); // => 'o'
+tail('Hello', 2); // => 'lo'
+tail('Hello', 100); // => 'Hello'
+```
+
+**Aliases**
+
+```javascript
+import {tail} from 'plexis';
+import {rest} from 'plexis';
+import {last} from 'plexis';
+import {pop} from 'plexis';
+```
diff --git a/packages/tail/package.json b/packages/tail/package.json
new file mode 100644
index 0000000..bad04e3
--- /dev/null
+++ b/packages/tail/package.json
@@ -0,0 +1,26 @@
+{
+    "name": "@plexis/tail",
+    "version": "0.0.21",
+    "main": "dist/index.js",
+    "module": "dist/index.esm.js",
+    "jsnext:main": "./src/index.js",
+    "files": [
+      "dist",
+      "src"
+    ],
+    "license": "MIT",
+    "keywords": [
+      "utility",
+      "string",
+      "parse",
+      "format",
+      "validate"
+    ],
+    "repository": {
+      "type": "git",
+      "url": "git+https://github.com/plexis-js/plexis.git"
+    },
+    "publishConfig": {
+      "access": "public"
+    }
+  }
\ No newline at end of file
diff --git a/packages/tail/src/index.js b/packages/tail/src/index.js
new file mode 100644
index 0000000..266fc68
--- /dev/null
+++ b/packages/tail/src/index.js
@@ -0,0 +1,15 @@
+/**
+ * @description Extracts the last length characters from the input text
+ * @param {String} text
+ * @param {Number} endingPosition, optional
+ * @example
+ * tail(); // returns ''
+ * tail('Hello'); // returns 'o'
+ * tail('Hello', 2); // returns 'lo'
+ * tail('Hello', 100); // returns 'Hello'
+ */
+
+const tail = (text, endingPosition = 1) =>
+  text ? text.substring(text.length - endingPosition) : '';
+
+export default tail;


Test patch:
diff --git a/packages/head/test/index.js b/packages/head/test/index.js
new file mode 100644
index 0000000..1d837fd
--- /dev/null
+++ b/packages/head/test/index.js
@@ -0,0 +1,40 @@
+import head from '../src';
+
+describe('returns the first length characters from the input text', () => {
+  const cases = [{text: 'John'}, {text: 'Doe', length: 2}, {text: 'Hello', length: 100}];
+  const expected = ['J', 'Do', 'Hello'];
+
+  cases.forEach(({text, length}, i) => {
+    test(`head(${text},${length})`, () => {
+      expect(head(text, length)).toBe(expected[i]);
+    });
+  });
+});
+
+test('returns empty string if there is no input text provided', () => {
+  expect(head()).toBe('');
+});
+
+describe('throws error if text is invalid', () => {
+  const invalids = [null, NaN, {key: 'value'}, ['invalid'], 55];
+
+  invalids.forEach(invalid => {
+    test(`head(${invalid})`, () => {
+      expect(() => {
+        head(invalid);
+      }).toThrow();
+    });
+  });
+});
+
+describe('throws error if length is invalid', () => {
+  const invalids = [NaN, null, '55', {key: 'value'}, ['invalid']];
+
+  invalids.forEach(invalid => {
+    test(`head('Hello',${invalid})`, () => {
+      expect(() => {
+        head('Hello', invalid);
+      }).toThrow();
+    });
+  });
+});
diff --git a/packages/plexis/test/__snapshots__/index.js.snap b/packages/plexis/test/__snapshots__/index.js.snap
index 6780a90..5d09292 100644
--- a/packages/plexis/test/__snapshots__/index.js.snap
+++ b/packages/plexis/test/__snapshots__/index.js.snap
@@ -13,6 +13,9 @@ Array [
   "levenshtein",
   "endsWith",
   "escapeHTML",
+  "head",
+  "first",
+  "popFirst",
   "isAlpha",
   "isLower",
   "isLowerCase",
@@ -61,5 +64,9 @@ Array [
   "isAlphaDigit",
   "withoutHTML",
   "removeHTML",
+  "tail",
+  "rest",
+  "last",
+  "pop",
 ]
 `;
diff --git a/packages/tail/test/index.js b/packages/tail/test/index.js
new file mode 100644
index 0000000..d26b091
--- /dev/null
+++ b/packages/tail/test/index.js
@@ -0,0 +1,8 @@
+import tail from '../src';
+
+it('Extracts the last length characters from the input text', () => {
+  expect(tail()).toBe('');
+  expect(tail('Hello')).toBe('o');
+  expect(tail('Hello', 2)).toBe('lo');
+  expect(tail('Hello', 100)).toBe('Hello');
+});