From 3df01f92aaa79cd2eaf303b472f264398aa944fb Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 13 Mar 2013 19:32:08 -0700 Subject: [PATCH 01/13] Implemented field padding and integer precision --- Makefile | 3 +- lib/string-format.js | 102 ++++++++++++++++++++++++++++++++++++--- src/string-format.coffee | 78 ++++++++++++++++++++++++++++-- 3 files changed, 170 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 8ad0bc9..cdc520d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: compile clean release setup test -bin = node_modules/.bin +#bin = node_modules/.bin +bin = /usr/local/share/npm/bin/ compile: @$(bin)/coffee --compile --output lib src diff --git a/lib/string-format.js b/lib/string-format.js index 457694d..33a7fca 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -1,7 +1,17 @@ -// Generated by CoffeeScript 1.4.0 +// Generated by CoffeeScript 1.6.1 + +/* +This fork is an attempt to implement python-style string formatting, as documented here: +http://docs.python.org/2/library/string.html#format-string-syntax + +The format spec part is not complete, but it can handle field padding, float precision, and such +*/ + + (function() { - var format, lookup, resolve, - __slice = [].slice; + var applyFormat, format, lookup, resolve, + __slice = [].slice, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; format = String.prototype.format = function() { var args, explicit, idx, implicit, message, @@ -17,7 +27,7 @@ idx = 0; explicit = implicit = false; message = 'cannot switch from {} to {} numbering'.format(); - return this.replace(/([{}])\1|[{](.*?)(?:!(.+?))?[}]/g, function(match, literal, key, transformer) { + return this.replace(/([{}])\1|[{](.*?)(?:!([^:]+?)?)?(?::(.+?))?[}]/g, function(match, literal, key, conversion, formatSpec) { var fn, value, _ref, _ref1, _ref2; if (literal) { return literal; @@ -35,8 +45,12 @@ } value = (_ref1 = args[idx++]) != null ? _ref1 : ''; } - value = value.toString(); - if (fn = format.transformers[transformer]) { + if (formatSpec) { + value = applyFormat(value, formatSpec); + } else { + value = value.toString(); + } + if (fn = format.conversions[conversion]) { return (_ref2 = fn.call(value)) != null ? _ref2 : ''; } else { return value; @@ -66,7 +80,81 @@ } }; - format.transformers = {}; + applyFormat = function(value, formatSpec) { + var align, comma, fill, hash, isNumeric, pattern, precision, sign, type, width, zeropad, _ref, _ref1; + pattern = /([^{}](?=[<>=^]))?([<>]^)?([\+\-\x20])?(\#)?(0)?(\d+)?(,)?(?:\.(\d+))?([bcdeEfFgGnosxX%])?/; + _ref = formatSpec.match(pattern).slice(1), fill = _ref[0], align = _ref[1], sign = _ref[2], hash = _ref[3], zeropad = _ref[4], width = _ref[5], comma = _ref[6], precision = _ref[7], type = _ref[8]; + if (zeropad) { + fill = "0"; + align = "="; + } + if (!align) { + align = '>'; + } + switch (type) { + case 'b': + case 'c': + case 'd': + case 'o': + case 'x': + case 'X': + case 'n': + isNumeric = true; + value = '' + parseInt(value); + break; + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'n': + case '%': + isNumeric = true; + value = parseFloat(value); + if (precision) { + value = value.toFixed(parseInt(precision)); + } else { + value = '' + float; + } + break; + case 's': + isNumeric = false; + value = '' + value; + } + if (isNumeric && sign) { + debugger; + if (sign === "+" || sign === " ") { + if (value[0] !== '-') { + value = sign + value; + } + } + } + if (fill) { + while (value.length < parseInt(width)) { + switch (align) { + case '=': + if (_ref1 = value[0], __indexOf.call("+- ", _ref1) >= 0) { + value = value[0] + fill + value.slice(1); + } else { + value = fill + value; + } + break; + case '<': + value = value + fill; + break; + case '>': + value = fill + value; + break; + case '^': + throw new Error("Not implemented"); + } + } + } + return value; + }; + + format.conversions = {}; format.version = '0.2.1'; diff --git a/src/string-format.coffee b/src/string-format.coffee index 042a1d7..d28baa0 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -1,3 +1,10 @@ +# vim: ts=2:sw=2:expandtab +### +This fork is an attempt to implement python-style string formatting, as documented here: +http://docs.python.org/2/library/string.html#format-string-syntax + +The format spec part is not complete, but it can handle field padding, float precision, and such +### format = String::format = (args...) -> if args.length is 0 @@ -8,8 +15,8 @@ format = String::format = (args...) -> message = 'cannot switch from {} to {} numbering'.format() @replace \ - /([{}])\1|[{](.*?)(?:!(.+?))?[}]/g, - (match, literal, key, transformer) -> + /([{}])\1|[{](.*?)(?:!([^:]+?)?)?(?::(.+?))?[}]/g, + (match, literal, key, conversion, formatSpec) -> return literal if literal if key.length @@ -21,8 +28,11 @@ format = String::format = (args...) -> throw new Error message 'explicit', 'implicit' if explicit value = args[idx++] ? '' - value = value.toString() - if fn = format.transformers[transformer] then fn.call(value) ? '' + if formatSpec + value = applyFormat(value, formatSpec) + else + value = value.toString() + if fn = format.conversions[conversion] then fn.call(value) ? '' else value lookup = (object, key) -> @@ -37,6 +47,64 @@ resolve = (object, key) -> value = object[key] if typeof value is 'function' then value.call object else value -format.transformers = {} +# An implementation of http://docs.python.org/2/library/string.html#format-specification-mini-language +applyFormat = (value, formatSpec) -> + pattern = /// + ([^{}](?=[<>=^]))?([<>]^)? # fill & align + ([\+\-\x20])? # sign + (\#)? # integer base specifier + (0)? # zero-padding + (\d+)? # width + (,)? # use a comma thousands-seperator + (?:\.(\d+))? # precision + ([bcdeEfFgGnosxX%])? # type + /// + [fill, align, sign, hash, zeropad, width, comma, precision, type] = formatSpec.match(pattern)[1..] + if zeropad + fill = "0" + align = "=" + if ! align + align = '>' + + switch type + when 'b', 'c', 'd', 'o', 'x', 'X', 'n' # integer + isNumeric = true + value = '' + parseInt(value) + when 'e','E','f','F','g','G','n','%' # float + isNumeric = true + value = parseFloat(value) + if precision + value = value.toFixed(parseInt(precision)) + else + value = ''+float + when 's' #string + isNumeric = false + value = '' + value + + if isNumeric && sign + debugger; + if sign in ["+"," "] + if value[0] != '-' + value = sign + value + + + if fill + while value.length < parseInt(width) + switch align + when '=' + if value[0] in "+- " + value = value[0] + fill + value[1..] + else + value = fill + value + when '<' + value = value + fill + when '>' + value = fill + value + when '^' + throw new Error("Not implemented") + + return value + +format.conversions = {} format.version = '0.2.1' From d9444196438bbf948977a827daa4930f50193588 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 13 Mar 2013 19:45:25 -0700 Subject: [PATCH 02/13] Documentation updates --- README.md | 9 +++++++++ lib/string-format.js | 4 +++- src/string-format.coffee | 4 +++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8578747..3ff12e2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,15 @@ When `format` is invoked on a string, placeholders within the string are replaced with values determined by the arguments provided. A placeholder is a sequence of characters beginning with `{` and ending with `}`. +## About this Fork +dchamber's original version implememnted nested variable interpolation, and +included support for _transformations_ that would provide functionality +similar to the _conversions_ in python's str.format() + +It did not implement printf()-style number formatting, so here I'm attempting to do that. +At the moment, only signs, interger precision, and field padding are implemented. + + ### string.format(value1, value2, ..., valueN) Placeholders may contain numbers which refer to positional arguments: diff --git a/lib/string-format.js b/lib/string-format.js index 33a7fca..2fed16f 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -1,7 +1,9 @@ // Generated by CoffeeScript 1.6.1 /* -This fork is an attempt to implement python-style string formatting, as documented here: +This is a fork of https://github.com/davidchambers/string-format + +This project attempts to implement python-style string formatting, as documented here: http://docs.python.org/2/library/string.html#format-string-syntax The format spec part is not complete, but it can handle field padding, float precision, and such diff --git a/src/string-format.coffee b/src/string-format.coffee index d28baa0..6c0910c 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -1,6 +1,8 @@ # vim: ts=2:sw=2:expandtab ### -This fork is an attempt to implement python-style string formatting, as documented here: +This is a fork of https://github.com/davidchambers/string-format + +This project attempts to implement python-style string formatting, as documented here: http://docs.python.org/2/library/string.html#format-string-syntax The format spec part is not complete, but it can handle field padding, float precision, and such From 14b3af453dfefa3a402a0a28e0c7bdfe74a48655 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 13 Mar 2013 19:47:47 -0700 Subject: [PATCH 03/13] spelling. derp. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3ff12e2..5f1d37a 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ replaced with values determined by the arguments provided. A placeholder is a sequence of characters beginning with `{` and ending with `}`. ## About this Fork -dchamber's original version implememnted nested variable interpolation, and +dchamber's original version implemented nested variable interpolation, and included support for _transformations_ that would provide functionality similar to the _conversions_ in python's str.format() -It did not implement printf()-style number formatting, so here I'm attempting to do that. +It did not implement **printf()**-style number formatting, so here I'm attempting to do that. At the moment, only signs, interger precision, and field padding are implemented. From 16e8d1aeeaa3d3eb1e08e25c3ecdf2aec32135d1 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 13 Mar 2013 19:51:51 -0700 Subject: [PATCH 04/13] removed a debug breakpoint --- lib/string-format.js | 1 - src/string-format.coffee | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/string-format.js b/lib/string-format.js index 2fed16f..325c7e1 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -125,7 +125,6 @@ The format spec part is not complete, but it can handle field padding, float pre value = '' + value; } if (isNumeric && sign) { - debugger; if (sign === "+" || sign === " ") { if (value[0] !== '-') { value = sign + value; diff --git a/src/string-format.coffee b/src/string-format.coffee index 6c0910c..d3840a8 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -84,7 +84,6 @@ applyFormat = (value, formatSpec) -> value = '' + value if isNumeric && sign - debugger; if sign in ["+"," "] if value[0] != '-' value = sign + value From 871d473a62be401a332777a5774b9e40caea3f6d Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 13 Mar 2013 20:06:00 -0700 Subject: [PATCH 05/13] var name bugfix --- lib/string-format.js | 2 +- src/string-format.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/string-format.js b/lib/string-format.js index 325c7e1..f2eeee3 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -117,7 +117,7 @@ The format spec part is not complete, but it can handle field padding, float pre if (precision) { value = value.toFixed(parseInt(precision)); } else { - value = '' + float; + value = '' + value; } break; case 's': diff --git a/src/string-format.coffee b/src/string-format.coffee index d3840a8..c105cec 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -78,7 +78,7 @@ applyFormat = (value, formatSpec) -> if precision value = value.toFixed(parseInt(precision)) else - value = ''+float + value = ''+value when 's' #string isNumeric = false value = '' + value From c296e94d4be71c7d9b36a6250b1babad45b7910c Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 13 Mar 2013 20:29:29 -0700 Subject: [PATCH 06/13] Usage --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5f1d37a..2e3802c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ similar to the _conversions_ in python's str.format() It did not implement **printf()**-style number formatting, so here I'm attempting to do that. At the moment, only signs, interger precision, and field padding are implemented. +## Usage ### string.format(value1, value2, ..., valueN) From 524eaa09e0306d5699588f7bc157173acbc2a163 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Thu, 14 Mar 2013 14:52:02 -0700 Subject: [PATCH 07/13] Comments and a test script --- src/string-format.coffee | 3 ++- test/test.string.format.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/test.string.format.js diff --git a/src/string-format.coffee b/src/string-format.coffee index c105cec..000a51a 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -1,6 +1,7 @@ # vim: ts=2:sw=2:expandtab ### -This is a fork of https://github.com/davidchambers/string-format +Source code and build tools for this file are available at: +https://github.com/deleted/string-format This project attempts to implement python-style string formatting, as documented here: http://docs.python.org/2/library/string.html#format-string-syntax diff --git a/test/test.string.format.js b/test/test.string.format.js new file mode 100644 index 0000000..e62129a --- /dev/null +++ b/test/test.string.format.js @@ -0,0 +1,18 @@ +require("../lib/string-format.js"); + +var context = { + stationIndex: 13, + plan: { + site:{ + id: 3, + }, + platform: { + id: 0, + }, + planNumber: 8, + planVersion: "CHARLIE", + }, +}; +var template = "{plan.site.id}_{plan.platform.id}{plan.planNumber:-03.2f}{plan.planVersion}_STN{stationIndex}"; + +console.log( template.format(context) ); From 41c131c0740e7cd1a485916e4fd2a6ac2b83c246 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Thu, 14 Mar 2013 14:52:42 -0700 Subject: [PATCH 08/13] propagate comments to js --- lib/string-format.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/string-format.js b/lib/string-format.js index f2eeee3..016bfec 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -1,7 +1,8 @@ // Generated by CoffeeScript 1.6.1 /* -This is a fork of https://github.com/davidchambers/string-format +Source code and build tools for this file are available at: +https://github.com/deleted/string-format This project attempts to implement python-style string formatting, as documented here: http://docs.python.org/2/library/string.html#format-string-syntax From d1d351c0b0173a4a6f002fe886192d2a7448c2ac Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 17 Apr 2013 18:12:30 -0700 Subject: [PATCH 09/13] Zero padding bug fixed. All original tests now pass, plus new tests involving string formatting. --- Makefile | 3 +-- lib/string-format.js | 22 +++++++++++++++------- src/string-format.coffee | 17 ++++++++++++----- test/string-format.coffee | 6 ++++++ test/test.string.format.js | 18 ------------------ 5 files changed, 34 insertions(+), 32 deletions(-) delete mode 100644 test/test.string.format.js diff --git a/Makefile b/Makefile index cdc520d..8ad0bc9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ .PHONY: compile clean release setup test -#bin = node_modules/.bin -bin = /usr/local/share/npm/bin/ +bin = node_modules/.bin compile: @$(bin)/coffee --compile --output lib src diff --git a/lib/string-format.js b/lib/string-format.js index 016bfec..956ae0f 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.6.1 +// Generated by CoffeeScript 1.4.0 /* Source code and build tools for this file are available at: @@ -30,7 +30,7 @@ The format spec part is not complete, but it can handle field padding, float pre idx = 0; explicit = implicit = false; message = 'cannot switch from {} to {} numbering'.format(); - return this.replace(/([{}])\1|[{](.*?)(?:!([^:]+?)?)?(?::(.+?))?[}]/g, function(match, literal, key, conversion, formatSpec) { + return this.replace(/([{}])\1|[{](.*?)(?:!([^:]+?)?)?(?::(.+?))?[}]/g, function(match, literal, key, transformer, formatSpec) { var fn, value, _ref, _ref1, _ref2; if (literal) { return literal; @@ -53,7 +53,7 @@ The format spec part is not complete, but it can handle field padding, float pre } else { value = value.toString(); } - if (fn = format.conversions[conversion]) { + if (fn = format.transformers[transformer]) { return (_ref2 = fn.call(value)) != null ? _ref2 : ''; } else { return value; @@ -84,7 +84,7 @@ The format spec part is not complete, but it can handle field padding, float pre }; applyFormat = function(value, formatSpec) { - var align, comma, fill, hash, isNumeric, pattern, precision, sign, type, width, zeropad, _ref, _ref1; + var align, comma, fill, hash, isNumeric, memoSign, pattern, precision, sign, type, width, zeropad, _ref, _ref1, _ref2; pattern = /([^{}](?=[<>=^]))?([<>]^)?([\+\-\x20])?(\#)?(0)?(\d+)?(,)?(?:\.(\d+))?([bcdeEfFgGnosxX%])?/; _ref = formatSpec.match(pattern).slice(1), fill = _ref[0], align = _ref[1], sign = _ref[2], hash = _ref[3], zeropad = _ref[4], width = _ref[5], comma = _ref[6], precision = _ref[7], type = _ref[8]; if (zeropad) { @@ -132,11 +132,16 @@ The format spec part is not complete, but it can handle field padding, float pre } } } + if (isNumeric && (_ref1 = value[0], __indexOf.call("+-", _ref1) >= 0)) { + memoSign = value[0]; + value = value.slice(1); + } if (fill) { - while (value.length < parseInt(width)) { + value = '' + value; + while (value.split('.')[0].length < parseInt(width)) { switch (align) { case '=': - if (_ref1 = value[0], __indexOf.call("+- ", _ref1) >= 0) { + if (_ref2 = value[0], __indexOf.call("+- ", _ref2) >= 0) { value = value[0] + fill + value.slice(1); } else { value = fill + value; @@ -153,10 +158,13 @@ The format spec part is not complete, but it can handle field padding, float pre } } } + if (memoSign) { + value = memoSign + value; + } return value; }; - format.conversions = {}; + format.transformers = format.transformers || {}; format.version = '0.2.1'; diff --git a/src/string-format.coffee b/src/string-format.coffee index 000a51a..82e5df8 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -19,7 +19,7 @@ format = String::format = (args...) -> @replace \ /([{}])\1|[{](.*?)(?:!([^:]+?)?)?(?::(.+?))?[}]/g, - (match, literal, key, conversion, formatSpec) -> + (match, literal, key, transformer, formatSpec) -> return literal if literal if key.length @@ -35,7 +35,7 @@ format = String::format = (args...) -> value = applyFormat(value, formatSpec) else value = value.toString() - if fn = format.conversions[conversion] then fn.call(value) ? '' + if fn = format.transformers[transformer] then fn.call(value) ? '' else value lookup = (object, key) -> @@ -88,10 +88,14 @@ applyFormat = (value, formatSpec) -> if sign in ["+"," "] if value[0] != '-' value = sign + value - + + if isNumeric && value[0] in "+-" + memoSign = value[0] + value = value[1..] if fill - while value.length < parseInt(width) + value = ''+value + while value.split('.')[0].length < parseInt(width) switch align when '=' if value[0] in "+- " @@ -105,8 +109,11 @@ applyFormat = (value, formatSpec) -> when '^' throw new Error("Not implemented") + if memoSign + value = memoSign + value + return value -format.conversions = {} +format.transformers = format.transformers || {} format.version = '0.2.1' diff --git a/test/string-format.coffee b/test/string-format.coffee index 525fc7b..ecdeaaf 100644 --- a/test/string-format.coffee +++ b/test/string-format.coffee @@ -101,3 +101,9 @@ describe 'String::format', -> '{{{{0}}}}'.format(null).should.equal '{{0}}' '}}{{'.format(null).should.equal '}{' '}}x{{'.format(null).should.equal '}x{' + + it "correctly formats floats", -> + '{:0}'.format(1.2345).should.equal '1.2345' + '{:03.2f}'.format(1.2345).should.equal '001.23' + '{:03.2f}'.format(-1.2345).should.equal '-001.23' + '{:+02.3f}'.format(1.2345).should.equal '+01.234' diff --git a/test/test.string.format.js b/test/test.string.format.js deleted file mode 100644 index e62129a..0000000 --- a/test/test.string.format.js +++ /dev/null @@ -1,18 +0,0 @@ -require("../lib/string-format.js"); - -var context = { - stationIndex: 13, - plan: { - site:{ - id: 3, - }, - platform: { - id: 0, - }, - planNumber: 8, - planVersion: "CHARLIE", - }, -}; -var template = "{plan.site.id}_{plan.platform.id}{plan.planNumber:-03.2f}{plan.planVersion}_STN{stationIndex}"; - -console.log( template.format(context) ); From 067f47ea13bd4a3c2254e07f18eb73329387d549 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Wed, 17 Apr 2013 18:24:03 -0700 Subject: [PATCH 10/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e3802c..ba4e79a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ included support for _transformations_ that would provide functionality similar to the _conversions_ in python's str.format() It did not implement **printf()**-style number formatting, so here I'm attempting to do that. -At the moment, only signs, interger precision, and field padding are implemented. +At the moment, only signs, integer precision, and field padding are implemented. ## Usage From be9c843fea41397ef231dccc48555fb6d3a85071 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Thu, 18 Apr 2013 16:39:45 -0700 Subject: [PATCH 11/13] Edits for style per dchambers. --- README.md | 2 +- lib/string-format.js | 26 +++++++++++--------------- src/string-format.coffee | 32 +++++++++++++++----------------- 3 files changed, 27 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ba4e79a..393ae96 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ replaced with values determined by the arguments provided. A placeholder is a sequence of characters beginning with `{` and ending with `}`. ## About this Fork -dchamber's original version implemented nested variable interpolation, and +dchambers's original version implemented nested variable interpolation, and included support for _transformations_ that would provide functionality similar to the _conversions_ in python's str.format() diff --git a/lib/string-format.js b/lib/string-format.js index 956ae0f..e77223a 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -51,7 +51,7 @@ The format spec part is not complete, but it can handle field padding, float pre if (formatSpec) { value = applyFormat(value, formatSpec); } else { - value = value.toString(); + value = "" + value; } if (fn = format.transformers[transformer]) { return (_ref2 = fn.call(value)) != null ? _ref2 : ''; @@ -85,15 +85,13 @@ The format spec part is not complete, but it can handle field padding, float pre applyFormat = function(value, formatSpec) { var align, comma, fill, hash, isNumeric, memoSign, pattern, precision, sign, type, width, zeropad, _ref, _ref1, _ref2; - pattern = /([^{}](?=[<>=^]))?([<>]^)?([\+\-\x20])?(\#)?(0)?(\d+)?(,)?(?:\.(\d+))?([bcdeEfFgGnosxX%])?/; + pattern = /([^{}](?=[<>=^]))?([<>]^)?([-+\x20])?(\#)?(0)?(\d+)?(,)?(?:\.(\d+))?([bcdeEfFgGnosxX%])?/; _ref = formatSpec.match(pattern).slice(1), fill = _ref[0], align = _ref[1], sign = _ref[2], hash = _ref[3], zeropad = _ref[4], width = _ref[5], comma = _ref[6], precision = _ref[7], type = _ref[8]; if (zeropad) { - fill = "0"; - align = "="; - } - if (!align) { - align = '>'; + fill = '0'; + align = '='; } + align || (align = '>'); switch (type) { case 'b': case 'c': @@ -103,7 +101,7 @@ The format spec part is not complete, but it can handle field padding, float pre case 'X': case 'n': isNumeric = true; - value = '' + parseInt(value); + value = '' + parseInt(value, 10); break; case 'e': case 'E': @@ -123,7 +121,7 @@ The format spec part is not complete, but it can handle field padding, float pre break; case 's': isNumeric = false; - value = '' + value; + value = "" + value; } if (isNumeric && sign) { if (sign === "+" || sign === " ") { @@ -133,8 +131,8 @@ The format spec part is not complete, but it can handle field padding, float pre } } if (isNumeric && (_ref1 = value[0], __indexOf.call("+-", _ref1) >= 0)) { - memoSign = value[0]; - value = value.slice(1); + memoSign = value.charAt(0); + value = value.substr(1); } if (fill) { value = '' + value; @@ -158,13 +156,11 @@ The format spec part is not complete, but it can handle field padding, float pre } } } - if (memoSign) { - value = memoSign + value; - } + value = memoSign ? "" + memoSign + value : value; return value; }; - format.transformers = format.transformers || {}; + format.transformers || (format.transformers = {}); format.version = '0.2.1'; diff --git a/src/string-format.coffee b/src/string-format.coffee index 82e5df8..942515f 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -32,9 +32,9 @@ format = String::format = (args...) -> value = args[idx++] ? '' if formatSpec - value = applyFormat(value, formatSpec) + value = applyFormat value, formatSpec else - value = value.toString() + value = "#{value}" if fn = format.transformers[transformer] then fn.call(value) ? '' else value @@ -54,7 +54,7 @@ resolve = (object, key) -> applyFormat = (value, formatSpec) -> pattern = /// ([^{}](?=[<>=^]))?([<>]^)? # fill & align - ([\+\-\x20])? # sign + ([-+\x20])? # sign (\#)? # integer base specifier (0)? # zero-padding (\d+)? # width @@ -64,15 +64,14 @@ applyFormat = (value, formatSpec) -> /// [fill, align, sign, hash, zeropad, width, comma, precision, type] = formatSpec.match(pattern)[1..] if zeropad - fill = "0" - align = "=" - if ! align - align = '>' + fill = '0' + align = '=' + align or= '>' switch type when 'b', 'c', 'd', 'o', 'x', 'X', 'n' # integer - isNumeric = true - value = '' + parseInt(value) + isNumeric = yes + value = '' + parseInt(value, 10) when 'e','E','f','F','g','G','n','%' # float isNumeric = true value = parseFloat(value) @@ -82,16 +81,16 @@ applyFormat = (value, formatSpec) -> value = ''+value when 's' #string isNumeric = false - value = '' + value + value = "#{value}" - if isNumeric && sign + if isNumeric and sign if sign in ["+"," "] if value[0] != '-' value = sign + value - if isNumeric && value[0] in "+-" - memoSign = value[0] - value = value[1..] + if isNumeric and value[0] in "+-" + memoSign = value.charAt 0 + value = value.substr 1 if fill value = ''+value @@ -109,11 +108,10 @@ applyFormat = (value, formatSpec) -> when '^' throw new Error("Not implemented") - if memoSign - value = memoSign + value + value = if memoSign then "#{memoSign}#{value}" else value return value -format.transformers = format.transformers || {} +format.transformers or= {} format.version = '0.2.1' From 311999025ec11f24c7cd6f2abbd5192ad21cd055 Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Thu, 18 Apr 2013 16:43:36 -0700 Subject: [PATCH 12/13] charAt instead of [] for better compatibility --- lib/string-format.js | 2 +- src/string-format.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/string-format.js b/lib/string-format.js index e77223a..e7f5b63 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -130,7 +130,7 @@ The format spec part is not complete, but it can handle field padding, float pre } } } - if (isNumeric && (_ref1 = value[0], __indexOf.call("+-", _ref1) >= 0)) { + if (isNumeric && (_ref1 = value.charAt(0), __indexOf.call("+-", _ref1) >= 0)) { memoSign = value.charAt(0); value = value.substr(1); } diff --git a/src/string-format.coffee b/src/string-format.coffee index 942515f..f1fddbf 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -88,7 +88,7 @@ applyFormat = (value, formatSpec) -> if value[0] != '-' value = sign + value - if isNumeric and value[0] in "+-" + if isNumeric and value.charAt(0) in "+-" memoSign = value.charAt 0 value = value.substr 1 From 17af19bc78e36200df158c02d44656bc4be8398a Mon Sep 17 00:00:00 2001 From: Ted Scharff Date: Mon, 22 Apr 2013 12:07:21 -0700 Subject: [PATCH 13/13] Fixes a padding work like python. More comprehensive tests based on actual Python behavior --- lib/string-format.js | 28 +++++++++++++++------------- src/string-format.coffee | 22 ++++++++++++++-------- test/string-format.coffee | 18 +++++++++++++++--- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/lib/string-format.js b/lib/string-format.js index e7f5b63..a42877c 100644 --- a/lib/string-format.js +++ b/lib/string-format.js @@ -84,14 +84,15 @@ The format spec part is not complete, but it can handle field padding, float pre }; applyFormat = function(value, formatSpec) { - var align, comma, fill, hash, isNumeric, memoSign, pattern, precision, sign, type, width, zeropad, _ref, _ref1, _ref2; - pattern = /([^{}](?=[<>=^]))?([<>]^)?([-+\x20])?(\#)?(0)?(\d+)?(,)?(?:\.(\d+))?([bcdeEfFgGnosxX%])?/; + var align, comma, fill, hash, isNumeric, pattern, precision, sign, type, width, zeropad, _ref, _ref1; + pattern = /([^{}](?=[<>=^]))?([<>=^])?([-+\x20])?(\#)?(0)?(\d+)?(,)?(?:\.(\d+))?([bcdeEfFgGnosxX%])?/; _ref = formatSpec.match(pattern).slice(1), fill = _ref[0], align = _ref[1], sign = _ref[2], hash = _ref[3], zeropad = _ref[4], width = _ref[5], comma = _ref[6], precision = _ref[7], type = _ref[8]; if (zeropad) { - fill = '0'; - align = '='; + fill || (fill = '0'); + align || (align = '='); } align || (align = '>'); + fill || (fill = ' '); switch (type) { case 'b': case 'c': @@ -116,7 +117,7 @@ The format spec part is not complete, but it can handle field padding, float pre if (precision) { value = value.toFixed(parseInt(precision)); } else { - value = '' + value; + value = "" + value; } break; case 's': @@ -130,17 +131,19 @@ The format spec part is not complete, but it can handle field padding, float pre } } } - if (isNumeric && (_ref1 = value.charAt(0), __indexOf.call("+-", _ref1) >= 0)) { - memoSign = value.charAt(0); - value = value.substr(1); - } + /* + if isNumeric and value.charAt(0) in "+-" + memoSign = value.charAt 0 + value = value.substr 1 + */ + if (fill) { value = '' + value; - while (value.split('.')[0].length < parseInt(width)) { + while (value.length < parseInt(width)) { switch (align) { case '=': - if (_ref2 = value[0], __indexOf.call("+- ", _ref2) >= 0) { - value = value[0] + fill + value.slice(1); + if (_ref1 = value.charAt(0), __indexOf.call("+- ", _ref1) >= 0) { + value = value.charAt(0) + fill + value.slice(1); } else { value = fill + value; } @@ -156,7 +159,6 @@ The format spec part is not complete, but it can handle field padding, float pre } } } - value = memoSign ? "" + memoSign + value : value; return value; }; diff --git a/src/string-format.coffee b/src/string-format.coffee index f1fddbf..4745ea1 100644 --- a/src/string-format.coffee +++ b/src/string-format.coffee @@ -53,7 +53,7 @@ resolve = (object, key) -> # An implementation of http://docs.python.org/2/library/string.html#format-specification-mini-language applyFormat = (value, formatSpec) -> pattern = /// - ([^{}](?=[<>=^]))?([<>]^)? # fill & align + ([^{}](?=[<>=^]))?([<>=^])? # fill & align ([-+\x20])? # sign (\#)? # integer base specifier (0)? # zero-padding @@ -64,9 +64,10 @@ applyFormat = (value, formatSpec) -> /// [fill, align, sign, hash, zeropad, width, comma, precision, type] = formatSpec.match(pattern)[1..] if zeropad - fill = '0' - align = '=' + fill or= '0' + align or= '=' align or= '>' + fill or= ' ' switch type when 'b', 'c', 'd', 'o', 'x', 'X', 'n' # integer @@ -78,7 +79,7 @@ applyFormat = (value, formatSpec) -> if precision value = value.toFixed(parseInt(precision)) else - value = ''+value + value = "#{value}" when 's' #string isNumeric = false value = "#{value}" @@ -88,27 +89,32 @@ applyFormat = (value, formatSpec) -> if value[0] != '-' value = sign + value + ### if isNumeric and value.charAt(0) in "+-" memoSign = value.charAt 0 value = value.substr 1 + ### if fill value = ''+value - while value.split('.')[0].length < parseInt(width) + while value.length < parseInt(width) switch align when '=' - if value[0] in "+- " - value = value[0] + fill + value[1..] + # Forces the padding to be placed after the sign (if any) but before the digits. + if value.charAt(0) in "+- " + value = value.charAt(0) + fill + value[1..] else value = fill + value when '<' + # Forces the field to be left-aligned within the available space (this is the default for most objects). value = value + fill when '>' + # Forces the field to be right-aligned within the available space (this is the default for numbers). value = fill + value when '^' throw new Error("Not implemented") - value = if memoSign then "#{memoSign}#{value}" else value + #value = if memoSign then "#{memoSign}#{value}" else value return value diff --git a/test/string-format.coffee b/test/string-format.coffee index ecdeaaf..92e177a 100644 --- a/test/string-format.coffee +++ b/test/string-format.coffee @@ -102,8 +102,20 @@ describe 'String::format', -> '}}{{'.format(null).should.equal '}{' '}}x{{'.format(null).should.equal '}x{' + it "correctly pads integer values", -> + '{:4d}'.format(1).should.equal ' 1' + '{:04d}'.format(1).should.equal '0001' + '{:04d}'.format(-1).should.equal '-001' + '{:+04d}'.format(1).should.equal '+001' + '{: 04d}'.format(1).should.equal ' 001' + '{:x>04d}'.format(1).should.equal 'xxx1' + '{:x<04d}'.format(1).should.equal '1xxx' + it "correctly formats floats", -> '{:0}'.format(1.2345).should.equal '1.2345' - '{:03.2f}'.format(1.2345).should.equal '001.23' - '{:03.2f}'.format(-1.2345).should.equal '-001.23' - '{:+02.3f}'.format(1.2345).should.equal '+01.234' + '{:.2f}'.format(1.2345).should.equal '1.23' + '{:.1f}'.format(-1.2345).should.equal '-1.2' + '{:+.2f}'.format(1.23456).should.equal '+1.23' + '{:06.2f}'.format(1.2345).should.equal '001.23' + '{:06.2f}'.format(-1.2345).should.equal '-01.23' + '{:+07.3f}'.format(1.2345).should.equal '+01.234'