Joey Mazzarelli avatar Joey Mazzarelli committed dad2e7c

support for arguments and namespaces

Comments (0)

Files changed (2)

+/**
+ * Advanced example using namespaces for a library and named arguments
+ *
+ * Run:
+ *   node example4.js --help
+ * and play with the options to see the behavior.
+ */
+
+var opts = require('./opts')
+  , puts = require('sys').puts
+  , host = 'localhost'; // default host value
+
+// Example of using some library in the same app
+var libOpts = [
+  { short       : 'l'
+  , long        : 'list'
+  , description : 'Show the library list'
+  , callback    : function () { puts('mylib list!'); },
+  },
+];
+opts.add(libOpts, 'mylib');
+
+var options = [
+  { short       : 'l' // deliberately conflicting with 'mylib' option
+  , long        : 'list'
+  , description : 'List all files'
+  },
+  { short       : 'd'
+  , long        : 'debug'
+  , description : 'Set a debug level'
+  , value       : true
+  },
+];
+
+var arguments = [ { name : 'script' , required : true }
+                , { name : 'timeout' }
+                ];
+
+opts.parse(options, arguments, true);
+
+var debug = opts.get('d') || 'info'  // default debug value
+  , list  = opts.get('list');
+
+var script  = opts.arg('script')
+  , timeout = opts.arg('timeout') || 30;
+
+
+if (list) puts('List arg was set');
+puts('Debug level is: ' + debug);
+puts('Script is: ' + script);
+puts('Timeout is: ' + timeout);
+
+process.exit(0);
+
 
 var puts        = require('sys').puts
   , values      = {}
-  , args        = []
+  , args        = {}
+  , argv        = []
   , errors      = []
-  , descriptors = [];
+  , descriptors = {opts:[], args:[]};
 
+/**
+ * Add a set of option descriptors, not yet ready to be parsed.
+ * See exports.parse for description of options object
+ *
+ * Additionally, it takes a namespace as an argument, useful for
+ * building options for a library in addition to the main app.
+ */
+exports.add = function (options, namespace) {
+  for (var i=0; i<options.length; i++) {
+    options[i].namespace = namespace;
+    descriptors.opts.push(options[i]);
+  }
+};
 
 /**
  * Parse the command line options
- * @param array options Options to parse
+ * @param array options  Options to parse
+ * @param array args     Arguments to parse
+ * @param bool  help     Automatically generate help message, default false
  *
+ * ===== Options Docs =====
  * Each option in the array can have the following fields. None are required, 
  * but you should at least provide a short or long name.
  *   {
  *     short       : 'l',
  *     long        : 'list',
  *     description : 'Show a list',
- *     value       : false,
- *     required    : true,
- *     callback    : function (value) { ... }
+ *     value       : false,  // default false
+ *     required    : true,   // default false
+ *     callback    : function (value) { ... },
  *   }
  *
  * You can add an automatically generated help message by passing
  *     description : 'Show this help message',
  *     callback    : require('./opts').help,
  *   }
+ *
+ * ===== Arguments Docs =====
+ * Arguments are different than options, and simpler. They typically come 
+ * after the options, but the library really doesn't care. Each argument
+ * can have the form of:
+ *   {
+ *     name     : 'script',
+ *     required : true,      // default false
+ *     callback : function (value) { ... },
+ *   }
  */
