From 803a417eea2dad9be6d8a466b9c63534534316ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20H=C3=B6rl?= Date: Sat, 21 Jun 2014 01:04:49 +0200 Subject: [PATCH 1/2] setup XRegExp as replacement for build in RegExp --- index.js | 27 ++++++++++++++++++++++++--- package.json | 6 ++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 2003f38..c8b0d6e 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,24 @@ 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.flags = XRegExp('(?[-])'); + reLib.position = XRegExp.build('(?{{posInt}})\$', reLib); + //reLib.width = XRegExp.build('(?(?{{posInt}})|(?{{star}})|(?{{nth}}))', reLib); + reLib.width = XRegExp.build('(?{{posInt}})|(?{{star}})|(?{{nth}})', reLib); + reLib.type = XRegExp('(?[sIL])'); + + return XRegExp.build('%((?%)|{{position}}?{{flags}}?{{width}}?{{type}})', reLib, 'g'); +})(); + /** * Expose `format()`. */ @@ -34,8 +53,10 @@ exports = module.exports = format; function format(fmt) { var i = 1; var args = arguments; - return fmt.replace(/%([%sIL])/g, function(_, type){ - if ('%' == type) return '%'; + + return XRegExp.replace(fmt, RegExpMain, function(matches, type){ + console.log(matches); + if (matches.percent !== undefined) return '%'; var arg = args[i++]; switch (type) { @@ -112,4 +133,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 +} From 894e637434edf239ae06caa1be10c65f33769d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20H=C3=B6rl?= Date: Sun, 22 Jun 2014 20:17:24 +0200 Subject: [PATCH 2/2] Implementation of the advanced features of the format function --- Readme.md | 5 ++- index.js | 82 +++++++++++++++++++++++++++++++++++++++++------- test/advanced.js | 54 +++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 test/advanced.js 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 c8b0d6e..26f673a 100644 --- a/index.js +++ b/index.js @@ -25,16 +25,77 @@ var RegExpMain = (function(){ var reLib = {}; reLib.posInt = /[1-9][0-9]*/; reLib.star = /\*/; - reLib.nth = XRegExp.build('{{star}}{{posInt}}\$', reLib); - reLib.flags = XRegExp('(?[-])'); - reLib.position = XRegExp.build('(?{{posInt}})\$', reLib); - //reLib.width = XRegExp.build('(?(?{{posInt}})|(?{{star}})|(?{{nth}}))', reLib); - reLib.width = XRegExp.build('(?{{posInt}})|(?{{star}})|(?{{nth}})', reLib); + 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])'); - return XRegExp.build('%((?%)|{{position}}?{{flags}}?{{width}}?{{type}})', reLib, 'g'); + 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()`. */ @@ -51,15 +112,14 @@ exports = module.exports = format; */ function format(fmt) { - var i = 1; - var args = arguments; + var args = new Args(arguments); return XRegExp.replace(fmt, RegExpMain, function(matches, type){ - console.log(matches); if (matches.percent !== undefined) return '%'; - var arg = args[i++]; - switch (type) { + 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); 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'); + }); +});