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.
File without changes
@@ -0,0 +1,4 @@
1
+ .yardoc
2
+ doc
3
+ *.swp
4
+ *.gem
@@ -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
- cd slop
17
- rake install
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
- s = Slop.parse(ARGV) do
23
- option(:v, :verbose, "Enable verbose mode", :default => false)
24
- option(:n, :name, "Your name", true) # compulsory argument
25
- option(:c, :country, "Your country", :argument => true) # the same thing
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
- option(:a, :age, "Your age", true, :optional => true) # optional argument
28
- option(:address, "Your address", :optional => true) # the same
39
+ You can also return your options as a Hash
29
40
 
30
- # shortcut option aliases
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
- # using `--name Lee -a 100`
36
- s.options_hash #=> {:verbose=>false, :name=>"Lee", :age=>"100", :address=>nil}
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
- # or grab the Option object directly
40
- option = s.option_for(:name)
41
- option.description #=> "Your name"
46
+ on :v, :verbose
47
+ opt :v, :verbose
48
+ option :v, :verbose
42
49
 
43
- # You can also use switch values to set options according to arguments
44
- s = Slop.parse(ARGV) do
45
- option(:v, :verbose, :default => false, :switch => true)
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
- # without `-v`
50
- s[:verbose] #=> false
54
+ Slop.parse do |opts|
55
+ opts.on :v, :verbose
56
+ opts.on :n, :name, 'Your name', true
57
+ end
51
58
 
52
- # using `-v`
53
- s[:verbose] #=> true
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
- # using `--applicable_age`
56
- s[:applicable_age] #=> 20
62
+ puts opts
63
+ puts opts.help
57
64
 
58
- Want these options back in a nice readable help string? Just call `Slop.to_s`
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
- opts = Slop.new do
63
- banner("Usage: foo [options]")
67
+ -v, --verbose Enable verbose mode
68
+ -n, --name Your name
69
+ -a, --age Your age
64
70
 
65
- opt(:n, :name, "Your name", true)
66
- end
71
+ You can also add a banner using the `banner` method
67
72
 
68
- puts opts
73
+ opts = Slop.parse
74
+ opts.banner = "Usage: foo.rb [options]"
69
75
 
70
- Returns:
76
+ or
71
77
 
72
- Usage: foo [options]
73
- -n, --name <name> Your name
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(ARGV) do
82
- opt(:v, :version, "Display the version") do
83
- puts "Version 1.0.1"
84
- exit
85
- end
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
- Casting
92
- -------
98
+ Ugh, Symbols
99
+ ------------
93
100
 
94
- If you want to return values of specific types, for example a Symbol or Integer
95
- you can pass the `:as` attribute to your option.
101
+ Fine, don't use them
96
102
 
97
- s = Slop.parse("--age 20") do
98
- opt(:age, true, :as => Integer) # :int/:integer both also work
99
- end
100
- s[:age] #=> 20 # not "20"
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
- opt(:n, "Your name")
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
- opt(:n, nil, "Your name")
132
+ on :n, nil, "Your name", true
115
133
 
116
134
  You can also try other variations:
117
135
 
118
- opt(:name, "Your name")
119
- opt(:n, :name)
120
- opt(:name)
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
- s = Slop.parse("--people lee,injekt") do
128
- opt(:people, true, :as => Array)
129
- end
130
- s[:people] #=> ["lee", "injekt"]
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
- s = Slop.parse("--people lee:injekt:bob") do
135
- opt(:people, true, :as => Array, :delimiter => ':', :limit => 2)
154
+ opts = Slop.parse do
155
+ opt :people, true, :as => Array, :delimiter => ':', :limit => 2)
136
156
  end
137
- s[:people] #=> ["lee", "injekt:bob"]
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 specs, and send
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.
@@ -0,0 +1,7 @@
1
+ task :test do
2
+ $LOAD_PATH.unshift './lib'
3
+ require 'slop'
4
+ require 'minitest/autorun'
5
+ begin; require 'turn'; rescue LoadError; end
6
+ Dir.glob("test/**/*_test.rb").each { |test| require_relative test }
7
+ end
@@ -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
- # @return [Set]
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
- # @return [Set] the last set of options used
17
- def self.options
18
- @@options
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
- # Sugar for new(..).parse(stuff)
22
- def self.parse(values=[], &blk)
23
- new(&blk).parse(values)
26
+ def banner(text=nil)
27
+ @banner = text if text
28
+ @banner
24
29
  end
25
30
 
26
- def initialize(&blk)
27
- @banner = nil
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
- # set the help string banner
34
- # @param [String,#to_s] banner
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
- # add an option
42
- def option(*args, &blk)
43
- opts = args.pop if args.last.is_a?(Hash)
44
- opts ||= {}
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
- if args.size > 4
47
- raise ArgumentError, "Argument size must be no more than 4"
48
- end
45
+ def [](key)
46
+ option = @options[key]
47
+ option ? option.argument_value : nil
48
+ end
49
49
 
50
- attributes = [:flag, :option, :description, :argument]
51
- options = Hash[attributes.zip(pad_options(args))]
52
- options.merge!(opts)
53
- options[:callback] = blk if block_given?
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
- @options << Option.new(options)
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 :o :option
64
+ alias :on :option
65
+
66
+ def to_hash
67
+ @options.to_hash
68
+ end
59
69
 
60
- # add an argument
61
- def argument(*args)
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 :arg :argument
65
- alias :args :argument
66
- alias :arguments :argument
82
+ alias :help :to_s
83
+
84
+ private
67
85
 
68
- # Parse an Array (usually ARGV) of options
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
- values.each do |value|
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
- next unless option = option_for(opt) # skip unknown values for now
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
- option.execute_callback if option.has_callback?
83
- option.switch_argument_value if option.has_switch?
98
+ if option
99
+ option.argument_value = true
84
100
 
85
- if option.requires_argument?
86
- value = values.at(index + 1)
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
- unless option.optional_argument?
89
- if not value or flag_or_option?(value)
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
- "#{option.key} requires a compulsory argument, none given"
92
- end
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
- else
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
- # A simple Hash of options with option labels or flags as keys
108
- # and option values as.. values.
109
- #
110
- # @return [Hash]
111
- def options_hash
112
- out = {}
113
- options.each do |opt|
114
- if opt.requires_argument? or opt.has_default?
115
- out[opt.key] = opt.argument_value || opt.default
116
- end
117
- end
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
- # Find an options argument using the option name.
140
- # Essentially this is the same as `s.options_hash[:name]`
141
- #
142
- # @example When passing --name Lee
143
- # s = Slop.new do
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
- def flag_or_option?(flag)
173
- return unless flag && flag.size > 1
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
- if flag[1] == '-'
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