pry 0.10.4 → 0.11.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -18
  3. data/LICENSE +1 -1
  4. data/README.md +28 -26
  5. data/bin/pry +3 -7
  6. data/lib/pry.rb +3 -2
  7. data/lib/pry/basic_object.rb +6 -0
  8. data/lib/pry/cli.rb +39 -34
  9. data/lib/pry/code.rb +6 -1
  10. data/lib/pry/code/code_file.rb +8 -2
  11. data/lib/pry/code_object.rb +23 -0
  12. data/lib/pry/color_printer.rb +11 -8
  13. data/lib/pry/command.rb +40 -16
  14. data/lib/pry/command_set.rb +9 -2
  15. data/lib/pry/commands/cat/exception_formatter.rb +11 -10
  16. data/lib/pry/commands/cat/file_formatter.rb +7 -3
  17. data/lib/pry/commands/code_collector.rb +16 -14
  18. data/lib/pry/commands/easter_eggs.rb +9 -9
  19. data/lib/pry/commands/edit.rb +6 -2
  20. data/lib/pry/commands/edit/file_and_line_locator.rb +1 -1
  21. data/lib/pry/commands/find_method.rb +1 -1
  22. data/lib/pry/commands/gem_open.rb +1 -1
  23. data/lib/pry/commands/gem_readme.rb +25 -0
  24. data/lib/pry/commands/gem_search.rb +40 -0
  25. data/lib/pry/commands/hist.rb +2 -2
  26. data/lib/pry/commands/jump_to.rb +7 -7
  27. data/lib/pry/commands/ls/formatter.rb +1 -0
  28. data/lib/pry/commands/ls/jruby_hacks.rb +2 -2
  29. data/lib/pry/commands/ls/self_methods.rb +2 -0
  30. data/lib/pry/commands/play.rb +2 -2
  31. data/lib/pry/commands/reload_code.rb +2 -2
  32. data/lib/pry/commands/ri.rb +4 -0
  33. data/lib/pry/commands/shell_command.rb +34 -8
  34. data/lib/pry/commands/show_info.rb +10 -2
  35. data/lib/pry/commands/watch_expression/expression.rb +1 -1
  36. data/lib/pry/commands/whereami.rb +6 -6
  37. data/lib/pry/config.rb +3 -16
  38. data/lib/pry/config/behavior.rb +139 -49
  39. data/lib/pry/config/default.rb +21 -33
  40. data/lib/pry/config/lazy.rb +25 -0
  41. data/lib/pry/editor.rb +1 -1
  42. data/lib/pry/exceptions.rb +1 -1
  43. data/lib/pry/helpers/base_helpers.rb +6 -10
  44. data/lib/pry/helpers/documentation_helpers.rb +1 -0
  45. data/lib/pry/helpers/options_helpers.rb +1 -1
  46. data/lib/pry/helpers/text.rb +69 -76
  47. data/lib/pry/history.rb +22 -1
  48. data/lib/pry/history_array.rb +1 -1
  49. data/lib/pry/hooks.rb +48 -107
  50. data/lib/pry/indent.rb +6 -2
  51. data/lib/pry/input_completer.rb +118 -118
  52. data/lib/pry/method.rb +13 -13
  53. data/lib/pry/method/disowned.rb +1 -0
  54. data/lib/pry/method/patcher.rb +0 -3
  55. data/lib/pry/output.rb +37 -38
  56. data/lib/pry/pager.rb +11 -8
  57. data/lib/pry/plugins.rb +20 -5
  58. data/lib/pry/pry_class.rb +29 -3
  59. data/lib/pry/pry_instance.rb +8 -6
  60. data/lib/pry/repl.rb +37 -5
  61. data/lib/pry/repl_file_loader.rb +1 -1
  62. data/lib/pry/rubygem.rb +3 -1
  63. data/lib/pry/slop.rb +661 -0
  64. data/lib/pry/slop/LICENSE +20 -0
  65. data/lib/pry/slop/commands.rb +196 -0
  66. data/lib/pry/slop/option.rb +208 -0
  67. data/lib/pry/terminal.rb +16 -5
  68. data/lib/pry/test/helper.rb +11 -2
  69. data/lib/pry/version.rb +1 -1
  70. data/lib/pry/wrapped_module.rb +5 -5
  71. data/lib/pry/{module_candidate.rb → wrapped_module/candidate.rb} +2 -4
  72. metadata +14 -20
@@ -60,6 +60,16 @@ class Pry
60
60
 
61
61
  alias active? active
62
62
  alias enabled? enabled
63
+
64
+ def supported?
65
+ pry_version = Gem::Version.new(VERSION)
66
+ spec.dependencies.each do |dependency|
67
+ if dependency.name == "pry"
68
+ return dependency.requirement.satisfied_by?(pry_version)
69
+ end
70
+ end
71
+ true
72
+ end
63
73
  end
64
74
 
65
75
  def initialize
@@ -68,11 +78,11 @@ class Pry
68
78
 
