Mathias Panzenböck avatar Mathias Panzenböck committed d5a0558

moved some modules into subdirectories

Comments (0)

Files changed (8)

demo/optparse.js

-const OptionParser = require('./../optparse').OptionParser;
-
-var parser = new OptionParser({
-	minargs: 1,
-	maxargs: 10,
-	strings: { help: 'N/A', metavars: { integer: 'INT' } },
-	options: [
-		{
-			names: ['--help', '-h'],
-			type: 'flag',
-			help: 'Show this help message.',
-			onOption: function (value) {
-				if (value) {
-					parser.usage('This is a help text. (Could also be credits etc.)');
-				}
-				// returning true canceles any further option parsing
-				// and parser.parse() returns null
-				return value;
-			}
-		}
-	]
-});
-
-parser.add(['--foo', '-f'], {
-	type: 'flag',
-	help: 'This is a flag.',
-	details: ['bla','blub']
-});
-
-parser.add('--bar', {
-	type: 'flag',
-	value: false,
-	target: 'foo',
-	help: 'This is the inverse of --foo.'
-});
-
-parser.add('-s');
-parser.add('--s2', {default: 'blub'});
-parser.add('-i', {type: 'int', required: true, redefinable: false});
-parser.add('--int32', {type: 'integer',
-	min: -2147483648, max: 2147483647, metavar: 'INT32'});
-parser.add('--hex', {type: 'integer', base: 16});
-parser.add(['-n','-d','--float'], {type: 'float', NaN: true});
-parser.add('-o', {type: 'object', default: {foo: 'bar', egg: null, spam: true}});
-parser.add('-b', {type: 'boolean', default: true});
-parser.add('-x', {type: 'flag', redefinable: false});
-parser.add('--r1', {type: 'integer', default: 1, min: 0, base: 8});
-parser.add('--r2', {type: 'integer', default: 3, max: 20, base: 16});
-parser.add('--r3', {type: 'integer', default: 4, min: -10, max: 10, base: 2});
-parser.add('--egg', {type: 'option', default: 'none'});
-parser.add('--spam', {type: 'option', target: 'egg'});
-parser.add('--tomato', {type: 'option', target: 'egg', value: 'bla'});
-parser.add('--sel1', {type: 'enum', values: {foo: 42, bar: 23}});
-parser.add('--sel2', {type: 'enum', values: ['egg', 'spam spam'], default: ' '});
-parser.add('--foo-bar', {default: 'bla "bla" \t\n \'bla\' \\'});
-parser.add('--EGG--SPAM');
-parser.add('--b64', {type: 'custom', argc: 1, metavar: 'BASE64-STRING',
-	parse: function (s) { return new Buffer(s, 'base64'); },
-	stringify: function (buffer) { return buffer.toString('base64'); },
-	help: 'Pass binary data as a base64 encoded string.'});
-parser.add('-t', {type: 'record',
-	default: [11., 5, 'foo'],
-	args: [
-		{type: 'float'},
-		{type: 'integer', min: 1, max: 22},
-		{type: 'string'}
-	]});
-parser.add(['--record','-T'], {type: 'record',
-	default: ['bla', [10, 'bleh']],
-	args: [
-		{type: 'string'},
-		{type: 'record', args: [
-			{type: 'integer'},
-			{type: 'string'},
-		]},
-	],
-	onOption: function (str, rec) {
-		console.log('args: '+JSON.stringify(str)+', '+JSON.stringify(rec));
-	}});
-parser.add('--named', {type: 'record',
-	create: Object,
-	default: {a: 0.5, b: 300, c: 'bla'},
-	args: [
-		{type: 'float',   target: 'a'},
-		{type: 'integer', target: 'b'},
-		{type: 'string',  target: 'c'}
-	]});
-
-var data = parser.parse();
-
-if (data) {
-	console.log('Options:');
-	var displayed = {};
-	for (var name in parser.options) {
-		var opt = parser.options[name];
-		if (!(opt.target in displayed)) {
-			var value = data.options[opt.target];
-			console.log('\t'+opt.target+': '+ (value === undefined ?
-				value : opt.stringify(value)));
-			displayed[opt.target] = true;
-		}
-	}
-	console.log(''); 
-	console.log('Arguments:');
-	for (var i in data.arguments) {
-		console.log('\t'+data.arguments[i]);
-	}
-}

demo/xml-markup.js

-const xmlmarkup = require('./../xml-markup');
-
-var xm  = new xmlmarkup.XmlMarkup(process.stdout);
-var ns1  = 'http://example.com/';
-var ns2 = 'http://example.com/1';
-var ns3 = 'http://example.com/2';
-
-xm.space('\n');
-xm.doctype('doc', 'baz', '-//foo bar//-');
-xm.space('\n');
-xm.comment('foo bar');
-xm.space('\n');
-xm.$('doc', {'lang': {uri: 'xml', value: 'en_GB'}}, function () {
-	xm.space('\n\t');
-	xm.$('e0', {
-		att1: 'value',
-		att2: '"<val> "\' &',
-		att3: {uri: ns1, value: 'foo'},
-		att4: ''});
-	xm.space('\n\t');
-	xm.$('e1');
-	xm.space('\n\t');
-	xm.$('e2', {xmlns: ns1}, function () {
-		xm.space('\n\t\t');
-		xm.$('e3', {xmlns: ns1});
-		xm.space('\n\t');
-	});
-	xm.space('\n\t');
-	xm.ns({ns1: ns1, ns2: ns2, '': ns3}, function () {
-		xm.$('e4');
-		xm.space('\n\t');
-		xm.$('e5', {xmlns: ns1});
-		xm.space('\n\t');
-		xm.$('e6', {xmlns: ns2}, function () {
-			xm.$('e7', {attr1: {uri: ns2, value: 'bla'}});
-		});
-		xm.space('\n\t');
-		xm.$('e8', function () {
-			xm.space('\n\t\t');
-			xm.$('e9', {xmlns: ns3});
-			xm.space('\n\t');
-		});
-		xm.space('\n\t');
-	});
-	xm.$('e10', 'foo bar');
-	xm.space('\n\t');
-	xm.text('<foo> "bar" baz\'s; egg bacon & spam\nbla\nbla');
-	xm.space('\n');
-});
-xm.space('\n\n');
-
-xm = new xmlmarkup.PrettyXmlMarkup(process.stdout);
-xm.doctype('doc', 'baz', '-//foo bar//-');
-xm.comment('foo bar');
-xm.$('doc', {'lang': {uri: 'xml', value: 'en_GB'}}, function () {
-	xm.$('e0', {
-		att1: 'value',
-		att2: '"<val> "\' &',
-		att3: {uri: ns1, value: 'foo'},
-		att4: ''});
-	xm.$('e1');
-	xm.$('e2', {xmlns: ns1}, function () {
-		xm.$('e3', {xmlns: ns1});
-		xm.$('e3', {xmlns: ns2});
-	});
-	xm.ns({ns1: ns1, ns2: ns2, '': ns3}, function () {
-		xm.$('e4');
-		xm.$('e5', {xmlns: ns1});
-		xm.$('e6', {xmlns: ns2}, function () {
-			xm.$('e7', {attr1: {uri: ns2, value: 'bla'}});
-		});
-		xm.$('e8', function () {
-			xm.$('e9', {xmlns: ns3});
-		});
-	});
-	xm.$('e10', 'foo bar');
-	xm.text('<foo> "bar" baz\'s; egg bacon & spam\nbla\nbla');
-});

optparse.js

