mirror of
				https://github.com/flynx/argv.js.git
				synced 2025-10-30 19:20:08 +00:00 
			
		
		
		
	refactoring + more defaults... (starting to get a feeling of completeness)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
		
							parent
							
								
									87cd4abe96
								
							
						
					
					
						commit
						ece1a86d1c
					
				
							
								
								
									
										136
									
								
								argv.js
									
									
									
									
									
								
							
							
						
						
									
										136
									
								
								argv.js
									
									
									
									
									
								
							| @ -127,6 +127,24 @@ var afterCallback = function(name){ | |||||||
| // 	}
 | // 	}
 | ||||||
| //
 | //
 | ||||||
| //
 | //
 | ||||||
|  | //
 | ||||||
|  | // General runtime architecture:
 | ||||||
|  | //
 | ||||||
|  | // 		Parser(..) -> parser(..) -> result
 | ||||||
|  | //
 | ||||||
|  | //	Parse(..) 
 | ||||||
|  | //		- constructs a parser object (instance)
 | ||||||
|  | //	parse(..) 
 | ||||||
|  | //		- parse is instance of Parse
 | ||||||
|  | //		- contains the parsing configuration / grammar
 | ||||||
|  | //		- parses the argv
 | ||||||
|  | //		- creates/returns a result object
 | ||||||
|  | //	result 
 | ||||||
|  | //		- parse is prototype of result
 | ||||||
|  | //		- contains all the data resulting from the parse
 | ||||||
|  | //
 | ||||||
|  | //
 | ||||||
|  | //
 | ||||||
| // It is recommended not to do any processing with side-effects in 
 | // It is recommended not to do any processing with side-effects in 
 | ||||||
| // option/command handlers directly, prepare for the execution and to 
 | // option/command handlers directly, prepare for the execution and to 
 | ||||||
| // the actual work in the .then(..) callback. The reason being that the 
 | // the actual work in the .then(..) callback. The reason being that the 
 | ||||||
| @ -134,6 +152,8 @@ var afterCallback = function(name){ | |||||||
| // yet know of any error or stop conditions triggered later in the argv.
 | // yet know of any error or stop conditions triggered later in the argv.
 | ||||||
| //
 | //
 | ||||||
| //
 | //
 | ||||||
|  | //
 | ||||||
|  | //
 | ||||||
| // NOTE: essentially this parser is a very basic stack language...
 | // NOTE: essentially this parser is a very basic stack language...
 | ||||||
| // 		XXX can we implement the whole thing directly as a stack language???
 | // 		XXX can we implement the whole thing directly as a stack language???
 | ||||||
| //
 | //
 | ||||||
| @ -271,6 +291,10 @@ object.Constructor('Parser', { | |||||||
| 	// XXX need to test option definitions... (???)
 | 	// XXX need to test option definitions... (???)
 | ||||||
| 	// 		i.e. report loops and dead ends...
 | 	// 		i.e. report loops and dead ends...
 | ||||||
| 
 | 
 | ||||||
|  | 	// Builtin options/commands and their configuration...
 | ||||||
|  | 	 | ||||||
|  | 	// Help...
 | ||||||
|  | 	//
 | ||||||
| 	// doc config...
 | 	// doc config...
 | ||||||
| 	helpColumnOffset: 3, | 	helpColumnOffset: 3, | ||||||
| 	helpColumnPrefix: '- ', | 	helpColumnPrefix: '- ', | ||||||
| @ -280,7 +304,6 @@ object.Constructor('Parser', { | |||||||
| 	helpValueSeparator: ' ', | 	helpValueSeparator: ' ', | ||||||
| 
 | 
 | ||||||
| 	// doc sections...
 | 	// doc sections...
 | ||||||
| 	version: undefined, |  | ||||||
| 	license: undefined, | 	license: undefined, | ||||||
| 	usage: '$SCRIPTNAME [OPTIONS]', | 	usage: '$SCRIPTNAME [OPTIONS]', | ||||||
| 	doc: undefined, | 	doc: undefined, | ||||||
| @ -306,7 +329,6 @@ object.Constructor('Parser', { | |||||||
| 			.replace(/\$VERSION/g, this.version || '0.0.0') | 			.replace(/\$VERSION/g, this.version || '0.0.0') | ||||||
| 			.replace(/\$SCRIPTNAME/g, this.scriptName) }, | 			.replace(/\$SCRIPTNAME/g, this.scriptName) }, | ||||||
| 
 | 
 | ||||||
| 	// Builtin options/commands...
 |  | ||||||
| 	'-h': '-help', | 	'-h': '-help', | ||||||
| 	'-help': { | 	'-help': { | ||||||
| 		doc: 'print this message and exit', | 		doc: 'print this message and exit', | ||||||
| @ -412,6 +434,11 @@ object.Constructor('Parser', { | |||||||
| 				.join('\n'))) | 				.join('\n'))) | ||||||
| 			return module.STOP }}, | 			return module.STOP }}, | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 	// Version...
 | ||||||
|  | 	//
 | ||||||
|  | 	version: undefined, | ||||||
|  | 
 | ||||||
| 	'-v': '-version', | 	'-v': '-version', | ||||||
| 	'-version': { | 	'-version': { | ||||||
| 		doc: 'show $SCRIPTNAME verion and exit', | 		doc: 'show $SCRIPTNAME verion and exit', | ||||||
| @ -420,6 +447,7 @@ object.Constructor('Parser', { | |||||||
| 			this.print(this.version || '0.0.0') | 			this.print(this.version || '0.0.0') | ||||||
| 			return module.STOP }, }, | 			return module.STOP }, }, | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| 	// Stop processing arguments and continue into .then(..) handlers...
 | 	// Stop processing arguments and continue into .then(..) handlers...
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// If .then(..) does not handle rest in the nested context then this
 | 	// If .then(..) does not handle rest in the nested context then this
 | ||||||
| @ -433,6 +461,7 @@ object.Constructor('Parser', { | |||||||
| 		handler: function(){ | 		handler: function(){ | ||||||
| 			return module.THEN }, }, | 			return module.THEN }, }, | ||||||
| 	 | 	 | ||||||
|  | 
 | ||||||
| 	// common short-hands...
 | 	// common short-hands...
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// NOTE: defining this as a loop will enable the user to define any 
 | 	// NOTE: defining this as a loop will enable the user to define any 
 | ||||||
| @ -442,6 +471,7 @@ object.Constructor('Parser', { | |||||||
| 	//'-verbose': '-v',
 | 	//'-verbose': '-v',
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| 	// Handle arguments with no explicit handlers found...
 | 	// Handle arguments with no explicit handlers found...
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// 	Handle dynamic/unknown argument...
 | 	// 	Handle dynamic/unknown argument...
 | ||||||
| @ -473,6 +503,19 @@ object.Constructor('Parser', { | |||||||
| 		this.printError('Unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key) | 		this.printError('Unknown '+ (key.startsWith('-') ? 'option:' : 'command:'), key) | ||||||
| 		return module.ERROR },  | 		return module.ERROR },  | ||||||
| 
 | 
 | ||||||
|  | 	// Default handler action...
 | ||||||
|  | 	//
 | ||||||
|  | 	// This is called when .handler is not set...
 | ||||||
|  | 	handlerDefault: function(handler, rest, key, value){ | ||||||
|  | 		key = handler.key | ||||||
|  | 			|| handler.arg | ||||||
|  | 			// get the final key...
 | ||||||
|  | 			|| this.handler(key)[0].slice(1) | ||||||
|  | 		this[key] = value === undefined ? | ||||||
|  | 			true | ||||||
|  | 			: value | ||||||
|  | 		return this }, | ||||||
|  | 
 | ||||||
| 	// Handle argument value conversion...
 | 	// Handle argument value conversion...
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// If this is false/undefined value is passed to the handler as-is...
 | 	// If this is false/undefined value is passed to the handler as-is...
 | ||||||
| @ -527,51 +570,54 @@ object.Constructor('Parser', { | |||||||
| 
 | 
 | ||||||
| 	//
 | 	//
 | ||||||
| 	//	parser(argv)
 | 	//	parser(argv)
 | ||||||
| 	//		-> parser
 | 	//		-> result
 | ||||||
| 	//
 | 	//
 | ||||||
| 	//	parser(argv, main)
 | 	//	parser(argv, main)
 | ||||||
| 	//		-> parser
 | 	//		-> result
 | ||||||
| 	//
 | 	//
 | ||||||
|  | 	// NOTE: the result is an object inherited from parser and containing 
 | ||||||
|  | 	// 		all the parse data...
 | ||||||
| 	// NOTE: this (i.e. parser) can be used as a nested command/option 
 | 	// NOTE: this (i.e. parser) can be used as a nested command/option 
 | ||||||
| 	// 		handler...
 | 	// 		handler...
 | ||||||
| 	//
 | 	//
 | ||||||
| 	__call__: function(context, argv, main, root_value){ | 	__call__: function(context, argv, main, root_value){ | ||||||
| 		var that = this | 		var parsed = Object.create(this) | ||||||
| 		var nested = false | 		var nested = parsed.nested = false | ||||||
| 		var rest = this.rest =  | 		var rest = parsed.rest =  | ||||||
| 			argv == null ? | 			argv == null ? | ||||||
| 				(typeof(process) != 'unhandled' ? | 				(typeof(process) != 'unhandled' ? | ||||||
| 					process.argv  | 					process.argv  | ||||||
| 					: []) | 					: []) | ||||||
| 				: argv | 				: argv | ||||||
| 		argv = rest.slice()  | 		parsed.argv = rest.slice()  | ||||||
| 		main = main  | 		main = main  | ||||||
| 			|| require.main.filename | 			|| require.main.filename | ||||||
| 
 |  | ||||||
| 		// nested command handler...
 | 		// nested command handler...
 | ||||||
| 		if(context instanceof Parser){ | 		if(context instanceof Parser){ | ||||||
| 			nested = true | 			nested = parsed.nested = true | ||||||
| 			main = context.scriptName +' '+ main  | 			main = context.scriptName +' '+ main  | ||||||
| 			rest.unshift(main) } | 			rest.unshift(main) } | ||||||
| 
 |  | ||||||
| 		// normalize the argv...
 | 		// normalize the argv...
 | ||||||
| 		if(main != null){ | 		if(main != null){ | ||||||
| 			this.pre_argv = rest.splice(0, rest.indexOf(main)) | 			parsed.pre_argv = rest.splice(0, rest.indexOf(main)) | ||||||
| 			rest.includes(main) | 			rest.includes(main) | ||||||
| 				|| rest.unshift(main) } | 				|| rest.unshift(main) } | ||||||
|  | 		// script stuff...
 | ||||||
|  | 		var script = parsed.script = rest.shift() | ||||||
|  | 		parsed.scriptName = script.split(/[\\\/]/).pop()  | ||||||
|  | 		parsed.scriptPath = script.slice(0,  | ||||||
|  | 			script.length - parsed.scriptName.length) | ||||||
| 
 | 
 | ||||||
| 		this.script = rest[0] | 		var opt_pattern = parsed.optionInputPattern | ||||||
| 		this.scriptName = rest.shift().split(/[\\\/]/).pop()  |  | ||||||
| 		this.scriptPath = this.script.slice(0,  |  | ||||||
| 			this.script.length - this.scriptName.length) |  | ||||||
| 
 |  | ||||||
| 		var opt_pattern = this.optionInputPattern |  | ||||||
| 
 | 
 | ||||||
| 		// helpers...
 | 		// helpers...
 | ||||||
| 		var handleError = function(reason, arg, rest){ | 		var handleError = function(reason, arg, rest){ | ||||||
| 			that.error(reason, arg, rest) | 			parsed.error(reason, arg, rest) | ||||||
| 			that.handleErrorExit | 			parsed.handleErrorExit | ||||||
| 				&& that.handleErrorExit(arg, reason) } | 				&& parsed.handleErrorExit(arg, reason) } | ||||||
|  | 		var defaultHandler = function(handler){ | ||||||
|  | 			return function(rest, arg, value) { | ||||||
|  | 				return parsed.handlerDefault(handler, rest, arg, value) } } | ||||||
| 		var runHandler = function(handler, arg, rest){ | 		var runHandler = function(handler, arg, rest){ | ||||||
| 			var [arg, value] = arg.split(/=/) | 			var [arg, value] = arg.split(/=/) | ||||||
| 			// get option value...
 | 			// get option value...
 | ||||||
| @ -585,14 +631,15 @@ object.Constructor('Parser', { | |||||||
| 				: value | 				: value | ||||||
| 			// value conversion...
 | 			// value conversion...
 | ||||||
| 			value = (value != null  | 			value = (value != null  | ||||||
| 					&& that.handleArgumentValue) ? | 					&& parsed.handleArgumentValue) ? | ||||||
| 				that.handleArgumentValue(handler, value) | 				parsed.handleArgumentValue(handler, value) | ||||||
| 				: value | 				: value | ||||||
| 			// run handler...
 | 			// run handler...
 | ||||||
| 			var res = (typeof(handler) == 'function' ? | 			var res = (typeof(handler) == 'function' ? | ||||||
| 					handler | 					handler | ||||||
| 					: handler.handler) | 					: (handler.handler  | ||||||
| 				.call(that,  | 						|| defaultHandler(handler))) | ||||||
|  | 				.call(parsed,  | ||||||
| 					rest, | 					rest, | ||||||
| 					arg, | 					arg, | ||||||
| 					...(value != null ?  | 					...(value != null ?  | ||||||
| @ -600,7 +647,7 @@ object.Constructor('Parser', { | |||||||
| 						: [])) | 						: [])) | ||||||
| 			// handle .STOP / .ERROR
 | 			// handle .STOP / .ERROR
 | ||||||
| 			res === module.STOP | 			res === module.STOP | ||||||
| 				&& that.stop(arg, rest) | 				&& parsed.stop(arg, rest) | ||||||
| 			// XXX might be a good idea to use exceptions for this...
 | 			// XXX might be a good idea to use exceptions for this...
 | ||||||
| 			res === module.ERROR | 			res === module.ERROR | ||||||
| 				// XXX is this the correct reason???
 | 				// XXX is this the correct reason???
 | ||||||
| @ -612,7 +659,7 @@ object.Constructor('Parser', { | |||||||
| 			var [arg, value] = arg.split(/=/) | 			var [arg, value] = arg.split(/=/) | ||||||
| 			// skip single letter unknown options or '--' options...
 | 			// skip single letter unknown options or '--' options...
 | ||||||
| 			if(arg.length <= 2  | 			if(arg.length <= 2  | ||||||
| 					|| arg.startsWith(that.optionPrefix.repeat(2))){ | 					|| arg.startsWith(parsed.optionPrefix.repeat(2))){ | ||||||
| 				return undefined } | 				return undefined } | ||||||
| 			// split and normalize...
 | 			// split and normalize...
 | ||||||
| 			var [a, ...r] =  | 			var [a, ...r] =  | ||||||
| @ -623,7 +670,7 @@ object.Constructor('Parser', { | |||||||
| 				&& r.push(r.pop() +'='+ value)  | 				&& r.push(r.pop() +'='+ value)  | ||||||
| 			// push new options back to option "stack"...
 | 			// push new options back to option "stack"...
 | ||||||
| 			rest.splice(0, 0, ...r) | 			rest.splice(0, 0, ...r) | ||||||
| 			var handler = that.handler(a)[1] | 			var handler = parsed.handler(a)[1] | ||||||
| 			return handler  | 			return handler  | ||||||
| 				&& [a, handler] } | 				&& [a, handler] } | ||||||
| 
 | 
 | ||||||
| @ -634,21 +681,20 @@ object.Constructor('Parser', { | |||||||
| 			var arg = rest.shift() | 			var arg = rest.shift() | ||||||
| 			var type = opt_pattern.test(arg) ? | 			var type = opt_pattern.test(arg) ? | ||||||
| 					'opt' | 					'opt' | ||||||
| 				: this.isCommand(arg) ? | 				: parsed.isCommand(arg) ? | ||||||
| 					'cmd' | 					'cmd' | ||||||
| 				: 'unhandled' | 				: 'unhandled' | ||||||
| 			// options / commands...
 | 			// options / commands...
 | ||||||
| 			if(type != 'unhandled'){ | 			if(type != 'unhandled'){ | ||||||
| 				// get handler...
 | 				// get handler...
 | ||||||
| 				// XXX revise -- arg replacement feels clunky...
 | 				var handler = parsed.handler(arg)[1] | ||||||
| 				var handler = this.handler(arg)[1] |  | ||||||
| 					// handle merged options
 | 					// handle merged options
 | ||||||
| 					// NOTE: if successful returns array...
 | 					// NOTE: if successful returns array...
 | ||||||
| 					|| (type == 'opt'  | 					|| (type == 'opt'  | ||||||
| 						&& this.splitOptions | 						&& parsed.splitOptions | ||||||
| 						&& splitArgs(arg, rest)) | 						&& splitArgs(arg, rest)) | ||||||
| 					// dynamic or error...
 | 					// dynamic or error...
 | ||||||
| 					|| this.handleArgument | 					|| parsed.handleArgument | ||||||
| 				// normalize output of splitArgs(..)
 | 				// normalize output of splitArgs(..)
 | ||||||
| 				;[arg, handler] = handler instanceof Array ? | 				;[arg, handler] = handler instanceof Array ? | ||||||
| 					handler | 					handler | ||||||
| @ -665,8 +711,8 @@ object.Constructor('Parser', { | |||||||
| 				if(res === module.STOP || res === module.ERROR){ | 				if(res === module.STOP || res === module.ERROR){ | ||||||
| 					return nested ? | 					return nested ? | ||||||
| 						res | 						res | ||||||
| 						: this } | 						: parsed } | ||||||
| 				// break processing -> .then(...)
 | 				// finish arg processing now...
 | ||||||
| 				if(res === module.THEN){ | 				if(res === module.THEN){ | ||||||
| 					arg = null | 					arg = null | ||||||
| 					break } | 					break } | ||||||
| @ -676,17 +722,17 @@ object.Constructor('Parser', { | |||||||
| 				&& unhandled.push(arg) } | 				&& unhandled.push(arg) } | ||||||
| 		// call value handlers with .env or .default values that were 
 | 		// call value handlers with .env or .default values that were 
 | ||||||
| 		// not explicitly called yet...
 | 		// not explicitly called yet...
 | ||||||
| 		this.optionsWithValue() | 		parsed.optionsWithValue() | ||||||
| 			.forEach(function([k, a, d, handler]){ | 			.forEach(function([k, a, d, handler]){ | ||||||
| 				values.has(handler)	 | 				values.has(handler)	 | ||||||
| 					|| (((typeof(process) != 'undefined'  | 					|| (((typeof(process) != 'undefined'  | ||||||
| 								&& handler.env in process.env)  | 								&& handler.env in process.env)  | ||||||
| 							|| handler.default) | 							|| handler.default) | ||||||
| 						&& seen.add(handler) | 						&& seen.add(handler) | ||||||
| 						&& runHandler(handler, a || k[0], null, rest)) }) | 						&& runHandler(handler, a || k[0], rest)) }) | ||||||
| 
 | 
 | ||||||
| 		// check required options...
 | 		// check required options...
 | ||||||
| 		var missing = this | 		var missing = parsed | ||||||
| 			.requiredOptions() | 			.requiredOptions() | ||||||
| 				.filter(function([k, a, d, h]){ | 				.filter(function([k, a, d, h]){ | ||||||
| 					return !seen.has(h) }) | 					return !seen.has(h) }) | ||||||
| @ -694,14 +740,18 @@ object.Constructor('Parser', { | |||||||
| 					return k.pop() }) | 					return k.pop() }) | ||||||
| 		if(missing.length > 0){ | 		if(missing.length > 0){ | ||||||
| 			handleError('required', missing, rest) | 			handleError('required', missing, rest) | ||||||
| 			this.printError('Required but missing:', missing.join(', ')) | 			parsed.printError('Required but missing:', missing.join(', ')) | ||||||
| 			return this } | 			return parsed } | ||||||
| 
 | 
 | ||||||
| 		// post handlers...
 | 		// post handlers...
 | ||||||
| 		root_value = root_value && this.handleArgumentValue ? | 		root_value = root_value && parsed.handleArgumentValue ? | ||||||
| 			this.handleArgumentValue(this, root_value) | 			parsed.handleArgumentValue(parsed, root_value) | ||||||
| 			: root_value | 			: root_value | ||||||
| 		return this.then(unhandled, root_value, rest) }, | 		parsed.then(unhandled, root_value, rest)  | ||||||
|  | 		// XXX should we detach parsed from this???
 | ||||||
|  | 		// 		i.e. set: 
 | ||||||
|  | 		// 			parsed.__proto__ = {}.__proto__
 | ||||||
|  | 		return parsed }, | ||||||
| 
 | 
 | ||||||
| 	// NOTE: see general doc...
 | 	// NOTE: see general doc...
 | ||||||
| 	__init__: function(spec){ | 	__init__: function(spec){ | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "ig-argv", |   "name": "ig-argv", | ||||||
|   "version": "2.0.7", |   "version": "2.1.0", | ||||||
|   "description": "simple argv parser", |   "description": "simple argv parser", | ||||||
|   "main": "argv.js", |   "main": "argv.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user