-exports.parse = function (options, help) {
+exports.parse = function (options, params, help) {
+
+  if (params === true) {
+    help == true;
+  } else if (!params) {
+    params = [];
+  } else {
+    for (var i=0; i<params.length; i++) {
+      descriptors.args.push(params[i]);
+    }
+  }
 
   if (help) {
     options.push({ long        : 'help'
                  , callback    : exports.help
                  });
   }
-  descriptors = options;
+  for (var i=0; i<options.length; i++) {
+    descriptors.opts.unshift(options[i]);
+  }
+  options = descriptors.opts;
+
+  var checkDup = function (opt, type) {
+    var prefix = (type == 'short')? '-': '--';
+    var name = opt[type];
+    if (!opts[prefix + name]) {
+      opts[prefix + name] = opt;
+    } else {
+      if (opt.namespace && !opts[prefix + opt.namespace + '.' + name]) {
+        opts[prefix + opt.namespace + '.' + name] = opt;
+        for (var i=0; i<descriptors.opts.length; i++) {
+          var desc = descriptors.opts[i];
+          if (desc.namespace == opt.namespace) {
+            if (type == 'long' && desc.long == opt.long) {
+                descriptors.opts[i].long = opt.namespace + '.' + opt.long;
+            } else if (type == 'short') {
+              delete descriptors.opts[i].short;
+            }
+          }
+        }
+      } else {
+        puts('Conflicting flags: ' + prefix + name + '\n');
+        puts(helpString());
+        process.exit(1);
+      }
+    }
+  };
 
   var opts = {};
   for (var i=0; i<options.length; i++) {
-    if (options[i].short) {
-      if (!opts['-' + options[i].short]) {
-        opts['-' + options[i].short] = options[i];
-      } else {
-        puts('Conflicting flags: -' + options[i].short);
-        process.exit(1);
-      }
-    }
-    if (options[i].long) {
-      if (!opts['--' + options[i].long]) {
-        opts['--' + options[i].long] = options[i];
-      } else {
-        puts('Conflicting flags: --' + options[i].long);
-        process.exit(1);
-      }
-    }
+    if (options[i].short) checkDup(options[i], 'short');
+    if (options[i].long) checkDup(options[i], 'long');
   }
 
   for (var i=2; i<process.argv.length; i++) {
         var next = process.argv[i+1];
         if (!next || opts[next]) {
           var flag = opt.short || opt.long;
-          errors.push('Missing value for argument: ' + flag);
+          errors.push('Missing value for option: ' + flag);
           if (opt.short) values[opt.short] = true;
           if (opt.long) values[opt.long] = true;
         } else {
       }
     } else {
       // No match. If it starts with a dash, show an error. Otherwise
-      // add it to the extra args.
+      // add it to the extra params.
       if (inp[0] == '-') {
         puts('Unknown option: ' + inp);
         if (opts['--help']) puts('Try --help');
         process.exit(1);
       } else {
-        args.push(inp);
+        argv.push(inp);
+        var arg = params.shift();
+        if (arg) {
+          args[arg.name] = inp;
+          if (arg.callback) arg.callback(inp);
+        }
       }
     }
   }
   for (var i=0; i<options.length; i++) {
     var flag = options[i].short || options[i].long;
     if (options[i].required && !exports.get(flag)) {
-      errors.push('Missing required argument: ' + flag);
+      errors.push('Missing required option: ' + flag);
+    }
+  }
+  for (var i=0; i<params.length; i++) {
+    if (params[i].required && !args[params[i].name]) {
+      errors.push('Missing required argument: ' + params[i].name);
     }
   }
   if (errors.length) {
     for (var i=0; i<errors.length; i++) puts(errors[i]);
+    puts('\n' + helpString());
     process.exit(1);
   }
 };
  * Get unknown args. Could have special meaning to client
  */
 exports.args = function () {
-  return args;
+  return argv;
 };
 
+/**
+ * Get an arg by name.
+ * This only works if arg names were passed into the parse function.
+ * @param string name Name of arg
+ * @return string Value of arg
+ */
+exports.arg = function (name) {
+  //puts(require('sys').inspect(arguments));
+  return args[name];
+};
+
+/**
+ * Print the help message and exit
+ */
 exports.help = function () {
-  for (var i=0; i<descriptors.length; i++) {
-    var opt = descriptors[i];
-    var line;
-    if (opt.description) puts(opt.description);
-    if (opt.short && !opt.long) line = '-' + opt.short;
-    else if (opt.long && !opt.short) line = '--' + opt.long;
-    else line = '-' + opt.short + ', --' + opt.long;
-    if (opt.value) line += ' <value>';
-    if (opt.required) line += ' (required)';
-    puts('    ' + line);
-  }
+  puts(helpString());
   process.exit(0);
 };
 
+
+// Create the help string
+var helpString = function () {
+  var str = 'Usage: ' + process.argv[0] + ' ' + process.argv[1];
+  if (descriptors.opts.length) str += ' [options]';
+  if (descriptors.args.length) {
+    for (var i=0; i<descriptors.args.length; i++) {
+      if (descriptors.args[i].required) {
+        str += ' ' + descriptors.args[i].name;
+      } else {
+        str += ' [' + descriptors.args[i].name + ']';
+      }
+    }
+  }
+  str += '\n';
+  for (var i=0; i<descriptors.opts.length; i++) {
+    var opt = descriptors.opts[i];
+    if (opt.description) str += (opt.description) + '\n';
+    var line = '';
+    if (opt.short && !opt.long) line += '-' + opt.short;
+    else if (opt.long && !opt.short) line += '--' + opt.long;
+    else line += '-' + opt.short + ', --' + opt.long;
+    if (opt.value) line += ' <value>';
+    if (opt.required) line += ' (required)';
+    str += '    ' + line + '\n';
+  }
+  return str;
+};
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.