├── .gitignore ├── .travis.yml ├── Makefile ├── package.json ├── LICENSE ├── README.md ├── test └── printf.coffee ├── lib └── printf.js └── doc └── coverage.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - 0.6 5 | - 0.7 6 | - 0.8 7 | - 0.9 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = dot 2 | 3 | test: 4 | @NODE_ENV=test ./node_modules/.bin/mocha --compilers coffee:coffee-script \ 5 | --reporter $(REPORTER) 6 | 7 | coverage: 8 | @jscoverage --no-highlight lib lib-cov 9 | @PRINTF_COV=1 $(MAKE) test REPORTER=html-cov > doc/coverage.html 10 | @rm -rf lib-cov 11 | 12 | .PHONY: test 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "printf", 3 | "version": "0.1.1", 4 | "description": "Full implementation of the `printf` family in pure JS.", 5 | "keywords": ["printf", "formatting", "sprintf", "format", "output", "print"], 6 | "homepage": "http://www.adaltas.com/projects/node-printf", 7 | "author": "David Worms ", 8 | "maintainers": [ "David Worms "], 9 | "contributors": [ 10 | "David Worms ", 11 | "Aluísio Augusto Silva Gonçalves ", 12 | "Xavier Mendez ", 13 | "LLeo " 14 | ], 15 | "main": "./lib/printf", 16 | "engines": { "node": ">= 0.1.90" }, 17 | "directories": { 18 | "lib": "./lib", 19 | "test": "./test", 20 | "doc": "./doc" 21 | }, 22 | "scripts": { 23 | "test": "make test" 24 | }, 25 | "devDependencies": { 26 | "coffee-script": "latest", 27 | "should": "latest", 28 | "mocha": "latest", 29 | "semver": "latest" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/wdavidw/node-printf.git" 34 | }, 35 | "bugs": { "url": "https://github.com/wdavidw/node-printf/issues" }, 36 | "licenses": [{ 37 | "type": "BSD", 38 | "url": "http://opensource.org/licenses/BSD-3-Clause" 39 | }] 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2010, SARL Adaltas. 2 | All rights reserved. 3 | 4 | Redistribution and use of this software in source and binary forms, with or 5 | without modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | - Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | - Neither the name of SARL Adaltas nor the names of its contributors may be 16 | used to endorse or promote products derived from this software without 17 | specific prior written permission of SARL Adaltas. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://secure.travis-ci.org/wdavidw/node-printf.png)](http://travis-ci.org/wdavidw/node-printf) 2 |
  3 |                 _       _    __ 
  4 |                (_)     | |  / _|
  5 |      _ __  _ __ _ _ __ | |_| |_ 
  6 |     | '_ \| '__| | '_ \| __|  _|
  7 |     | |_) | |  | | | | | |_| |  
  8 |     | .__/|_|  |_|_| |_|\__|_|  
  9 |     | |                         
 10 |     |_| 
 11 | 
 12 | 
