class Getopt @HAS_ARGUMENT = true @NO_ARGUMENT = false @MULTI_SUPPORTED = true @SINGLE_ONLY = false @VERSION = '0.3.1' # public constructor: (@optionsPattern) -> @short_options = {} @long_options = {} @long_names = [] @events = {} @argv = [] @options = {} @errorFunc = (e) -> console.info(e.message) process.exit(1) if process.argv[1] @help = "Usage: node #{process.argv[1].match(/(?:.*[\/\\])?(.*)$/)[1]}\n\n[[OPTIONS]]\n" else @help = "[[OPTIONS]]" names = {} for option in @optionsPattern [short_name, definition, comment, def] = option comment ?= '' definition ?= '' short_name ?= '' definition =~ /^([\w\-]*)/ long_name = \1 has_argument = definition.indexOf('=') != -1 multi_supported = definition.indexOf('+') != -1 optional = /\[=.*?\]/.test(definition) long_name = long_name.trim() short_name = short_name.trim() if optional && short_name throw new Error('optional argument can only work with long option'); long_name ||= short_name fixed_long_name = 'opt_' + long_name name = long_name if long_name == '' throw new Error("empty option found. the last option name is #{@long_names.slice(-1)}"); unless names[fixed_long_name]? @long_names.push long_name @long_options[long_name] = {name, short_name, long_name, has_argument, multi_supported, comment, optional, definition, def} names[fixed_long_name] = true else throw new Error("option #{long_name} is redefined."); if short_name != '' if short_name.length != 1 throw new Error 'short option must be single characters' @short_options[short_name] = @long_options[long_name] @ getOptionByName: (name) -> @long_options[name] ? @short_options[name] getOptionName: (name) -> @getOptionByName(name)?.name # Events on: (name, cb) -> if name iname = @getOptionName(name) unless iname throw new Error "unknown option #{name}" else iname = name @events[iname] = cb @ emit: (name, value) -> event = @events[@getOptionName(name)] if event event.call @, value else throw new Error("Getopt event on '#{name}' is not found") # Command line parser save_option_: (options, option, argv) -> if option.has_argument if argv.length == 0 throw new Error("option #{option.long_name} need argument") value = argv.shift() else value = true name = option.name if option.multi_supported options[name] ?= [] options[name].push value else options[name] = value @events[name]?.call @, value @ parse: (argv) -> try # clone argv argv = argv[0..] rt_options = {} rt_argv = [] for long_name in @long_names option = @long_options[long_name] # set all proto keys eg: constructor toString to undefined if option.def? || rt_options[option.long_name]? rt_options[option.long_name] = option.def while (arg = argv.shift())? if arg =~ /^-(\w[\w\-]*)/ # short option short_names = \1 for short_name, i in short_names option = @short_options[short_name] unless option throw new Error("invalid option #{short_name}") if option.has_argument if i < arg.length - 2 argv.unshift arg.slice(i+2) @save_option_(rt_options, option, argv) break else @save_option_(rt_options, option, argv) else if arg =~ /^--(\w[\w\-]*)((?:=[^]*)?)$/ # long option long_name = \1 value = \2 # value = arg.substring(long_name.length+2) option = @long_options[long_name] unless option throw new Error("invalid option #{long_name}") if value != '' value = value[1..] argv.unshift(value) else if option.optional argv.unshift('') @save_option_(rt_options, option, argv) else if arg == '--' rt_argv = rt_argv.concat(argv) for arg in argv @events['']?.call @, arg break else rt_argv.push arg @events['']?.call @, arg # assign to short name for name in Object.keys(rt_options) sname = @long_options[name].short_name if sname != '' rt_options[sname] = rt_options[name] catch e @errorFunc(e) @argv = rt_argv @options = rt_options @ parse_system: -> @parseSystem() parseSystem: -> @parse(process.argv.slice(2)) # Help Controller setHelp: (@help) -> @ sort: -> @long_names.sort (a, b) -> a > b && 1 || a < b && -1 || 0 getHelp: -> ws = [] options = [] table = [] for long_name in @long_names tr = [] option = @long_options[long_name] {short_name, long_name, comment, definition, def} = option tr.push( if short_name if short_name == long_name # only has short name " -#{short_name}" else # both has short name and long name " -#{short_name}, --#{definition}" else # only has long name " --#{definition}" ) tr.push " " + comment if def tr.push " (default: #{def})" table.push tr for tr in table for td, i in tr ws[i] ?= 0 ws[i] = Math.max(ws[i], td.length) lines = for tr in table line = '' for td, i in tr if i n = ws[i-1] - tr[i-1].length while n-- line += ' ' line += td line.trimRight() @help.replace('[[OPTIONS]]', lines.join("\n")) showHelp: -> console.info @getHelp() @ bindHelp: (help) -> if help @setHelp help @on 'help', -> @showHelp() process.exit(0) @ getVersion: -> Getopt.VERSION error: (@errorFunc) -> @ @getVersion: -> @VERSION # For oneline creator @create: (options) -> new Getopt(options) module.exports = Getopt # vim: sw=2 ts=2 sts=2 expandtab :