art-standard-lib
Version:
The Standard Library for JavaScript that aught to be.
493 lines (439 loc) • 16.3 kB
JavaScript
// Generated by CoffeeScript 1.12.7
(function() {
var FoundationMath, StringExtensions, Types, compactFlatten, escapedDoubleQuoteRegex, floor, intRand, isArray, isBrowser, isNumber, isPlainObject, isString, stringIsPresent, wordsRegex;
FoundationMath = require('./MathExtensions');
Types = require('./TypesExtended');
wordsRegex = require('./RegExpExtensions').wordsRegex;
intRand = FoundationMath.intRand;
isString = Types.isString, isNumber = Types.isNumber, isPlainObject = Types.isPlainObject, isArray = Types.isArray, stringIsPresent = Types.stringIsPresent;
compactFlatten = require('./Core').compactFlatten;
isBrowser = require('./Environment').isBrowser;
escapedDoubleQuoteRegex = /[\\]["]/g;
floor = Math.floor;
module.exports = StringExtensions = (function() {
var base62Characters, consistentJsonStringify, crypto, escapeDoubleQuoteJavascriptString, escapeJavascriptString, getPadding, jsStringifyR, randomString, realRequire, repeat, standardIndent;
function StringExtensions() {}
/*
IN: an array and optionally a string, in any order
joiner: the string
array-to-flatten-and-join: the array
OUT:
compactFlatten(array).join joiner || ""
NOTE: this uses Ruby's default value for joining - the empty array, not ',' which is JavaScripts
*/
StringExtensions.compactFlattenJoin = function(a, b) {
var array, joiner;
array = null;
joiner = isString(a) ? (array = b, a) : (array = a, b || "");
return compactFlatten(array).join(joiner);
};
StringExtensions.base62Characters = base62Characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringExtensions.fastHash = function(string) {
var hash, i, j, ref;
hash = 2147483647;
if (string.length > 0) {
for (i = j = 0, ref = string.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
hash = ((hash << 5) - hash) + string.charCodeAt(i) & ((1 << 31) - 1);
}
}
return hash;
};
/*
* CaffeineScript once we have reduce + til support:
@fastHash: (string) ->
* 22 tokens
reduce hash, i til string.length inject 0
hash << 5
- hash
+ string.charCodeAt i
| 0
*/
StringExtensions.randomString = randomString = function(length, chars, randomNumbers) {
var charsLength, i, result;
if (length == null) {
length = 32;
}
if (chars == null) {
chars = base62Characters;
}
result = '';
charsLength = chars.length;
if (randomNumbers) {
return ((function() {
var j, ref, results;
results = [];
for (i = j = 0, ref = length; j < ref; i = j += 1) {
results.push(chars[randomNumbers[i] % charsLength]);
}
return results;
})()).join('');
} else {
return ((function() {
var j, ref, results;
results = [];
for (i = j = 0, ref = length; j < ref; i = j += 1) {
results.push(chars[intRand(charsLength)]);
}
return results;
})()).join('');
}
};
StringExtensions.cryptoRandomString = isBrowser ? ((crypto = global.crypto, global), !crypto ? (realRequire = eval('require'), crypto = realRequire("crypto")) : void 0, crypto ? function(l, c) {
if (l == null) {
l = 16;
}
return randomString(l, c, crypto.getRandomValues(new Uint8Array(l)));
} : (console.warn("window.crypto not available, using standard random for cryptoRandomString"), function(l, c) {
if (l == null) {
l = 16;
}
return randomString(l, c);
})) : (realRequire = eval('require'), crypto = realRequire("crypto"), function(l, c) {
return randomString(l, c, crypto.randomBytes(l));
});
StringExtensions.randomBase62Character = function() {
return base62Characters[intRand(62)];
};
StringExtensions.replaceLast = function(str, find, replaceWith) {
var index;
index = str.lastIndexOf(find);
if (index >= 0) {
return str.substring(0, index) + replaceWith + str.substring(index + find.length);
} else {
return str.toString();
}
};
StringExtensions.getPadding = getPadding = function(length, padding) {
var i, j, out, ref;
if (padding == null) {
padding = " ";
}
out = "";
for (i = j = 0, ref = length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
out += padding;
}
return out;
};
StringExtensions.pad = function(str, length, padding, alignRight) {
var exactPadding;
str = String(str);
if (str.length >= length) {
return str;
}
exactPadding = getPadding(Math.max(length - str.length, 0), padding);
if (alignRight) {
return exactPadding + str;
} else {
return str + exactPadding;
}
};
StringExtensions.escapeDoubleQuoteJavascriptString = escapeDoubleQuoteJavascriptString = function(str) {
var s;
console.warn("DEPRICATED: escapeDoubleQuoteJavascriptString. USE: escapeJavascriptString");
s = String(str).replace(/[\\"]/g, "\\$&").replace(/[\0\b\f\n\r\t\v\u2028\u2029]/g, function(x) {
switch (x) {
case '\0':
return "\\0";
case '\b':
return "\\b";
case '\f':
return "\\f";
case '\n':
return "\\n";
case '\r':
return "\\r";
case '\t':
return "\\t";
case '\v':
return "\\v";
case '\u2028':
return "\\u2028";
case '\u2029':
return "\\u2029";
}
});
return s = '"' + s + '"';
};
/*
SBD for a while I only had JSON.stringify here, but I hate seeing: "I said, \"hello.\""
when I could be seeing: 'I said, "hello."'
Is this going to break anything? I figure if you really need "" only, just use stringify.
*/
StringExtensions.escapeJavascriptString = escapeJavascriptString = function(str, withoutQuotes) {
var s;
s = JSON.stringify(str);
if (withoutQuotes) {
return s.slice(1, -1);
} else if (s.match(escapedDoubleQuoteRegex)) {
return "'" + (s.replace(escapedDoubleQuoteRegex, '"').replace(/'/g, "\\'").slice(1, -1)) + "'";
} else {
return s;
}
};
StringExtensions.allIndexes = function(str, regex) {
var indexes, lastIndex, result;
indexes = [];
if (!((regex instanceof RegExp) && regex.global)) {
throw new Error("regex must be a global RegExp");
}
regex.lastIndex = 0;
while (result = regex.exec(str)) {
indexes.push(result.index);
lastIndex = result;
}
return indexes;
};
StringExtensions.repeat = repeat = " ".repeat ? function(str, times) {
return str.repeat(times);
} : function(str, count) {
var result;
count === floor(count);
result = '';
if (count > 0 && str.length > 0) {
while (true) {
if ((count & 1) === 1) {
result += str;
}
count >>>= 1;
if (count === 0) {
break;
}
str += str;
}
}
return result;
};
StringExtensions.rightAlign = function(str, width) {
if (str.length >= width) {
return str;
} else {
return repeat(" ", width - str.length) + str;
}
};
StringExtensions.eachMatch = function(str, regex, f) {
var result;
regex.lastIndex = 0;
while (result = regex.exec(str)) {
f(result);
}
return null;
};
standardIndent = {
joiner: ', ',
openObject: '{',
openArray: '[',
closeObject: "}",
closeArray: "]"
};
StringExtensions.jsStringify = function(obj) {
return jsStringifyR(obj, "");
};
jsStringifyR = function(o, s) {
var el, first, j, k, len, v;
if (isPlainObject(o)) {
s += "{";
first = true;
for (k in o) {
v = o[k];
if (first) {
first = false;
} else {
s += ",";
}
if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(k)) {
s += k;
} else {
s += JSON.stringify(k);
}
s += ":";
s = jsStringifyR(v, s);
}
return s + "}";
} else if (isArray(o)) {
s += "[";
first = true;
for (j = 0, len = o.length; j < len; j++) {
el = o[j];
if (first) {
first = false;
} else {
s += ",";
}
s = jsStringifyR(el, s);
}
return s + "]";
} else {
return s + JSON.stringify(o);
}
};
StringExtensions.consistentJsonStringify = consistentJsonStringify = function(object, indent) {
var closeArray, closeObject, error, indentObject, joiner, k, lastTotalIndent, openArray, openObject, out, totalIndent, v;
return out = (function() {
var ref;
if (object === false || object === true || object === null || isNumber(object)) {
return "" + object;
} else if (isString(object)) {
return JSON.stringify(object);
} else {
indentObject = indent ? typeof indent === "string" ? {
joiner: ",\n" + indent,
openObject: "{\n" + indent,
openArray: "[\n" + indent,
closeObject: "\n}",
closeArray: "\n]",
totalIndent: indent,
indent: indent
} : {
totalIndent: totalIndent = indent.indent + (lastTotalIndent = indent.totalIndent),
joiner: ",\n" + totalIndent,
openObject: "{\n" + totalIndent,
openArray: "[\n" + totalIndent,
closeObject: "\n" + lastTotalIndent + "}",
closeArray: "\n" + lastTotalIndent + "]",
indent: indent.indent
} : void 0;
ref = indentObject || standardIndent, joiner = ref.joiner, openObject = ref.openObject, openArray = ref.openArray, closeObject = ref.closeObject, closeArray = ref.closeArray;
if (isPlainObject(object)) {
return openObject + ((function() {
var j, len, ref1, results;
ref1 = (Object.keys(object)).sort();
results = [];
for (j = 0, len = ref1.length; j < len; j++) {
k = ref1[j];
if (object[k] !== void 0) {
results.push(JSON.stringify(k) + ": " + consistentJsonStringify(object[k], indentObject));
}
}
return results;
})()).join(joiner) + closeObject;
} else if (isArray(object)) {
return openArray + ((function() {
var j, len, results;
results = [];
for (j = 0, len = object.length; j < len; j++) {
v = object[j];
results.push(consistentJsonStringify(v, indentObject));
}
return results;
})()).join(joiner) + closeArray;
} else {
Neptune.Art.StandardLib.log.error(error = "invalid object type for Json. Expecting: null, false, true, number, string, plain-object or array", object);
throw new Error(error);
}
}
})();
};
StringExtensions.splitRuns = function(str) {
var ch, chCount, i, j, lastCh, ref, result;
if (str.length === 0) {
return [];
}
lastCh = str[0];
chCount = 1;
result = [];
for (i = j = 1, ref = str.length; j < ref; i = j += 1) {
ch = str[i];
if (ch === lastCh) {
chCount++;
} else {
result.push([lastCh, chCount]);
chCount = 1;
}
lastCh = ch;
}
result.push([lastCh, chCount]);
return result;
};
StringExtensions.eachRunAsCharCodes = function(str, f) {
var ch, chCount, i, j, lastCh, ref;
lastCh = str.charCodeAt(0);
chCount = 1;
for (i = j = 1, ref = str.length; j < ref; i = j += 1) {
ch = str.charCodeAt(i);
if (ch === lastCh) {
chCount++;
} else {
f(lastCh, chCount);
chCount = 1;
}
lastCh = ch;
}
f(lastCh, chCount);
return null;
};
/*
TODO: I think this can be generalized to cover most all ellipsies and word-wrap scenarios:
a) have an options object with options:
maxLength: number # similar to current maxLength
minLength: number # currently implied to be maxLength / 2, in additional customizable, it would also be optional
brokenWordEllipsis: "…" # used when only part of a word is included
moreWordsEllipsis: "…" # used when there are more words, but the last word is whole
wordLengthFunction: (string) -> string.length
* can be replaced with, say, the font pixel-width for a string
* in this way, this function can be used by text-layout
* minLength and maxLength would then be in pixels
breakWords: false # currently, this is effectively true - will break the last word on line in most situations
breakOnlyWord: true # even if breakWords is false, if this is the only word on the line and it doesn't fit, should we break it?
* should this even be an option?
* future:
wordBreakFunction: (word, maxLength) -> shorterWord
* given a word and the maximum length of that word, returns
* a word <= maxLength according to wordLengthFunction
b) Use cases
- TextLayout - uses pixels for length rather than characters
- Art.Engine.Element 'flow' layout
- if the input was an array of "words" and
- wordLengthFunction returns the Element's width...
I think this works. We'd need a way to handle margins though. I think this works:
spaceLength: (leftWord, rightWord) -> 1
- Shortend user display names:
Options:
wordBreakFunction: (word, maxLength) -> word[0]
brokenWordEllipsis: "." or ""
Example Output:
"Shane Delamore", 10 > "Shane D." or
"Shane Delamore", 10 > "Shane D"
Or, just leave breakwords: false and get:
"Shane Delamore", 10 > "Shane"
c) returns both the output string and the "string remaining" - everything not included
d) alternate input: an array of strings already broken up by words - the "remainging" return value would then also be an array of "words"
(this would be for efficiency when doing multi-line layout)
Right now, it works as follows:
The output string is guaranteed to be:
<= maxLength
>= maxLength / 2 in almost all secenarios as long as inputString is >= maxLength / 2
*/
StringExtensions.humanFriendlyShorten = function(inputString, maxLength) {
var j, len, minLength, part, string, stringParts;
if (!(maxLength > 0)) {
throw new error("maxLength must be > 0");
}
inputString = inputString.trim();
if (!(inputString.length > maxLength)) {
return inputString;
}
minLength = maxLength / 2;
stringParts = inputString.split(/\s+/);
string = "";
for (j = 0, len = stringParts.length; j < len; j++) {
part = stringParts[j];
if (string.length === 0) {
string = part;
} else if ((string.length < minLength) || string.length + part.length + 2 <= maxLength) {
string += " " + part;
} else {
break;
}
}
if (string.length > maxLength) {
string = string.slice(0, maxLength - 1).trim();
}
return string + "…";
};
StringExtensions.stripTrailingWhitespace = function(a) {
return a.split(/[ ]*\n/).join("\n").split(/[ ]*$/)[0].replace(/\n+$/, '');
};
return StringExtensions;
})();
}).call(this);
//# sourceMappingURL=StringExtensions.js.map