69
79
  # Find all installed Pry plugins and store them in an internal array.
70
80
  def locate_plugins
71
- Gem.refresh
72
- (Gem::Specification.respond_to?(:each) ? Gem::Specification : Gem.source_index.find_name('')).each do |gem|
81
+ gem_list.each do |gem|
73
82
  next if gem.name !~ PRY_PLUGIN_PREFIX
74
83
  plugin_name = gem.name.split('-', 2).last
75
- @plugins << Plugin.new(plugin_name, gem.name, gem, true) if !gem_located?(gem.name)
84
+ plugin = Plugin.new(plugin_name, gem.name, gem, false)
85
+ @plugins << plugin.tap(&:enable!) if plugin.supported? && !plugin_located?(plugin)
76
86
  end
77
87
  @plugins
78
88
  end
@@ -95,8 +105,13 @@ class Pry
95
105
  end
96
106
 
97
107
  private
98
- def gem_located?(gem_name)
99
- @plugins.any? { |plugin| plugin.gem_name == gem_name }
108
+ def plugin_located?(plugin)
109
+ @plugins.any? { |existing| existing.gem_name == plugin.gem_name }
110
+ end
111
+
112
+ def gem_list
113
+ Gem.refresh
114
+ Gem::Specification.respond_to?(:each) ? Gem::Specification : Gem.source_index.find_name('')
100
115
  end
101
116
  end
102
117
 
@@ -32,6 +32,21 @@ class Pry
32
32
  def history
33
33
  @history ||= History.new
34
34
  end
35
+
36
+ #
37
+ # @example
38
+ # Pry.configure do |config|
39
+ # config.eager_load! # optional
40
+ # config.input = # ..
41
+ # config.foo = 2
42
+ # end
43
+ #
44
+ # @yield [config]
45
+ # Yields a block with {Pry.config} as its argument.
46
+ #
47
+ def configure
48
+ yield config
49
+ end
35
50
  end
36
51
 
37
52
  #
@@ -81,7 +96,7 @@ class Pry
81
96
  expanded = Pathname.new(File.expand_path(file)).realpath.to_s
82
97
  # For rbx 1.9 mode [see rubinius issue #2165]
83
98
  File.exist?(expanded) ? expanded : nil
84
- rescue Errno::ENOENT
99
+ rescue Errno::ENOENT, Errno::EACCES
85
100
  nil
86
101
  end
87
102
 
@@ -124,6 +139,11 @@ you can add "Pry.config.windows_console_warning = false" to your .pryrc.
124
139
  # note these have to be loaded here rather than in pry_instance as
125
140
  # we only want them loaded once per entire Pry lifetime.
126
141
  load_rc_files
142
+ end
143
+
144
+ def self.final_session_setup
145
+ return if @session_finalized
146
+ @session_finalized = true
127
147
  load_plugins if Pry.config.should_load_plugins
128
148
  load_requires if Pry.config.should_load_requires
129
149
  load_history if Pry.config.history.should_load
@@ -141,6 +161,9 @@ you can add "Pry.config.windows_console_warning = false" to your .pryrc.
141
161
  # Pry.start(Object.new, :input => MyInput.new)
142
162
  def self.start(target=nil, options={})
143
163
  return if ENV['DISABLE_PRY']
164
+ if ENV['FAIL_PRY']
165
+ raise 'You have FAIL_PRY set to true, which results in Pry calls failing'
166
+ end
144
167
  options = options.to_hash
145
168
 
146
169
  if in_critical_section?
@@ -150,8 +173,8 @@ you can add "Pry.config.windows_console_warning = false" to your .pryrc.
150
173
  end
151
174
 
152
175
  options[:target] = Pry.binding_for(target || toplevel_binding)
153
- options[:hooks] = Pry::Hooks.from_hash options.delete(:hooks) if options.key?(:hooks)
154
176
  initial_session_setup
177
+ final_session_setup
155
178
 
156
179
  # Unless we were given a backtrace, save the current one
157
180
  if options[:backtrace].nil?
@@ -233,7 +256,7 @@ you can add "Pry.config.windows_console_warning = false" to your .pryrc.
233
256
  # @param [String] command_string The Pry command (including arguments,
234
257
  # if any).
235
258
  # @param [Hash] options Optional named parameters.
236
- # @return [Object] The return value of the Pry command.
259
+ # @return [nil]
237
260
  # @option options [Object, Binding] :target The object to run the
238
261
  # command under. Defaults to `TOPLEVEL_BINDING` (main).
239
262
  # @option options [Boolean] :show_output Whether to show command
@@ -258,6 +281,7 @@ you can add "Pry.config.windows_console_warning = false" to your .pryrc.
258
281
 
259
282
  pry = Pry.new(:output => output, :target => target, :commands => options[:commands])
260
283
  pry.eval command_string
284
+ nil
261
285
  end
262
286
 
263
287
  def self.default_editor_for_platform
@@ -306,6 +330,8 @@ Readline version #{Readline::VERSION} detected - will not auto_resize! correctly
306
330
  # Set all the configurable options back to their default values
