Repository: plexis-js/plexis (js)

Issue:
Feature: `head / popFirst`
Extracts the first length characters from the input text.

```javascript
  head(); // => ''
  head('Hello'); // => 'H'
  head('Hello', 2); // => 'He'
  head('Hello', 100); // => 'Hello'
```

**Aliases**

```javascript
import head from '@plexis/head';
import {head, first, popFirst} from 'plexis';
```

Pull request description:
feat: 🎸 head / popFirst
Extracts the first length characters from the input text.
closes #101 

Patch:
diff --git a/docs/pages/api.md b/docs/pages/api.md
index a709fe8..5a1c4f8 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)
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..def8575 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",
diff --git a/packages/plexis/src/index.js b/packages/plexis/src/index.js
index dfdcfbe..1942daf 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';


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..689a989 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",