Permalink
Browse files

Make --help nicer

  • Loading branch information...
dpup committed Mar 29, 2011
1 parent 42885ad commit e43ecce60c870c762d894a36bd02581947a50958
Showing with 156 additions and 36 deletions.
  1. +2 −3 README.md
  2. +9 −8 examples/example.js
  3. +145 −25 lib/flags.js
View
@@ -29,7 +29,6 @@ Then on the command line:
node example.js --name='Your Name' --age 43 --height=1.234 --pets=fred,bob --hobby biking --hobby=snowboarding
-
## Defining Flags
@@ -50,12 +49,13 @@ To define flags, use one of the defineX functions exported by the `flags` module
All the define methods take the same arguments:
- flags.defineX(name, defaultValue, opt_description, opt_validator);
+ flags.defineX(name, defaultValue, opt_description, opt_validator, opt_isSecret);
name - The flag's name
defaultValue - The default value if not specified on the command line
description - [optional] Description to show in the help text
validator - [optional] Function for validating the input, should throw if the input isn't valid.
+ isSecret - [optional] Whether the flag shold be omitted from the help text.
## Querying Flag Values
@@ -81,7 +81,6 @@ If you want to change flags between test cases, you may call:
## TODOs
* Support --flagsfile
- * Handle --help and --helpshort internally
* Support multi space separated flags, e.g. --files file1 file2 file3
* Set up for npm install
View
@@ -10,11 +10,15 @@
var flags = require('flags');
-flags.defineString('name', 'Billy Noone', 'Your name');
-flags.defineInteger('age', 21, 'Your age in whole years');
-flags.defineNumber('height', 1.80, 'Your height in meters');
-flags.defineStringList('pets', [], 'Comma separated list of your pets');
-flags.defineMultiString('hobby', [], 'A hobby');
+flags.defineString('name', 'Billy Noone', 'Your name.');
+flags.defineInteger('age', 21, 'Your age in whole years.\nThis is a really ' +
+ 'long and informative description that tells you how to accurately ' +
+ 'specify your age as a flag. It\'s not really useful, just using it to ' +
+ 'test out the help text with long descriptions.');
+flags.defineNumber('height', 1.80, 'Your height in meters.');
+flags.defineStringList('pets', [], 'Comma separated list of your pets.');
+flags.defineMultiString('hobby', ['chess', 'tv'], 'A hobby.');
+flags.defineBoolean('active', false, 'Whether the user is active.')
flags.parse();
@@ -28,6 +32,3 @@ info.push('Pets : ' + flags.get('pets').join(', '));
info.push('Hobbies : \n ' + flags.get('hobby').join('\n '));
console.log(info.join('\n'));
-
-console.log('\nHelp Text:');
-flags.help();
View
@@ -22,6 +22,15 @@ var FLAGS = exports.FLAGS = {};
exports.exitOnError = true;
+/**
+ * Allows an app to add extra usage information that will be shown in the help
+ * message, above the flags.
+ * @type {string}
+ */
+exports.usageInfo = 'Usage: node ' +
+ process.argv[1].split('/').pop() + ' [options]';
+
+
/**
* Defines a string flag. e.g. --servername=bob
* @param {string} name The flag name, should be [a-zA-Z0-9]+.
@@ -31,9 +40,13 @@ exports.exitOnError = true;
* @param {function(string)=} opt_validator Optional validator function that
* will be called when parsing flags. Should throw if the input is not
* valid.
+ * @param {boolean} opt_secret Whether the flag is 'secret' and should not be
+ * shown in the help text.
*/
-exports.defineString = function(name, defaultValue, opt_description, opt_validator) {
- addFlag(name, new Flag(name, defaultValue, opt_description, opt_validator));
+exports.defineString = function(
+ name, defaultValue, opt_description, opt_validator, opt_secret) {
+ addFlag(name, new Flag
+ (name, defaultValue, opt_description, opt_validator, opt_secret));
};
@@ -46,9 +59,13 @@ exports.defineString = function(name, defaultValue, opt_description, opt_validat
* @param {function(string)=} opt_validator Optional validator function that
* will be called when parsing flags. Should throw if the input is not
* valid.
+ * @param {boolean} opt_secret Whether the flag is 'secret' and should not be
+ * shown in the help text.
*/
-exports.defineBoolean = function(name, defaultValue, opt_description, opt_validator) {
- addFlag(name, new BooleanFlag(name, defaultValue, opt_description, opt_validator));
+exports.defineBoolean = function(
+ name, defaultValue, opt_description, opt_validator, opt_secret) {
+ addFlag(name, new BooleanFlag(
+ name, defaultValue, opt_description, opt_validator, opt_secret));
};
@@ -61,9 +78,13 @@ exports.defineBoolean = function(name, defaultValue, opt_description, opt_valida
* @param {function(string)=} opt_validator Optional validator function that
* will be called when parsing flags. Should throw if the input is not
* valid.
+ * @param {boolean} opt_secret Whether the flag is 'secret' and should not be
+ * shown in the help text.
*/
-exports.defineInteger = function(name, defaultValue, opt_description, opt_validator) {
- addFlag(name, new IntegerFlag(name, defaultValue, opt_description, opt_validator));
+exports.defineInteger = function(
+ name, defaultValue, opt_description, opt_validator, opt_secret) {
+ addFlag(name, new IntegerFlag(
+ name, defaultValue, opt_description, opt_validator, opt_secret));
};
@@ -76,49 +97,66 @@ exports.defineInteger = function(name, defaultValue, opt_description, opt_valida
* @param {function(string)=} opt_validator Optional validator function that
* will be called when parsing flags. Should throw if the input is not
* valid.
+ * @param {boolean} opt_secret Whether the flag is 'secret' and should not be
+ * shown in the help text.
*/
-exports.defineNumber = function(name, defaultValue, opt_description, opt_validator) {
- addFlag(name, new NumberFlag(name, defaultValue, opt_description, opt_validator));
+exports.defineNumber = function(
+ name, defaultValue, opt_description, opt_validator, opt_secret) {
+ addFlag(name, new NumberFlag(
+ name, defaultValue, opt_description, opt_validator, opt_secret));
};
/**
* Defines a string list flag. e.g. --anmial=frog,bat,chicken
* @param {string} name The flag name, should be [a-zA-Z0-9]+.
- * @param {!Array.<string>} defaultValue The default value, should the flag not be
- * explicitly specified.
+ * @param {!Array.<string>} defaultValue The default value, should the flag not
+ * be explicitly specified.
* @param {string=} opt_description Optional description to use in help text.
* @param {function(string)=} opt_validator Optional validator function that
* will be called when parsing flags. Should throw if the input is not
* valid.
+ * @param {boolean} opt_secret Whether the flag is 'secret' and should not be
+ * shown in the help text.
*/
-exports.defineStringList = function(name, defaultValue, opt_description, opt_validator) {
- addFlag(name, new StringListFlag(name, defaultValue, opt_description, opt_validator));
+exports.defineStringList = function(
+ name, defaultValue, opt_description, opt_validator, opt_secret) {
+ addFlag(name, new StringListFlag(
+ name, defaultValue, opt_description, opt_validator, opt_secret));
};
/**
* Defines a multi string flag. e.g. --allowedip=127.0.0.1 --allowedip=127.0.0.2
* @param {string} name The flag name, should be [a-zA-Z0-9]+.
- * @param {!Array.<string>} defaultValue The default value, should the flag not be
- * explicitly specified.
+ * @param {!Array.<string>} defaultValue The default value, should the flag not
+ * be explicitly specified.
* @param {string=} opt_description Optional description to use in help text.
* @param {function(string)=} opt_validator Optional validator function that
* will be called when parsing flags. Should throw if the input is not
* valid.
+ * @param {boolean} opt_secret Whether the flag is 'secret' and should not be
+ * shown in the help text.
*/
-exports.defineMultiString = function(name, defaultValue, opt_description, opt_validator) {
- addFlag(name, new MultiStringFlag(name, defaultValue, opt_description, opt_validator));
+exports.defineMultiString = function(
+ name, defaultValue, opt_description, opt_validator, opt_secret) {
+ addFlag(name, new MultiStringFlag(
+ name, defaultValue, opt_description, opt_validator, opt_secret));
};
/**
* Dumps the help text to the console.
*/
exports.help = function() {
- // TODO: make this suck less and automatically hook up to --help
+ if (exports.usageInfo) {
+ console.log(exports.usageInfo + '\n');
+ }
+ console.log('Options:');
for (var flag in FLAGS) {
- console.log('--' + flag + ' : ' + FLAGS[flag].description);
+ if (!FLAGS[flag].isSecret) {
+ console.log(FLAGS[flag].toHelpString());
+ }
}
};
@@ -129,6 +167,7 @@ exports.help = function() {
exports.reset = function() {
parseCalled = false;
FLAGS = exports.FLAGS = {};
+ registerInternalFlags();
};
@@ -202,6 +241,12 @@ exports.parse = function(opt_args) {
parseCalled = true;
+ // Intercept the --help flag.
+ if (FLAGS.help.get()) {
+ exports.help();
+ process.exit(0);
+ }
+
if (i != args.length) {
return args.slice(i + 1);
} else {
@@ -210,7 +255,6 @@ exports.parse = function(opt_args) {
};
-
// Private helpers
//==================
@@ -240,17 +284,59 @@ function addFlag(name, flag) {
}
+function wrapText(text, maxLen) {
+ var lines = text.split('\n');
+ var out = [];
+ for (var i = 0; i < lines.length; i++) {
+ // Adjust the maxLength for to take into account the lack of indent on the
+ // first line.
+ var maxLenx = maxLen + (out.length == 0 ? 4 : 0);
+ var line = lines[i];
+ if (line.length < maxLenx) {
+ // Line doesn't exceed length so just push it.
+ out.push(line)
+ } else {
+ // Wrap the line on spaces.
+ // TODO : Would be nice to split URLs and long phrases that have no
+ // natural spaces.
+ var current = wrapLine(line, ' ', maxLenx, out);
+ if (current != '') {
+ out.push(current);
+ }
+ }
+ }
+ return out.join('\n ');
+}
+
+
+function wrapLine(line, delimiter, maxLen, out) {
+ var parts = line.split(delimiter);
+ var current = '';
+ for (var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ var next = current + part + delimiter;
+ if (next.length > maxLen) {
+ out.push(current);
+ current = part + delimiter;
+ } else {
+ current = next;
+ }
+ }
+ return current;
+}
+
/**
* @constructor
*/
-function Flag(name, defaultValue, description, validator) {
+function Flag(name, defaultValue, description, validator, isSecret) {
this.name = name;
this.defaultValue = defaultValue;
this.description = description;
this.validator = validator;
this.currentValue = null;
this.isSet = false;
+ this.isSecret = !!isSecret;
}
@@ -274,11 +360,16 @@ Flag.prototype.parseInput = function(inp) {
};
+Flag.prototype.toHelpString = function() {
+ return wrapText(' --' + this.name + ': ' + this.description, 70) + '\n' +
+ ' (default: ' + JSON.stringify(this.defaultValue) + ')';
+};
+
/**
* @constructor
*/
-function BooleanFlag(flag, defaultValue, converter, description, validator) {
+function BooleanFlag() {
BooleanFlag.super_.apply(this, arguments);
}
util.inherits(BooleanFlag, Flag);
@@ -299,11 +390,17 @@ BooleanFlag.prototype.parseInput = function(inp) {
};
+BooleanFlag.prototype.toHelpString = function() {
+ return wrapText(' --[no]' + this.name + ': ' + this.description, 70) + '\n' +
+ ' (default: ' + JSON.stringify(this.defaultValue) + ')';
+};
+
+
/**
* @constructor
*/
-function IntegerFlag(flag, defaultValue, converter, description, validator) {
+function IntegerFlag() {
IntegerFlag.super_.apply(this, arguments);
}
util.inherits(IntegerFlag, Flag);
@@ -317,11 +414,16 @@ IntegerFlag.prototype.parseInput = function(inp) {
};
+IntegerFlag.prototype.toHelpString = function() {
+ return IntegerFlag.super_.prototype.toHelpString.call(this) +
+ '\n (an integer)';
+};
+
/**
* @constructor
*/
-function NumberFlag(flag, defaultValue, converter, description, validator) {
+function NumberFlag() {
NumberFlag.super_.apply(this, arguments);
}
util.inherits(NumberFlag, Flag);
@@ -335,11 +437,17 @@ NumberFlag.prototype.parseInput = function(inp) {
};
+NumberFlag.prototype.toHelpString = function() {
+ return NumberFlag.super_.prototype.toHelpString.call(this) +
+ '\n (a number)';
+};
+
+
/**
* @constructor
*/
-function StringListFlag(flag, defaultValue, converter, description, validator) {
+function StringListFlag() {
StringListFlag.super_.apply(this, arguments);
}
util.inherits(StringListFlag, Flag);
@@ -354,7 +462,7 @@ StringListFlag.prototype.parseInput = function(inp) {
/**
* @constructor
*/
-function MultiStringFlag(flag, defaultValue, converter, description, validator) {
+function MultiStringFlag() {
MultiStringFlag.super_.apply(this, arguments);
this.currentValue = [];
}
@@ -366,3 +474,15 @@ MultiStringFlag.prototype.set = function(input) {
this.currentValue.push(input);
this.isSet = true;
};
+
+
+
+// Internal flags
+//=================
+
+function registerInternalFlags() {
+ exports.defineBoolean('help', false, 'Shows this help text.', null, true);
+ // TODO: --flagsfile
+}
+
+registerInternalFlags();

0 comments on commit e43ecce

Please sign in to comment.