1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
module Slop
class Parser
# Our Options instance.
attr_reader :options
# A Hash of configuration options.
attr_reader :config
# Returns an Array of String arguments that were not parsed.
attr_reader :arguments
def initialize(options, **config)
@options = options
@config = config
reset
end
# Reset the parser, useful to use the same instance to parse a second
# time without duplicating state.
def reset
@arguments = []
@options.each(&:reset)
self
end
# Traverse `strings` and process options one by one. Anything after
# `--` is ignored. If a flag includes a equals (=) it will be split
# so that `flag, argument = s.split('=')`.
#
# The `call` method will be executed immediately for each option found.
# Once all options have been executed, any found options will have
# the `finish` method called on them.
#
# Returns a Slop::Result.
def parse(strings)
reset # reset before every parse
pairs = strings.each_cons(2).to_a
# this ensures we still support the last string being a flag,
# otherwise it'll only be used as an argument.
pairs << [strings.last, nil]
@arguments = strings.dup
pairs.each do |flag, arg|
break if !flag
# ignore everything after '--', flag or not
if flag == '--'
arguments.delete(flag)
break
end
# support `foo=bar`
if flag.include?("=")
flag, arg = flag.split("=")
end
if opt = try_process(flag, arg)
# since the option was parsed, we remove it from our
# arguments (plus the arg if necessary)
# delete argument first so that it doesn't mess up the index
if opt.expects_argument?
index = arg_index(flag, arg)
arguments.delete_at(index) if !index.nil?
end
arguments.delete(flag)
end
end
Result.new(self).tap do |result|
used_options.each { |o| o.finish(result) }
end
end
# Returns an Array of Option instances that were used.
def used_options
options.select { |o| o.count > 0 }
end
# Returns an Array of Option instances that were not used.
def unused_options
options.to_a - used_options
end
private
# We've found an option, process and return it
def process(option, arg)
option.ensure_call(arg)
option
end
# Try and find an option to process
def try_process(flag, arg)
if option = matching_option(flag)
process(option, arg)
elsif flag.start_with?("--no-") && option = matching_option(flag.sub("no-", ""))
process(option, false)
elsif flag =~ /\A-[^-]{2,}/
# try and process as a set of grouped short flags. drop(1) removes
# the prefixed -, then we add them back to each flag separately.
flags = flag.split("").drop(1).map { |f| "-#{f}" }
last = flags.pop
flags.each { |f| try_process(f, nil) }
try_process(last, arg) # send the argument to the last flag
else
if flag.start_with?("-") && !suppress_errors?
raise UnknownOption.new("unknown option `#{flag}'", "#{flag}")
end
end
end
def suppress_errors?
config[:suppress_errors]
end
def matching_option(flag)
options.find { |o| o.flags.include?(flag) }
end
# Returns the index of the argument corresponding to a flag.
def arg_index(flag, arg)
flag_index = arguments.index(flag)
return nil if flag_index.nil?
arg_index = arguments[flag_index..-1].index(arg)
return nil if arg_index.nil?
flag_index + arg_index
end
end
end
|