307
331
  def self.reset_defaults
308
332
  @initial_session = true
333
+ @session_finalized = nil
334
+
309
335
  self.config = Pry::Config.new Pry::Config::Default.new
310
336
  self.cli = false
311
337
  self.current_line = 1
@@ -16,7 +16,7 @@
16
16
  # This will show a list of available commands and their usage. For more
17
17
  # information about Pry you can refer to the following resources:
18
18
  #
19
- # * http://pry.github.com/
19
+ # * http://pryrepl.org/
20
20
  # * https://github.com/pry/pry
21
21
  # * the IRC channel, which is #pry on the Freenode network
22
22
  #
@@ -124,7 +124,7 @@ class Pry
124
124
  #
125
125
  # Generate completions.
126
126
  #
127
- # @param [String] input
127
+ # @param [String] str
128
128
  # What the user has typed so far
129
129
  #
130
130
  # @return [Array<String>]
@@ -269,7 +269,7 @@ class Pry
269
269
  @suppress_output = false
270
270
  inject_sticky_locals!
271
271
  begin
272
- if !process_command_safely(line.lstrip)
272
+ if !process_command_safely(line)
273
273
  @eval_string << "#{line.chomp}\n" if !line.empty? || !@eval_string.empty?
274
274
  end
275
275
  rescue RescuableException => e
@@ -373,7 +373,7 @@ class Pry
373
373
  # serialize something in the user's program, let's not assume we can serialize
374
374
  # the exception either.
375
375
  begin
376
- output.puts "(pry) output error: #{e.inspect}"
376
+ output.puts "(pry) output error: #{e.inspect}\n#{e.backtrace.join("\n")}"
377
377
  rescue RescuableException => e
378
378
  if last_result_is_exception?
379
379
  output.puts "(pry) output error: failed to show exception"
@@ -400,12 +400,14 @@ class Pry
400
400
  # @param [String] val The line to process.
401
401
  # @return [Boolean] `true` if `val` is a command, `false` otherwise
402
402
  def process_command(val)
403
+ val = val.lstrip if /^\s\S/ !~ val
403
404
  val = val.chomp
404
405
  result = commands.process_line(val,
405
406
  :target => current_binding,
406
407
  :output => output,
407
408
  :eval_string => @eval_string,
408
- :pry_instance => self
409
+ :pry_instance => self,
410
+ :hooks => hooks
409
411
  )
410
412
 
411
413
  # set a temporary (just so we can inject the value we want into eval_string)
@@ -433,7 +435,7 @@ class Pry
433
435
  # @return [Boolean] `true` if `val` is a command, `false` otherwise
434
436
  def process_command_safely(val)
435
437
  process_command(val)
436
- rescue CommandError, Slop::InvalidOptionError, MethodSource::SourceNotFoundError => e
438
+ rescue CommandError, Pry::Slop::InvalidOptionError, MethodSource::SourceNotFoundError => e
437
439
  Pry.last_internal_error = e
438
440
  output.puts "Error: #{e.message}"
439
441
  true
@@ -23,6 +23,8 @@ class Pry
23
23
  @pry = pry
24
24
  @indent = Pry::Indent.new
25
25
 
26
+ @readline_output = nil
27
+
26
28
  if options[:target]
27
29
  @pry.push_binding options[:target]
28
30
  end
@@ -168,20 +170,21 @@ class Pry
168
170
  # @return [String?] The next line of input, or `nil` on <Ctrl-D>.
169
171
  def read_line(current_prompt)
170
172
  handle_read_errors do
171
- if defined? Coolline and input.is_a? Coolline
173
+ if coolline_available?
172
174
  input.completion_proc = proc do |cool|
173
175
  completions = @pry.complete cool.completed_word
174
176
  completions.compact
175
177
  end
176
178
  elsif input.respond_to? :completion_proc=
177
- input.completion_proc = proc do |input|
178
- @pry.complete input
179
+ input.completion_proc = proc do |inp|
180
+ @pry.complete inp
179
181
  end
180
182
  end
181
183
 
182
- if defined?(Readline) and input == Readline
184
+ if readline_available?
185
+ set_readline_output
183
186
  input_readline(current_prompt, false) # false since we'll add it manually
184
- elsif defined? Coolline and input.is_a? Coolline
187
+ elsif coolline_available?
185
188
  input_readline(current_prompt)
186
189
  else
187
190
  if input.method(:readline).arity == 1
@@ -198,5 +201,34 @@ class Pry
198
201
  input.readline(*args)
199
202
  end
200
203
  end
