Skip to content
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ 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
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()

It did not implement **printf()**-style number formatting, so here I'm attempting to do that.
At the moment, only signs, integer precision, and field padding are implemented.

## Usage

### string.format(value1, value2, ..., valueN)

Placeholders may contain numbers which refer to positional arguments:
Expand Down
106 changes: 101 additions & 5 deletions lib/string-format.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 85 additions & 4 deletions src/string-format.coffee
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# vim: ts=2:sw=2:expandtab
###
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

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
Expand All @@ -8,8 +18,8 @@ format = String::format = (args...) ->
message = 'cannot switch from {} to {} numbering'.format()

@replace \
/([{}])\1|[{](.*?)(?:!(.+?))?[}]/g,
(match, literal, key, transformer) ->
/([{}])\1|[{](.*?)(?:!([^:]+?)?)?(?::(.+?))?[}]/g,
(match, literal, key, transformer, formatSpec) ->
return literal if literal

if key.length
Expand All @@ -21,7 +31,10 @@ format = String::format = (args...) ->
throw new Error message 'explicit', 'implicit' if explicit
value = args[idx++] ? ''

value = value.toString()
if formatSpec
value = applyFormat value, formatSpec
else
value = "#{value}"
if fn = format.transformers[transformer] then fn.call(value) ? ''
else value

Expand All @@ -37,6 +50,74 @@ 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 or= '0'
align or= '='
align or= '>'
fill or= ' '

switch type
when 'b', 'c', 'd', 'o', 'x', 'X', 'n' # integer
isNumeric = yes
value = '' + parseInt(value, 10)
when 'e','E','f','F','g','G','n','%' # float
isNumeric = true
value = parseFloat(value)
if precision
value = value.toFixed(parseInt(precision))
else
value = "#{value}"
when 's' #string
isNumeric = false
value = "#{value}"

if isNumeric and sign
if sign in ["+"," "]
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.length < parseInt(width)
switch align
when '='
# 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

return value
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if memoSign then "#{memoSign}#{value}" else value


format.transformers or= {}

format.version = '0.2.1'
18 changes: 18 additions & 0 deletions test/string-format.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,21 @@ describe 'String::format', ->
'{{{{0}}}}'.format(null).should.equal '{{0}}'
'}}{{'.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'
'{:.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'