13 | 14 | A complete implementation of the **`printf` C functions family** 15 | for [Node.JS][node], written in pure JavaScript. 16 | The code is strongly inspired by the one available in the [Dojo Toolkit][dojo]. 17 | 18 | **Bonus!** You get extra features, like the `%O` converter (which `inspect`s 19 | the argument). See [Extra Features](#extra-features) below. 20 | 21 | Installing 22 | ---------- 23 | 24 | Via [NPM][npm]: 25 | 26 | ``` bash 27 | $ npm install printf 28 | ``` 29 | 30 | Usage 31 | ----- 32 | 33 | Use it like you would in C (`printf`/`sprintf`): 34 | 35 | ``` javascript 36 | var printf = require('printf'); 37 | var result = printf(format, args...); 38 | ``` 39 | 40 | It can also output the result for you, as `fprintf`: 41 | 42 | ``` javascript 43 | var printf = require('printf'); 44 | printf(write_stream, format, args...); 45 | ``` 46 | 47 | Features 48 | -------- 49 | 50 | ``` javascript 51 | var printf = require('printf'); 52 | ``` 53 | 54 | ### Flags 55 | 56 | ##### ` ` (space) 57 | 58 | ``` javascript 59 | assert.eql(' -42', printf('% 5d', -42)); 60 | ``` 61 | 62 | ##### `+` (plus) 63 | 64 | ``` javascript 65 | assert.eql(' +42', printf('%+5d', 42)); 66 | ``` 67 | 68 | ##### `0` (zero) 69 | 70 | ``` javascript 71 | assert.eql('00042', printf('%05d', 42)); 72 | ``` 73 | 74 | ##### `-` (minus) 75 | 76 | ``` javascript 77 | assert.eql('42 ', printf('%-5d', 42)); 78 | ``` 79 | 80 | ### Width / precision 81 | 82 | ``` javascript 83 | assert.eql('42.90', printf('%.2f', 42.8952)); 84 | assert.eql('042.90', printf('%06.2f', 42.8952)); 85 | ``` 86 | 87 | ### Numerical bases 88 | 89 | ``` javascript 90 | assert.eql('\x7f', printf('%c', 0x7f)); 91 | assert.eql('a', printf('%c', 'a')); 92 | assert.eql('"', printf('%c', 34)); 93 | ``` 94 | 95 | ### Miscellaneous 96 | 97 | ``` javascript 98 | assert.eql('10%', printf('%d%%', 10)); 99 | assert.eql('+hello+', printf('+%s+', 'hello')); 100 | assert.eql('$', printf('%c", 36)); 101 | ``` 102 | 103 | Extra features! 104 | --------------- 105 | 106 | ### Inspector 107 | 108 | The `%O` converter will call [`util.inspect(...)`][util_inspect] at the argument: 109 | 110 | ``` javascript 111 | assert.eql("Debug: { hello: 'Node', repeat: false }", 112 | printf('Debug: %O', {hello: 'Node', "repeat": false}) 113 | ); 114 | assert.eql("Test: { hello: 'Node' }", 115 | printf('%2$s: %1$O', {"hello": 'Node'}, 'Test') 116 | ); 117 | ``` 118 | 119 | **Important:** it's a capital "O", *not* a zero! 120 | 121 | Specifying a precision lets you control the depth up to which the object is formatted: 122 | 123 | ``` javascript 124 | assert.eql("Debug: { depth0: { depth1_: 0, depth1: [Object] } }", 125 | printf('Debug: %.1O', {depth0: {depth1: {depth2: {depth3: true}}, depth1_: 0}}) 126 | ); 127 | ``` 128 | 129 | You can also use `%A` instead of `%O` to disable representation of non-enumerable properties: 130 | 131 | ``` javascript 132 | assert.eql("With non-enumerable properties: [ 1, 2, 3, 4, 5, [length]: 5 ]", 133 | printf('With non-enumerable properties: %O', [1, 2, 3, 4, 5]) 134 | ); 135 | assert.eql("Without non-enumerable properties: [ 1, 2, 3, 4, 5 ]", 136 | printf('Without non-enumerable properties: %A', [1, 2, 3, 4, 5]) 137 | ); 138 | ``` 139 | 140 | ### Argument mapping 141 | 142 | In addition to the old-fashioned `n$`, 143 | you can use **hashes** and **property names**! 144 | 145 | ``` javascript 146 | assert.eql('Hot Pockets', 147 | printf('%(temperature)s %(crevace)ss', { 148 | temperature: 'Hot', 149 | crevace: 'Pocket' 150 | }) 151 | ); 152 | assert.eql('Hot Pockets', 153 | printf('%2$s %1$ss', 'Pocket', 'Hot') 154 | ); 155 | ``` 156 | 157 | ### Positionals 158 | 159 | Lenght and precision can now be variable: 160 | 161 | ``` javascript 162 | assert.eql(' foo', printf('%*s', 'foo', 4)); 163 | assert.eql(' 3.14', printf('%*.*f', 3.14159265, 10, 2)); 164 | assert.eql('0000003.14', printf('%0*.*f', 3.14159265, 10, 2)); 165 | assert.eql('3.14 ', printf('%-*.*f', 3.14159265, 10, 2)); 166 | ``` 167 | 168 | Development 169 | ----------- 170 | 171 | Tests are executed with mocha. To install it, simple run `npm install`, it will install 172 | mocha and its dependencies in your project "node_modules" directory. 173 | 174 | To run the tests: 175 | ```bash 176 | npm test 177 | ``` 178 | 179 | To generate the JavaScript files: 180 | ```bash 181 | make build 182 | ``` 183 | 184 | The test suite is run online with [Travis][travis] against Node.js version 0.6, 0.7, 0.8 and 0.9. 185 | 186 | Contributors 187 | ------------ 188 | 189 | * David Worms: 190 | * Aluísio Augusto Silva Gonçalves 191 | * Xavier Mendez 192 | * LLeo 193 | 194 | 195 | [dojo]: http://www.dojotoolkit.org "The Dojo Toolkit" 196 | [node]: http://nodejs.org "The Node.JS platform" 197 | [npm]: https://github.com/isaacs/npm "The Node Package Manager" 198 | [util_inspect]: http://nodejs.org/api/util.html#util_util_inspect_object_showhidden_depth_colors "util.inspect() documentation" 199 | [expresso]: http://visionmedia.github.com/expresso "The Expresso TDD" 200 | -------------------------------------------------------------------------------- /test/printf.coffee: -------------------------------------------------------------------------------- 1 | 2 | semver = require 'semver' 3 | should = require 'should' 4 | printf = if process.env.PRINTF_COV then require '../lib-cov/printf' else require '../lib/printf' 5 | 6 | describe 'sprintf', -> 7 | it 'Specifier: b', -> 8 | printf('%b', 123).should.eql '1111011' 9 | 10 | it 'Flag: (space)', -> 11 | printf('% d', 42).should.eql ' 42' 12 | printf('% d', -42).should.eql '-42' 13 | printf('% 5d', 42).should.eql ' 42' 14 | printf('% 5d', -42).should.eql ' -42' 15 | printf('% 15d', 42).should.eql ' 42' 16 | printf('% 15d', -42).should.eql ' -42' 17 | 18 | it 'Flag: +', -> 19 | printf('%+d', 42).should.eql '+42' 20 | printf('%+d', -42).should.eql '-42' 21 | printf('%+5d', 42).should.eql ' +42' 22 | printf('%+5d', -42).should.eql ' -42' 23 | printf('%+15d', 42).should.eql ' +42' 24 | printf('%+15d', -42).should.eql ' -42' 25 | 26 | it 'Flag: 0', -> 27 | printf('%0d', 42).should.eql '42' 28 | printf('%0d', -42).should.eql '-42' 29 | printf('%05d', 42).should.eql '00042' 30 | printf('%05d', -42).should.eql '-00042' 31 | printf('%015d', 42).should.eql '000000000000042' 32 | printf('%015d', -42).should.eql '-000000000000042' 33 | 34 | it 'Flag: -', -> 35 | printf('%-d', 42).should.eql '42' 36 | printf('%-d', -42).should.eql '-42' 37 | printf('%-5d', 42).should.eql '42 ' 38 | printf('%-5d', -42).should.eql '-42 ' 39 | printf('%-15d', 42).should.eql '42 ' 40 | printf('%-15d', -42).should.eql '-42 ' 41 | printf('%-0d', 42).should.eql '42' 42 | printf('%-0d', -42).should.eql '-42' 43 | printf('%-05d', 42).should.eql '42 ' 44 | printf('%-05d', -42).should.eql '-42 ' 45 | printf('%-015d', 42).should.eql '42 ' 46 | printf('%-015d', -42).should.eql '-42 ' 47 | printf('%0-d', 42).should.eql '42' 48 | printf('%0-d', -42).should.eql '-42' 49 | printf('%0-5d', 42).should.eql '42 ' 50 | printf('%0-5d', -42).should.eql '-42 ' 51 | printf('%0-15d', 42).should.eql '42 ' 52 | printf('%0-15d', -42).should.eql '-42 ' 53 | 54 | it 'Precision', -> 55 | printf('%d', 42.8952).should.eql '42' 56 | printf('%.2d', 42.8952).should.eql '42' # Note: the %d format is an int 57 | printf('%.2i', 42.8952).should.eql '42' 58 | printf('%.2f', 42.8952).should.eql '42.90' 59 | printf('%.2F', 42.8952).should.eql '42.90' 60 | printf('%.10f', 42.8952).should.eql '42.8952000000' 61 | printf('%1.2f', 42.8952).should.eql '42.90' 62 | printf('%6.2f', 42.8952).should.eql ' 42.90' 63 | printf('%06.2f', 42.8952).should.eql '042.90' 64 | printf('%+6.2f', 42.8952).should.eql '+42.90' 65 | printf('%5.10f', 42.8952).should.eql '42.8952000000' 66 | 67 | it 'Bases', -> 68 | printf('%c', 0x7f).should.eql '' 69 | error = false 70 | try 71 | printf '%c', -100 72 | catch e 73 | e.message.should.eql 'invalid character code passed to %c in printf' 74 | error = true 75 | error.should.be.true 76 | error = false 77 | try 78 | printf '%c', 0x200000 79 | catch e 80 | e.message.should.eql 'invalid character code passed to %c in printf' 81 | error = true 82 | error.should.be.true 83 | 84 | it 'Mapping', -> 85 | # %1$s format 86 | printf('%1$').should.eql '%1$' 87 | printf('%0$s').should.eql '%0$s' 88 | printf('%1$s %2$s', 'Hot', 'Pocket').should.eql 'Hot Pocket' 89 | printf('%1$.1f %2$s %3$ss', 12, 'Hot', 'Pocket').should.eql '12.0 Hot Pockets' 90 | printf('%1$*.f', '42', 3).should.eql ' 42' 91 | error = false 92 | try 93 | printf '%2$*s', 'Hot Pocket' 94 | catch e 95 | e.message.should.eql "got 1 printf arguments, insufficient for '%2$*s'" 96 | error = true 97 | error.should.be.true 98 | # %(map)s format 99 | printf('%(foo', {}).should.eql '%(foo' 100 | printf('%(temperature)s %(crevace)s', 101 | temperature: 'Hot' 102 | crevace: 'Pocket' 103 | ).should.eql 'Hot Pocket' 104 | printf('%(quantity).1f %(temperature)s %(crevace)ss', 105 | quantity: 12 106 | temperature: 'Hot' 107 | crevace: 'Pocket' 108 | ).should.eql '12.0 Hot Pockets' 109 | error = false 110 | try 111 | printf '%(foo)s', 42 112 | catch e 113 | e.message.should.eql 'format requires a mapping' 114 | error = true 115 | error.should.be.true 116 | error = false 117 | try 118 | printf '%(foo)s %(bar)s', 'foo', 42 119 | catch e 120 | e.message.should.eql 'format requires a mapping' 121 | error = true 122 | error.should.be.true 123 | error = false 124 | try 125 | printf '%(foo)*s', 126 | foo: 'Hot Pocket' 127 | catch e 128 | e.message.should.eql '* width not supported in mapped formats' 129 | error = true 130 | error.should.be.true 131 | 132 | it 'Positionals', -> 133 | printf('%*s', 'foo', 4).should.eql ' foo' 134 | printf('%*.*f', 3.14159265, 10, 2).should.eql ' 3.14' 135 | printf('%0*.*f', 3.14159265, 10, 2).should.eql '0000003.14' 136 | printf('%-*.*f', 3.14159265, 10, 2).should.eql '3.14 ' 137 | error = false 138 | try 139 | printf '%*s', 'foo', 'bar' 140 | catch e 141 | e.message.should.eql 'the argument for * width at position 2 is not a number in %*s' 142 | error = true 143 | error.should.be.true 144 | error = false 145 | try 146 | printf '%10.*f', 'foo', 42 147 | catch e 148 | e.message.should.eql "format argument 'foo' not a float; parseFloat returned NaN" 149 | error = true 150 | error.should.be.true 151 | 152 | it 'vs. Formatter', -> 153 | i = 0 154 | while i < 1000 155 | printf '%d %s Pockets', i, 'Hot' 156 | i++ 157 | 158 | it 'Formatter', -> 159 | str = new printf.Formatter('%d %s Pockets') 160 | i = 0 161 | while i < 1000 162 | str.format i, 'Hot' 163 | i++ 164 | 165 | it 'Miscellaneous', -> 166 | printf('+%s+', 'hello').should.eql '+hello+' 167 | printf('+%d+', 10).should.eql '+10+' 168 | printf('%c', 'a').should.eql 'a' 169 | printf('%c', 34).should.eql '\"' 170 | printf('%c', 36).should.eql '$' 171 | printf('%d', 10).should.eql '10' 172 | error = false 173 | try 174 | printf '%s%s', 42 175 | catch e 176 | e.message.should.eql "got 1 printf arguments, insufficient for '%s%s'" 177 | error = true 178 | error.should.be.true 179 | error = false 180 | try 181 | printf '%c' 182 | catch e 183 | e.message.should.eql "got 0 printf arguments, insufficient for '%c'" 184 | error = true 185 | error.should.be.true 186 | printf('%10', 42).should.eql '%10' 187 | 188 | it 'Escape', -> 189 | printf('%d %', 10).should.eql '10 %' 190 | 191 | it 'Object inspection', -> 192 | test = 193 | foo: 194 | is: 195 | bar: true 196 | baz: false 197 | isnot: 198 | array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] 199 | maybe: undefined 200 | printf('%.0O', test).replace(/\s+/g, ' ').should.eql '{ foo: [Object] }' 201 | printf('%A', test.foo.isnot.array).should.eql '[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ]' 202 | # Object inspect serialize object in different order when showHidden is true 203 | return if semver.lt process.version, 'v0.9.0' 204 | printf('%O', test).replace(/\s+/g, ' ').should.eql '{ foo: { is: { bar: true, baz: false }, isnot: { array: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, [length]: 10 ] }, maybe: undefined } }' 205 | printf('%.2O', test).replace(/\s+/g, ' ').should.eql '{ foo: { is: { bar: true, baz: false }, isnot: { array: [Object] }, maybe: undefined } }' 206 | 207 | -------------------------------------------------------------------------------- /lib/printf.js: -------------------------------------------------------------------------------- 1 | 2 | var util = require('util'); 3 | 4 | var tokenize = function(/*String*/ str, /*RegExp*/ re, /*Function?*/ parseDelim, /*Object?*/ instance){ 5 | // summary: 6 | // Split a string by a regular expression with the ability to capture the delimeters 7 | // parseDelim: 8 | // Each group (excluding the 0 group) is passed as a parameter. If the function returns 9 | // a value, it's added to the list of tokens. 10 | // instance: 11 | // Used as the "this' instance when calling parseDelim 12 | var tokens = []; 13 | var match, content, lastIndex = 0; 14 | while(match = re.exec(str)){ 15 | content = str.slice(lastIndex, re.lastIndex - match[0].length); 16 | if(content.length){ 17 | tokens.push(content); 18 | } 19 | if(parseDelim){ 20 | var parsed = parseDelim.apply(instance, match.slice(1).concat(tokens.length)); 21 | if(typeof parsed != 'undefined'){ 22 | if(parsed.specifier === '%'){ 23 | tokens.push('%'); 24 | }else{ 25 | tokens.push(parsed); 26 | } 27 | } 28 | } 29 | lastIndex = re.lastIndex; 30 | } 31 | content = str.slice(lastIndex); 32 | if(content.length){ 33 | tokens.push(content); 34 | } 35 | return tokens; 36 | } 37 | 38 | var Formatter = function(/*String*/ format){ 39 | var tokens = []; 40 | this._mapped = false; 41 | this._format = format; 42 | this._tokens = tokenize(format, this._re, this._parseDelim, this); 43 | } 44 | 45 | Formatter.prototype._re = /\%(?:\(([\w_]+)\)|([1-9]\d*)\$)?([0 +\-\#]*)(\*|\d+)?(\.)?(\*|\d+)?[hlL]?([\%AbscdeEfFgGioOuxX])/g; 46 | Formatter.prototype._parseDelim = function(mapping, intmapping, flags, minWidth, period, precision, specifier){ 47 | if(mapping){ 48 | this._mapped = true; 49 | } 50 | return { 51 | mapping: mapping, 52 | intmapping: intmapping, 53 | flags: flags, 54 | _minWidth: minWidth, // May be dependent on parameters 55 | period: period, 56 | _precision: precision, // May be dependent on parameters 57 | specifier: specifier 58 | }; 59 | }; 60 | Formatter.prototype._specifiers = { 61 | b: { 62 | base: 2, 63 | isInt: true 64 | }, 65 | o: { 66 | base: 8, 67 | isInt: true 68 | }, 69 | x: { 70 | base: 16, 71 | isInt: true 72 | }, 73 | X: { 74 | extend: ['x'], 75 | toUpper: true 76 | }, 77 | d: { 78 | base: 10, 79 | isInt: true 80 | }, 81 | i: { 82 | extend: ['d'] 83 | }, 84 | u: { 85 | extend: ['d'], 86 | isUnsigned: true 87 | }, 88 | c: { 89 | setArg: function(token){ 90 | if(!isNaN(token.arg)){ 91 | var num = parseInt(token.arg); 92 | if(num < 0 || num > 127){ 93 | throw new Error('invalid character code passed to %c in printf'); 94 | } 95 | token.arg = isNaN(num) ? '' + num : String.fromCharCode(num); 96 | } 97 | } 98 | }, 99 | s: { 100 | setMaxWidth: function(token){ 101 | token.maxWidth = (token.period == '.') ? token.precision : -1; 102 | } 103 | }, 104 | e: { 105 | isDouble: true, 106 | doubleNotation: 'e' 107 | }, 108 | E: { 109 | extend: ['e'], 110 | toUpper: true 111 | }, 112 | f: { 113 | isDouble: true, 114 | doubleNotation: 'f' 115 | }, 116 | F: { 117 | extend: ['f'] 118 | }, 119 | g: { 120 | isDouble: true, 121 | doubleNotation: 'g' 122 | }, 123 | G: { 124 | extend: ['g'], 125 | toUpper: true 126 | }, 127 | O: { 128 | isObject: true, 129 | showNonEnumerable: true 130 | }, 131 | A: { 132 | isObject: true, 133 | showNonEnumerable: false 134 | }, 135 | }; 136 | Formatter.prototype.format = function(/*mixed...*/ filler){ 137 | if(this._mapped && typeof filler != 'object'){ 138 | throw new Error('format requires a mapping'); 139 | } 140 | 141 | var str = ''; 142 | var position = 0; 143 | for(var i = 0, token; i < this._tokens.length; i++){ 144 | token = this._tokens[i]; 145 | 146 | if(typeof token == 'string'){ 147 | str += token; 148 | }else{ 149 | if(this._mapped){ 150 | if(typeof filler[token.mapping] == 'undefined'){ 151 | throw new Error('missing key ' + token.mapping); 152 | } 153 | token.arg = filler[token.mapping]; 154 | }else{ 155 | if(token.intmapping){ 156 | var position = parseInt(token.intmapping) - 1; 157 | } 158 | if(position >= arguments.length){ 159 | throw new Error('got ' + arguments.length + ' printf arguments, insufficient for \'' + this._format + '\''); 160 | } 161 | token.arg = arguments[position++]; 162 | } 163 | 164 | if(!token.compiled){ 165 | token.compiled = true; 166 | token.sign = ''; 167 | token.zeroPad = false; 168 | token.rightJustify = false; 169 | token.alternative = false; 170 | 171 | var flags = {}; 172 | for(var fi = token.flags.length; fi--;){ 173 | var flag = token.flags.charAt(fi); 174 | flags[flag] = true; 175 | switch(flag){ 176 | case ' ': 177 | token.sign = ' '; 178 | break; 179 | case '+': 180 | token.sign = '+'; 181 | break; 182 | case '0': 183 | token.zeroPad = (flags['-']) ? false : true; 184 | break; 185 | case '-': 186 | token.rightJustify = true; 187 | token.zeroPad = false; 188 | break; 189 | case '\#': 190 | token.alternative = true; 191 | break; 192 | default: 193 | throw Error('bad formatting flag \'' + token.flags.charAt(fi) + '\''); 194 | } 195 | } 196 | 197 | token.minWidth = (token._minWidth) ? parseInt(token._minWidth) : 0; 198 | token.maxWidth = -1; 199 | token.toUpper = false; 200 | token.isUnsigned = false; 201 | token.isInt = false; 202 | token.isDouble = false; 203 | token.isObject = false; 204 | token.precision = 1; 205 | if(token.period == '.'){ 206 | if(token._precision){ 207 | token.precision = parseInt(token._precision); 208 | }else{ 209 | token.precision = 0; 210 | } 211 | } 212 | 213 | var mixins = this._specifiers[token.specifier]; 214 | if(typeof mixins == 'undefined'){ 215 | throw new Error('unexpected specifier \'' + token.specifier + '\''); 216 | } 217 | if(mixins.extend){ 218 | var s = this._specifiers[mixins.extend]; 219 | for(var k in s){ 220 | mixins[k] = s[k] 221 | } 222 | delete mixins.extend; 223 | } 224 | for(var k in mixins){ 225 | token[k] = mixins[k]; 226 | } 227 | } 228 | 229 | if(typeof token.setArg == 'function'){ 230 | token.setArg(token); 231 | } 232 | 233 | if(typeof token.setMaxWidth == 'function'){ 234 | token.setMaxWidth(token); 235 | } 236 | 237 | if(token._minWidth == '*'){ 238 | if(this._mapped){ 239 | throw new Error('* width not supported in mapped formats'); 240 | } 241 | token.minWidth = parseInt(arguments[position++]); 242 | if(isNaN(token.minWidth)){ 243 | throw new Error('the argument for * width at position ' + position + ' is not a number in ' + this._format); 244 | } 245 | // negative width means rightJustify 246 | if (token.minWidth < 0) { 247 | token.rightJustify = true; 248 | token.minWidth = -token.minWidth; 249 | } 250 | } 251 | 252 | if(token._precision == '*' && token.period == '.'){ 253 | if(this._mapped){ 254 | throw new Error('* precision not supported in mapped formats'); 255 | } 256 | token.precision = parseInt(arguments[position++]); 257 | if(isNaN(token.precision)){ 258 | throw Error('the argument for * precision at position ' + position + ' is not a number in ' + this._format); 259 | } 260 | // negative precision means unspecified 261 | if (token.precision < 0) { 262 | token.precision = 1; 263 | token.period = ''; 264 | } 265 | } 266 | if(token.isInt){ 267 | // a specified precision means no zero padding 268 | if(token.period == '.'){ 269 | token.zeroPad = false; 270 | } 271 | this.formatInt(token); 272 | }else if(token.isDouble){ 273 | if(token.period != '.'){ 274 | token.precision = 6; 275 | } 276 | this.formatDouble(token); 277 | }else if(token.isObject){ 278 | this.formatObject(token); 279 | } 280 | this.fitField(token); 281 | 282 | str += '' + token.arg; 283 | } 284 | } 285 | 286 | return str; 287 | }; 288 | Formatter.prototype._zeros10 = '0000000000'; 289 | Formatter.prototype._spaces10 = ' '; 290 | Formatter.prototype.formatInt = function(token) { 291 | var i = parseInt(token.arg); 292 | if(!isFinite(i)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY) 293 | // allow this only if arg is number 294 | if(typeof token.arg != 'number'){ 295 | throw new Error('format argument \'' + token.arg + '\' not an integer; parseInt returned ' + i); 296 | } 297 | //return '' + i; 298 | i = 0; 299 | } 300 | 301 | // if not base 10, make negatives be positive 302 | // otherwise, (-10).toString(16) is '-a' instead of 'fffffff6' 303 | if(i < 0 && (token.isUnsigned || token.base != 10)){ 304 | i = 0xffffffff + i + 1; 305 | } 306 | 307 | if(i < 0){ 308 | token.arg = (- i).toString(token.base); 309 | this.zeroPad(token); 310 | token.arg = '-' + token.arg; 311 | }else{ 312 | token.arg = i.toString(token.base); 313 | // need to make sure that argument 0 with precision==0 is formatted as '' 314 | if(!i && !token.precision){ 315 | token.arg = ''; 316 | }else{ 317 | this.zeroPad(token); 318 | } 319 | if(token.sign){ 320 | token.arg = token.sign + token.arg; 321 | } 322 | } 323 | if(token.base == 16){ 324 | if(token.alternative){ 325 | token.arg = '0x' + token.arg; 326 | } 327 | token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase(); 328 | } 329 | if(token.base == 8){ 330 | if(token.alternative && token.arg.charAt(0) != '0'){ 331 | token.arg = '0' + token.arg; 332 | } 333 | } 334 | }; 335 | Formatter.prototype.formatDouble = function(token) { 336 | var f = parseFloat(token.arg); 337 | if(!isFinite(f)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY) 338 | // allow this only if arg is number 339 | if(typeof token.arg != 'number'){ 340 | throw new Error('format argument \'' + token.arg + '\' not a float; parseFloat returned ' + f); 341 | } 342 | // C99 says that for 'f': 343 | // infinity -> '[-]inf' or '[-]infinity' ('[-]INF' or '[-]INFINITY' for 'F') 344 | // NaN -> a string starting with 'nan' ('NAN' for 'F') 345 | // this is not commonly implemented though. 346 | //return '' + f; 347 | f = 0; 348 | } 349 | 350 | switch(token.doubleNotation) { 351 | case 'e': { 352 | token.arg = f.toExponential(token.precision); 353 | break; 354 | } 355 | case 'f': { 356 | token.arg = f.toFixed(token.precision); 357 | break; 358 | } 359 | case 'g': { 360 | // C says use 'e' notation if exponent is < -4 or is >= prec 361 | // ECMAScript for toPrecision says use exponential notation if exponent is >= prec, 362 | // though step 17 of toPrecision indicates a test for < -6 to force exponential. 363 | if(Math.abs(f) < 0.0001){ 364 | //print('forcing exponential notation for f=' + f); 365 | token.arg = f.toExponential(token.precision > 0 ? token.precision - 1 : token.precision); 366 | }else{ 367 | token.arg = f.toPrecision(token.precision); 368 | } 369 | 370 | // In C, unlike 'f', 'gG' removes trailing 0s from fractional part, unless alternative format flag ('#'). 371 | // But ECMAScript formats toPrecision as 0.00100000. So remove trailing 0s. 372 | if(!token.alternative){ 373 | //print('replacing trailing 0 in \'' + s + '\''); 374 | token.arg = token.arg.replace(/(\..*[^0])0*/, '$1'); 375 | // if fractional part is entirely 0, remove it and decimal point 376 | token.arg = token.arg.replace(/\.0*e/, 'e').replace(/\.0$/,''); 377 | } 378 | break; 379 | } 380 | default: throw new Error('unexpected double notation \'' + token.doubleNotation + '\''); 381 | } 382 | 383 | // C says that exponent must have at least two digits. 384 | // But ECMAScript does not; toExponential results in things like '1.000000e-8' and '1.000000e+8'. 385 | // Note that s.replace(/e([\+\-])(\d)/, 'e$10$2') won't work because of the '$10' instead of '$1'. 386 | // And replace(re, func) isn't supported on IE50 or Safari1. 387 | token.arg = token.arg.replace(/e\+(\d)$/, 'e+0$1').replace(/e\-(\d)$/, 'e-0$1'); 388 | 389 | // if alt, ensure a decimal point 390 | if(token.alternative){ 391 | token.arg = token.arg.replace(/^(\d+)$/,'$1.'); 392 | token.arg = token.arg.replace(/^(\d+)e/,'$1.e'); 393 | } 394 | 395 | if(f >= 0 && token.sign){ 396 | token.arg = token.sign + token.arg; 397 | } 398 | 399 | token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase(); 400 | }; 401 | Formatter.prototype.formatObject = function(token) { 402 | // If no precision is specified, then reset it to null (infinite depth). 403 | var precision = (token.period === '.') ? token.precision : null; 404 | token.arg = util.inspect(token.arg, token.showNonEnumerable, precision); 405 | }; 406 | Formatter.prototype.zeroPad = function(token, /*Int*/ length) { 407 | length = (arguments.length == 2) ? length : token.precision; 408 | var negative = false; 409 | if(typeof token.arg != "string"){ 410 | token.arg = "" + token.arg; 411 | } 412 | if (token.arg.substr(0,1) === '-') { 413 | negative = true; 414 | token.arg = token.arg.substr(1); 415 | } 416 | 417 | var tenless = length - 10; 418 | while(token.arg.length < tenless){ 419 | token.arg = (token.rightJustify) ? token.arg + this._zeros10 : this._zeros10 + token.arg; 420 | } 421 | var pad = length - token.arg.length; 422 | token.arg = (token.rightJustify) ? token.arg + this._zeros10.substring(0, pad) : this._zeros10.substring(0, pad) + token.arg; 423 | if (negative) token.arg = '-' + token.arg; 424 | }; 425 | Formatter.prototype.fitField = function(token) { 426 | if(token.maxWidth >= 0 && token.arg.length > token.maxWidth){ 427 | return token.arg.substring(0, token.maxWidth); 428 | } 429 | if(token.zeroPad){ 430 | this.zeroPad(token, token.minWidth); 431 | return; 432 | } 433 | this.spacePad(token); 434 | }; 435 | Formatter.prototype.spacePad = function(token, /*Int*/ length) { 436 | length = (arguments.length == 2) ? length : token.minWidth; 437 | if(typeof token.arg != 'string'){ 438 | token.arg = '' + token.arg; 439 | } 440 | var tenless = length - 10; 441 | while(token.arg.length < tenless){ 442 | token.arg = (token.rightJustify) ? token.arg + this._spaces10 : this._spaces10 + token.arg; 443 | } 444 | var pad = length - token.arg.length; 445 | token.arg = (token.rightJustify) ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg; 446 | }; 447 | 448 | 449 | module.exports = function(){ 450 | var args = Array.prototype.slice.call(arguments), 451 | stream, format; 452 | if(args[0] instanceof require('stream').Stream){ 453 | stream = args.shift(); 454 | } 455 | format = args.shift(); 456 | var formatter = new Formatter(format); 457 | var string = formatter.format.apply(formatter, args); 458 | if(stream){ 459 | stream.write(string); 460 | }else{ 461 | return string; 462 | } 463 | }; 464 | 465 | module.exports.Formatter = Formatter; 466 | 467 | -------------------------------------------------------------------------------- /doc/coverage.html: -------------------------------------------------------------------------------- 1 | Coverage 35 |

Coverage

82%
226
186
40

printf.js

82%
226
186
40
LineHitsSource
1
21var util = require('util');
3
41var tokenize = function(/*String*/ str, /*RegExp*/ re, /*Function?*/ parseDelim, /*Object?*/ instance){
5 // summary:
6 // Split a string by a regular expression with the ability to capture the delimeters
7 // parseDelim:
8 // Each group (excluding the 0 group) is passed as a parameter. If the function returns
9 // a value, it's added to the list of tokens.
10 // instance:
11 // Used as the "this' instance when calling parseDelim
121081 var tokens = [];
131081 var match, content, lastIndex = 0;
141081 while(match = re.exec(str)){
152086 content = str.slice(lastIndex, re.lastIndex - match[0].length);
162086 if(content.length){
171011 tokens.push(content);
18 }
192086 if(parseDelim){
202086 var parsed = parseDelim.apply(instance, match.slice(1).concat(tokens.length));
212086 if(typeof parsed != 'undefined'){
222086 if(parsed.specifier === '%'){
230 tokens.push('%');
24 }else{
252086 tokens.push(parsed);
26 }
27 }
28 }
292086 lastIndex = re.lastIndex;
30 }
311081 content = str.slice(lastIndex);
321081 if(content.length){
331010 tokens.push(content);
34 }
351081 return tokens;
36}
37
381var Formatter = function(/*String*/ format){
391081 var tokens = [];
401081 this._mapped = false;
411081 this._format = format;
421081 this._tokens = tokenize(format, this._re, this._parseDelim, this);
43}
44
451Formatter.prototype._re = /\%(?:\(([\w_]+)\)|([1-9]\d*)\$)?([0 +\-\#]*)(\*|\d+)?(\.)?(\*|\d+)?[hlL]?([\%bscdeEfFgGioOuxX])/g;
461Formatter.prototype._parseDelim = function(mapping, intmapping, flags, minWidth, period, precision, specifier){
472086 if(mapping){
489 this._mapped = true;
49 }
502086 return {
51 mapping: mapping,
52 intmapping: intmapping,
53 flags: flags,
54 _minWidth: minWidth, // May be dependent on parameters
55 period: period,
56 _precision: precision, // May be dependent on parameters
57 specifier: specifier
58 };
59};
601Formatter.prototype._specifiers = {
61 b: {
62 base: 2,
63 isInt: true
64 },
65 o: {
66 base: 8,
67 isInt: true
68 },
69 x: {
70 base: 16,
71 isInt: true
72 },
73 X: {
74 extend: ['x'],
75 toUpper: true
76 },
77 d: {
78 base: 10,
79 isInt: true
80 },
81 i: {
82 extend: ['d']
83 },
84 u: {
85 extend: ['d'],
86 isUnsigned: true
87 },
88 c: {
89 setArg: function(token){
906 if(!isNaN(token.arg)){
915 var num = parseInt(token.arg);
925 if(num < 0 || num > 127){
932 throw new Error('invalid character code passed to %c in printf');
94 }
953 token.arg = isNaN(num) ? '' + num : String.fromCharCode(num);
96 }
97 }
98 },
99 s: {
100 setMaxWidth: function(token){
1012013 token.maxWidth = (token.period == '.') ? token.precision : -1;
102 }
103 },
104 e: {
105 isDouble: true,
106 doubleNotation: 'e'
107 },
108 E: {
109 extend: ['e'],
110 toUpper: true
111 },
112 f: {
113 isDouble: true,
114 doubleNotation: 'f'
115 },
116 F: {
117 extend: ['f']
118 },
119 g: {
120 isDouble: true,
121 doubleNotation: 'g'
122 },
123 G: {
124 extend: ['g'],
125 toUpper: true
126 },
127 O: {
128 setArg: function(token){
1291 token.arg = util.inspect(token.arg, true, null);
130 }
131 },
132};
1331Formatter.prototype.format = function(/*mixed...*/ filler){
1342080 if(this._mapped && typeof filler != 'object'){
1352 throw new Error('format requires a mapping');
136 }
137
1382078 var str = '';
1392078 var position = 0;
1402078 for(var i = 0, token; i < this._tokens.length; i++){
1418099 token = this._tokens[i];
142
1438099 if(typeof token == 'string'){
1444018 str += token;
145 }else{
1464081 if(this._mapped){
1476 if(typeof filler[token.mapping] == 'undefined'){
1480 throw new Error('missing key ' + token.mapping);
149 }
1506 token.arg = filler[token.mapping];
151 }else{
1524075 if(token.intmapping){
1537 var position = parseInt(token.intmapping) - 1;
154 }
1554075 if(position >= arguments.length){
1563 throw new Error('got ' + arguments.length + ' printf arguments, insufficient for \'' + this._format + '\'');
157 }
1584072 token.arg = arguments[position++];
159 }
160
1614078 if(!token.compiled){
1622080 token.compiled = true;
1632080 token.sign = '';
1642080 token.zeroPad = false;
1652080 token.rightJustify = false;
1662080 token.alternative = false;
167
1682080 var flags = {};
1692080 for(var fi = token.flags.length; fi--;){
17052 var flag = token.flags.charAt(fi);
17152 flags[flag] = true;
17252 switch(flag){
173 case ' ':
1746 token.sign = ' ';
1756 break;
176 case '+':
1777 token.sign = '+';
1787 break;
179 case '0':
18020 token.zeroPad = (flags['-']) ? false : true;
18120 break;
182 case '-':
18319 token.rightJustify = true;
18419 token.zeroPad = false;
18519 break;
186 case '\#':
1870 token.alternative = true;
1880 break;
189 default:
1900 throw Error('bad formatting flag \'' + token.flags.charAt(fi) + '\'');
191 }
192 }
193
1942080 token.minWidth = (token._minWidth) ? parseInt(token._minWidth) : 0;
1952080 token.maxWidth = -1;
1962080 token.toUpper = false;
1972080 token.isUnsigned = false;
1982080 token.isInt = false;
1992080 token.isDouble = false;
2002080 token.precision = 1;
2012080 if(token.period == '.'){
20217 if(token._precision){
20316 token.precision = parseInt(token._precision);
204 }else{
2051 token.precision = 0;
206 }
207 }
208
2092080 var mixins = this._specifiers[token.specifier];
2102080 if(typeof mixins == 'undefined'){
2110 throw new Error('unexpected specifier \'' + token.specifier + '\'');
212 }
2132080 if(mixins.extend){
2142 var s = this._specifiers[mixins.extend];
2152 for(var k in s){
2164 mixins[k] = s[k]
217 }
2182 delete mixins.extend;
219 }
2202080 for(var k in mixins){
2213139 token[k] = mixins[k];
222 }
223 }
224
2254078 if(typeof token.setArg == 'function'){
2267 token.setArg(token);
227 }
228
2294076 if(typeof token.setMaxWidth == 'function'){
2302013 token.setMaxWidth(token);
231 }
232
2334076 if(token._minWidth == '*'){
2347 if(this._mapped){
2351 throw new Error('* width not supported in mapped formats');
236 }
2376 token.minWidth = parseInt(arguments[position++]);
2386 if(isNaN(token.minWidth)){
2391 throw new Error('the argument for * width at position ' + position + ' is not a number in ' + this._format);
240 }
241 // negative width means rightJustify
2425 if (token.minWidth < 0) {
2430 token.rightJustify = true;
2440 token.minWidth = -token.minWidth;
245 }
246 }
247
2484074 if(token._precision == '*' && token.period == '.'){
2494 if(this._mapped){
2500 throw new Error('* precision not supported in mapped formats');
251 }
2524 token.precision = parseInt(arguments[position++]);
2534 if(isNaN(token.precision)){
2540 throw Error('the argument for * precision at position ' + position + ' is not a number in ' + this._format);
255 }
256 // negative precision means unspecified
2574 if (token.precision < 0) {
2580 token.precision = 1;
2590 token.period = '';
260 }
261 }
2624074 if(token.isInt){
263 // a specified precision means no zero padding
2642043 if(token.period == '.'){
2652 token.zeroPad = false;
266 }
2672043 this.formatInt(token);
2682031 }else if(token.isDouble){
26915 if(token.period != '.'){
2700 token.precision = 6;
271 }
27215 this.formatDouble(token);
273 }
2744073 this.fitField(token);
275
2764073 str += '' + token.arg;
277 }
278 }
279
2802070 return str;
281};
2821Formatter.prototype._zeros10 = '0000000000';
2831Formatter.prototype._spaces10 = ' ';
2841Formatter.prototype.formatInt = function(token) {
2852043 var i = parseInt(token.arg);
2862043 if(!isFinite(i)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
287 // allow this only if arg is number
2880 if(typeof token.arg != 'number'){
2890 throw new Error('format argument \'' + token.arg + '\' not an integer; parseInt returned ' + i);
290 }
291 //return '' + i;
2920 i = 0;
293 }
294
295 // if not base 10, make negatives be positive
296 // otherwise, (-10).toString(16) is '-a' instead of 'fffffff6'
2972043 if(i < 0 && (token.isUnsigned || token.base != 10)){
2980 i = 0xffffffff + i + 1;
299 }
300
3012043 if(i < 0){
30218 token.arg = (- i).toString(token.base);
30318 this.zeroPad(token);
30418 token.arg = '-' + token.arg;
305 }else{
3062025 token.arg = i.toString(token.base);
307 // need to make sure that argument 0 with precision==0 is formatted as ''
3082025 if(!i && !token.precision){
3090 token.arg = '';
310 }else{
3112025 this.zeroPad(token);
312 }
3132025 if(token.sign){
3146 token.arg = token.sign + token.arg;
315 }
316 }
3172043 if(token.base == 16){
3180 if(token.alternative){
3190 token.arg = '0x' + token.arg;
320 }
3210 token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
322 }
3232043 if(token.base == 8){
3240 if(token.alternative && token.arg.charAt(0) != '0'){
3250 token.arg = '0' + token.arg;
326 }
327 }
328};
3291Formatter.prototype.formatDouble = function(token) {
33015 var f = parseFloat(token.arg);
33115 if(!isFinite(f)){ // isNaN(f) || f == Number.POSITIVE_INFINITY || f == Number.NEGATIVE_INFINITY)
332 // allow this only if arg is number
3331 if(typeof token.arg != 'number'){
3341 throw new Error('format argument \'' + token.arg + '\' not a float; parseFloat returned ' + f);
335 }
336 // C99 says that for 'f':
337 // infinity -> '[-]inf' or '[-]infinity' ('[-]INF' or '[-]INFINITY' for 'F')
338 // NaN -> a string starting with 'nan' ('NAN' for 'F')
339 // this is not commonly implemented though.
340 //return '' + f;
3410 f = 0;
342 }
343
34414 switch(token.doubleNotation) {
345 case 'e': {
3460 token.arg = f.toExponential(token.precision);
3470 break;
348 }
349 case 'f': {
35014 token.arg = f.toFixed(token.precision);
35114 break;
352 }
353 case 'g': {
354 // C says use 'e' notation if exponent is < -4 or is >= prec
355 // ECMAScript for toPrecision says use exponential notation if exponent is >= prec,
356 // though step 17 of toPrecision indicates a test for < -6 to force exponential.
3570 if(Math.abs(f) < 0.0001){
358 //print('forcing exponential notation for f=' + f);
3590 token.arg = f.toExponential(token.precision > 0 ? token.precision - 1 : token.precision);
360 }else{
3610 token.arg = f.toPrecision(token.precision);
362 }
363
364 // In C, unlike 'f', 'gG' removes trailing 0s from fractional part, unless alternative format flag ('#').
365 // But ECMAScript formats toPrecision as 0.00100000. So remove trailing 0s.
3660 if(!token.alternative){
367 //print('replacing trailing 0 in \'' + s + '\'');
3680 token.arg = token.arg.replace(/(\..*[^0])0*/, '$1');
369 // if fractional part is entirely 0, remove it and decimal point
3700 token.arg = token.arg.replace(/\.0*e/, 'e').replace(/\.0$/,'');
371 }
3720 break;
373 }
3740 default: throw new Error('unexpected double notation \'' + token.doubleNotation + '\'');
375 }
376
377 // C says that exponent must have at least two digits.
378 // But ECMAScript does not; toExponential results in things like '1.000000e-8' and '1.000000e+8'.
379 // Note that s.replace(/e([\+\-])(\d)/, 'e$10$2') won't work because of the '$10' instead of '$1'.
380 // And replace(re, func) isn't supported on IE50 or Safari1.
38114 token.arg = token.arg.replace(/e\+(\d)$/, 'e+0$1').replace(/e\-(\d)$/, 'e-0$1');
382
383 // if alt, ensure a decimal point
38414 if(token.alternative){
3850 token.arg = token.arg.replace(/^(\d+)$/,'$1.');
3860 token.arg = token.arg.replace(/^(\d+)e/,'$1.e');
387 }
388
38914 if(f >= 0 && token.sign){
3901 token.arg = token.sign + token.arg;
391 }
392
39314 token.arg = token.toUpper ? token.arg.toUpperCase() : token.arg.toLowerCase();
394};
3951Formatter.prototype.zeroPad = function(token, /*Int*/ length) {
3962051 length = (arguments.length == 2) ? length : token.precision;
3972051 var negative = false;
3982051 if(typeof token.arg != "string"){
3990 token.arg = "" + token.arg;
400 }
4012051 if (token.arg.substr(0,1) === '-') {
4023 negative = true;
4033 token.arg = token.arg.substr(1);
404 }
405
4062051 var tenless = length - 10;
4072051 while(token.arg.length < tenless){
4082 token.arg = (token.rightJustify) ? token.arg + this._zeros10 : this._zeros10 + token.arg;
409 }
4102051 var pad = length - token.arg.length;
4112051 token.arg = (token.rightJustify) ? token.arg + this._zeros10.substring(0, pad) : this._zeros10.substring(0, pad) + token.arg;
4122054 if (negative) token.arg = '-' + token.arg;
413};
4141Formatter.prototype.fitField = function(token) {
4154073 if(token.maxWidth >= 0 && token.arg.length > token.maxWidth){
4160 return token.arg.substring(0, token.maxWidth);
417 }
4184073 if(token.zeroPad){
4198 this.zeroPad(token, token.minWidth);
4208 return;
421 }
4224065 this.spacePad(token);
423};
4241Formatter.prototype.spacePad = function(token, /*Int*/ length) {
4254065 length = (arguments.length == 2) ? length : token.minWidth;
4264065 if(typeof token.arg != 'string'){
4271 token.arg = '' + token.arg;
428 }
4294065 var tenless = length - 10;
4304065 while(token.arg.length < tenless){
43110 token.arg = (token.rightJustify) ? token.arg + this._spaces10 : this._spaces10 + token.arg;
432 }
4334065 var pad = length - token.arg.length;
4344065 token.arg = (token.rightJustify) ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg;
435};
436
437
4381module.exports = function(){
4391080 var args = Array.prototype.slice.call(arguments),
440 stream, format;
4411080 if(args[0] instanceof require('stream').Stream){
4420 stream = args.shift();
443 }
4441080 format = args.shift();
4451080 var formatter = new Formatter(format);
4461080 var string = formatter.format.apply(formatter, args);
4471070 if(stream){
4480 stream.write(string);
449 }else{
4501070 return string;
451 }
452};
453
4541module.exports.Formatter = Formatter;
455
--------------------------------------------------------------------------------