-/**
- * JavaScript Option Parser (optparse)
- * Copyright (C) 2010  Mathias Panzenböck <grosser.meister.morti@gmx.net>
- * 
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- * 
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA 
- */
-
-/**
- * Construct a new OptionParser.
- * See the end of this file for example usage.
- *
- * @param object params   optional Parameter-Object
- *
- * ===== Parameter-Object =====
- *   {
- *      minargs: integer, optional
- *      maxargs: integer, optional
- *      program: string, per default inferred from process.argv
- *      strings: object, optional
- *               Table of strings used in the output. See below.
- *      options: array, optional
- *               Array of option definitions. See below.
- *   }
- *
- * ===== String-Table =====
- *   {
- *      help:      string, default: 'No help available for this option.'
- *      usage:     string, default: 'Usage'
- *      options:   string, default: 'OPTIONS'
- *      arguments: string, default: 'ARGUMENTS'
- *      required:  string, default: 'required'
- *      default:   string, default: 'default'
- *      base:      string, default: 'base'
- *      metavars:  object, optional
- *                 Table of default metavar names per type.
- *                 Per default the type name in capital letters or derived
- *                 from the possible values.
- *   }
- *
- * ===== Option Definition =====
- *   {
- *      // Only when passed to the OptionParser constructor:
- *      name:        string or array
- *      names:       string or array, alias of name
- *                   Only one of both may be used at the same time.
- *
- *                   Names can be long options (e.g. '--foo') and short options
- *                   (e.g. '-f'). The first name is used to indentify the option.
- *                   Names musst be unique and may not contain '='.
- *
- *                   Short options may be combined when passed to a programm. E.g.
- *                   the options '-f' and '-b' can be combined to '-fb'. Only one
- *                   of these combined options may require an argument.
- *
- *                   Short options are separated from ther arguments by space,
- *                   long options per '='. If a long option requires an argument
- *                   and none is passed using '=' it also uses the next commandline
- *                   argument as it's argument (like short options).
- *
- *                   If '--' is encountered all remaining arguments are treated as
- *                   arguments and not as options.
- *
- *      // General fields:
- *      target:      string, per deflault inferred from first name
- *                   This defines the name used in the returned options object.
- *                   Multiple options may have the same target.
- *      default:     any, default: undefined
- *                   The default value associated with a certain target and is
- *                   overwritten by each new option with the same target.
- *      type:        string, default: 'string', see below
- *      required:    boolean, default: false
- *      redefinable: boolean, default: true
- *      help:        string, optional
- *      details:     array, optional
- *                   short list of details shown in braces after the option name
- *                   e.g. integer type options add 'base: '+base if base !== undefined
- *      metavar:     string or array, per deflault inferred from type
- *      onOption:    function (value) -> boolean, optional
- *                   Returning true canceles any further option parsing
- *                   and the parse() method returns null.
- *
- *      // Type: string  (alias: str)
- *      // Type: boolean (alias: bool)
- *      // Type: object  (alias: obj)
- *
- *      // Type: integer (alias: int)
- *      min:         integer, optional
- *      max:         integer, optional
- *      NaN:         boolean, default: false
- *      base:        integer, optional
- *
- *      // Type: float   (alias: number)
- *      min:         float, optional
- *      max:         float, optional
- *      NaN:         boolean, default: false
- *
- *      // Type: flag
- *      value:       boolean, default: true
- *      default:     boolean, default: false
- *
- *      // Type: option
- *      value:       any, per default inferred from first name
- *
- *      // Type: enum
- *      ignoreCase:  boolean, default: true
- *      values:      array or object where the user enteres the field name of
- *                   the object and you get the value of the field
- *
- *      // Type: record
- *      create:      function () -> object, default: Array
- *      args:        array of type definitions (type part of option definitions)
- *
- *      // Type: custom
- *      argc:        integer, default: -1
- *                   Number of required arguments.
- *                   -1 means one optional argument.
- *      parse:       function (string, ...) -> value
- *      stringify:   function (value) -> string, optional
- *   }
- *
- * ===== Option-Arguments =====
- * For the following types exactly one argument is required:
- *   integer, float, string, boolean, object, enum
- *
- * The following types have optional arguments:
- *   flag
- *
- * The following types have no arguments:
- *   option
- *
- * Custom types may set this through the argc field.
- */
-function OptionParser (params) {
-	this.optionsPerName = {};
-	this.defaultValues  = {};
-	this.options        = [];
-
-	if (params !== undefined) {
-		this.minargs = params.minargs == 0 ? undefined : params.minargs;
-		this.maxargs = params.maxargs;
-		this.program = params.program;
-		this.strings = params.strings;
-
-		if (this.minargs > this.maxargs) {
-			throw new Error('minargs > maxargs');
-		}
-	}
-
-	if (this.strings === undefined) {
-		this.strings = {};
-	}
-
-	defaults(this.strings, {
-		help:      'No help available for this option.',
-		usage:     'Usage',
-		options:   'OPTIONS',
-		arguments: 'ARGUMENTS',
-		required:  'required',
-		default:   'default',
-		base:      'base',
-		metavars:  {}
-	});
-
-	defaults(this.strings.metavars, METAVARS);
-
-	if (this.program === undefined) {
-		this.program = process.argv[0] + ' ' + process.argv[1];
-	}
-
-	if (params !== undefined && params.options !== undefined) {
-		for (var i in params.options) {
-			var opt = params.options[i];
-			var names;
-
-			if (opt instanceof Array || typeof(opt) == 'string') {
-				opt = undefined;
-				names = opt;
-			}
-			else {
-				names = opt.names;
-				if (names === undefined) {
-					names = opt.name;
-					delete opt.name;
-				}
-				else {
-					delete opt.names;
-				}
-			}
-			this.add(names, opt);
-		}
-	}
-}
-
-OptionParser.prototype = {
-	/**
-	 * Parse command line options.
-	 * 
-	 * @param array args  Commandline arguments.
-	 *                    If undefined process.argv.slice(2) is used.
-	 *
-	 * @return object
-	 *   {
-	 *      arguments: array
-	 *      options:   object, { target -> value }
-	 *   }
-	 */
-	parse: function (args) {
-		if (args === undefined) {
-			args = process.argv.slice(2);
-		}
-
-		var data = {
-			options:   {},
-			arguments: []
-		};
-
-		for (var name in this.defaultValues) {
-			var value = this.defaultValues[name];
-
-			if (value !== undefined) {
-				data.options[this.optionsPerName[name].target] = value;
-			}
-		}
-
-		var got = {};
-		var i = 0;
-		for (; i < args.length; ++ i) {
-			var arg = args[i];
-
-			if (arg == '--') {
-				++ i;
-				break;
-			}
-			else if (/^--.+$/.test(arg)) {
-				var j = arg.indexOf('=');
-				var name, value = undefined;
-
-				if (j == -1) {
-					name  = arg;
-				}
-				else {
-					name  = arg.substring(0,j);
-					value = arg.substring(j+1);
-				}
-
-				var optdef = this.optionsPerName[name];
-				
-				if (optdef === undefined) {
-					throw new Error('unknown option: '+name);
-				}
-
-				if (value === undefined) {
-					if (optdef.argc < 1) {
-						value = optdef.value;
-					}
-					else if ((i + optdef.argc) >= args.length) {
-						throw new Error('option '+name+' needs '+optdef.argc+' arguments');
-					}
-					else {
-						value = optdef.parse.apply(optdef, args.slice(i+1, i+1+optdef.argc));
-						i += optdef.argc;
-					}
-				}
-				else if (optdef.argc == 0) {
-					throw new Error('option '+name+' does not need an argument');
-				}
-				else if (optdef.argc > 1) {
-					throw new Error('option '+name+' needs '+optdef.argc+' arguments');
-				}
-				else {
-					value = optdef.parse(value);
-				}
-
-				if (!optdef.redefinable && optdef.target in got) {
-					throw new Error('cannot redefine option '+name);
-				}
-
-				got[optdef.target] = true;
-				data.options[optdef.target] = value;
-
-				if (optdef.onOption && optdef.onOption(value) === true) {
-					return null;
-				}
-			}
-			else if (/^-.+$/.test(arg)) {
-				if (arg.indexOf('=') != -1) {
-					throw new Error('illegal option syntax: '+arg);
-				}
-
-				var tookarg = false;
-				arg = arg.substring(1);
-
-				for (var j = 0; j < arg.length; ++ j) {
-					var name = '-'+arg[j];
-					var optdef = this.optionsPerName[name];
-					var value;
-					
-					if (optdef === undefined) {
-						throw new Error('unknown option: '+name);
-					}
-
-					if (optdef.argc < 1) {
-						value = optdef.value;
-					}
-					else {
-						if (tookarg || (i+optdef.argc) >= args.length) {
-							throw new Error('option '+name+' needs '+optdef.argc+' arguments');
-						}
-
-						value = optdef.parse.apply(optdef, args.slice(i+1, i+1+optdef.argc));
-						i += optdef.argc;
-						tookarg = true;
-					}
-
-					if (!optdef.redefinable && optdef.target in got) {
-						throw new Error('redefined option: '+name);
-					}
-
-					got[optdef.target] = true;
-					data.options[optdef.target] = value;
-
-					if (optdef.onOption && optdef.onOption(value) === true) {
-						return null;
-					}
-				}
-			}
-			else {
-				data.arguments.push(arg);
-			}
-		}
-
-		for (; i < args.length; ++ i) {
-			data.arguments.push(args[i]);
-		}
-
-		var argc = data.arguments.length;
-		if ((this.maxargs !== undefined && argc > this.maxargs) ||
-				(this.minargs !== undefined && argc < this.minargs)) {
-			var msg = 'illegal number of arguments: ' + argc;
-
-			if (this.minargs !== undefined) {
-				msg += ', minumum is ' + this.minargs;
-				if (this.maxargs !== undefined) {
-					msg += ' and maximum is ' + this.maxargs;
-				}
-			}
-			else {
-				msg += ', maximum is ' + this.maxargs;
-			}
-
-			throw new Error(msg);
-		}
-
-		for (var i in this.options) {
-			var optdef = this.options[i];
-			if (optdef.required && !(optdef.target in got)) {
-				throw new Error('missing required option: ' + optdef.names[0]);
-			}
-		}
-		
-		return data;
-	},
-	/**
-	 * Add an option definition.
-	 *
-	 * @param string or array names  Option names
-	 * @param object optdef          Option definition
-	 */
-	add: function (names, optdef) {
-		if (typeof(names) == 'string') {
-			names = [names];
-		}
-		else if (names === undefined || names.length == 0) {
-			throw new Error('no option name given');
-		}
-
-		if (optdef === undefined) {
-			optdef = {};
-		}
-
-		optdef.names = names;
-		
-		for (var i in names) {
-			var name = names[i];
-			var match = /(-*)(.*)/.exec(name);
-
-			if (name.length == 0 || match[1].length < 1 ||
-					match[1].length > 2 || match[2].length == 0 ||
-					(match[1].length == 1 && match[2].length > 1) ||
-					match[2].indexOf('=') != -1) {
-				throw new Error('illegal option name: ' + name);
-			}
-
-			if (name in this.optionsPerName) {
-				throw new Error('option already exists: '+name);
-			}
-		}
-
-		if (optdef.target === undefined) {
-			var target = names[0].replace(/^--?/,'');
-			
-			if (target.toUpperCase() == target) {
-				// FOO-BAR -> FOO_BAR
-				target = target.replace(/[^a-zA-Z0-9]+/,'_');
-			}
-			else {
-				// foo-bar -> fooBar
-				target = target.split(/[^a-zA-Z0-9]+/);
-				for (var i = 1; i < target.length; ++ i) {
-					var part = target[i];
-	
-					if (part) {
-						target[i] = part[0].toUpperCase() + part.substring(1);
-					}
-				}
-				target = target.join('');
-			}
-
-			optdef.target = target;
-		}
-
-		this._initType(optdef, optdef.names[0]);
-
-		if (optdef.redefinable === undefined) {
-			optdef.redefinable = true;
-		}
-
-		if (optdef.required === undefined) {
-			optdef.required = false;
-		}
-
-		if (optdef.help === undefined) {
-			optdef.help = this.strings.help;
-		}
-		else {
-			optdef.help = optdef.help.trim();
-		}
-		
-		for (var i in names) {
-			this.optionsPerName[names[i]] = optdef;
-		}
-		
-		if (optdef.default !== undefined) {
-			this.defaultValues[names[0]] = optdef.default;
-		}
-
-		this.options.push(optdef);
-	},
-	/**
-	 * Show an error message, usage and exit program with exit code 1.
-	 * 
-	 * @param string msg       The error message
-	 * @param WriteStream out  Where to write the message.
-	 *                         If undefined process.stdout is used.
-	 */
-	error: function (msg, out) {
-		if (!out) {
-			out = process.stdout;
-		}
-		out.write('*** '+msg+'\n\n');
-		this.usage(undefined, out);
-		process.exit(1);
-	},
-	/**
-	 * Print usage message.
-	 *
-	 * @param string help      Optional additional help message.
-	 * @param WriteStream out  Where to write the message.
-	 *                         If undefined process.stdout is used.
-	 */
-	usage: function (help, out) {
-		if (!out) {
-			out = process.stdout;
-		}
-
-		out.write(this.strings.usage+': '+this.program+' ['+
-			this.strings.options+']'+(this.maxargs != 0 ?
-				' ['+this.strings.arguments+']\n' : '\n'));
-		out.write('\n');
-		out.write(this.strings.options+':\n');
-
-		for (var i in this.options) {
-			var optdef = this.options[i];
-			var optnames = [];
-			var metavar = optdef.metavar;
-
-			if (metavar instanceof Array) {
-				metavar = metavar.join(' ');
-			}
-
-			for (var j in optdef.names) {
-				var optname = optdef.names[j];
-
-				if (metavar !== undefined) {
-					if (optdef.argc < 2 && optname.substring(0,2) == '--') {
-						if (optdef.argc < 0) {
-							optname = optname+'[='+metavar+']';
-						}
-						else {
-							optname = optname+'='+metavar;
-						}
-					}
-					else {
-						optname = optname+' '+metavar;
-					}
-				}
-				optnames.push(optname);
-			}
-
-			var details = optdef.details !== undefined ? optdef.details.slice() : [];
-			if (optdef.required) {
-				details.push(this.strings.required);
-			}
-			else if (optdef.argc > 0 && optdef.default !== undefined) {
-				details.push(this.strings.default+': '+optdef.stringify(optdef.default));
-			}
-
-			if (details.length > 0) {
-				details = '  (' + details.join(', ') + ')';
-			}
-
-			if (metavar !== undefined) {
-				optnames[0] += details;
-				out.write('  '+optnames.join('\n  '));
-			}
-			else {
-				out.write('  '+optnames.join(', ')+details);
-			}
-			if (optdef.help) {
-				var lines = optdef.help.split('\n');
-				for (var j in lines) {
-					out.write('\n        '+lines[j]);
-				}
-			}
-			out.write('\n\n');
-		}
-
-		if (help !== undefined) {
-			out.write(help);
-			if (help[help.length-1] != '\n') {
-				out.write('\n');
-			}
-		}
-	},
-	_initType: function (optdef, name) {
-		optdef.name = name;
-	
-		if (optdef.type === undefined) {
-			optdef.type = 'string';
-		}
-		else if (optdef.type in TYPE_ALIAS) {
-			optdef.type = TYPE_ALIAS[optdef.type];
-		}
-		
-		switch (optdef.type) {
-			case 'flag':
-				if (optdef.value === undefined) {
-					optdef.value = true;
-				}
-				optdef.parse = parseBool;
-				optdef.argc  = -1;
-	
-				if (optdef.default === undefined) {
-					optdef.default = this.defaultValues[name];
-
-					if (optdef.default === undefined) {
-						optdef.default = false;
-					}
-				}
-				break;
-	
-			case 'option':
-				optdef.argc = 0;
-	
-				if (optdef.value === undefined) {
-					optdef.value = name.replace(/^--?/,'');
-				}
-				break;
-	
-			case 'enum':
-				this._initEnum(optdef, name);
-				break;
-	
-			case 'integer':
-			case 'float':
-				this._initNumber(optdef, name);
-				break;
-	
-			case 'record':
-				if (optdef.args === undefined || optdef.args.length == 0) {
-					throw new Error('record '+name+' needs at least one argument');
-				}
-				optdef.argc = 0;
-				var metavar = [];
-				for (var i in optdef.args) {
-					var arg = optdef.args[i];
-					if (arg.target === undefined) {
-						arg.target = i;
-					}
-					this._initType(arg, name+'['+i+']');
-	
-					if (arg.argc < 1) {
-						throw new Error('argument '+i+' of option '+name+
-							' has illegal number of arguments');
-					}
-					if (arg.metavar instanceof Array) {
-						for (var j in arg.metavar) {
-							metavar.push(arg.metavar[j]);
-						}
-					}
-					else {
-						metavar.push(arg.metavar);
-					}
-					delete arg.metavar;
-					optdef.argc += arg.argc;
-				}
-				if (optdef.metavar === undefined) {
-					optdef.metavar = metavar;
-				}
-				var onOption = optdef.onOption;
-				if (onOption !== undefined) {
-					optdef.onOption = function (values) {
-						return onOption.apply(this, values);
-					};
-				}
-				if (optdef.create === undefined) {
-					optdef.create = Array;
-				}
-				optdef.parse = function () {
-					var values = this.create();
-					var parserIndex = 0;
-					for (var i = 0; i < arguments.length;) {
-						var arg = optdef.args[parserIndex ++];
-						var raw = [];
-						for (var j = 0; j < arg.argc; ++ j) {
-							raw.push(arguments[i+j]);
-						}
-						values[arg.target] = arg.parse.apply(arg, raw);
-						i += arg.argc;
-					}
-					return values;
-				};
-				break;
-	
-			case 'custom':
-				if (optdef.argc === undefined || optdef.argc < -1) {
-					optdef.argc = -1;
-				}
-	
-				if (optdef.parse === undefined) {
-					throw new Error(
-						'no parse function defined for custom type option '+name);
-				}
-				break;
-	
-			default:
-				optdef.argc = 1;
-				optdef.parse = PARSERS[optdef.type];
-	
-				if (optdef.parse === undefined) {
-					throw new Error('type of option '+name+' is unknown: '+optdef.type);
-				}
-		}
-	
-		initStringify(optdef);
-		
-		var count = 1;
-		if (optdef.metavar === undefined) {
-			optdef.metavar = this.strings.metavars[optdef.type];
-		}
-		
-		if (optdef.metavar === undefined) {
-			count = 0;
-		}
-		else if (optdef.metavar instanceof Array) {
-			count = optdef.metavar.length;
-		}
-	
-		if (optdef.argc == -1) {
-			if (count > 1) {
-				throw new Error('illegal number of metavars for option '+name+
-					': '+JSON.stringify(optdef.metavar));
-			}
-		}
-		else if (optdef.argc != count) {
-			throw new Error('illegal number of metavars for option '+name+
-				': '+JSON.stringify(optdef.metavar));
-		}
-	},
-	_initEnum: function (optdef, name) {
-		optdef.argc = 1;
-	
-		if (optdef.ignoreCase === undefined) {
-			optdef.ignoreCase = true;
-		}
-	
-		if (optdef.values === undefined || optdef.values.length == 0) {
-			throw new Error('no values for enum '+name+' defined');
-		}
-	
-		initStringify(optdef);
-
-		var labels = [];
-		var values = {};
-		if (optdef.values instanceof Array) {
-			for (var i in optdef.values) {
-				var value = optdef.values[i];
-				var label = String(value);
-				values[optdef.ignoreCase ? label.toLowerCase() : label] = value;
-				labels.push(optdef.stringify(value));
-			}
-		}
-		else {
-			for (var label in optdef.values) {
-				var value = optdef.values[label];
-				values[optdef.ignoreCase ? label.toLowerCase() : label] = value;
-				labels.push(optdef.stringify(label));
-			}
-			labels.sort();
-		}
-		optdef.values = values;
-		
-		
-		if (optdef.metavar === undefined) {
-			optdef.metavar = '<'+labels.join(', ')+'>';
-		}
-	
-		optdef.parse = function (s) {
-			var value = values[optdef.ignoreCase ? s.toLowerCase() : s];
-			if (value !== undefined) {
-				return value;
-			}
-			throw new Error('illegal value for option '+name+': '+s);
-		};
-	},
-	_initNumber: function (optdef, name) {
-		optdef.argc = 1;
-	
-		if (optdef.NaN === undefined) {
-			optdef.NaN = false;
-		}
-
-		if (optdef.min > optdef.max) {
-			throw new Error('min > max for option '+name);
-		}
-		
-		var parse, toStr;
-		if (optdef.type == 'integer') {
-			parse = function (s) {
-				var i = NaN;
-				if (s.indexOf('.') == -1) {
-					i = parseInt(s, optdef.base)
-				}
-				return i;
-			};
-			if (optdef.base === undefined) {
-				toStr = dec;
-			}
-			else {
-				switch (optdef.base) {
-					case  8: toStr = oct; break;
-					case 10: toStr = dec; break;
-					case 16: toStr = hex; break;
-					default: toStr = function (val) {
-							return val.toString(optdef.base);
-						};
-						var detail = this.strings.base+': '+optdef.base;
-						if (optdef.details) {
-							optdef.details.push(detail);
-						}
-						else {
-							optdef.details = [detail];
-						}
-				}
-			}
-		}
-		else {
-			parse = parseFloat;
-			toStr = dec;
-		}
-	
-		if (optdef.metavar === undefined) {
-			if (optdef.min === undefined && optdef.max === undefined) {
-				optdef.metavar = this.strings.metavars[optdef.type];
-			}
-			else if (optdef.min === undefined) {
-				optdef.metavar = '...'+toStr(optdef.max);
-			}
-			else if (optdef.max === undefined) {
-				optdef.metavar = toStr(optdef.min)+'...';
-			}
-			else {
-				optdef.metavar = toStr(optdef.min)+'...'+toStr(optdef.max);
-			}
-		}
-		optdef.parse = function (s) {
-			var n = parse(s);
-					
-			if ((!this.NaN && isNaN(n))
-					|| (optdef.min !== undefined && n < optdef.min)
-					|| (optdef.max !== undefined && n > optdef.max)) {
-				throw new Error('illegal value for option '+name+': '+s);
-			}
-	
-			return n;
-		};
-	}
-};
-
-function initStringify (optdef) {
-	if (optdef.stringify === undefined) {
-		optdef.stringify = STRINGIFIERS[optdef.type];
-	}
-	
-	if (optdef.stringify === undefined) {
-		optdef.stringify = stringifyAny;
-	}
-}
-
-function defaults (target, defaults) {
-	for (var name in defaults) {
-		if (target[name] === undefined) {
-			target[name] = defaults[name];
-		}
-	}
-}
-
-function dec (val) {
-	return val.toString();
-}
-
-function oct (val) {
-	return '0'+val.toString(8);
-}
-
-function hex (val) {
-	return '0x'+val.toString(16);
-}
-
-const TRUE_VALUES  = {true:  true, on:  true, 1: true, yes: true};
-const FALSE_VALUES = {false: true, off: true, 0: true, no:  true};
-
-function parseBool (s) {
-	s = s.trim().toLowerCase();
-	if (s in TRUE_VALUES) {
-		return true;
-	}
-	else if (s in FALSE_VALUES) {
-		return false;
-	}
-	else {
-		throw new Error('illegal boolean value: '+s);
-	}
-}
-
-function id (x) {
-	return x;
-}
-
-const PARSERS = {
-	boolean: parseBool,
-	string:  id,
-	object:  JSON.parse
-};
-
-const TYPE_ALIAS = {
-	int:    'integer',
-	number: 'float',
-	bool:   'boolean',
-	str:    'string',
-	obj:    'object'
-};
-
-const METAVARS = {
-	string:  'STRING',
-	integer: 'INTEGER',
-	float:   'FLOAT',
-	boolean: 'BOOLEAN',
-	object:  'OBJECT',
-	enum:    'VALUE',
-	custom:  'VALUE'
-};
-
-function stringifyString(s) {
-	if (/[\s'"\\<>,]/.test(s)) {
-//		s = "'"+s.replace(/\\/g,'\\\\').replace(/'/g, "'\\''")+"'";
-		s = JSON.stringify(s);
-	}
-	return s;
-}
-
-function stringifyPrimitive(value) {
-	return ''+value;
-}
-
-function stringifyAny (value) {
-	if (value instanceof Array) {
-		var buf = [];
-		for (var i in value) {
-			buf.push(stringifyAny(value[i]));
-		}
-		return buf.join(' ');
-	}
-	else if (typeof(value) == 'string') {
-		return stringifyString(value);
-	}
-	else {
-		return String(value);
-	}
-}
-
-function stringifyInteger (value) {
-	if (this.base === undefined) {
-		return value.toString();
-	}
-
-	switch (this.base) {
-		case  8: return oct(value);
-		case 16: return hex(value);
-		default: return value.toString(this.base);
-	}
-}
-
-function stringifyRecord (record) {
-	var buf = [];
-	for (var i = 0; i < this.args.length; ++ i) {
-		var arg = this.args[i];
-		buf.push(arg.stringify(record[arg.target]));
-	}
-	return buf.join(' ');
-}
-
-const STRINGIFIERS = {
-	string:          stringifyString,
-	integer:         stringifyInteger,
-	boolean:         stringifyPrimitive,
-	float:           stringifyPrimitive,
-	object:          JSON.stringify,
-	enum:            stringifyAny,
-	custom:          stringifyAny,
-	record:          stringifyRecord
-};
-
-exports.OptionParser = OptionParser;

optparse/demo/demo1.js

+const OptionParser = require('./../optparse').OptionParser;
+
+var parser = new OptionParser({
+	minargs: 1,
+	maxargs: 10,
+	strings: { help: 'N/A', metavars: { integer: 'INT' } },
+	options: [
+		{
+			names: ['--help', '-h'],
+			type: 'flag',
+			help: 'Show this help message.',
+			onOption: function (value) {
+				if (value) {
+					parser.usage('This is a help text. (Could also be credits etc.)');
+				}
+				// returning true canceles any further option parsing
+				// and parser.parse() returns null
+				return value;
+			}
+		}
+	]
+});
+
+parser.add(['--foo', '-f'], {
+	type: 'flag',
+	help: 'This is a flag.',
+	details: ['bla','blub']
+});
+
+parser.add('--bar', {
+	type: 'flag',
+	value: false,
+	target: 'foo',
+	help: 'This is the inverse of --foo.'
+});
+
+parser.add('-s');
+parser.add('--s2', {default: 'blub'});
+parser.add('-i', {type: 'int', required: true, redefinable: false});
+parser.add('--int32', {type: 'integer',
+	min: -2147483648, max: 2147483647, metavar: 'INT32'});
+parser.add('--hex', {type: 'integer', base: 16});
+parser.add(['-n','-d','--float'], {type: 'float', NaN: true});
+parser.add('-o', {type: 'object', default: {foo: 'bar', egg: null, spam: true}});
+parser.add('-b', {type: 'boolean', default: true});
+parser.add('-x', {type: 'flag', redefinable: false});
+parser.add('--r1', {type: 'integer', default: 1, min: 0, base: 8});
+parser.add('--r2', {type: 'integer', default: 3, max: 20, base: 16});
+parser.add('--r3', {type: 'integer', default: 4, min: -10, max: 10, base: 2});
+parser.add('--egg', {type: 'option', default: 'none'});
+parser.add('--spam', {type: 'option', target: 'egg'});
+parser.add('--tomato', {type: 'option', target: 'egg', value: 'bla'});
+parser.add('--sel1', {type: 'enum', values: {foo: 42, bar: 23}});
+parser.add('--sel2', {type: 'enum', values: ['egg', 'spam spam'], default: ' '});
+parser.add('--foo-bar', {default: 'bla "bla" \t\n \'bla\' \\'});
+parser.add('--EGG--SPAM');
+parser.add('--b64', {type: 'custom', argc: 1, metavar: 'BASE64-STRING',
+	parse: function (s) { return new Buffer(s, 'base64'); },
+	stringify: function (buffer) { return buffer.toString('base64'); },
+	help: 'Pass binary data as a base64 encoded string.'});
+parser.add('-t', {type: 'record',
+	default: [11., 5, 'foo'],
+	args: [
+		{type: 'float'},
+		{type: 'integer', min: 1, max: 22},
+		{type: 'string'}
+	]});
+parser.add(['--record','-T'], {type: 'record',
+	default: ['bla', [10, 'bleh']],
+	args: [
+		{type: 'string'},
+		{type: 'record', args: [
+			{type: 'integer'},
+			{type: 'string'},
+		]},
+	],
+	onOption: function (str, rec) {
+		console.log('args: '+JSON.stringify(str)+', '+JSON.stringify(rec));
+	}});
+parser.add('--named', {type: 'record',
+	create: Object,
+	default: {a: 0.5, b: 300, c: 'bla'},
+	args: [
+		{type: 'float',   target: 'a'},
+		{type: 'integer', target: 'b'},
+		{type: 'string',  target: 'c'}
+	]});
+
+var data = parser.parse();
+
+if (data) {
+	console.log('Options:');
+	var displayed = {};
+	for (var name in parser.options) {
+		var opt = parser.options[name];
+		if (!(opt.target in displayed)) {
+			var value = data.options[opt.target];
+			console.log('\t'+opt.target+': '+ (value === undefined ?
+				value : opt.stringify(value)));
+			displayed[opt.target] = true;
+		}
+	}
+	console.log(''); 
+	console.log('Arguments:');
+	for (var i in data.arguments) {
+		console.log('\t'+data.arguments[i]);
+	}
+}

optparse/optparse.js

+/**
+ * JavaScript Option Parser (optparse)
+ * Copyright (C) 2010  Mathias Panzenböck <grosser.meister.morti@gmx.net>
+ * 
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
+ * Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA 
+ */
+
+/**
+ * Construct a new OptionParser.
+ * See the end of this file for example usage.
+ *
+ * @param object params   optional Parameter-Object
+ *
+ * ===== Parameter-Object =====
+ *   {
+ *      minargs: integer, optional
+ *      maxargs: integer, optional
+ *      program: string, per default inferred from process.argv
+ *      strings: object, optional
+ *               Table of strings used in the output. See below.
+ *      options: array, optional
+ *               Array of option definitions. See below.
+ *   }
+ *
+ * ===== String-Table =====
+ *   {
+ *      help:      string, default: 'No help available for this option.'
+ *      usage:     string, default: 'Usage'
+ *      options:   string, default: 'OPTIONS'
+ *      arguments: string, default: 'ARGUMENTS'
+ *      required:  string, default: 'required'
+ *      default:   string, default: 'default'
+ *      base:      string, default: 'base'
+ *      metavars:  object, optional
+ *                 Table of default metavar names per type.
+ *                 Per default the type name in capital letters or derived
+ *                 from the possible values.
+ *   }
+ *
+ * ===== Option Definition =====
+ *   {
+ *      // Only when passed to the OptionParser constructor:
+ *      name:        string or array
+ *      names:       string or array, alias of name
+ *                   Only one of both may be used at the same time.
+ *
+ *                   Names can be long options (e.g. '--foo') and short options
+ *                   (e.g. '-f'). The first name is used to indentify the option.
+ *                   Names musst be unique and may not contain '='.
+ *
+ *                   Short options may be combined when passed to a programm. E.g.
+ *                   the options '-f' and '-b' can be combined to '-fb'. Only one
+ *                   of these combined options may require an argument.
+ *
+ *                   Short options are separated from ther arguments by space,
+ *                   long options per '='. If a long option requires an argument
+ *                   and none is passed using '=' it also uses the next commandline
+ *                   argument as it's argument (like short options).
+ *
+ *                   If '--' is encountered all remaining arguments are treated as
+ *                   arguments and not as options.
+ *
+ *      // General fields:
+ *      target:      string, per deflault inferred from first name
+ *                   This defines the name used in the returned options object.
+ *                   Multiple options may have the same target.
+ *      default:     any, default: undefined
+ *                   The default value associated with a certain target and is
+ *                   overwritten by each new option with the same target.
+ *      type:        string, default: 'string', see below
+ *      required:    boolean, default: false
+ *      redefinable: boolean, default: true
+ *      help:        string, optional
+ *      details:     array, optional
+ *                   short list of details shown in braces after the option name
+ *                   e.g. integer type options add 'base: '+base if base !== undefined
+ *      metavar:     string or array, per deflault inferred from type
+ *      onOption:    function (value) -> boolean, optional
+ *                   Returning true canceles any further option parsing
+ *                   and the parse() method returns null.
+ *
+ *      // Type: string  (alias: str)
+ *      // Type: boolean (alias: bool)
+ *      // Type: object  (alias: obj)
+ *
+ *      // Type: integer (alias: int)
+ *      min:         integer, optional
+ *      max:         integer, optional
+ *      NaN:         boolean, default: false
+ *      base:        integer, optional
+ *
+ *      // Type: float   (alias: number)
+ *      min:         float, optional
+ *      max:         float, optional
+ *      NaN:         boolean, default: false
+ *
+ *      // Type: flag
+ *      value:       boolean, default: true
+ *      default:     boolean, default: false
+ *
+ *      // Type: option
+ *      value:       any, per default inferred from first name
+ *
+ *      // Type: enum
+ *      ignoreCase:  boolean, default: true
+ *      values:      array or object where the user enteres the field name of
+ *                   the object and you get the value of the field
+ *
+ *      // Type: record
+ *      create:      function () -> object, default: Array
+ *      args:        array of type definitions (type part of option definitions)
+ *
+ *      // Type: custom
+ *      argc:        integer, default: -1
+ *                   Number of required arguments.
+ *                   -1 means one optional argument.
+ *      parse:       function (string, ...) -> value
+ *      stringify:   function (value) -> string, optional
+ *   }
+ *
+ * ===== Option-Arguments =====
+ * For the following types exactly one argument is required:
+ *   integer, float, string, boolean, object, enum
+ *
+ * The following types have optional arguments:
+ *   flag
+ *
+ * The following types have no arguments:
+ *   option
+ *
+ * Custom types may set this through the argc field.
+ */
+function OptionParser (params) {
+	this.optionsPerName = {};
+	this.defaultValues  = {};
+	this.options        = [];
+
+	if (params !== undefined) {
+		this.minargs = params.minargs == 0 ? undefined : params.minargs;
+		this.maxargs = params.maxargs;
+		this.program = params.program;
+		this.strings = params.strings;
+
+		if (this.minargs > this.maxargs) {
+			throw new Error('minargs > maxargs');
+		}
+	}
+
+	if (this.strings === undefined) {
+		this.strings = {};
+	}
+
+	defaults(this.strings, {
+		help:      'No help available for this option.',
+		usage:     'Usage',
+		options:   'OPTIONS',
+		arguments: 'ARGUMENTS',
+		required:  'required',
+		default:   'default',
+		base:      'base',
+		metavars:  {}
+	});
+
+	defaults(this.strings.metavars, METAVARS);
+
+	if (this.program === undefined) {
+		this.program = process.argv[0] + ' ' + process.argv[1];
+	}
+
+	if (params !== undefined && params.options !== undefined) {
+		for (var i in params.options) {
+			var opt = params.options[i];
+			var names;
+
+			if (opt instanceof Array || typeof(opt) == 'string') {
+				opt = undefined;
+				names = opt;
+			}
+			else {
+				names = opt.names;
+				if (names === undefined) {
+					names = opt.name;
+					delete opt.name;
+				}
+				else {
+					delete opt.names;
+				}
+			}
+			this.add(names, opt);
+		}
+	}
+}
+
+OptionParser.prototype = {
+	/**
+	 * Parse command line options.
+	 * 
+	 * @param array args  Commandline arguments.
+	 *                    If undefined process.argv.slice(2) is used.
+	 *
+	 * @return object
+	 *   {
+	 *      arguments: array
+	 *      options:   object, { target -> value }
+	 *   }
+	 */
+	parse: function (args) {
+		if (args === undefined) {
+			args = process.argv.slice(2);
+		}
+
+		var data = {
+			options:   {},
+			arguments: []
+		};
+
+		for (var name in this.defaultValues) {
+			var value = this.defaultValues[name];
+
+			if (value !== undefined) {
+				data.options[this.optionsPerName[name].target] = value;
+			}
+		}
+
+		var got = {};
+		var i = 0;
+		for (; i < args.length; ++ i) {
+			var arg = args[i];
+
+			if (arg == '--') {
+				++ i;
+				break;
+			}
+			else if (/^--.+$/.test(arg)) {
+				var j = arg.indexOf('=');
+				var name, value = undefined;
+
+				if (j == -1) {
+					name  = arg;
+				}
+				else {
+					name  = arg.substring(0,j);
+					value = arg.substring(j+1);
+				}
+
+				var optdef = this.optionsPerName[name];
+				
+				if (optdef === undefined) {
+					throw new Error('unknown option: '+name);
+				}
+
+				if (value === undefined) {
+					if (optdef.argc < 1) {
+						value = optdef.value;
+					}
+					else if ((i + optdef.argc) >= args.length) {
+						throw new Error('option '+name+' needs '+optdef.argc+' arguments');
+					}
+					else {
+						value = optdef.parse.apply(optdef, args.slice(i+1, i+1+optdef.argc));
+						i += optdef.argc;
+					}
+				}
+				else if (optdef.argc == 0) {
+					throw new Error('option '+name+' does not need an argument');
+				}
+				else if (optdef.argc > 1) {
+					throw new Error('option '+name+' needs '+optdef.argc+' arguments');
+				}
+				else {
+					value = optdef.parse(value);
+				}
+
+				if (!optdef.redefinable && optdef.target in got) {
+					throw new Error('cannot redefine option '+name);
+				}
+
+				got[optdef.target] = true;
+				data.options[optdef.target] = value;
+
+				if (optdef.onOption && optdef.onOption(value) === true) {
+					return null;
+				}
+			}
+			else if (/^-.+$/.test(arg)) {
+				if (arg.indexOf('=') != -1) {
+					throw new Error('illegal option syntax: '+arg);
+				}
+
+				var tookarg = false;
+				arg = arg.substring(1);
+
+				for (var j = 0; j < arg.length; ++ j) {
+					var name = '-'+arg[j];
+					var optdef = this.optionsPerName[name];
+					var value;
+					
+					if (optdef === undefined) {
+						throw new Error('unknown option: '+name);
+					}
+
+					if (optdef.argc < 1) {
+						value = optdef.value;
+					}
+					else {
+						if (tookarg || (i+optdef.argc) >= args.length) {
+							throw new Error('option '+name+' needs '+optdef.argc+' arguments');
+						}
+
+						value = optdef.parse.apply(optdef, args.slice(i+1, i+1+optdef.argc));
+						i += optdef.argc;
+						tookarg = true;
+					}
+
+					if (!optdef.redefinable && optdef.target in got) {
+						throw new Error('redefined option: '+name);
+					}
+
+					got[optdef.target] = true;
+					data.options[optdef.target] = value;
+
+					if (optdef.onOption && optdef.onOption(value) === true) {
+						return null;
+					}
+				}
+			}
+			else {
+				data.arguments.push(arg);
+			}
+		}
+
+		for (; i < args.length; ++ i) {
+			data.arguments.push(args[i]);
+		}
+
+		var argc = data.arguments.length;
+		if ((this.maxargs !== undefined && argc > this.maxargs) ||
+				(this.minargs !== undefined && argc < this.minargs)) {
+			var msg = 'illegal number of arguments: ' + argc;
+
+			if (this.minargs !== undefined) {
+				msg += ', minumum is ' + this.minargs;
+				if (this.maxargs !== undefined) {
+					msg += ' and maximum is ' + this.maxargs;
+				}
+			}
+			else {
+				msg += ', maximum is ' + this.maxargs;
+			}
+
+			throw new Error(msg);
+		}
+
+		for (var i in this.options) {
+			var optdef = this.options[i];
+			if (optdef.required && !(optdef.target in got)) {
+				throw new Error('missing required option: ' + optdef.names[0]);
+			}
+		}
+		
+		return data;
+	},
+	/**
+	 * Add an option definition.
+	 *
+	 * @param string or array names  Option names
+	 * @param object optdef          Option definition
+	 */
+	add: function (names, optdef) {
+		if (typeof(names) == 'string') {
+			names = [names];
+		}
+		else if (names === undefined || names.length == 0) {
+			throw new Error('no option name given');
+		}
+
+		if (optdef === undefined) {
+			optdef = {};
+		}
+
+		optdef.names = names;
+		
+		for (var i in names) {
+			var name = names[i];
+			var match = /(-*)(.*)/.exec(name);
+
+			if (name.length == 0 || match[1].length < 1 ||
+					match[1].length > 2 || match[2].length == 0 ||
+					(match[1].length == 1 && match[2].length > 1) ||
+					match[2].indexOf('=') != -1) {
+				throw new Error('illegal option name: ' + name);
+			}
+
+			if (name in this.optionsPerName) {
+				throw new Error('option already exists: '+name);
+			}
+		}
+
+		if (optdef.target === undefined) {
+			var target = names[0].replace(/^--?/,'');
+			
+			if (target.toUpperCase() == target) {
+				// FOO-BAR -> FOO_BAR
+				target = target.replace(/[^a-zA-Z0-9]+/,'_');
+			}
+			else {
+				// foo-bar -> fooBar
+				target = target.split(/[^a-zA-Z0-9]+/);
+				for (var i = 1; i < target.length; ++ i) {
+					var part = target[i];
+	
+					if (part) {
+						target[i] = part[0].toUpperCase() + part.substring(1);
+					}
+				}
+				target = target.join('');
+			}
+
+			optdef.target = target;
+		}
+
+		this._initType(optdef, optdef.names[0]);
+
+		if (optdef.redefinable === undefined) {
+			optdef.redefinable = true;
+		}
+
+		if (optdef.required === undefined) {
+			optdef.required = false;
+		}
+
+		if (optdef.help === undefined) {
+			optdef.help = this.strings.help;
+		}
+		else {
+			optdef.help = optdef.help.trim();
+		}
+		
+		for (var i in names) {
+			this.optionsPerName[names[i]] = optdef;
+		}
+		
+		if (optdef.default !== undefined) {
+			this.defaultValues[names[0]] = optdef.default;
+		}
+
+		this.options.push(optdef);
+	},
+	/**
+	 * Show an error message, usage and exit program with exit code 1.
+	 * 
+	 * @param string msg       The error message
+	 * @param WriteStream out  Where to write the message.
+	 *                         If undefined process.stdout is used.
+	 */
+	error: function (msg, out) {
+		if (!out) {
+			out = process.stdout;
+		}
+		out.write('*** '+msg+'\n\n');
+		this.usage(undefined, out);
+		process.exit(1);
+	},
+	/**
+	 * Print usage message.
+	 *
+	 * @param string help      Optional additional help message.
+	 * @param WriteStream out  Where to write the message.
+	 *                         If undefined process.stdout is used.
+	 */
+	usage: function (help, out) {
+		if (!out) {
+			out = process.stdout;
+		}
+
+		out.write(this.strings.usage+': '+this.program+' ['+
+			this.strings.options+']'+(this.maxargs != 0 ?
+				' ['+this.strings.arguments+']\n' : '\n'));
+		out.write('\n');
+		out.write(this.strings.options+':\n');
+
+		for (var i in this.options) {
+			var optdef = this.options[i];
+			var optnames = [];
+			var metavar = optdef.metavar;
+
+			if (metavar instanceof Array) {
+				metavar = metavar.join(' ');
+			}
+
+			for (var j in optdef.names) {
+				var optname = optdef.names[j];
+
+				if (metavar !== undefined) {
+					if (optdef.argc < 2 && optname.substring(0,2) == '--') {
+						if (optdef.argc < 0) {
+							optname = optname+'[='+metavar+']';
+						}
+						else {
+							optname = optname+'='+metavar;
+						}
+					}
+					else {
+						optname = optname+' '+metavar;
+					}
+				}
+				optnames.push(optname);
+			}
+
+			var details = optdef.details !== undefined ? optdef.details.slice() : [];
+			if (optdef.required) {
+				details.push(this.strings.required);
+			}
+			else if (optdef.argc > 0 && optdef.default !== undefined) {
+				details.push(this.strings.default+': '+optdef.stringify(optdef.default));
+			}
+
+			if (details.length > 0) {
+				details = '  (' + details.join(', ') + ')';
+			}
+
+			if (metavar !== undefined) {
+				optnames[0] += details;
+				out.write('  '+optnames.join('\n  '));
+			}
+			else {
+				out.write('  '+optnames.join(', ')+details);
+			}
+			if (optdef.help) {
+				var lines = optdef.help.split('\n');
+				for (var j in lines) {
+					out.write('\n        '+lines[j]);
+				}
+			}
+			out.write('\n\n');
+		}
+
+		if (help !== undefined) {
+			out.write(help);
+			if (help[help.length-1] != '\n') {
+				out.write('\n');
+			}
+		}
+	},
+	_initType: function (optdef, name) {
+		optdef.name = name;
+	
+		if (optdef.type === undefined) {
+			optdef.type = 'string';
+		}
+		else if (optdef.type in TYPE_ALIAS) {
+			optdef.type = TYPE_ALIAS[optdef.type];
+		}
+		
+		switch (optdef.type) {
+			case 'flag':
+				if (optdef.value === undefined) {
+					optdef.value = true;
+				}
+				optdef.parse = parseBool;
+				optdef.argc  = -1;
+	
+				if (optdef.default === undefined) {
+					optdef.default = this.defaultValues[name];
+
+					if (optdef.default === undefined) {
+						optdef.default = false;
+					}
+				}
+				break;
+	
+			case 'option':
+				optdef.argc = 0;
+	
+				if (optdef.value === undefined) {
+					optdef.value = name.replace(/^--?/,'');
+				}
+				break;
+	
+			case 'enum':
+				this._initEnum(optdef, name);
+				break;
+	
+			case 'integer':
+			case 'float':
+				this._initNumber(optdef, name);
+				break;
+	
+			case 'record':
+				if (optdef.args === undefined || optdef.args.length == 0) {
+					throw new Error('record '+name+' needs at least one argument');
+				}
+				optdef.argc = 0;
+				var metavar = [];
+				for (var i in optdef.args) {
+					var arg = optdef.args[i];
+					if (arg.target === undefined) {
+						arg.target = i;
+					}
+					this._initType(arg, name+'['+i+']');
+	
+					if (arg.argc < 1) {
+						throw new Error('argument '+i+' of option '+name+
+							' has illegal number of arguments');
+					}
+					if (arg.metavar instanceof Array) {
+						for (var j in arg.metavar) {
+							metavar.push(arg.metavar[j]);
+						}
+					}
+					else {
+						metavar.push(arg.metavar);
+					}
+					delete arg.metavar;
+					optdef.argc += arg.argc;
+				}
+				if (optdef.metavar === undefined) {
+					optdef.metavar = metavar;
+				}
+				var onOption = optdef.onOption;
+				if (onOption !== undefined) {
+					optdef.onOption = function (values) {
+						return onOption.apply(this, values);
+					};
+				}
+				if (optdef.create === undefined) {
+					optdef.create = Array;
+				}
+				optdef.parse = function () {
+					var values = this.create();
+					var parserIndex = 0;
+					for (var i = 0; i < arguments.length;) {
+						var arg = optdef.args[parserIndex ++];
+						var raw = [];
+						for (var j = 0; j < arg.argc; ++ j) {
+							raw.push(arguments[i+j]);
+						}
+						values[arg.target] = arg.parse.apply(arg, raw);
+						i += arg.argc;
+					}
+					return values;
+				};
+				break;
+	
+			case 'custom':
+				if (optdef.argc === undefined || optdef.argc < -1) {
+					optdef.argc = -1;
+				}
+	
+				if (optdef.parse === undefined) {
+					throw new Error(
+						'no parse function defined for custom type option '+name);
+				}
+				break;
+	
+			default:
+				optdef.argc = 1;
+				optdef.parse = PARSERS[optdef.type];
+	
+				if (optdef.parse === undefined) {
+					throw new Error('type of option '+name+' is unknown: '+optdef.type);
+				}
+		}
+	
+		initStringify(optdef);
+		
+		var count = 1;
+		if (optdef.metavar === undefined) {
+			optdef.metavar = this.strings.metavars[optdef.type];
+		}
+		
+		if (optdef.metavar === undefined) {
+			count = 0;
+		}
+		else if (optdef.metavar instanceof Array) {
+			count = optdef.metavar.length;
+		}
+	
+		if (optdef.argc == -1) {
+			if (count > 1) {
+				throw new Error('illegal number of metavars for option '+name+
+					': '+JSON.stringify(optdef.metavar));
+			}
+		}
+		else if (optdef.argc != count) {
+			throw new Error('illegal number of metavars for option '+name+
+				': '+JSON.stringify(optdef.metavar));
+		}
+	},
+	_initEnum: function (optdef, name) {
+		optdef.argc = 1;
+	
+		if (optdef.ignoreCase === undefined) {
+			optdef.ignoreCase = true;
+		}
+	
+		if (optdef.values === undefined || optdef.values.length == 0) {
+			throw new Error('no values for enum '+name+' defined');
+		}
+	
+		initStringify(optdef);
+
+		var labels = [];
+		var values = {};
+		if (optdef.values instanceof Array) {
+			for (var i in optdef.values) {
+				var value = optdef.values[i];
+				var label = String(value);
+				values[optdef.ignoreCase ? label.toLowerCase() : label] = value;
+				labels.push(optdef.stringify(value));
+			}
+		}
+		else {
+			for (var label in optdef.values) {
+				var value = optdef.values[label];
+				values[optdef.ignoreCase ? label.toLowerCase() : label] = value;
+				labels.push(optdef.stringify(label));
+			}
+			labels.sort();
+		}
+		optdef.values = values;
+		
+		
+		if (optdef.metavar === undefined) {
+			optdef.metavar = '<'+labels.join(', ')+'>';
+		}
+	
+		optdef.parse = function (s) {
+			var value = values[optdef.ignoreCase ? s.toLowerCase() : s];
+			if (value !== undefined) {
+				return value;
+			}
+			throw new Error('illegal value for option '+name+': '+s);
+		};
+	},
+	_initNumber: function (optdef, name) {
+		optdef.argc = 1;
+	
+		if (optdef.NaN === undefined) {
+			optdef.NaN = false;
+		}
+
+		if (optdef.min > optdef.max) {
+			throw new Error('min > max for option '+name);
+		}
+		
+		var parse, toStr;
+		if (optdef.type == 'integer') {
+			parse = function (s) {
+				var i = NaN;
+				if (s.indexOf('.') == -1) {
+					i = parseInt(s, optdef.base)
+				}
+				return i;
+			};
+			if (optdef.base === undefined) {
+				toStr = dec;
+			}
+			else {
+				switch (optdef.base) {
+					case  8: toStr = oct; break;
+					case 10: toStr = dec; break;
+					case 16: toStr = hex; break;
+					default: toStr = function (val) {
+							return val.toString(optdef.base);
+						};
+						var detail = this.strings.base+': '+optdef.base;
+						if (optdef.details) {
+							optdef.details.push(detail);
+						}
+						else {
+							optdef.details = [detail];
+						}
+				}
+			}
+		}
+		else {
+			parse = parseFloat;
+			toStr = dec;
+		}
+	
+		if (optdef.metavar === undefined) {
+			if (optdef.min === undefined && optdef.max === undefined) {
+				optdef.metavar = this.strings.metavars[optdef.type];
+			}
+			else if (optdef.min === undefined) {
+				optdef.metavar = '...'+toStr(optdef.max);
+			}
+			else if (optdef.max === undefined) {
+				optdef.metavar = toStr(optdef.min)+'...';
+			}
+			else {
+				optdef.metavar = toStr(optdef.min)+'...'+toStr(optdef.max);
+			}
+		}
+		optdef.parse = function (s) {
+			var n = parse(s);
+					
+			if ((!this.NaN && isNaN(n))
+					|| (optdef.min !== undefined && n < optdef.min)
+					|| (optdef.max !== undefined && n > optdef.max)) {
+				throw new Error('illegal value for option '+name+': '+s);
+			}
+	
+			return n;
+		};
+	}
+};
+
+function initStringify (optdef) {
+	if (optdef.stringify === undefined) {
+		optdef.stringify = STRINGIFIERS[optdef.type];
+	}
+	
+	if (optdef.stringify === undefined) {
+		optdef.stringify = stringifyAny;
+	}
+}
+
+function defaults (target, defaults) {
+	for (var name in defaults) {
+		if (target[name] === undefined) {
+			target[name] = defaults[name];
+		}
+	}
+}
+
+function dec (val) {
+	return val.toString();
+}
+
+function oct (val) {
+	return '0'+val.toString(8);
+}
+
+function hex (val) {
+	return '0x'+val.toString(16);
+}
+
+const TRUE_VALUES  = {true:  true, on:  true, 1: true, yes: true};
+const FALSE_VALUES = {false: true, off: true, 0: true, no:  true};
+
+function parseBool (s) {
+	s = s.trim().toLowerCase();
+	if (s in TRUE_VALUES) {
+		return true;
+	}
+	else if (s in FALSE_VALUES) {
+		return false;
+	}
+	else {
+		throw new Error('illegal boolean value: '+s);
+	}
+}
+
+function id (x) {
+	return x;
+}
+
+const PARSERS = {
+	boolean: parseBool,
+	string:  id,
+	object:  JSON.parse
+};
+
+const TYPE_ALIAS = {
+	int:    'integer',
+	number: 'float',
+	bool:   'boolean',
+	str:    'string',
+	obj:    'object'
+};
+
+const METAVARS = {
+	string:  'STRING',
+	integer: 'INTEGER',
+	float:   'FLOAT',
+	boolean: 'BOOLEAN',
+	object:  'OBJECT',
+	enum:    'VALUE',
+	custom:  'VALUE'
+};
+
+function stringifyString(s) {
+	if (/[\s'"\\<>,]/.test(s)) {
+//		s = "'"+s.replace(/\\/g,'\\\\').replace(/'/g, "'\\''")+"'";
+		s = JSON.stringify(s);
+	}
+	return s;
+}
+
+function stringifyPrimitive(value) {
+	return ''+value;
+}
+
+function stringifyAny (value) {
+	if (value instanceof Array) {
+		var buf = [];
+		for (var i in value) {
+			buf.push(stringifyAny(value[i]));
+		}
+		return buf.join(' ');
+	}
+	else if (typeof(value) == 'string') {
+		return stringifyString(value);
+	}
+	else {
+		return String(value);
+	}
+}
+
+function stringifyInteger (value) {
+	if (this.base === undefined) {
+		return value.toString();
+	}
+
+	switch (this.base) {
+		case  8: return oct(value);
+		case 16: return hex(value);
+		default: return value.toString(this.base);
+	}
+}
+
+function stringifyRecord (record) {
+	var buf = [];
+	for (var i = 0; i < this.args.length; ++ i) {
+		var arg = this.args[i];
+		buf.push(arg.stringify(record[arg.target]));
+	}
+	return buf.join(' ');
+}
+
+const STRINGIFIERS = {
+	string:          stringifyString,
+	integer:         stringifyInteger,
+	boolean:         stringifyPrimitive,
+	float:           stringifyPrimitive,
+	object:          JSON.stringify,
+	enum:            stringifyAny,
+	custom:          stringifyAny,
+	record:          stringifyRecord
+};
+
+exports.OptionParser = OptionParser;

xml-markup.js

-/**
- * JavaScript XML Generator (xml-markup)
- * Copyright (C) 2010  Mathias Panzenböck <grosser.meister.morti@gmx.net>
- * 
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- * 
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
- * Lesser General Public License for more details.
- * 
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA 
- */
-
-function XmlMarkup (stream, encoding, standalone) {
-	this._stream = stream;
-	this._open_tag = null;
-	this._prefixes = {xml: 'xml'};
-	this._unflushed_nsdecls = {};
-
-	if (encoding === undefined) {
-		this.encoding = 'UTF-8';
-	}
-	else if (encoding.indexOf('"') != -1) {
-		throw new Error('illegal encoding: '+encoding);
-	}
-	else {
-		this.encoding = encoding;
-	}
-	var content = 'version="1.0" encoding="'+this.encoding+'"';
-	if (standalone !== undefined) {
-		content += ' standalone="' + (standalone ? 'yes"' : 'no"');
-	}
-	this.pinst('xml', content);
-}
-
-XmlMarkup.prototype = {
-	_finish_tag: function () {
-		if (this._open_tag && !this._open_tag.finished) {
-			this._stream.write('>');
-			this._open_tag.finished = true;
-			this._open_tag = null;
-		}
-	},
-	doctype: function (name, systemId, publicId) {
-		if (!isXmlName(name)) {
-			throw new Error('illegal name: '+name);
-		}
-		if (systemId.indexOf('"') != -1) {
-			if (systemId.indexOf("'") != -1) {
-				throw new Error('illegal systemId: '+systemId);
-			}
-			systemId = "'"+systemId+"'";
-		}
-		else {
-			systemId = '"'+systemId+'"';
-		}
-		this._finish_tag();
-		if (publicId === undefined) {
-			this._stream.write('<!DOCTYPE SYSTEM '+systemId+'>');
-		}
-		else if (/[^-'()+,\./:=?;!*#@\$_% \r\na-zA-Z0-9]/.test(publicId)) {
-			throw new Error('illegal publicId: '+publicId);
-		}
-		else {
-			this._stream.write('<!DOCTYPE PUBLIC "'+publicId+'" '+systemId+'>');
-		}
-	},
-	pinst: function (target, content) {
-		if (!isXmlName(target)) {
-			throw new Error('illegal target: '+target);
-		}
-		if (content.indexOf('?>') != -1) {
-			throw new Error('illegal content: '+content);
-		}
-		this._finish_tag();
-		this._stream.write('<?'+target+' '+content+'?>');
-	},
-	text: function (text) {
-		this._finish_tag();
-		this._stream.write(escapeXml(text));
-	},
-	cdata: function (cdata) {
-		if (text.indexOf(']]>') != -1) {
-			throw new Error('illegal cdata: '+cdata);
-		}
-		this._finish_tag();
-		this._stream.write('<[CDATA[');
-		this._stream.write(cdata);
-		this._stream.write(']]>');
-	},
-	space: function (space) {
-		if (/[^\s]/.test(space)) {
-			throw new Error('illegal space argument: '+space);
-		}
-		this._finish_tag();
-		this._stream.write(space);
-	},
-	comment: function (comment) {
-		if (comment.indexOf('--') != -1) {
-			throw new Error('illegal comment argument: '+comment);
-		}
-		this._finish_tag();
-		this._stream.write('<!-- '+comment+' -->');
-	},
-	ns: function (prefixes, closure) {
-		var oldPrefixes = this._prefixes;
-		var newPrefixes = {};
-		var oldUnflushed = this._unflushed_nsdecls;
-		var newUnflushed = {};
-
-		for (var uri in oldUnflushed) {
-			newUnflushed[uri] = oldUnflushed[uri];
-		}
-
-		for (var uri in oldPrefixes) {
-			newPrefixes[uri] = oldPrefixes[uri];
-		}
-
-		var uris = {};
-		for (var prefix in prefixes) {
-			var uri = prefixes[prefix];
-			if (uri in uris) {
-				throw new Error('redeclared namespace uri: '+uri);
-			}
-			if ((prefix.length > 0 && !isXmlNameNS(prefix)) || prefix.toLowerCase() == 'xml') {
-				throw new Error('illegal prefix name: '+prefix);
-			}
-			uris[uri] = true;
-			newPrefixes[uri] = prefix;
-			newUnflushed[uri] = prefix;
-		}
-
-		this._prefixes = newPrefixes;
-		this._unflushed_nsdecls = newUnflushed;
-		closure.call(this);
-		this._prefixes = oldPrefixes;
-		this._unflushed_nsdecls = oldUnflushed;
-	},
-	makePrefix: function (uri) {
-		var prefix = this._prefixes[uri];
-
-		if (prefix === undefined) {
-			var prefixes = {};
-			
-			for (var knownuri in this._prefixes) {
-				prefixes[this._prefixes[knownuri]] = true;
-			}
-			
-			for (var i = 0;; ++ i) {
-				prefix = '_'+i;
-				if (!(prefix in prefixes)) {
-					break;
-				}
-			}
-			this._prefixes[uri] = prefix;
-
-			return {prefix: prefix, isNew: true};
-		}
-		else {
-			return {prefix: prefix, isNew: false};
-		}
-	},