Skip to content

Commit

Permalink
improve parsing of --query parameter
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hegemonic committed Nov 7, 2012
1 parent 37a7fd0 commit b0536de
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 25 deletions.
6 changes: 1 addition & 5 deletions jsdoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
64 changes: 61 additions & 3 deletions rhino_modules/jsdoc/opts/args.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 45 additions & 17 deletions test/specs/jsdoc/opts/args.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand All @@ -177,41 +205,41 @@ 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();

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();

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
});
});

0 comments on commit b0536de

Please sign in to comment.