204
+
205
+ def readline_available?
206
+ defined?(Readline) && input == Readline
207
+ end
208
+
209
+ def coolline_available?
210
+ defined?(Coolline) && input.is_a?(Coolline)
211
+ end
212
+
213
+ # If `$stdout` is not a tty, it's probably a pipe.
214
+ # @example
215
+ # # `piping?` returns `false`
216
+ # % pry
217
+ # [1] pry(main)
218
+ #
219
+ # # `piping?` returns `true`
220
+ # % pry | tee log
221
+ def piping?
222
+ return false unless $stdout.respond_to?(:tty?)
223
+ !$stdout.tty? && $stdin.tty? && !Pry::Helpers::BaseHelpers.windows?
224
+ end
225
+
226
+ # @return [void]
227
+ def set_readline_output
228
+ return if @readline_output
229
+ if piping?
230
+ @readline_output = (Readline.output = Pry.config.output)
231
+ end
232
+ end
201
233
  end
202
234
  end
@@ -13,7 +13,7 @@ class Pry
13
13
  class REPLFileLoader
14
14
  def initialize(file_name)
15
15
  full_name = File.expand_path(file_name)
16
- raise RuntimeError, "No such file: #{full_name}" if !File.exists?(full_name)
16
+ raise RuntimeError, "No such file: #{full_name}" if !File.exist?(full_name)
17
17
 
18
18
  define_additional_commands
19
19
  @content = File.read(full_name)
@@ -57,7 +57,9 @@ class Pry
57
57
  # @param [String] name
58
58
  # @return [void]
59
59
  def install(name)
60
- gemrc_opts = Gem.configuration['gem'].split(' ')
60
+ require 'rubygems/dependency_installer'
61
+ gem_config = Gem.configuration['gem']
62
+ gemrc_opts = (gem_config.nil? ? "" : gem_config.split(' '))
61
63
  destination = if gemrc_opts.include?('--user-install')
62
64
  Gem.user_dir
63
65
  elsif File.writable?(Gem.dir)
