slop 0.2.0 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/README.md +87 -67
- data/Rakefile +7 -0
- data/lib/slop.rb +105 -162
- data/lib/slop/option.rb +70 -152
- data/lib/slop/version.rb +3 -0
- data/slop.gemspec +17 -0
- data/test/helper.rb +14 -0
- data/test/option_test.rb +80 -0
- data/test/slop_test.rb +113 -0
- metadata +23 -38
- data/spec/option_spec.rb +0 -186
- data/spec/slop_spec.rb +0 -204
data/.gemtest
ADDED
File without changes
|
data/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--hide-void-return -m markdown
|
data/README.md
CHANGED
@@ -3,74 +3,81 @@ Slop
|
|
3
3
|
|
4
4
|
Slop is a simple option collector with an easy to remember syntax and friendly API.
|
5
5
|
|
6
|
+
**NOTE** This branch refers to the prerelease 1.0.0.rc1 version, this version has
|
7
|
+
incompatible changes from version 0.0.3 (the latest stable version). You **should**
|
8
|
+
upgrade.
|
9
|
+
|
6
10
|
Installation
|
7
11
|
------------
|
8
12
|
|
9
13
|
### Rubygems
|
10
14
|
|
11
|
-
gem install slop
|
15
|
+
gem install slop --pre
|
12
16
|
|
13
17
|
### GitHub
|
14
18
|
|
15
19
|
git clone git://github.com/injekt/slop.git
|
16
|
-
|
17
|
-
|
20
|
+
gem build slop.gemspec
|
21
|
+
gem install slop-<version>.gem
|
18
22
|
|
19
23
|
Usage
|
20
24
|
-----
|
25
|
+
# parse assumes ARGV, otherwise you can pass it your own Array
|
26
|
+
opts = Slop.parse do
|
27
|
+
on :v, :verbose, 'Enable verbose mode' # boolean value
|
28
|
+
on :n, :name, 'Your name', true # compulsory argument
|
29
|
+
on :a, :age, 'Your age', :optional => true # optional argument
|
30
|
+
end
|
21
31
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
32
|
+
# if ARGV is `-v --name 'lee jarvis'`
|
33
|
+
opts.verbose? #=> true
|
34
|
+
opts.name? #=> true
|
35
|
+
opts[:name] #=> 'lee jarvis'
|
36
|
+
opts.age? #=> false
|
37
|
+
opts[:age] #=> nil
|
26
38
|
|
27
|
-
|
28
|
-
option(:address, "Your address", :optional => true) # the same
|
39
|
+
You can also return your options as a Hash
|
29
40
|
|
30
|
-
|
31
|
-
opt(:height, "Your height")
|
32
|
-
o(:weight, "Your weight")
|
33
|
-
end
|
41
|
+
opts.to_hash #=> {:name => 'Lee Jarvis', :verbose => true, :age => nil}
|
34
42
|
|
35
|
-
|
36
|
-
|
37
|
-
s.value_for(:name) #=> "Lee"
|
43
|
+
If you don't like the method `on` (because it sounds like the option **expects**
|
44
|
+
a callback), you can use the `opt` or `option` alternatives.
|
38
45
|
|
39
|
-
|
40
|
-
|
41
|
-
|
46
|
+
on :v, :verbose
|
47
|
+
opt :v, :verbose
|
48
|
+
option :v, :verbose
|
42
49
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
option(:applicable_age, :default => 10, :switch => 20)
|
47
|
-
end
|
50
|
+
If you don't like that Slop evaluates your block, or you want slop access
|
51
|
+
inside of your block without referring to `self`, you can pass a block argument to
|
52
|
+
`parse`.
|
48
53
|
|
49
|
-
|
50
|
-
|
54
|
+
Slop.parse do |opts|
|
55
|
+
opts.on :v, :verbose
|
56
|
+
opts.on :n, :name, 'Your name', true
|
57
|
+
end
|
51
58
|
|
52
|
-
|
53
|
-
|
59
|
+
If you want some pretty output for the user to see your options, you can just
|
60
|
+
send the Slop object to `puts` or use the `help` method.
|
54
61
|
|
55
|
-
|
56
|
-
|
62
|
+
puts opts
|
63
|
+
puts opts.help
|
57
64
|
|
58
|
-
|
59
|
-
and Slop will return a nice indented option list. You can even add a banner to
|
60
|
-
the help text using the `banner` method like so:
|
65
|
+
Will output something like
|
61
66
|
|
62
|
-
|
63
|
-
|
67
|
+
-v, --verbose Enable verbose mode
|
68
|
+
-n, --name Your name
|
69
|
+
-a, --age Your age
|
64
70
|
|
65
|
-
|
66
|
-
end
|
71
|
+
You can also add a banner using the `banner` method
|
67
72
|
|
68
|
-
|
73
|
+
opts = Slop.parse
|
74
|
+
opts.banner = "Usage: foo.rb [options]"
|
69
75
|
|
70
|
-
|
76
|
+
or
|
71
77
|
|
72
|
-
|
73
|
-
|
78
|
+
opts = Slop.parse do
|
79
|
+
banner "Usage: foo.rb [options]"
|
80
|
+
end
|
74
81
|
|
75
82
|
Callbacks
|
76
83
|
---------
|
@@ -78,26 +85,37 @@ Callbacks
|
|
78
85
|
If you'd like to trigger an event when an option is used, you can pass a
|
79
86
|
block to your option. Here's how:
|
80
87
|
|
81
|
-
Slop.parse
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
Slop.parse do
|
89
|
+
on :V, :version, 'Print the version' do
|
90
|
+
puts 'Version 1.0.0'
|
91
|
+
exit
|
92
|
+
end
|
86
93
|
end
|
87
94
|
|
88
95
|
Now when using the `--version` option on the command line, the trigger will
|
89
96
|
be called and its contents executed.
|
90
97
|
|
91
|
-
|
92
|
-
|
98
|
+
Ugh, Symbols
|
99
|
+
------------
|
93
100
|
|
94
|
-
|
95
|
-
you can pass the `:as` attribute to your option.
|
101
|
+
Fine, don't use them
|
96
102
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
103
|
+
Slop.parse do
|
104
|
+
on :n, :name, 'Your name'
|
105
|
+
on 'n', 'name', 'Your name'
|
106
|
+
on '-n', '--name', 'Your name'
|
107
|
+
end
|
108
|
+
|
109
|
+
All of these options will do the same thing
|
110
|
+
|
111
|
+
Ugh, Blocks
|
112
|
+
-----------
|
113
|
+
|
114
|
+
C'mon man, this is Ruby, GTFO if you don't like blocks.
|
115
|
+
|
116
|
+
opts = Slop.new
|
117
|
+
opts.on :v, :verbose
|
118
|
+
opts.parse
|
101
119
|
|
102
120
|
Smart
|
103
121
|
-----
|
@@ -106,39 +124,41 @@ Slop is pretty smart when it comes to building your options, for example if you
|
|
106
124
|
want your option to have a flag attribute, but no `--option` attribute, you
|
107
125
|
can do this:
|
108
126
|
|
109
|
-
|
127
|
+
on :n, "Your name"
|
110
128
|
|
111
129
|
and Slop will detect a description in place of an option, so you don't have to
|
112
130
|
do this:
|
113
131
|
|
114
|
-
|
132
|
+
on :n, nil, "Your name", true
|
115
133
|
|
116
134
|
You can also try other variations:
|
117
135
|
|
118
|
-
|
119
|
-
|
120
|
-
|
136
|
+
on :name, "Your name"
|
137
|
+
on :n, :name)
|
138
|
+
on :name, true
|
121
139
|
|
122
140
|
Lists
|
123
141
|
-----
|
124
142
|
|
125
143
|
You can of course also parse lists into options. Here's how:
|
126
144
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
145
|
+
opts = Slop.parse do
|
146
|
+
opt :people, true, :as => Array
|
147
|
+
end
|
148
|
+
|
149
|
+
# ARGV is `--people lee,john,bill`
|
150
|
+
opts[:people] #=> ['lee', 'john', 'bill']
|
131
151
|
|
132
152
|
You can also change both the split delimiter and limit
|
133
153
|
|
134
|
-
|
135
|
-
opt
|
154
|
+
opts = Slop.parse do
|
155
|
+
opt :people, true, :as => Array, :delimiter => ':', :limit => 2)
|
136
156
|
end
|
137
|
-
|
157
|
+
opts[:people] #=> ["lee", "injekt:bob"]
|
138
158
|
|
139
159
|
Contributing
|
140
160
|
------------
|
141
161
|
|
142
162
|
If you'd like to contribute to Slop (it's **really** appreciated) please fork
|
143
|
-
the GitHub repository, create your feature/bugfix branch, add
|
163
|
+
the GitHub repository, create your feature/bugfix branch, add tests, and send
|
144
164
|
me a pull request. I'd be more than happy to look at it.
|
data/Rakefile
ADDED
data/lib/slop.rb
CHANGED
@@ -1,208 +1,151 @@
|
|
1
|
-
require 'set'
|
2
|
-
|
3
1
|
require 'slop/option'
|
2
|
+
require 'slop/version'
|
4
3
|
|
5
4
|
class Slop
|
6
5
|
include Enumerable
|
7
6
|
|
8
|
-
VERSION = '0.2.0'
|
9
|
-
|
10
|
-
# Raised when an option expects an argument and none is given
|
11
7
|
class MissingArgumentError < ArgumentError; end
|
12
8
|
|
13
|
-
|
9
|
+
def self.parse(items=ARGV, &block)
|
10
|
+
slop = new(&block)
|
11
|
+
slop.parse(items)
|
12
|
+
slop
|
13
|
+
end
|
14
|
+
|
14
15
|
attr_reader :options
|
16
|
+
attr_writer :banner
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
def initialize(&block)
|
19
|
+
@options = Options.new
|
20
|
+
@banner = nil
|
21
|
+
if block_given?
|
22
|
+
block.arity == 1 ? yield(self) : instance_eval(&block)
|
23
|
+
end
|
19
24
|
end
|
20
25
|
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
def banner(text=nil)
|
27
|
+
@banner = text if text
|
28
|
+
@banner
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
|
28
|
-
@options = Set.new
|
29
|
-
@@options = @options
|
30
|
-
instance_eval(&blk) if block_given?
|
31
|
+
def parse(items=ARGV)
|
32
|
+
parse_items(items)
|
31
33
|
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
# @return The banner, or nil
|
36
|
-
def banner(banner=nil)
|
37
|
-
@banner = banner if banner
|
38
|
-
@banner
|
35
|
+
def parse!(items=ARGV)
|
36
|
+
parse_items(items, true)
|
39
37
|
end
|
40
38
|
|
41
|
-
#
|
42
|
-
def
|
43
|
-
|
44
|
-
|
39
|
+
# Enumerable interface
|
40
|
+
def each
|
41
|
+
return enum_for(:each) unless block_given?
|
42
|
+
@options.each { |option| yield option }
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def [](key)
|
46
|
+
option = @options[key]
|
47
|
+
option ? option.argument_value : nil
|
48
|
+
end
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
# :short_flag
|
51
|
+
# :long_flag
|
52
|
+
# :description
|
53
|
+
# :argument
|
54
|
+
def option(*args, &block)
|
55
|
+
options = args.pop if args.last.is_a?(Hash)
|
56
|
+
options ||= {}
|
54
57
|
|
55
|
-
|
58
|
+
option = Option.new(*clean_options(args), options, &block)
|
59
|
+
@options << option
|
60
|
+
|
61
|
+
option
|
56
62
|
end
|
57
63
|
alias :opt :option
|
58
|
-
alias :
|
64
|
+
alias :on :option
|
65
|
+
|
66
|
+
def to_hash
|
67
|
+
@options.to_hash
|
68
|
+
end
|
59
69
|
|
60
|
-
|
61
|
-
|
70
|
+
def method_missing(meth, *args, &block)
|
71
|
+
if meth.to_s =~ /\?$/
|
72
|
+
!!self[meth.to_s.chomp('?')]
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
62
77
|
|
78
|
+
def to_s
|
79
|
+
banner = "#{@banner}\n" if @banner
|
80
|
+
(banner || '') + options.map(&:to_s).join("\n")
|
63
81
|
end
|
64
|
-
alias :
|
65
|
-
|
66
|
-
|
82
|
+
alias :help :to_s
|
83
|
+
|
84
|
+
private
|
67
85
|
|
68
|
-
|
69
|
-
|
70
|
-
# @param [Array, #split] Array or String of options to parse
|
71
|
-
# @raise [MissingArgumentError] raised when a compulsory argument is missing
|
72
|
-
def parse(values=[])
|
73
|
-
values = values.split(/\s+/) if values.respond_to?(:split)
|
86
|
+
def parse_items(items, delete=false)
|
87
|
+
trash = []
|
74
88
|
|
75
|
-
|
76
|
-
if flag_or_option?(value)
|
77
|
-
opt = value.size == 2 ? value[1] : value[2..-1]
|
78
|
-
index = values.index(value)
|
89
|
+
items.each do |item|
|
79
90
|
|
80
|
-
|
91
|
+
flag = item.to_s.sub(/^--?/, '')
|
92
|
+
if flag.length == 1
|
93
|
+
option = find { |option| option.short_flag == flag }
|
94
|
+
else
|
95
|
+
option = find { |option| option.long_flag == flag }
|
96
|
+
end
|
81
97
|
|
82
|
-
|
83
|
-
option.
|
98
|
+
if option
|
99
|
+
option.argument_value = true
|
84
100
|
|
85
|
-
if option.
|
86
|
-
|
101
|
+
if option.expects_argument? || option.accepts_optional_argument?
|
102
|
+
argument = items.at(items.index(item) + 1)
|
103
|
+
trash << argument if delete && argument !~ /^--?/
|
87
104
|
|
88
|
-
|
89
|
-
|
105
|
+
if argument
|
106
|
+
option.argument_value = argument
|
107
|
+
option.callback.call(option.argument_value) if option.has_callback?
|
108
|
+
else
|
109
|
+
if option.accepts_optional_argument?
|
110
|
+
option.callback.call(nil) if option.has_callback?
|
111
|
+
else
|
90
112
|
raise MissingArgumentError,
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
unless not value or flag_or_option?(value)
|
96
|
-
option.argument_value = values.delete_at(values.index(value))
|
113
|
+
"'#{flag}' expects an argument, none given"
|
114
|
+
end
|
97
115
|
end
|
116
|
+
elsif option.has_callback?
|
117
|
+
option.callback.call(nil)
|
98
118
|
end
|
99
|
-
|
100
|
-
# not a flag or option, parse as an argument
|
119
|
+
trash << item if delete
|
101
120
|
end
|
102
121
|
end
|
103
|
-
|
104
|
-
self
|
122
|
+
items.delete_if { |item| trash.include? item }
|
105
123
|
end
|
106
124
|
|
107
|
-
#
|
108
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
out
|
119
|
-
end
|
120
|
-
alias :to_hash :options_hash
|
121
|
-
alias :to_h :options_hash
|
122
|
-
|
123
|
-
# Find an option using its flag or label
|
124
|
-
#
|
125
|
-
# @example
|
126
|
-
# s = Slop.new do
|
127
|
-
# option(:n, :name, "Your name")
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# s.option_for(:name).description #=> "Your name"
|
131
|
-
#
|
132
|
-
# @return [Option] the option flag or label
|
133
|
-
def option_for(flag)
|
134
|
-
find do |opt|
|
135
|
-
opt.has_flag?(flag) || opt.has_option?(flag)
|
125
|
+
# @param [Array] args
|
126
|
+
# @return [Array]
|
127
|
+
def clean_options(args)
|
128
|
+
options = []
|
129
|
+
|
130
|
+
short = args.first.to_s.sub(/^--?/, '')
|
131
|
+
if short.size == 1
|
132
|
+
options.push short
|
133
|
+
args.shift
|
134
|
+
else
|
135
|
+
options.push nil
|
136
136
|
end
|
137
|
-
end
|
138
137
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
# option(:n, :name, true)
|
145
|
-
# end
|
146
|
-
#
|
147
|
-
# s.value_for(:name) #=> "Lee"
|
148
|
-
#
|
149
|
-
def value_for(flag)
|
150
|
-
return unless option = option_for(flag)
|
151
|
-
option.argument_value
|
152
|
-
end
|
153
|
-
alias :[] :value_for
|
154
|
-
|
155
|
-
# Implement #each so our options set is enumerable
|
156
|
-
def each
|
157
|
-
return enum_for(:each) unless block_given?
|
158
|
-
@options.each { |opt| yield opt }
|
159
|
-
end
|
160
|
-
|
161
|
-
def to_s
|
162
|
-
str = ""
|
163
|
-
str << @banner + "\n" if @banner
|
164
|
-
each do |opt|
|
165
|
-
str << "#{opt}\n"
|
138
|
+
long = args.first
|
139
|
+
if !long.is_a?(TrueClass) && !long.is_a?(FalseClass) && long.to_s =~ /\A(--?)?[a-zA-Z0-9_-]+\z/
|
140
|
+
options.push args.shift.to_s.sub(/^--?/, '')
|
141
|
+
else
|
142
|
+
options.push nil
|
166
143
|
end
|
167
|
-
str
|
168
|
-
end
|
169
|
-
|
170
|
-
private
|
171
144
|
|
172
|
-
|
173
|
-
|
145
|
+
options.push args.first.respond_to?(:to_sym) ? args.shift : nil
|
146
|
+
options.push args.shift ? true : false # force true/false
|
174
147
|
|
175
|
-
|
176
|
-
return flag[0] == '-' && flag[3]
|
177
|
-
elsif flag[0] == '-'
|
178
|
-
return !flag[3]
|
179
|
-
end
|
148
|
+
options
|
180
149
|
end
|
181
150
|
|
182
|
-
def pad_options(args)
|
183
|
-
# if the first value is not a single character, it's probably an option
|
184
|
-
# so we just replace it with nil
|
185
|
-
args.unshift nil if args.first.nil? || args.first.size > 1
|
186
|
-
|
187
|
-
# if there's only one or two arguments, we pad the values out
|
188
|
-
# with nil values, eventually adding a 'false' to the end of the stack
|
189
|
-
# because that's the default to represent required arguments
|
190
|
-
args.push nil if args.size == 1
|
191
|
-
args.push nil if args.size == 2
|
192
|
-
args.push false if args.size == 3
|
193
|
-
|
194
|
-
# if the second argument includes a space or some odd character it's
|
195
|
-
# probably a description, so we insert a nil into the option field and
|
196
|
-
# insert the original value into the description field
|
197
|
-
args[1..2] = [nil, args[1]] unless args[1].to_s =~ /\A[a-zA-Z_-]*\z/
|
198
|
-
|
199
|
-
# if there's no description given but the option requires an argument, it'll
|
200
|
-
# probably look like this: `[:f, :option, true]`
|
201
|
-
# so we replace the third option with nil to represent the description
|
202
|
-
# and push the true to the end of the stack
|
203
|
-
args[2..3] = [nil, true] if args[2] == true
|
204
|
-
|
205
|
-
# phew
|
206
|
-
args
|
207
|
-
end
|
208
151
|
end
|