lib/string-utilz.js
/**
* Copyright 2017 MaddHacker
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/*
* MAKE STRINGS USEFUL => (optional) Prototypes for String so it's not as annoying...
*
* To use, simply include:
* require('string-utilz');
* in your main entry point (typically index.js)
*/
/* #fmt constants */
const __open = '%{';
const __close = '}';
const __esc = '%';
/**
* Simple method to escape all special RegExp fxns
*
* Special values:
* - [ ] / { } ( ) * + ? . \ ^ $ |
*
* @param {string} tmpStr to escape
* @returns {string} with all special values escaped
* $& means the whole matched string
*/
const escapeRegEx = (tmpStr) => { return tmpStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); }
/**
* Abstract away RegEx management...
*
* @param {string} tmpStr to escape
* @param {string} flags RegEx flags requested
* @return {RegExp} escaped string RegExp with given flags
*
* @see #escapeRegEx(tmpStr)
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags
*/
const generateRegEx = (tmpStr, flags) => { return new RegExp(escapeRegEx(tmpStr), flags || ''); }
/**
* Generic replace to make code climate happier (and code more DRY)
*
* @param {string} tmpStr string to do the 'replace old with new' on
* @param {string} oldStr string to replace
* @param {string} newStr string to replace with
* @param {string} flags RegExp flags
* @return {string}
*
* @see #generateRegEx(tmpStr, flags)
* @see #escapeRegEx(tmpStr)
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags
*/
const replaceAllGeneric = (tmpStr, oldStr, newStr, flags) => { return tmpStr.replace(generateRegEx(oldStr, flags), newStr); }
/**
* Combine 2 strings together
* with limited maximum 1 space between strings or words
*
* @param {string} tmpStr1 string 1 to combine
* @param {string} tmpStr2 string 2 to combine
* @returns {string} combined string
*
* @usage 'Hello '.combineStr('Monday'); => 'Hello Monday'
* @usage 'Hello ‘。combineStr('Mondau); => 'Hello Monday'
* @usage 'String1 '.combineStr('String2') => 'String1 String2'
* @usage 'I want a '.scombineStr('hotdog') => 'I want a hotdog'
*
* @see https://www.w3schools.com/jsref/jsref_concat_string.asp
*/
const combineStr = (tmpStr1, tmpStr2) => { //return tmpStr1.concat(tmpStr2); }
var tmp1 = tmpStr1.replace(/\s+/g, ' ');
var tmp2 = tmpStr2.replace(/\s+/g, ' ');
var newStr = tmp1.concat(tmp2);
var wantedStr = newStr.replace(/\s+/g, ' ');
return wantedStr;
};
/**
* starts with functionality
*
* @param {string} tmpStr string to check
* @param {string} matchStr string to match against
* @returns {boolean} <b>true</b> if this ends with string, <b>false</b> otherwise
*
* @usage 'bob'.startsWith('b'); => true
* @usage 'A long string'.startsWith('A lon') => true
* @usage 'A long string'.startsWith('A lone') => false
*/
const startsWith = (tmpStr, matchStr) => { return tmpStr.slice(0, matchStr.length) == matchStr; }
/**
* ends with functionality
*
* @param {string} tmpStr string to check
* @param {string} matchStr string to match against
* @returns {boolean} <b>true</b> if this ends with string, <b>false</b> otherwise
*
* @usage 'bob'.endsWith('b'); => true
* @usage 'A long string'.endsWith('string') => true
* @usage 'A long string'.endsWith('a string') => false
*/
const endsWith = (tmpStr, matchStr) => { return tmpStr.slice(-matchStr.length) == matchStr; }
/**
* Simple true/false to tell if the given string matches (ignoring case)
* some subset of <b>this</b> string
*
* @param {string} tmpStr string to check
* @param {string} matchStr string to match against (ignoring case)
* @returns {boolean} <b>true</b> if the string is contained (without matching case), <b>false</b> otherwise
*
* @usage 'my string'.containsIgnoreCase('str') => true
* @usage 'my long string'.containsIgnoreCase('long') => true
* @usage 'my long string'.containsIgnoreCase('LONG') => true
* @usage 'my super long string'.containsIgnoreCase('rings') => false
*/
const containsIgnoreCase = (tmpStr, matchStr) => { return tmpStr.search(generateRegEx(matchStr, 'i')) > -1; }
/**
* Replace all functionality
*
* @param {string} tmpStr string to do the 'replace old with new' on
* @param {string} oldStr string to replace
* @param {string} newStr string to replace with
* @returns {string} with values replaced
*
* @usage 'bob'.replaceAll('b','m'); => 'mom'
* @usage 'My very long string'.replaceAll(' ','_'); => 'My_very_long_string'
*/
const replaceAll = (tmpStr, oldStr, newStr) => { return replaceAllGeneric(tmpStr, oldStr, newStr, 'g'); }
/**
* Replace all functionality that ignores case
*
* @param {string} tmpStr string to do the 'replace old with new' on
* @param {string} oldStr string to replace
* @param {string} newStr string to replace with
* @returns {string} with values replaced
*
* @usage 'Bob'.replaceAll('b','m'); => 'mom'
* @usage 'My very long string'.replaceAll(' ','_'); => 'My_very_long_string'
*/
const replaceAllIgnoreCase = (tmpStr, oldStr, newStr) => { return replaceAllGeneric(tmpStr, oldStr, newStr, 'gi'); }
/**
* Multiplication for a given string.
*
* @param {string} string to multiply
* @param {number} number of times to multiply string
* @return {string} that is multiplied the given number of times. Multiplying by 0 will return {null}. Multiplying by a negative number or 1 will return the given string only
*
* @usage '*'.times(2); => '**'
* @usage '*'.times(0); => `null`
*/
const times = (tmpStr, num) => {
var tmpRtn = tmpStr;
if (num == 0) {
tmpRtn = null;
} else if (num > 1) {
for (var i = 1; i < num; i++) {
tmpRtn += tmpStr;
}
}
return tmpRtn;
};
/**
* Padding for a string, either left (negative) or right (positive)
*
* @param {string} string to pad
* @param {number} number of times to repeat given padding character(s)
* @param {string} string to use as a padding character(s). Defaults to ' ' (empty string) when not provided
* @return {string} with left padding (if given size is negative) or right padding (if given size is positive). Will return the string without any padding when size = 0
*
* @usage '*'.pad(1,'-'); => '*-'
* @usage '*'.pad(-1,'-'); => '-*'
*/
const pad = (tmpStr, size, char) => {
if (size == 0)
return tmpStr;
char = char || ' ';
var padding = times(char, Math.abs(size));
return ((size > 0) ? (tmpStr + padding) : (padding + tmpStr));
};
/**
* Chops a string => used to "trim" characters from the end (positive number) or beginning (negative number) of a given string
*
* @param {string} string to chop
* @param {number} number of characters to remove from the beginning (<0) or end (>0)
* @return {string} chopped string, or `null` if `size` > `tmpStr.length`
*
* @usage 'testing'.chop(3); => 'test'
* @usage 'testing'.chop(-4); => 'ing'
*/
const chop = (tmpStr, size) => {
var absSize = Math.abs(size);
var rtn = null;
if (tmpStr.length - absSize > 0) {
var start = ((size < 0) ? absSize : 0);
var end = ((size > 0) ? (tmpStr.length - absSize) : tmpStr.length);
rtn = tmpStr.substring(start, end);
}
return rtn;
};
/**
* Creates a fixed sized string by chopping or padding it as needed.
*
* @param {string} string to fix size of
* @param {number} number of characters the string should have. Negative notes any chopping/padding starts left, positive starts all chopping/padding from the right
* @param {string} char
* @return {string} chopped or padded string, or `null` if `size` = 0
*
* @see #chop(tmpStr, size)
* @see #pad(tmpStr, size, char)
*/
const fixSize = (tmpStr, size, char) => {
var rtn = tmpStr;
if (size == 0) {
rtn = null;
} else {
var diff = tmpStr.length - Math.abs(size);
var sign = (size < 0) ? -1 : 1;
if (diff > 0) {
rtn = chop(tmpStr, diff * sign);
} else if (diff < 0) {
rtn = pad(tmpStr, Math.abs(diff) * sign, char)
}
}
return rtn;
};
/**
* Format functionality for String class
*
* @param {string} first parameter is the format string
* @param {string...} string(s) to replace
* @returns {string} with values replaced
*
* @usage String.fmt('%{s}','bob'); => 'bob'
* @usage String.fmt('My %{s} long %{s}','very', 'string'); => 'My very long string'
* @usage String.fmt('%{0} says %{1}, thanks %{0}!','Bob', 'Hi'); => 'Bob says Hi, thanks Bob!'
*/
const fmt = function () {
var args = Array.from(arguments);
var tmpStr = args.shift(),
argIndex = 0;
// handle simple indicies
tmpStr = tmpStr.replace(/%\{(\d+)\}/g, function (match, group1) {
if (/\d/g > args.length) {
return 'undefined'
}
return args[group1];
});
// handle simple string replacements
tmpStr = tmpStr.replace(/%\{s\}/g, function (match, offset, str) {
if (str.charAt(offset - 1) != __esc) {
return args[argIndex++];
}
return match;
});
return tmpStr;
};
/**
* Adds the following to the `String.prototype` if it's not already a function:
* - combineStr
* - startsWith
* - endsWith
* - containsIgnoreCase
* - replaceAll
* - replaceAllIgnoreCase
* - escapeRegEx
* - times
* - pad
* - chop
* - fixSize
* - fmt
*
* Also sets `String.fmt` = fmt
*
* This is NON-DESTRUCTIVE! If there is already a function defined, no new function will be set.
*/
const addPrototypes = () => {
if (typeof String.prototype.combineStr != 'function')
String.prototype.combineStr = function(tmpStr2) {return combineStr.call(null, this, tmpStr2); }
if (typeof String.prototype.startsWith != 'function')
String.prototype.startsWith = function (matchStr) { return startsWith.call(null, this, matchStr); }
if (typeof String.prototype.endsWith != 'function')
String.prototype.endsWith = function (matchStr) { return endsWith.call(null, this, matchStr); }
if (typeof String.prototype.containsIgnoreCase != 'function')
String.prototype.containsIgnoreCase = function (matchStr) { return containsIgnoreCase.call(null, this, matchStr); }
if (typeof String.prototype.replaceAll != 'function')
String.prototype.replaceAll = function (oldStr, newStr) { return replaceAll.call(null, this, oldStr, newStr); }
if (typeof String.prototype.replaceAllIgnoreCase != 'function')
String.prototype.replaceAllIgnoreCase = function (oldStr, newStr) { return replaceAllIgnoreCase.call(null, this, oldStr, newStr); }
if (typeof String.prototype.escapeRegEx != 'function')
String.prototype.escapeRegEx = function () { return escapeRegEx.call(null, this); }
if (typeof String.prototype.times != 'function')
String.prototype.times = function (size) { return times.call(null, this, size); }
if (typeof String.prototype.pad != 'function')
String.prototype.pad = function (size, char) { return pad.call(null, this, size, char); }
if (typeof String.prototype.chop != 'function')
String.prototype.chop = function (size) { return chop.call(null, this, size); }
if (typeof String.prototype.fixSize != 'function')
String.prototype.fixSize = function (size, char) { return fixSize.call(null, this, size, char); }
if (typeof String.prototype.fmt != 'function')
String.prototype.fmt = function () { return fmt.apply(null, [this].concat(Array.from(arguments))); }
if (typeof String.fmt != 'function')
String.fmt = fmt;
}
module.exports = {
combineStr: combineStr,
startsWith: startsWith,
endsWith: endsWith,
containsIgnoreCase: containsIgnoreCase,
replaceAll: replaceAll,
replaceAllIgnoreCase: replaceAllIgnoreCase,
escapeRegEx: escapeRegEx,
times: times,
pad: pad,
chop: chop,
fixSize: fixSize,
fmt: fmt,
addStringPrototypes: addPrototypes
}