text: var path = require('path');
  var wordwrap = require('wordwrap');
  var tty = require('tty');
  var events = require('events');
  
  /*  Hack an instance of Argv with process.argv into Argv
      so people can do
          require('optimist')(['--beeble=1','-z','zizzle']).argv
      to parse a list of args and
          require('optimist').argv
      to get a parsed version of process.argv.
  */
  
  var inst = Argv(process.argv.slice(2));
  Object.keys(inst).forEach(function (key) {
      Argv[key] = typeof inst[key] == 'function'
          ? inst[key].bind(inst)
          : inst[key];
  });
  
  var exports = module.exports = Argv;
  function Argv (args, cwd) {
      var self = {};
      if (!cwd) cwd = process.cwd();
      
      self.$0 = process.argv
          .slice(0,2)
          .map(function (x) {
              var b = rebase(cwd, x);
              return x.match(/^\//) && b.length < x.length
                  ? b : x
          })
          .join(' ')
      ;
      
      if (process.argv[1] == process.env._) {
          self.$0 = process.env._.replace(
              path.dirname(process.execPath) + '/', ''
          );
      }
      
      var flags = { bools : {}, strings : {} };
      
      self.boolean = function (bools) {
          if (!Array.isArray(bools)) {
              bools = [].slice.call(arguments);
          }
          
          bools.forEach(function (name) {
              flags.bools[name] = true;
          });
          
          return self;
      };
      
      self.string = function (strings) {
          if (!Array.isArray(strings)) {
              strings = [].slice.call(arguments);
          }
          
          strings.forEach(function (name) {
              flags.strings[name] = true;
          });
          
          return self;
      };
      
      var aliases = {};
      self.alias = function (x, y) {
          if (typeof x === 'object') {
              Object.keys(x).forEach(function (key) {
                  self.alias(key, x[key]);
              });
          }
          else if (Array.isArray(y)) {
              y.forEach(function (yy) {
                  self.alias(x, yy);
              });
          }
          else {
              var zs = (aliases[x] || []).concat(aliases[y] || []).concat(x, y);
              aliases[x] = zs.filter(function (z) { return z != x });
              aliases[y] = zs.filter(function (z) { return z != y });
          }
          
          return self;
      };
      
      var demanded = {};
      self.demand = function (keys) {
          if (typeof keys == 'number') {
              if (!demanded._) demanded._ = 0;
              demanded._ += keys;
          }
          else if (Array.isArray(keys)) {
              keys.forEach(function (key) {
                  self.demand(key);
              });
          }
          else {
              demanded[keys] = true;
          }
          
          return self;
      };
      
      var usage;
      self.usage = function (msg, opts) {
          if (!opts && typeof msg === 'object') {
              opts = msg;
              msg = null;
          }
          
          usage = msg;
          
          if (opts) self.options(opts);
          
          return self;
      };
      
      function fail (msg) {
          self.showHelp();
          if (msg) console.error(msg);
          process.exit(1);
      }
      
      var checks = [];
      self.check = function (f) {
          checks.push(f);
          return self;
      };
      
      var defaults = {};
      self.default = function (key, value) {
          if (typeof key === 'object') {
              Object.keys(key).forEach(function (k) {
                  self.default(k, key[k]);
              });
          }
          else {
              defaults[key] = value;
          }
          
          return self;
      };
      
      var descriptions = {};
      self.describe = function (key, desc) {
          if (typeof key === 'object') {
              Object.keys(key).forEach(function (k) {
                  self.describe(k, key[k]);
              });
          }
          else {
              descriptions[key] = desc;
          }
          return self;
      };
  
      // Usage
      // .protect("p")
      // .protect("p", "Enter Password:")
      var protecting = false;
      self.protect = function (key, prompt) {
          protecting = {
              name: key,
              prompt: prompt || false
          };
          return self;
      };
      
      self.parse = function (args) {
          return Argv(args).argv;
      };
      
      self.option = self.options = function (key, opt) {
          if (typeof key === 'object') {
              Object.keys(key).forEach(function (k) {
                  self.options(k, key[k]);
              });
          }
          else {
              if (opt.alias) self.alias(key, opt.alias);
              if (opt.demand) self.demand(key);
              if (typeof opt.default !== 'undefined') {
                  self.default(key, opt.default);
              }
              
              if (opt.boolean || opt.type === 'boolean') {
                  self.boolean(key);
              }
              if (opt.string || opt.type === 'string') {
                  self.string(key);
              }
              
              var desc = opt.describe || opt.description || opt.desc;
              if (desc) {
                  self.describe(key, desc);
              }
          }
          
          return self;
      };
      
      var wrap = null;
      self.wrap = function (cols) {
          wrap = cols;
          return self;
      };
      
      self.showHelp = function (fn) {
          if (!fn) fn = console.error;
          fn(self.help());
      };
      
      self.help = function () {
          var keys = Object.keys(
              Object.keys(descriptions)
              .concat(Object.keys(demanded))
              .concat(Object.keys(defaults))
              .reduce(function (acc, key) {
                  if (key !== '_') acc[key] = true;
                  return acc;
              }, {})
          );
          
          var help = keys.length ? [ 'Options:' ] : [];
          
          if (usage) {
              help.unshift(usage.replace(/\$0/g, self.$0), '');
          }
          
          var switches = keys.reduce(function (acc, key) {
              acc[key] = [ key ].concat(aliases[key] || [])
                  .map(function (sw) {
                      return (sw.length > 1 ? '--' : '-') + sw
                  })
                  .join(', ')
              ;
              return acc;
          }, {});
          
          var switchlen = longest(Object.keys(switches).map(function (s) {
              return switches[s] || '';
          }));
          
          var desclen = longest(Object.keys(descriptions).map(function (d) { 
              return descriptions[d] || '';
          }));
          
          keys.forEach(function (key) {
              var kswitch = switches[key];
              var desc = descriptions[key] || '';
              
              if (wrap) {
                  desc = wordwrap(switchlen + 4, wrap)(desc)
                      .slice(switchlen + 4)
                  ;
              }
              
              var spadding = new Array(
                  Math.max(switchlen - kswitch.length + 3, 0)
              ).join(' ');
              
              var dpadding = new Array(
                  Math.max(desclen - desc.length + 1, 0)
              ).join(' ');
              
              var type = null;
              
              if (flags.bools[key]) type = '[boolean]';
              if (flags.strings[key]) type = '[string]';
              
              if (!wrap && dpadding.length > 0) {
                  desc += dpadding;
              }
              
              var prelude = '  ' + kswitch + spadding;
              var extra = [
                  type,
                  demanded[key]
                      ? '[required]'
                      : null
                  ,
                  defaults[key] !== undefined
                      ? '[default: ' + JSON.stringify(defaults[key]) + ']'
                      : null
                  ,
              ].filter(Boolean).join('  ');
              
              var body = [ desc, extra ].filter(Boolean).join('  ');
              
              if (wrap) {
                  var dlines = desc.split('\n');
                  var dlen = dlines.slice(-1)[0].length
                      + (dlines.length === 1 ? prelude.length : 0)
                  
                  body = desc + (dlen + extra.length > wrap - 2
                      ? '\n'
                          + new Array(wrap - extra.length + 1).join(' ')
                          + extra
                      : new Array(wrap - extra.length - dlen + 1).join(' ')
                          + extra
                  );
              }
              
              help.push(prelude + body);
          });
          
          help.push('');
          return help.join('\n');
      };
      
      Object.defineProperty(self, 'argv', {
          get : parseArgs,
          enumerable : true,
      });
      
      function parseArgs () {
          var argv = { _ : [], $0 : self.$0 };
          Object.keys(flags.bools).forEach(function (key) {
              setArg(key, defaults[key] || false);
          });
          
          function setArg (key, val) {
              var num = Number(val);
              var value = typeof val !== 'string' || isNaN(num) ? val : num;
              if (flags.strings[key]) value = val;
              
              setKey(argv, key.split('.'), value);
              
              (aliases[key] || []).forEach(function (x) {
                  argv[x] = argv[key];
              });
          }
          
          for (var i = 0; i < args.length; i++) {
              var arg = args[i];
              
              if (arg === '--') {
                  argv._.push.apply(argv._, args.slice(i + 1));
                  break;
              }
              else if (arg.match(/^--.+=/)) {
                  var m = arg.match(/^--([^=]+)=(.*)/);
                  setArg(m[1], m[2]);
              }
              else if (arg.match(/^--no-.+/)) {
                  var key = arg.match(/^--no-(.+)/)[1];
                  setArg(key, false);
              }
              else if (arg.match(/^--.+/)) {
                  var key = arg.match(/^--(.+)/)[1];
                  var next = args[i + 1];
                  if (next !== undefined && !next.match(/^-/)
                  && !flags.bools[key]
                  && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
                      setArg(key, next);
                      i++;
                  }
                  else if (/true|false/.test(next)) {
                      setArg(key, next === 'true');
                      i++;
                  }
                  else {
                      setArg(key, true);
                  }
              }
              else if (arg.match(/^-[^-]+/)) {
                  var letters = arg.slice(1,-1).split('');
                  
                  var broken = false;
                  for (var j = 0; j < letters.length; j++) {
                      if (letters[j+1] && letters[j+1].match(/\W/)) {
                          setArg(letters[j], arg.slice(j+2));
                          broken = true;
                          break;
                      }
                      else {
                          setArg(letters[j], true);
                      }
                  }
                  
                  if (!broken) {
                      var key = arg.slice(-1)[0];
                      
                      if (args[i+1] && !args[i+1].match(/^-/)
                      && !flags.bools[key]
                      && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
                          setArg(key, args[i+1]);
                          i++;
                      }
                      else if (args[i+1] && /true|false/.test(args[i+1])) {
                          setArg(key, args[i+1] === 'true');
                          i++;
                      }
                      else {
                          setArg(key, true);
                      }
                  }
              }
              else {
                  var n = Number(arg);
                  argv._.push(flags.strings['_'] || isNaN(n) ? arg : n);
              }
          }
          
          Object.keys(defaults).forEach(function (key) {
              if (!(key in argv)) {
                  argv[key] = defaults[key];
                  if (key in aliases) {
                      argv[aliases[key]] = defaults[key];
                  }
              }
          });
          
          if (demanded._ && argv._.length < demanded._) {
              fail('Not enough non-option arguments: got '
                  + argv._.length + ', need at least ' + demanded._
              );
          }
          
          var missing = [];
          Object.keys(demanded).forEach(function (key) {
              if (!argv[key]) missing.push(key);
          });
          
          if (missing.length) {
              fail('Missing required arguments: ' + missing.join(', '));
          }
          
          checks.forEach(function (f) {
              try {
                  if (f(argv) === false) {
                      fail('Argument check failed: ' + f.toString());
                  }
              }
              catch (err) {
                  fail(err)
              }
          });
  
          if (protecting) {
  
              argv._io = new events.EventEmitter();
              if (argv[protecting.name]) {
                  var buffer = '';
  
                  console.log(protecting.prompt || '');
                  process.stdin.resume();
                  tty.setRawMode(true);
  
                  process.stdin.on('keypress', function(chr, key){
                      if (key && 'enter' === key.name) {
                          process.stdin.removeAllListeners('keypress');
                          tty.setRawMode(false);
                          process.stdin.pause();
                          argv[protecting.name] = buffer;
                          argv._io.emit('input', argv, protecting.name);
                          return;
                      }
                      buffer += chr;
                  });
              }
          }
          
          return argv;
      }
      
      function longest (xs) {
          return Math.max.apply(
              null,
              xs.map(function (x) { return x.length })
          );
      }
      
      return self;
  };
  
  // rebase an absolute path to a relative one with respect to a base directory
  // exported for tests
  exports.rebase = rebase;
  function rebase (base, dir) {
      var ds = path.normalize(dir).split('/').slice(1);
      var bs = path.normalize(base).split('/').slice(1);
      
      for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
      ds.splice(0, i); bs.splice(0, i);
      
      var p = path.normalize(
          bs.map(function () { return '..' }).concat(ds).join('/')
      ).replace(/\/$/,'').replace(/^$/, '.');
      return p.match(/^[.\/]/) ? p : './' + p;
  };
  
  function setKey (obj, keys, value) {
      var o = obj;
      keys.slice(0,-1).forEach(function (key) {
          if (o[key] === undefined) o[key] = {};
          o = o[key];
      });
      
      var key = keys[keys.length - 1];
      if (o[key] === undefined || typeof o[key] === 'boolean') {
          o[key] = value;
      }
      else if (Array.isArray(o[key])) {
          o[key].push(value);
      }
      else {
          o[key] = [ o[key], value ];
      }
  }
  