Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 079a184
Showing
10 changed files
with
1,581 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/** | ||
* Tests for ES6 string formatting proposal: | ||
* http://wiki.ecmascript.org/doku.php?id=strawman:string_format_take_two | ||
* | ||
* (c) 2012 Rob Brackett (rob@robbrackett.com) | ||
* This code is free to use under the terms of the accompanying LICENSE.txt file | ||
*/ | ||
|
||
// Util | ||
this.assertLogger = this.assertLogger || function (pass, message) { | ||
console[pass ? "log" : "error"](message); | ||
}; | ||
|
||
var assertEquals = function (value, expected, message) { | ||
if (expected === value) { | ||
assertLogger(true, "PASS: " + (message || expected)); | ||
} | ||
else { | ||
assertLogger(false, "FAIL: " + (message || "") + | ||
"\nExpected: " + expected + | ||
"\nResult: " + value); | ||
} | ||
}; | ||
|
||
|
||
// The tests | ||
assertEquals("{0}'s father has two sons, {0} and {1}.".format("tom", "jerry"), | ||
"tom's father has two sons, tom and jerry.", | ||
"Formatting with indices substitues the argument of the given index."); | ||
|
||
assertEquals("The car is made by {brand} in year {make}.".format({brand: "Nissan", make: 2009}), | ||
"The car is made by Nissan in year 2009.", | ||
"Formatting with names substitutes the value of the given key from the first argument."); | ||
|
||
var car = {brand: "Nissan", make: 2009}; | ||
assertEquals("The car is made by {0.brand} in year {0.make}.".format(car), | ||
"The car is made by Nissan in year 2009.", | ||
"Deep substitutions work with dot-delimited identifiers."); | ||
|
||
assertEquals("The car is made by {0[brand]} in year {0[make]}.".format(car), | ||
"The car is made by Nissan in year 2009.", | ||
"Deep substitutions work with square-bracket-delimited identifiers that are names."); | ||
|
||
assertEquals("This year’s top two Japanese brands are {0[0]} and {0[1]}.".format(["Honda", "Toyota"]), | ||
"This year’s top two Japanese brands are Honda and Toyota.", | ||
"Deep substitutions work with square-bracket-delimited identifiers that are numbers."); | ||
|
||
assertEquals("This is a {very.very.deep} substitution.".format({very: {very: {deep: "very deep"}}}), | ||
"This is a very deep substitution.", | ||
"Substitutions work with a long series of dot-delimited identifiers."); | ||
|
||
assertEquals("This is a {very[very][deep]} substitution.".format({very: {very: {deep: "very deep"}}}), | ||
"This is a very deep substitution.", | ||
"Substitutions work with long series of square-bracket-delimited identifiers."); | ||
|
||
assertEquals("This is a {very.very[very]} {very[very].deep} substitution.".format({very: {very: {very: "very", deep: "deep"}}}), | ||
"This is a very deep substitution.", | ||
"Substitutions work with long series of mixed dot- and square-bracket-delimited identifiers."); | ||
|
||
assertEquals("{0}".format(5), "5", "Positive numbers are replaced without a sign."); | ||
assertEquals("{0}".format(-5), "-5", "Negative numbers are replaced with a sign."); | ||
assertEquals("{0:+}".format(5), "+5", "The '+' specifier causes positive numbers to be replaced with a sign."); | ||
assertEquals("{0:4}".format(5), " 5", "A width format specifier right-aligns the number."); | ||
assertEquals("{0:04}".format(5), "0005", "A '0' flag and width format specifier pads with 0s instead of spaces."); | ||
assertEquals("{0:-4}".format(5), "5 ", "A negative width format specifier left-aligns the number."); | ||
assertEquals("{0:-04}".format(5), "5 ", "A negative width format specifier with a '0' flag still pads with spaces."); | ||
assertEquals("{0:+4}".format(5), " +5", "A plus and width format specifier right-aligns the number with a plus sign."); | ||
assertEquals("{0:+04}".format(5), "+005", "A plus with the '0' flag puts padding after the plus sign."); | ||
assertEquals("{0:.4}".format(5), "5.0000", "A precision specifier results in at least that number of digits after the decimal."); | ||
assertEquals("{0:.4}".format(5.14326), "5.14326", "A precision specifier does not truncate a number that is more precise."); | ||
assertEquals("{0:b}".format(10), "1010", "The 'b' type converts a number to binary.") | ||
assertEquals("{0:o}".format(10), "12", "The 'o' type converts a number to octal.") | ||
assertEquals("{0:x}".format(10), "a", "The 'x' type converts a number to lower-case hexadecimal.") | ||
assertEquals("{0:X}".format(10), "A", "The 'X' type converts a number to upper-case hexadecimal.") | ||
|
||
assertEquals("Number {0} can be presented as decimal {0:d}, octex {0:o}, hex {0:x}".format(56), | ||
"Number 56 can be presented as decimal 56, octex 70, hex 38", | ||
"Basic format specifiers for numbers work."); | ||
|
||
assertEquals("Number formatter: {0} formatted string: {1:{0}}".format("03d", 56), | ||
"Number formatter: 03d formatted string: 056", | ||
"Format specifiers can be identifiers themselves."); | ||
|
||
assertEquals("Curly {0} can be {{ escaped }} by doubling }}".format("brackets"), | ||
"Curly brackets can be { escaped } by doubling }", | ||
"Curly brackets can be escaped by doubling them up."); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<title>ES6 String.format() Shim</title> | ||
<script src="ES6StringFormat.js" type="text/javascript" charset="utf-8"></script> | ||
<script type="text/javascript" charset="utf-8"> | ||
this.assertLogger = function (pass, message) { | ||
console[pass ? "log" : "error"](message); | ||
|
||
var domResult = document.createElement("li"); | ||
domResult.innerHTML = message.split("\n").join("<br/>"); | ||
domResult.className = pass ? "pass" : "fail"; | ||
document.getElementById("results").appendChild(domResult); | ||
}; | ||
</script> | ||
<style type="text/css" media="screen"> | ||
body { | ||
font-family: Helvetica, arial, sans-serif; | ||
} | ||
|
||
#results { | ||
margin: 0; | ||
padding: 0; | ||
list-style-type: none; | ||
font-family: Monaco, Courier, sans-serif; | ||
} | ||
|
||
#results li { | ||
padding: 0.5em; | ||
} | ||
|
||
.pass { | ||
background: #dfd; | ||
} | ||
|
||
.fail { | ||
color: #fff; | ||
background: #a00; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
|
||
<h1>ECMAScript 6 <code>String.format()</code> Proposal Implementation</h1> | ||
<p>See the proposal at: <a href="http://wiki.ecmascript.org/doku.php?id=strawman:string_format_take_two">http://wiki.ecmascript.org/doku.php?id=strawman:string_format_take_two</a></p> | ||
<ul id="results"></ul> | ||
<script src="ES6StringFormat-tests.js" type="text/javascript" charset="utf-8"></script> | ||
|
||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
/** | ||
* An experimental (and propably very inefficient) implementation of | ||
* the string formatting proposal from ECMAScript 6. | ||
* Proposal: http://wiki.ecmascript.org/doku.php?id=strawman:string_format_take_two | ||
* | ||
* It's not quite complete; some number formatting isn't yet there: | ||
* - The '#' flag isn't supported | ||
* - e/E format specifier isn't supported | ||
* - g/G format specifier isn't supported | ||
* - Capitalization is incorrect with f/F | ||
* | ||
* (c) 2012 Rob Brackett (rob@robbrackett.com) | ||
* This code is free to use under the terms of the accompanying LICENSE.txt file | ||
*/ | ||
|
||
(function (exports) { | ||
|
||
// regex for separating the various parts of an identifier from each other | ||
var identifierIdentifier = /^(?:\[([^\.\[\]]+)\]|\.?([^\.\[\]]+))(.*)$/ | ||
// convert an identifier into the actual value that will be substituted | ||
var findByPath = function (path, data, top) { | ||
var identifiers = path.match(identifierIdentifier); | ||
if (!identifiers) { | ||
throw "Invalid identifier: " + path; | ||
} | ||
var key = identifiers[1] || identifiers[2]; | ||
// For the first identifier, named keys are a shortcut to "0.key" | ||
if (top && !isFinite(key)) { | ||
data = data[0]; | ||
} | ||
var value = data[key]; | ||
// recurse as necessary | ||
return identifiers[3] ? findByPath(identifiers[3], value) : value; | ||
}; | ||
|
||
// the actual format function | ||
var format = function (template, data) { | ||
// NOTE: other versions of this algorithm are in performance/parsing-algorithms.js | ||
// Generally, this version performs best on small-ish strings and a regex-based | ||
// versions performs best on very large strings. Since people will probably use | ||
// more complicated templating libraries for big strings, we use the small-string | ||
// optimized version here. | ||
|
||
var args = Array.prototype.slice.call(arguments, 1); | ||
|
||
var outputBuffer = ""; | ||
var tokenBuffer = ""; | ||
// true if we are currently buffering a token that will be replaced | ||
var bufferingToken = false; | ||
// true if we've encountered a specifier that will be replaced | ||
var specifierIsReplaced = false; | ||
// track the {identifier:specifier} replacement parts | ||
var identifier, specifier; | ||
|
||
// walk the template | ||
for (var i = 0, length = template.length; i < length; i++) { | ||
var current = template.charAt(i); | ||
|
||
if (bufferingToken) { | ||
// ":" designates end of identifier, start of specifier | ||
if (current === ":") { | ||
identifier = tokenBuffer; | ||
tokenBuffer = ""; | ||
// if the first character of the specifier is "{", assume it is replaced | ||
if (template.charAt(i + 1) === "{") { | ||
specifierIsReplaced = true; | ||
i += 1; | ||
} | ||
} | ||
// end of token | ||
else if (current === "}") { | ||
// if we've already captured an identifier, the buffer contains the specifier | ||
// (see check for ":" above) | ||
if (identifier) { | ||
specifier = tokenBuffer; | ||
} | ||
else { | ||
identifier = tokenBuffer; | ||
} | ||
|
||
var foundValue = findByPath(identifier, args, true); | ||
// if a specifier is an identifier itself, do the replacement and | ||
// if we're dealing with a replaced specifier, we should end with a "}}" | ||
// so deal with the second "}" | ||
if (specifierIsReplaced) { | ||
specifier = findByPath(specifier, args, true); | ||
i += 1; | ||
} | ||
|
||
// format the value | ||
outputBuffer += formatValue(foundValue, specifier); | ||
|
||
// cleanup | ||
bufferingToken = false; | ||
specifierIsReplaced = false; | ||
specifier = identifier = null; | ||
} | ||
// non-special characters | ||
else { | ||
tokenBuffer += current; | ||
} | ||
} | ||
// when not buffering a token | ||
else if (current === "{") { | ||
// doubled up {{ is an escape sequence for { | ||
if (template.charAt(i + 1) === "{") { | ||
outputBuffer += current; | ||
i += 1; | ||
} | ||
// otherwise start buffering a new token | ||
else { | ||
tokenBuffer = ""; | ||
bufferingToken = true; | ||
} | ||
} | ||
// doubled up }} is an escape sequence for } | ||
// TODO: what should happen when encountering a single "}"? | ||
else if (current === "}" && template.charAt(i + 1) === "}") { | ||
outputBuffer += "}"; | ||
i += 1; | ||
} | ||
// non-special character | ||
else { | ||
outputBuffer += current; | ||
} | ||
} | ||
|
||
return outputBuffer; | ||
}; | ||
|
||
var formatValue = function (value, specifier) { | ||
if (!value) return ""; | ||
if (value.toFormat) return value.toFormat(specifier); | ||
if (typeof(value) === "number") return numberToFormat(value, specifier); | ||
return value.toString(); | ||
}; | ||
|
||
var numberToFormat = function (value, specifier) { | ||
if (!specifier) { | ||
return value.toString(); | ||
} | ||
var formatters = specifier.match(/^([\+\-#0]*)(\d*)(?:\.(\d+))?(.*)$/); | ||
var flags = formatters[1], | ||
width = formatters[2], | ||
precision = formatters[3], | ||
type = formatters[4]; | ||
|
||
var repeatCharacter = function (character, times) { | ||
var result = ""; | ||
while (times--) { | ||
result += character; | ||
} | ||
return result; | ||
} | ||
|
||
var applyPrecision = function (result) { | ||
if (precision) { | ||
var afterDecimal = result.split(".")[1]; | ||
var extraPrecision = precision - afterDecimal; | ||
if (isNaN(extraPrecision)) { | ||
extraPrecision = precision; | ||
} | ||
if (extraPrecision > 0) { | ||
if (result.indexOf(".") === -1) { | ||
result += "."; | ||
} | ||
for (; extraPrecision > 0; extraPrecision--) { | ||
result += "0"; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
var result = ""; | ||
switch (type) { | ||
case "d": | ||
result = Math.round(value - 0.5).toString(10); | ||
result = applyPrecision(result); | ||
break; | ||
case "x": | ||
result = Math.round(value - 0.5).toString(16); | ||
break; | ||
case "X": | ||
result = Math.round(value - 0.5).toString(16).toUpperCase(); | ||
break; | ||
case "b": | ||
result = Math.round(value - 0.5).toString(2); | ||
break; | ||
case "o": | ||
result = Math.round(value - 0.5).toString(8); | ||
break; | ||
// TODO: e,E,g,G types | ||
// not quite clear on whether g/G ignores the precision specifier | ||
case "f": | ||
case "F": | ||
// TODO: proper case for NaN, Infinity | ||
// proposal talks about INF and INFINITY, but not sure when each would be used :\ | ||
default: | ||
result = value.toString(10); | ||
result = applyPrecision(result); | ||
} | ||
|
||
if (~flags.indexOf("+")) { | ||
if (value >= 0) { | ||
result = "+" + result; | ||
} | ||
} | ||
|
||
if (width && result.length < width) { | ||
// "-" flag is right-fill | ||
if (~flags.indexOf("-")) { | ||
result += repeatCharacter(" ", width - result.length); | ||
} | ||
else { | ||
var padding = repeatCharacter(~flags.indexOf("0") ? "0" : " ", width - result.length); | ||
if (~flags.indexOf("0") && (result[0] === "+" || result[0] === "-")) { | ||
result = result[0] + padding + result.slice(1); | ||
} | ||
else { | ||
result = padding + result; | ||
} | ||
} | ||
} | ||
// TODO: # flag | ||
return result; | ||
}; | ||
|
||
// exports | ||
exports.format = format; | ||
|
||
// prototype modification | ||
String.prototype.format = function (data) { | ||
var args = [this].concat(Array.prototype.slice.call(arguments)); | ||
return format.apply(null, args); | ||
}; | ||
|
||
Number.prototype.toFormat = function (specifier) { | ||
return numberToFormat.call(null, this, specifier); | ||
}; | ||
|
||
})(typeof(exports) !== "undefined" ? exports : this); |
Oops, something went wrong.