Skip to content

Instantly share code, notes, and snippets.

@naquad
Created August 2, 2013 22:15
Show Gist options
  • Save naquad/6143913 to your computer and use it in GitHub Desktop.
Save naquad/6143913 to your computer and use it in GitHub Desktop.
"" Option parser.
""
"" Arguments
"" [
"" {
"" \ 'short': 'X',
"" \ 'long': '--xxx',
"" \ 'output': 'name',
"" \ 'has_value': 1,
"" \ 'default': 'some_value',
"" \ 'validator': funcref,
"" \ },
"" " ...
"" ]
""
"" short - short option name
"" long - long optio name
"" output - name in output dictionary. by default it is either long or short
"" whatever is available
"" has_value - if 0 (default) option is treated as flag
"" default - default value. used only if has_value = 1
"" validator - funcref to function(option_name, option_value, other_options)
"" where
"" option_name - same as output,
"" option_value - passed value or 1 if flag
"" other_options - dictionary with options and arguments
"" ALL VALIDATOR FUNCTIONS ARE CALLED AFTER PARSING IS FINISHED
"" Invoked only if has_value = 1.
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
if &cp || exists('g:loaded_command_opts')
finish
endif
let s:allowed_keys = [
\ 'short',
\ 'long',
\ 'output',
\ 'has_value',
\ 'default',
\ 'validator'
\ ]
let command#opts#Parser = {}
" Constructor take 1 parameter: list of options
" returns command#opts#Parser object
function! command#opts#Parser.new(options) dict abort
let this = copy(self)
let this.options = a:options
call this.setupOptions()
return this
endfunction
" Internal method for validating and setting defaults for options
function! command#opts#Parser.setupOptions() dict abort
if type(self.options) != 3
throw 'command#opts#Parser options should be a list of dictionaries'
endif
" Because options will be modified to avoid unpleasant surprises
" passed options are copied
let self.options = deepcopy(self.options)
let self.keys = {'short': {}, 'long': {}, 'output': {}}
let i = 1
for o in self.options
if type(o) != 4
throw 'command#opts#Parser option #' . i . ' is not a dictionary'
endif
if !has_key(o, 'short') && !has_key(o, 'long')
throw 'command#opts#Parser option #' . i . ' has no short / long options'
endif
if !has_key(o, 'output')
let o.output = get(o, 'long', get(o, 'short'))
endif
for t in ['short', 'long', 'output']
if has_key(o, t)
if has_key(self.keys[t], o[t])
throw 'command#opts#Parser option #' . i . ' has duplicate ' . t . ' option'
endif
if o[t] == ''
throw 'command#opts#Parser option #' . i . ' has empty ' . t . ' option'
endif
let self.keys[t][o[t]] = o
endif
endfor
if has_key(o, 'short') && strlen(o.short) > 1
throw 'command#opts#Parser option #' . i . ' has short name longet than 1 character'
endif
if !has_key(o, 'has_value')
let o.has_value = 0
endif
let unknown_keys = filter(keys(o), 'index(s:allowed_keys, v:val) == -1')
if !empty(unknown_keys)
throw 'Unknown keys found in #' . i . ': ' . join(unknown_keys, ', ')
endif
let i = i + 1
endfor
endfunction
" Just a helper for making error result
function! command#opts#Parser.error(msg) dict abort
return {'success': 0, 'message': a:msg}
endfunction
" Parser itself. Takes a list of arguments.
" Should be a list of strings.
function! command#opts#Parser.parse(params) dict abort
let result = {'success': 1, 'arguments': [], 'options': {}}
for opt in self.options
if has_key(opt, 'default')
let result.options[opt.output] = opt.default
endif
endfor
let i = 0
let pl = len(a:params)
let skip = 0
while i < pl
if a:params[i][0] == '-' && a:params[i] != '-' && !skip
if a:params[i] == '--'
let skip = 1
let i = i + 1
continue
endif
if a:params[i][1] == '-' " long option
let eq = stridx(a:params[i], '=')
let option = a:params[i][2 : (eq == -1 ? eq : eq - 1)]
if !has_key(self.keys.long, option)
return self.error('Unknown option: ' . a:params[i])
endif
let spec = self.keys.long[option]
if spec.has_value
if eq == -1
if i == pl - 1
return self.error('Option ' . option . ' requres argument')
endif
let result.options[spec.output] = a:params[i + 1]
let i += 1
else
let result.options[spec.output] = a:params[i][eq + 1 :]
endif
elseif eq != -1
return self.error('Option ' . option . ' doesnt take any arguments')
else
let result.options[spec.output] = 1
endif
else " short option
let j = 1
let al = strlen(a:params[i])
while j < al
let option = a:params[i][j]
if !has_key(self.keys.short, option)
return self.error('Unknown option -' . option)
endif
let spec = self.keys.short[option]
if spec.has_value
if j == al - 1
if i == pl - 1
return self.error('Option -' . option . ' requires argument')
else
let result.options[spec.output] = a:params[i + 1]
let i = i + 1
endif
break
else
let result.options[spec.output] = a:params[i][j + 1 :]
break
endif
else
let result.options[spec.output] = 1
endif
let j = j + 1
endwhile
endif
else
call add(result.arguments, a:params[i])
endif
let i += 1
endwhile
for [o, v] in items(result.options)
let spec = self.keys.output[o]
if spec.has_value && has_key(spec, 'validator')
let ret = spec.validator(o, v, result)
if type(ret) == 1 && ret != ''
return self.error(ret)
endif
endif
endfor
return result
endfunction
let g:loaded_command_opts = 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment