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.
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