module Docopt
Constants
- VERSION
Public Class Methods
docopt(doc, params={})
click to toggle source
# File lib/docopt.rb, line 645 def docopt(doc, params={}) default = {:version => nil, :argv => nil, :help => true, :options_first => false} params = default.merge(params) params[:argv] = ARGV if !params[:argv] Exit.set_usage(printable_usage(doc)) options = parse_defaults(doc) pattern = parse_pattern(formal_usage(Exit.usage), options) argv = parse_argv(TokenStream.new(params[:argv], Exit), options, params[:options_first]) pattern_options = pattern.flat(Option).uniq pattern.flat(AnyOptions).each do |ao| doc_options = parse_defaults(doc) ao.children = doc_options.reject { |o| pattern_options.include?(o) }.uniq end extras(params[:help], params[:version], argv, doc) matched, left, collected = pattern.fix().match(argv) collected ||= [] if matched and (left.count == 0) return Hash[(pattern.flat + collected).map { |a| [a.name, a.value] }] end raise Exit end
dump_patterns(pattern, indent=0)
click to toggle source
# File lib/docopt.rb, line 607 def dump_patterns(pattern, indent=0) ws = " " * 4 * indent out = "" if pattern.class == Array if pattern.count > 0 out << ws << "[\n" for p in pattern out << dump_patterns(p, indent+1).rstrip << "\n" end out << ws << "]\n" else out << ws << "[]\n" end elsif pattern.class.ancestors.include?(ParentPattern) out << ws << pattern.class.name << "(\n" for p in pattern.children out << dump_patterns(p, indent+1).rstrip << "\n" end out << ws << ")\n" else out << ws << pattern.inspect end return out end
extras(help, version, options, doc)
click to toggle source
# File lib/docopt.rb, line 634 def extras(help, version, options, doc) if help and options.any? { |o| ['-h', '--help'].include?(o.name) && o.value } Exit.set_usage(nil) raise Exit, doc.strip end if version and options.any? { |o| o.name == '--version' && o.value } Exit.set_usage(nil) raise Exit, version end end
formal_usage(printable_usage)
click to toggle source
# File lib/docopt.rb, line 592 def formal_usage(printable_usage) pu = printable_usage.split().drop(1) # split and drop "usage:" ret = [] for s in pu.drop(1) if s == pu[0] ret << ') | (' else ret << s end end return '( ' + ret.join(' ') + ' )' end
parse_argv(tokens, options, options_first=false)
click to toggle source
# File lib/docopt.rb, line 557 def parse_argv(tokens, options, options_first=false) parsed = [] while tokens.current() != nil if tokens.current() == '--' return parsed + tokens.map { |v| Argument.new(nil, v) } elsif tokens.current().start_with?('--') parsed += parse_long(tokens, options) elsif tokens.current().start_with?('-') and tokens.current() != '-' parsed += parse_shorts(tokens, options) elsif options_first return parsed + tokens.map { |v| Argument.new(nil, v) } else parsed << Argument.new(nil, tokens.move()) end end return parsed end
parse_atom(tokens, options)
click to toggle source
# File lib/docopt.rb, line 525 def parse_atom(tokens, options) token = tokens.current() result = [] if ['(' , '['].include? token tokens.move() if token == '(' matching = ')' pattern = Required else matching = ']' pattern = Optional end result = pattern.new(*parse_expr(tokens, options)) if tokens.move() != matching raise tokens.error, "unmatched '#{token}'" end return [result] elsif token == 'options' tokens.move() return [AnyOptions.new] elsif token.start_with?('--') and token != '--' return parse_long(tokens, options) elsif token.start_with?('-') and not ['-', '--'].include? token return parse_shorts(tokens, options) elsif token.start_with?('<') and token.end_with?('>') or (token.upcase == token && token.match(/[A-Z]/)) return [Argument.new(tokens.move())] else return [Command.new(tokens.move())] end end
parse_defaults(doc)
click to toggle source
# File lib/docopt.rb, line 575 def parse_defaults(doc) split = doc.split(/^ *(<\S+?>|-\S+?)/).drop(1) split = split.each_slice(2).reject { |pair| pair.count != 2 }.map { |s1, s2| s1 + s2 } split.select { |s| s.start_with?('-') }.map { |s| Option.parse(s) } end
parse_expr(tokens, options)
click to toggle source
# File lib/docopt.rb, line 496 def parse_expr(tokens, options) seq = parse_seq(tokens, options) if tokens.current() != '|' return seq end result = seq.count > 1 ? [Required.new(*seq)] : seq while tokens.current() == '|' tokens.move() seq = parse_seq(tokens, options) result += seq.count > 1 ? [Required.new(*seq)] : seq end return result.count > 1 ? [Either.new(*result)] : result end
parse_long(tokens, options)
click to toggle source
# File lib/docopt.rb, line 397 def parse_long(tokens, options) long, eq, value = tokens.move().partition('=') unless long.start_with?('--') raise RuntimeError end value = (eq == value and eq == '') ? nil : value similar = options.select { |o| o.long and o.long == long } if tokens.error == Exit and similar == [] similar = options.select { |o| o.long and o.long.start_with?(long) } end if similar.count > 1 ostr = similar.map { |o| o.long }.join(', ') raise tokens.error, "#{long} is not a unique prefix: #{ostr}?" elsif similar.count < 1 argcount = (eq == '=' ? 1 : 0) o = Option.new(nil, long, argcount) options << o if tokens.error == Exit o = Option.new(nil, long, argcount, (argcount == 1 ? value : true)) end else s0 = similar[0] o = Option.new(s0.short, s0.long, s0.argcount, s0.value) if o.argcount == 0 if !value.nil? raise tokens.error, "#{o.long} must not have an argument" end else if value.nil? if tokens.current().nil? raise tokens.error, "#{o.long} requires argument" end value = tokens.move() end end if tokens.error == Exit o.value = (!value.nil? ? value : true) end end return [o] end
parse_pattern(source, options)
click to toggle source
# File lib/docopt.rb, line 485 def parse_pattern(source, options) tokens = TokenStream.new(source.gsub(/([\[\]\(\)\|]|\.\.\.)/, ' \1 '), DocoptLanguageError) result = parse_expr(tokens, options) if tokens.current() != nil raise tokens.error, "unexpected ending: #{tokens.join(" ")}" end return Required.new(*result) end
parse_seq(tokens, options)
click to toggle source
# File lib/docopt.rb, line 511 def parse_seq(tokens, options) result = [] stop = [nil, ']', ')', '|'] while !stop.include?(tokens.current) atom = parse_atom(tokens, options) if tokens.current() == '...' atom = [OneOrMore.new(*atom)] tokens.move() end result += atom end return result end
parse_shorts(tokens, options)
click to toggle source
# File lib/docopt.rb, line 442 def parse_shorts(tokens, options) token = tokens.move() unless token.start_with?('-') && !token.start_with?('--') raise RuntimeError end left = token[1..-1] parsed = [] while left != '' short, left = '-' + left[0], left[1..-1] similar = options.select { |o| o.short == short } if similar.count > 1 raise tokens.error, "#{short} is specified ambiguously #{similar.count} times" elsif similar.count < 1 o = Option.new(short, nil, 0) options << o if tokens.error == Exit o = Option.new(short, nil, 0, true) end else s0 = similar[0] o = Option.new(short, s0.long, s0.argcount, s0.value) value = nil if o.argcount != 0 if left == '' if tokens.current().nil? raise tokens.error, "#{short} requires argument" end value = tokens.move() else value = left left = '' end end if tokens.error == Exit o.value = (!value.nil? ? value : true) end end parsed << o end return parsed end
printable_usage(doc)
click to toggle source
# File lib/docopt.rb, line 581 def printable_usage(doc) usage_split = doc.split(/([Uu][Ss][Aa][Gg][Ee]:)/) if usage_split.count < 3 raise DocoptLanguageError, '"usage:" (case-insensitive) not found.' end if usage_split.count > 3 raise DocoptLanguageError, 'More than one "usage:" (case-insensitive).' end return usage_split.drop(1).join().split(/\n\s*\n/)[0].strip end