slop 0.2.0 → 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|