@@ -0,0 +1,661 @@
1
+ class Pry::Slop
2
+ require_relative 'slop/option'
3
+ require_relative 'slop/commands'
4
+ include Enumerable
5
+ VERSION = '3.4.0'
6
+
7
+ # The main Error class, all Exception classes inherit from this class.
8
+ class Error < StandardError; end
9
+
10
+ # Raised when an option argument is expected but none are given.
11
+ class MissingArgumentError < Error; end
12
+
13
+ # Raised when an option is expected/required but not present.
14
+ class MissingOptionError < Error; end
15
+
16
+ # Raised when an argument does not match its intended match constraint.
17
+ class InvalidArgumentError < Error; end
18
+
19
+ # Raised when an invalid option is found and the strict flag is enabled.
20
+ class InvalidOptionError < Error; end
21
+
22
+ # Raised when an invalid command is found and the strict flag is enabled.
23
+ class InvalidCommandError < Error; end
24
+
25
+ # Returns a default Hash of configuration options this Slop instance uses.
26
+ DEFAULT_OPTIONS = {
27
+ :strict => false,
28
+ :help => false,
29
+ :banner => nil,
30
+ :ignore_case => false,
31
+ :autocreate => false,
32
+ :arguments => false,
33
+ :optional_arguments => false,
34
+ :multiple_switches => true,
35
+ :longest_flag => 0
36
+ }
37
+
38
+ class << self
39
+
40
+ # items - The Array of items to extract options from (default: ARGV).
41
+ # config - The Hash of configuration options to send to Slop.new().
42
+ # block - An optional block used to add options.
43
+ #
44
+ # Examples:
45
+ #
46
+ # Slop.parse(ARGV, :help => true) do
47
+ # on '-n', '--name', 'Your username', :argument => true
48
+ # end
49
+ #
50
+ # Returns a new instance of Slop.
51
+ def parse(items = ARGV, config = {}, &block)
52
+ parse! items.dup, config, &block
53
+ end
54
+
55
+ # items - The Array of items to extract options from (default: ARGV).
56
+ # config - The Hash of configuration options to send to Slop.new().
57
+ # block - An optional block used to add options.
58
+ #
59
+ # Returns a new instance of Slop.
60
+ def parse!(items = ARGV, config = {}, &block)
61
+ config, items = items, ARGV if items.is_a?(Hash) && config.empty?
62
+ slop = Pry::Slop.new config, &block
63
+ slop.parse! items
64
+ slop
65
+ end
66
+
67
+ # Build a Slop object from a option specification.
68
+ #
69
+ # This allows you to design your options via a simple String rather
70
+ # than programatically. Do note though that with this method, you're
71
+ # unable to pass any advanced options to the on() method when creating
72
+ # options.
73
+ #
74
+ # string - The optspec String
75
+ # config - A Hash of configuration options to pass to Slop.new
76
+ #
77
+ # Examples:
78
+ #
79
+ # opts = Slop.optspec(<<-SPEC)
80
+ # ruby foo.rb [options]
81
+ # ---
82
+ # n,name= Your name
83
+ # a,age= Your age
84
+ # A,auth Sign in with auth
85
+ # p,passcode= Your secret pass code
86
+ # SPEC
87
+ #
88
+ # opts.fetch_option(:name).description #=> "Your name"
89
+ #
90
+ # Returns a new instance of Slop.
91
+ def optspec(string, config = {})
92
+ config[:banner], optspec = string.split(/^--+$/, 2) if string[/^--+$/]
93
+ lines = optspec.split("\n").reject(&:empty?)
94
+ opts = Slop.new(config)
95
+
96
+ lines.each do |line|
97
+ opt, description = line.split(' ', 2)
98
+ short, long = opt.split(',').map { |s| s.sub(/\A--?/, '') }
99
+ opt = opts.on(short, long, description)
100
+
101
+ if long && long.end_with?('=')
102
+ long.sub!(/\=$/, '')
103
+ opt.config[:argument] = true
104
+ end
105
+ end
106
+
107
+ opts
108
+ end
109
+
110
+ end
111
+
112
+ # The Hash of configuration options for this Slop instance.
113
+ attr_reader :config
114
+
115
+ # The Array of Slop::Option objects tied to this Slop instance.
116
+ attr_reader :options
117
+
118
+ # Create a new instance of Slop and optionally build options via a block.
119
+ #
120
+ # config - A Hash of configuration options.
121
+ # block - An optional block used to specify options.
122
+ def initialize(config = {}, &block)
123
+ @config = DEFAULT_OPTIONS.merge(config)
124
+ @options = []
125
+ @commands = {}
126
+ @trash = []
127
+ @triggered_options = []
128
+ @unknown_options = []
129
+ @callbacks = {}
130
+ @separators = {}
131
+ @runner = nil
132
+
133
+ if block_given?
134
+ block.arity == 1 ? yield(self) : instance_eval(&block)
135
+ end
136
+
137
+ if config[:help]
138
+ on('-h', '--help', 'Display this help message.', :tail => true) do
139
+ $stderr.puts help
140
+ end
141
+ end
142
+ end
143
+
144
+ # Is strict mode enabled?
145
+ #
146
+ # Returns true if strict mode is enabled, false otherwise.
147
+ def strict?
148
+ config[:strict]
149
+ end
150
+
151
+ # Set the banner.
152
+ #
153
+ # banner - The String to set the banner.
154
+ def banner=(banner)
155
+ config[:banner] = banner
156
+ end
157
+
158
+ # Get or set the banner.
159
+ #
160
+ # banner - The String to set the banner.
161
+ #
162
+ # Returns the banner String.
163
+ def banner(banner = nil)
164
+ config[:banner] = banner if banner
165
+ config[:banner]
166
+ end
167
+
168
+ # Set the description (used for commands).
169
+ #
170
+ # desc - The String to set the description.
171
+ def description=(desc)
172
+ config[:description] = desc
173
+ end
174
+
175
+ # Get or set the description (used for commands).
176
+ #
177
+ # desc - The String to set the description.
178
+ #
179
+ # Returns the description String.
180
+ def description(desc = nil)
181
+ config[:description] = desc if desc
182
+ config[:description]
183
+ end
184
+
185
+ # Add a new command.
186
+ #
187
+ # command - The Symbol or String used to identify this command.
188
+ # options - A Hash of configuration options (see Slop::new)
189
+ #
190
+ # Returns a new instance of Slop mapped to this command.
191
+ def command(command, options = {}, &block)
192
+ @commands[command.to_s] = Pry::Slop.new(options, &block)
193
+ end
194
+
195
+ # Parse a list of items, executing and gathering options along the way.
196
+ #
197
+ # items - The Array of items to extract options from (default: ARGV).
198
+ # block - An optional block which when used will yield non options.
199
+ #
200
+ # Returns an Array of original items.
201
+ def parse(items = ARGV, &block)
202
+ parse! items.dup, &block
203
+ items
204
+ end
205
+
206
+ # Parse a list of items, executing and gathering options along the way.
207
+ # unlike parse() this method will remove any options and option arguments
208
+ # from the original Array.
209
+ #
210
+ # items - The Array of items to extract options from (default: ARGV).
211
+ # block - An optional block which when used will yield non options.
212
+ #
213
+ # Returns an Array of original items with options removed.
214
+ def parse!(items = ARGV, &block)
215
+ if items.empty? && @callbacks[:empty]
216
+ @callbacks[:empty].each { |cb| cb.call(self) }
217
+ return items
218
+ end
219
+
220
+ if cmd = @commands[items[0]]
221
+ return cmd.parse! items[1..-1]
222
+ end
223
+
224
+ items.each_with_index do |item, index|
225
+ @trash << index && break if item == '--'
226
+ autocreate(items, index) if config[:autocreate]
227
+ process_item(items, index, &block) unless @trash.include?(index)
228
+ end
229
+ items.reject!.with_index { |item, index| @trash.include?(index) }
230
+
231
+ missing_options = options.select { |opt| opt.required? && opt.count < 1 }
232
+ if missing_options.any?
233
+ raise MissingOptionError,
234
+ "Missing required option(s): #{missing_options.map(&:key).join(', ')}"
235
+ end
236
+
237
+ if @unknown_options.any?
238
+ raise InvalidOptionError, "Unknown options #{@unknown_options.join(', ')}"
239
+ end
240
+
241
+ if @triggered_options.empty? && @callbacks[:no_options]
242
+ @callbacks[:no_options].each { |cb| cb.call(self) }
243
+ end
244
+
245
+ @runner.call(self, items) if @runner.respond_to?(:call)
246
+
247
+ items
248
+ end
249
+
250
+ # Add an Option.
251
+ #
252
+ # objects - An Array with an optional Hash as the last element.
253
+ #
254
+ # Examples:
255
+ #
256
+ # on '-u', '--username=', 'Your username'
257
+ # on :v, :verbose, 'Enable verbose mode'
258
+ #
259
+ # Returns the created instance of Slop::Option.
260
+ def on(*objects, &block)
261
+ option = build_option(objects, &block)
262
+ options << option
263
+ option
264
+ end
265
+ alias option on
266
+ alias opt on
267
+
268
+ # Fetch an options argument value.
269
+ #
270
+ # key - The Symbol or String option short or long flag.
271
+ #
272
+ # Returns the Object value for this option, or nil.
273
+ def [](key)
274
+ option = fetch_option(key)
275
+ option.value if option
276
+ end
277
+ alias get []
278
+
279
+ # Returns a new Hash with option flags as keys and option values as values.
280
+ #
281
+ # include_commands - If true, merge options from all sub-commands.
282
+ def to_hash(include_commands = false)
283
+ hash = Hash[options.map { |opt| [opt.key.to_sym, opt.value] }]
284
+ if include_commands
285
+ @commands.each { |cmd, opts| hash.merge!(cmd.to_sym => opts.to_hash) }
286
+ end
287
+ hash
288
+ end
289
+ alias to_h to_hash
290
+
291
+ # Enumerable interface. Yields each Slop::Option.
292
+ def each(&block)
293
+ options.each(&block)
294
+ end
295
+
296
+ # Specify code to be executed when these options are parsed.
297
+ #
298
+ # callable - An object responding to a call method.
299
+ #
300
+ # yields - The instance of Slop parsing these options
301
+ # An Array of unparsed arguments
302
+ #
303
+ # Example:
304
+ #
305
+ # Slop.parse do
306
+ # on :v, :verbose
307
+ #
308
+ # run do |opts, args|
309
+ # puts "Arguments: #{args.inspect}" if opts.verbose?
310
+ # end
311
+ # end
312
+ def run(callable = nil, &block)
313
+ @runner = callable || block
314
+ unless @runner.respond_to?(:call)
315
+ raise ArgumentError, "You must specify a callable object or a block to #run"
316
+ end
317
+ end
318
+
319
+ # Check for an options presence.
320
+ #
321
+ # Examples:
322
+ #
323
+ # opts.parse %w( --foo )
324
+ # opts.present?(:foo) #=> true
325
+ # opts.present?(:bar) #=> false
326
+ #
327
+ # Returns true if all of the keys are present in the parsed arguments.
328
+ def present?(*keys)
329
+ keys.all? { |key| (opt = fetch_option(key)) && opt.count > 0 }
330
+ end
331
+
332
+ # Override this method so we can check if an option? method exists.
333
+ #
334
+ # Returns true if this option key exists in our list of options.
335
+ def respond_to_missing?(method_name, include_private = false)
336
+ options.any? { |o| o.key == method_name.to_s.chop } || super
337
+ end
338
+
339
+ # Fetch a list of options which were missing from the parsed list.
340
+ #
341
+ # Examples:
342
+ #
343
+ # opts = Slop.new do
344
+ # on :n, :name=
345
+ # on :p, :password=
346
+ # end
347
+ #
348
+ # opts.parse %w[ --name Lee ]
349
+ # opts.missing #=> ['password']
350
+ #
351
+ # Returns an Array of Strings representing missing options.
352
+ def missing
353
+ (options - @triggered_options).map(&:key)
354
+ end
355
+
356
+ # Fetch a Slop::Option object.
357
+ #
358
+ # key - The Symbol or String option key.
359
+ #
360
+ # Examples:
361
+ #
362
+ # opts.on(:foo, 'Something fooey', :argument => :optional)
363
+ # opt = opts.fetch_option(:foo)
364
+ # opt.class #=> Slop::Option
365
+ # opt.accepts_optional_argument? #=> true
366
+ #
367
+ # Returns an Option or nil if none were found.
368
+ def fetch_option(key)
369
+ options.find { |option| [option.long, option.short].include?(clean(key)) }
370
+ end
371
+
372
+ # Fetch a Slop object associated with this command.
373
+ #
374
+ # command - The String or Symbol name of the command.
375
+ #
376
+ # Examples:
377
+ #
378
+ # opts.command :foo do
379
+ # on :v, :verbose, 'Enable verbose mode'
380
+ # end
381
+ #
382
+ # # ruby run.rb foo -v
383
+ # opts.fetch_command(:foo).verbose? #=> true
384
+ def fetch_command(command)
385
+ @commands[command.to_s]
386
+ end
387
+
388
+ # Add a callback.
389
+ #
390
+ # label - The Symbol identifier to attach this callback.
391
+ #
392
+ # Returns nothing.
393
+ def add_callback(label, &block)
394
+ (@callbacks[label] ||= []) << block
395
+ end
396
+
397
+ # Add string separators between options.
398
+ #
399
+ # text - The String text to print.
400
+ def separator(text)
401
+ if @separators[options.size]
402
+ @separators[options.size] << "\n#{text}"
403
+ else
404
+ @separators[options.size] = text
405
+ end
406
+ end
407
+
408
+ # Print a handy Slop help string.
409
+ #
410
+ # Returns the banner followed by available option help strings.
411
+ def to_s
412
+ heads = options.reject(&:tail?)
413
+ tails = (options - heads)
414
+ opts = (heads + tails).select(&:help).map(&:to_s)
415
+ optstr = opts.each_with_index.map { |o, i|
416
+ (str = @separators[i + 1]) ? [o, str].join("\n") : o
417
+ }.join("\n")
418
+
419
+ if @commands.any?
420
+ optstr << "\n" if !optstr.empty?
421
+ optstr << "\nAvailable commands:\n\n"
422
+ optstr << commands_to_help
423
+ optstr << "\n\nSee `<command> --help` for more information on a specific command."
424
+ end
425
+
426
+ banner = config[:banner]
427
+ banner = "Usage: #{File.basename($0, '.*')}#{' [command]' if @commands.any?} [options]" if banner.nil?
428
+ if banner
429
+ "#{banner}\n#{@separators[0] ? "#{@separators[0]}\n" : ''}#{optstr}"
430
+ else
431
+ optstr
432
+ end
433
+ end
434
+ alias help to_s
435
+
436
+ private
437
+
438
+ # Convenience method for present?(:option).
439
+ #
440
+ # Examples:
441
+ #
442
+ # opts.parse %( --verbose )
443
+ # opts.verbose? #=> true
444
+ # opts.other? #=> false
445
+ #
446
+ # Returns true if this option is present. If this method does not end
447
+ # with a ? character it will instead call super().
448
+ def method_missing(method, *args, &block)
449
+ meth = method.to_s
450
+ if meth.end_with?('?')
451
+ meth.chop!
452
+ present?(meth) || present?(meth.gsub('_', '-'))
453
+ else
454
+ super
455
+ end
456
+ end
457
+
458
+ # Process a list item, figure out if it's an option, execute any
459
+ # callbacks, assign any option arguments, and do some sanity checks.
460
+ #
461
+ # items - The Array of items to process.
462
+ # index - The current Integer index of the item we want to process.
463
+ # block - An optional block which when passed will yield non options.
464
+ #
465
+ # Returns nothing.
466
+ def process_item(items, index, &block)
467
+ return unless item = items[index]
468
+ option, argument = extract_option(item) if item.start_with?('-')
469
+
470
+ if option
471
+ option.count += 1 unless item.start_with?('--no-')
472
+ option.count += 1 if option.key[0, 3] == "no-"
473
+ @trash << index
474
+ @triggered_options << option
475
+
476
+ if option.expects_argument?
477
+ argument ||= items.at(index + 1)
478
+
479
+ if !argument || argument =~ /\A--?[a-zA-Z][a-zA-Z0-9_-]*\z/
480
+ raise MissingArgumentError, "#{option.key} expects an argument"
481
+ end
482
+
483
+ execute_option(option, argument, index, item)
484
+ elsif option.accepts_optional_argument?
485
+ argument ||= items.at(index + 1)
486
+
487
+ if argument && argument =~ /\A([^\-?]|-\d)+/
488
+ execute_option(option, argument, index, item)
489
+ else
490
+ option.call(nil)
491
+ end
492
+ elsif config[:multiple_switches] && argument
493
+ execute_multiple_switches(option, argument, index)
494
+ else
495
+ option.value = option.count > 0
496
+ option.call(nil)
497
+ end
498
+ else
499
+ @unknown_options << item if strict? && item =~ /\A--?/
500
+ block.call(item) if block && !@trash.include?(index)
501
+ end
502
+ end
503
+
504
+ # Execute an option, firing off callbacks and assigning arguments.
505
+ #
506
+ # option - The Slop::Option object found by #process_item.
507
+ # argument - The argument Object to assign to this option.
508
+ # index - The current Integer index of the object we're processing.
509
+ # item - The optional String item we're processing.
510
+ #
511
+ # Returns nothing.
512
+ def execute_option(option, argument, index, item = nil)
513
+ if !option
514
+ if config[:multiple_switches] && strict?
515
+ raise InvalidOptionError, "Unknown option -#{item}"
516
+ end
517
+ return
518
+ end
519
+
520
+ if argument
521
+ unless item && item.end_with?("=#{argument}")
522
+ @trash << index + 1 unless option.argument_in_value
523
+ end
524
+ option.value = argument
525
+ else
526
+ option.value = option.count > 0
527
+ end
528
+
529
+ if option.match? && !argument.match(option.config[:match])
530
+ raise InvalidArgumentError, "#{argument} is an invalid argument"
531
+ end
532
+
533
+ option.call(option.value)
534
+ end
535
+
536
+ # Execute a `-abc` type option where a, b and c are all options. This
537
+ # method is only executed if the multiple_switches argument is true.
538
+ #
539
+ # option - The first Option object.
540
+ # argument - The argument to this option. (Split into multiple Options).
541
+ # index - The index of the current item being processed.
542
+ #
543
+ # Returns nothing.
544
+ def execute_multiple_switches(option, argument, index)
545
+ execute_option(option, nil, index)
546
+ argument.split('').each do |key|
547
+ next unless opt = fetch_option(key)
548
+ opt.count += 1
549
+ execute_option(opt, nil, index, key)
550
+ end
551
+ end
552
+
553
+ # Extract an option from a flag.
554
+ #
555
+ # flag - The flag key used to extract an option.
556
+ #
557
+ # Returns an Array of [option, argument].
558
+ def extract_option(flag)
559
+ option = fetch_option(flag)
560
+ option ||= fetch_option(flag.downcase) if config[:ignore_case]
561
+ option ||= fetch_option(flag.gsub(/([^-])-/, '\1_'))
562
+
563
+ unless option
564
+ case flag
565
+ when /\A--?([^=]+)=(.+)\z/, /\A-([a-zA-Z])(.+)\z/, /\A--no-(.+)\z/
566
+ option, argument = fetch_option($1), ($2 || false)
567
+ option.argument_in_value = true if option
568
+ end
569
+ end
570
+
571
+ [option, argument]
572
+ end
573
+
574
+ # Autocreate an option on the fly. See the :autocreate Slop config option.
575
+ #
576
+ # items - The Array of items we're parsing.
577
+ # index - The current Integer index for the item we're processing.
578
+ #
579
+ # Returns nothing.
580
+ def autocreate(items, index)
581
+ flag = items[index]
582
+ if !fetch_option(flag) && !@trash.include?(index)
583
+ option = build_option(Array(flag))
584
+ argument = items[index + 1]
585
+ option.config[:argument] = (argument && argument !~ /\A--?/)
586
+ option.config[:autocreated] = true
587
+ options << option
588
+ end
589
+ end
590
+
591
+ # Build an option from a list of objects.
592
+ #
593
+ # objects - An Array of objects used to build this option.
594
+ #
595
+ # Returns a new instance of Slop::Option.
596
+ def build_option(objects, &block)
597
+ config = {}
598
+ config[:argument] = true if @config[:arguments]
599
+ config[:optional_argument] = true if @config[:optional_arguments]
600
+
601
+ if objects.last.is_a?(Hash)
602
+ config.merge!(objects.last)
603
+ objects.pop
604
+ end
605
+ short = extract_short_flag(objects, config)
606
+ long = extract_long_flag(objects, config)
607
+ desc = objects[0].respond_to?(:to_str) ? objects.shift : nil
608
+
609
+ Option.new(self, short, long, desc, config, &block)
610
+ end
611
+
612
+ # Extract the short flag from an item.
613
+ #
614
+ # objects - The Array of objects passed from #build_option.
615
+ # config - The Hash of configuration options built in #build_option.
616
+ def extract_short_flag(objects, config)
617
+ flag = clean(objects.first)
618
+
619
+ if flag.size == 2 && flag.end_with?('=')
620
+ config[:argument] ||= true
621
+ flag.chop!
622
+ end
623
+
624
+ if flag.size == 1
625
+ objects.shift
626
+ flag
627
+ end
628
+ end
629
+
630
+ # Extract the long flag from an item.
631
+ #
632
+ # objects - The Array of objects passed from #build_option.
633
+ # config - The Hash of configuration options built in #build_option.
634
+ def extract_long_flag(objects, config)
635
+ flag = objects.first.to_s
636
+ if flag =~ /\A(?:--?)?[a-zA-Z][a-zA-Z0-9_-]+\=?\??\z/
637
+ config[:argument] ||= true if flag.end_with?('=')
638
+ config[:optional_argument] = true if flag.end_with?('=?')
639
+ objects.shift
640
+ clean(flag).sub(/\=\??\z/, '')
641
+ end
642
+ end
643
+
644
+ # Remove any leading -- characters from a string.
645
+ #
646
+ # object - The Object we want to cast to a String and clean.
647
+ #
648
+ # Returns the newly cleaned String with leading -- characters removed.
649
+ def clean(object)
650
+ object.to_s.sub(/\A--?/, '')
651
+ end
652
+
653
+ def commands_to_help
654
+ padding = 0
655
+ @commands.each { |c, _| padding = c.size if c.size > padding }
656
+ @commands.map do |cmd, opts|
657
+ " #{cmd}#{' ' * (padding - cmd.size)} #{opts.description}"
658
+ end.join("\n")
659
+ end
660
+
661
+ end