diff --git a/Readme.md b/Readme.md index 0e2b19a..ecc624f 100644 --- a/Readme.md +++ b/Readme.md @@ -47,6 +47,9 @@ INSERT INTO books VALUES('O''Reilly') - `%L` quotes the argument value as an SQL literal. A null value is displayed as the string NULL, without quotes. - `%%` In addition to the format specifiers described above, the special sequence %% may be used to output a literal % character. +Also all other "advanced" features (postition, flag, width) of the `format` function are implemented, +see [the Postgresql documentation](http://www.postgresql.org/docs/current/static/functions-string.html#FUNCTIONS-STRING-FORMAT). + # License - MIT \ No newline at end of file + MIT diff --git a/index.js b/index.js index 2003f38..26f673a 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var assert = require('assert'); var fs = require('fs'); +var XRegExp = require('xregexp').XRegExp; /** * Reserved word map. @@ -16,6 +17,85 @@ var reserved = txt.split('\n').reduce(function(map, word){ return map; }, {}); +/** + * setup the regex stuff/lib + */ + +var RegExpMain = (function(){ + var reLib = {}; + reLib.posInt = /[1-9][0-9]*/; + reLib.star = /\*/; + reLib.nth = XRegExp.build('{{star}}(?{{posInt}})[$]', reLib); + reLib.flag = XRegExp('(?[-])'); + reLib.position = XRegExp.build('(?{{posInt}})[$]', reLib); + reLib.width = XRegExp.build('(?{{posInt}})|(?{{star}})|{{nth}}', reLib); + reLib.type = XRegExp('(?[sIL])'); + + var reMain = XRegExp.build('%((?%)|{{position}}?{{flag}}?{{width}}?{{type}})', reLib, 'g'); + + return reMain; +})(); + +/** + * Helper class for argument handling + * + * @param {Array} args + * @api private + */ +function Args(args) { + var curIndex = 1; + + this.get = function(idx) { + curIndex = idx ? parseInt(idx, 10) : curIndex; + assert(curIndex > 0, 'Minimum usable index is 1'); + return args[curIndex++]; + }; +}; + +/** + * get the current argument depending on all the format magic + * + * @param {Args} args + * @param {Object} matches + * @return {String} + * @api private + */ + +function getArg(args, matches) { + var width = matches.widthInt; + var spacesOnLeft = matches.flag !== '-'; + var pos = matches.position || null; + var arg = args.get(pos); + + if (matches.widthStar === '*') { + width = arg; + arg = args.get(pos); + } + + if (matches.widthNth) { + var argIdx = parseInt(matches.widthNth, 10); + width = args.get(argIdx); + arg = args.get(pos); + } + + width = parseInt(width, 10); + + if (width < 0) { + width *= -1; + if (spacesOnLeft) { + spacesOnLeft = !spacesOnLeft; + } + } + + var numSpaces = width - arg.length; + if (numSpaces > 0) { + var spaces = (new Array(numSpaces+1)).join(' '); + arg = spacesOnLeft ? spaces.concat(arg) : arg.concat(spaces); + } + + return arg; +} + /** * Expose `format()`. */ @@ -32,13 +112,14 @@ exports = module.exports = format; */ function format(fmt) { - var i = 1; - var args = arguments; - return fmt.replace(/%([%sIL])/g, function(_, type){ - if ('%' == type) return '%'; + var args = new Args(arguments); - var arg = args[i++]; - switch (type) { + return XRegExp.replace(fmt, RegExpMain, function(matches, type){ + if (matches.percent !== undefined) return '%'; + + var arg = getArg(args, matches); + + switch (matches.type) { case 's': return exports.string(arg); case 'I': return exports.ident(arg); case 'L': return exports.literal(arg); @@ -112,4 +193,4 @@ function validIdent(id) { function quoteIdent(id) { id = id.replace(/"/g, '""'); return '"' + id + '"'; -} \ No newline at end of file +} diff --git a/package.json b/package.json index f8d41f4..41d0192 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ "escape", "query" ], - "dependencies": {}, + "dependencies": { + "xregexp": "^2.0.0" + }, "devDependencies": { "mocha": "*", "should": "*" }, "license": "MIT" -} \ No newline at end of file +} diff --git a/test/advanced.js b/test/advanced.js new file mode 100644 index 0000000..0c94b08 --- /dev/null +++ b/test/advanced.js @@ -0,0 +1,54 @@ +var assert = require('assert'); +var escape = require('..'); + +describe('advanced escape(fmt, ...)', function(){ + it('should handle |%10s|', function(){ + escape('|%10s|', 'foo') + .should.equal('| foo|'); + }); + + it('should handle |%-10s|', function(){ + escape('|%-10s|', 'foo') + .should.equal('|foo |'); + }); + + it('should handle |%*s|', function(){ + escape('|%*s|', 10, 'foo') + .should.equal('| foo|'); + }); + + it('should handle |%*s|', function(){ + escape('|%*s|', -10, 'foo') + .should.equal('|foo |'); + }); + + it('should handle |%-*s|', function(){ + escape('|%-*s|', 10, 'foo') + .should.equal('|foo |'); + }); + + it('should handle |%-*s|', function(){ + escape('|%-*s|', -10, 'foo') + .should.equal('|foo |'); + }); + + it('should handle %3$s, %2$s, %1$s', function(){ + escape('Testing %3$s, %2$s, %1$s', 'one', 'two', 'three') + .should.equal('Testing three, two, one'); + }); + + it('should handle |%*2$s|', function(){ + escape('|%*2$s|', 'foo', 10, 'bar') + .should.equal('| bar|'); + }); + + it('should handle |%1$*2$s|', function(){ + escape('|%1$*2$s|', 'foo', 10, 'bar') + .should.equal('| foo|'); + }); + + it('should handle advanced position', function(){ + escape('Testing %3$s, %2$s, %s', 'one', 'two', 'three') + .should.equal('Testing three, two, three'); + }); +});