From b0536de3ff9e061bfb0622c266870aa40d1b965d Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Tue, 6 Nov 2012 20:24:48 -0800 Subject: [PATCH] improve parsing of --query parameter The argument parser now converts the query string to an object, and it casts booleans and numbers in the query string to the correct JavaScript types. --- jsdoc.js | 6 +-- rhino_modules/jsdoc/opts/args.js | 64 ++++++++++++++++++++++++++++++-- test/specs/jsdoc/opts/args.js | 62 ++++++++++++++++++++++--------- 3 files changed, 107 insertions(+), 25 deletions(-) diff --git a/jsdoc.js b/jsdoc.js index e603f966..5e0d1e35 100644 --- a/jsdoc.js +++ b/jsdoc.js @@ -319,17 +319,13 @@ function main() { } } - if (env.opts.query) { - env.opts.query = require('querystring').parse(env.opts.query); - } - // which version of javascript will be supported? (rhino only) if (typeof version === 'function') { version(env.conf.jsVersion || 180); } if (env.opts.help) { - console.log( jsdoc.opts.parser.help() ); + console.log( jsdoc.opts.args.help() ); process.exit(0); } else if (env.opts.test) { include('test/runner.js'); diff --git a/rhino_modules/jsdoc/opts/args.js b/rhino_modules/jsdoc/opts/args.js index 42490b87..efc8d2f3 100644 --- a/rhino_modules/jsdoc/opts/args.js +++ b/rhino_modules/jsdoc/opts/args.js @@ -7,22 +7,80 @@ var ArgParser = require('jsdoc/opts/argparser'), argParser = new ArgParser(), + hasOwnProp = Object.prototype.hasOwnProperty, ourOptions, + querystring = require('querystring'), + util = require('util'), defaults = { destination: './out/' }; + +// cast strings to booleans or integers where appropriate +function castTypes(item) { + var result = item; + + switch (result) { + case 'true': + return true; + case 'false': + return false; + default: + // might be an integer + var integer = parseInt(result, 10); + if (String(integer) === result && integer !== 'NaN') { + return integer; + } else { + return result; + } + } +} + +// check for strings that we need to cast to other types +function fixTypes(item) { + var result = item; + + // recursively process arrays and objects + if ( util.isArray(result) ) { + for (var i = 0, l = result.length; i < l; i++) { + result[i] = fixTypes(result[i]); + } + } else if (typeof result === 'object') { + for (var prop in result) { + if ( hasOwnProp.call(result, prop) ) { + result[prop] = fixTypes(result[prop]); + } + } + } else { + result = castTypes(result); + } + + return result; +} + +function parseQuery(str) { + var result = querystring.parse(str); + + for (var prop in result) { + if ( hasOwnProp.call(result, prop) ) { + result[prop] = fixTypes(result[prop]); + } + } + + return result; +} + argParser.addOption('t', 'template', true, 'The name of the template to use. Default: the "default" template'); argParser.addOption('c', 'configure', true, 'The path to the configuration file. Default: jsdoc env.dirname + /conf.json'); argParser.addOption('e', 'encoding', true, 'Assume this encoding when reading all source files. Default: utf-8'); argParser.addOption('T', 'test', false, 'Run all tests and quit.'); argParser.addOption('d', 'destination', true, 'The path to the output folder. Use "console" to dump data to the console. Default: console'); -argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Default: false.'); +argParser.addOption('p', 'private', false, 'Display symbols marked with the @private tag. Default: false'); argParser.addOption('r', 'recurse', false, 'Recurse into subdirectories when scanning for source code files.'); -argParser.addOption('l', 'lenient', false, 'Continue to generate output if a doclet is incomplete or contains errors. Default: false.'); +argParser.addOption('l', 'lenient', false, 'Continue to generate output if a doclet is incomplete or contains errors. Default: false'); argParser.addOption('h', 'help', false, 'Print this message and quit.'); argParser.addOption('X', 'explain', false, 'Dump all found doclet internals to console and quit.'); -argParser.addOption('q', 'query', true, 'Provide a querystring to define custom variable names/values to add to the options hash.'); +argParser.addOption('q', 'query', true, 'A query string to parse and store in env.opts.query. Example: foo=bar&baz=true', false, parseQuery); argParser.addOption('u', 'tutorials', true, 'Directory in which JSDoc should search for tutorials.'); //TODO [-R, recurseonly] = a number representing the depth to recurse diff --git a/test/specs/jsdoc/opts/args.js b/test/specs/jsdoc/opts/args.js index 4100bf95..cf672a71 100644 --- a/test/specs/jsdoc/opts/args.js +++ b/test/specs/jsdoc/opts/args.js @@ -1,6 +1,7 @@ /*global describe: true, expect: true, it: true */ describe("jsdoc/opts/args", function() { var args = require('jsdoc/opts/args'); + var querystring = require('querystring'); it("should exist", function() { expect(args).toBeDefined(); @@ -121,6 +122,20 @@ describe("jsdoc/opts/args", function() { expect(r.recurse).toEqual(true); }); + it("should accept a '-l' option and return an object with a 'lenient' property", function() { + args.parse(['-l']); + var r = args.get(); + + expect(r.lenient).toEqual(true); + }); + + it("should accept a '--lenient' option and return an object with a 'lenient' property", function() { + args.parse(['--lenient']); + var r = args.get(); + + expect(r.lenient).toEqual(true); + }); + it("should accept a '-h' option and return an object with a 'help' property", function() { args.parse(['-h']); var r = args.get(); @@ -153,14 +168,27 @@ describe("jsdoc/opts/args", function() { args.parse(['-q', 'foo=bar&fab=baz']); var r = args.get(); - expect(r.query).toEqual('foo=bar&fab=baz'); + expect(r.query).toEqual({ foo: 'bar', fab: 'baz' }); }); it("should accept a '--query' option and return an object with a 'query' property", function() { args.parse(['--query', 'foo=bar&fab=baz']); var r = args.get(); - expect(r.query).toEqual('foo=bar&fab=baz'); + expect(r.query).toEqual({ foo: 'bar', fab: 'baz' }); + }); + + it("should use type coercion on the 'query' property so it has real booleans and numbers", function() { + var obj = { + foo: 'fab', + bar: true, + baz: false, + qux: [1, -97] + }; + args.parse(['-q', querystring.stringify(obj)]); + var r = args.get(); + + expect(r.query).toEqual(obj); }); it("should accept a '-t' option and return an object with a 'tutorials' property", function() { @@ -177,13 +205,6 @@ describe("jsdoc/opts/args", function() { expect(r.tutorials).toEqual('mytutorials'); }); - it("should accept a naked option (i.e. no '-') and return an object with a '_' property", function() { - args.parse(['myfile1', 'myfile2']); - var r = args.get(); - - expect(r._).toEqual(['myfile1', 'myfile2']); - }); - it("should accept a '--verbose' option and return an object with a 'verbose' property", function() { args.parse(['--verbose']); var r = args.get(); @@ -191,13 +212,6 @@ describe("jsdoc/opts/args", function() { expect(r.verbose).toEqual(true); }); - it("should accept a '--nocolor' option and return an object with a 'nocolor' property", function() { - args.parse(['--nocolor']); - var r = args.get(); - - expect(r.nocolor).toEqual(true); - }); - it("should accept a '--match' option and return an object with a 'match' property", function() { args.parse(['--match', '.*tag']); var r = args.get(); @@ -205,13 +219,27 @@ describe("jsdoc/opts/args", function() { expect(r.match).toEqual('.*tag'); }); - it("should accept a multiple '--match' options and return an object with a 'match' property", function() { + it("should accept multiple '--match' options and return an object with a 'match' property", function() { args.parse(['--match', '.*tag', '--match', 'parser']); var r = args.get(); expect(r.match).toEqual(['.*tag', 'parser']); }); + it("should accept a '--nocolor' option and return an object with a 'nocolor' property", function() { + args.parse(['--nocolor']); + var r = args.get(); + + expect(r.nocolor).toEqual(true); + }); + + it("should accept a naked option (i.e. no '-') and return an object with a '_' property", function() { + args.parse(['myfile1', 'myfile2']); + var r = args.get(); + + expect(r._).toEqual(['myfile1', 'myfile2']); + }); + //TODO: tests for args that must have values }); }); \ No newline at end of file