pry 0.9.8pre2 → 0.9.8pre3

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,41 +8,6 @@ class Pry
8
8
  # This class is used to create sets of commands. Commands can be imported from
9
9
  # different sets, aliased, removed, etc.
10
10
  class CommandSet
11
- class Command < Struct.new(:name, :description, :options, :block)
12
-
13
- def call(context, *args)
14
- context.command_name = options[:listing]
15
-
16
- if stub_block = options[:stub_info]
17
- context.instance_eval(&stub_block)
18
- else
19
- ret = context.instance_exec(*correct_arg_arity(block.arity, args), &block)
20
- if options[:keep_retval]
21
- ret
22
- else
23
- Pry::CommandContext::VOID_VALUE
24
- end
25
- end
26
- end
27
-
28
- private
29
- def correct_arg_arity(arity, args)
30
- case arity <=> 0
31
- when -1
32
- args
33
- when 0
34
- []
35
- when 1
36
- # another jruby hack
37
- if Pry::Helpers::BaseHelpers.jruby?
38
- args[0..(arity - 1)]
39
- else
40
- args.values_at 0..(arity - 1)
41
- end
42
- end
43
- end
44
- end
45
-
46
11
  include Enumerable
47
12
  include Pry::Helpers::BaseHelpers
48
13
 
@@ -117,29 +82,42 @@ class Pry
117
82
  # # pry(main)> help number
118
83
  # # number-N regex command
119
84
  def command(name, description="No description.", options={}, &block)
85
+ options = default_options(name).merge!(options)
120
86
 
121
- options = {
122
- :requires_gem => [],
123
- :keep_retval => false,
124
- :argument_required => false,
125
- :interpolate => true,
126
- :shellwords => true,
127
- :listing => name,
128
- :use_prefix => true
129
- }.merge!(options)
130
-
131
- unless command_dependencies_met? options
132
- gems_needed = Array(options[:requires_gem])
133
- gems_not_installed = gems_needed.select { |g| !gem_installed?(g) }
87
+ commands[name] = Pry::BlockCommand.subclass(name, description, options, helper_module, &block)
88
+ end
134
89
 
135
- options[:stub_info] = proc do
136
- output.puts "\nThe command '#{name}' is #{Helpers::Text.bold("unavailable")} because it requires the following gems to be installed: #{(gems_not_installed.join(", "))}"
137
- output.puts "-"
138
- output.puts "Type `install-command #{name}` to install the required gems and activate this command."
139
- end
140
- end
90
+ # Defines a new Pry command class.
91
+ #
92
+ # @param [String, Regexp] name The name of the command. Can be
93
+ # Regexp as well as String.
94
+ # @param [String] description A description of the command.
95
+ # @param [Hash] options The optional configuration parameters, see {#command}
96
+ # @param &Block The class body's definition.
97
+ #
98
+ # @example
99
+ # Pry::Commands.command_class "echo", "echo's the input", :shellwords => false do
100
+ # def options(opt)
101
+ # opt.banner "Usage: echo [-u | -d] <string to echo>"
102
+ # opt.on :u, :upcase, "ensure the output is all upper-case"
103
+ # opt.on :d, :downcase, "ensure the output is all lower-case"
104
+ # end
105
+ #
106
+ # def process
107
+ # raise Pry::CommandError, "-u and -d makes no sense" if opts.present?(:u) && opts.present?(:d)
108
+ # result = args.join(" ")
109
+ # result.downcase! if opts.present?(:downcase)
110
+ # result.upcase! if opts.present?(:upcase)
111
+ # output.puts result
112
+ # end
113
+ # end
114
+ #
115
+ def command_class(name, description="No description.", options={}, &block)
116
+ options = default_options(name).merge!(options)
141
117
 
142
- commands[name] = Command.new(name, description, options, block)
118
+ commands[name] = Pry::ClassCommand.subclass(name, description, options, helper_module, &block)
119
+ commands[name].class_eval(&block)
120
+ commands[name]
143
121
  end
144
122
 
145
123
  # Execute a block of code before a command is invoked. The block also
@@ -153,13 +131,7 @@ class Pry
153
131
  # end
154
132
  def before_command(name, &block)
155
133
  cmd = find_command_by_name_or_listing(name)
156
- prev_block = cmd.block
157
-
158
- wrapper_block = proc do |*args|
159
- instance_exec(*args, &block)
160
- instance_exec(*args, &prev_block)
161
- end
162
- cmd.block = wrapper_block
134
+ cmd.hooks[:before].unshift block
163
135
  end
164
136
 
165
137
  # Execute a block of code after a command is invoked. The block also
@@ -173,13 +145,7 @@ class Pry
173
145
  # end
174
146
  def after_command(name, &block)
175
147
  cmd = find_command_by_name_or_listing(name)
176
- prev_block = cmd.block
177
-
178
- wrapper_block = proc do |*args|
179
- instance_exec(*args, &prev_block)
180
- instance_exec(*args, &block)
181
- end
182
- cmd.block = wrapper_block
148
+ cmd.hooks[:after] << block
183
149
  end
184
150
 
185
151
  def each &block
@@ -267,27 +233,6 @@ class Pry
267
233
  commands.delete(cmd.name)
268
234
  end
269
235
 
270
- # Runs a command.
271
- # @param [Object] context Object which will be used as self during the
272
- # command.
273
- # @param [String] name Name of the command to be run
274
- # @param [Array<Object>] args Arguments passed to the command
275
- # @raise [NoCommandError] If the command is not defined in this set
276
- def run_command(context, name, *args)
277
- context.extend helper_module
278
- command = commands[name]
279
-
280
- if command.nil?
281
- raise NoCommandError.new(name, self)
282
- end
283
-
284
- if command.options[:argument_required] && args.empty?
285
- puts "The command '#{command.name}' requires an argument."
286
- else
287
- command.call context, *args
288
- end
289
- end
290
-
291
236
  # Sets or gets the description for a command (replacing the old
292
237
  # description). Returns current description if no description
293
238
  # parameter provided.
@@ -328,7 +273,58 @@ class Pry
328
273
  commands.keys
329
274
  end
330
275
 
276
+ # Find a command that matches the given line
277
+ #
278
+ # @param [String] the line that may be a command invocation
279
+ # @return [Pry::Command, nil]
280
+ def find_command(val)
281
+ commands.values.detect{ |c| c.matches?(val) }
282
+ end
283
+
284
+ # Is the given line a command invocation?
285
+ #
286
+ # @param [String]
287
+ # @return [Boolean]
288
+ def valid_command?(val)
289
+ !!find_command(val)
290
+ end
291
+
292
+ # Process the given line to see whether it needs executing as a command.
293
+ #
294
+ # @param String the line to execute
295
+ # @param Hash the context to execute the commands with
296
+ # @return CommandSet::Result
297
+ #
298
+ def process_line(val, context={})
299
+ if command = find_command(val)
300
+ context = context.merge(:command_set => self)
301
+ retval = command.new(context).process_line(val)
302
+ Result.new(true, retval)
303
+ else
304
+ Result.new(false)
305
+ end
306
+ end
307
+
308
+ # @nodoc used for testing
309
+ def run_command(context, name, *args)
310
+ command = commands[name] or raise NoCommandError.new(name, self)
311
+ command.new(context).call_safely(*args)
312
+ end
313
+
331
314
  private
315
+
316
+ def default_options(name)
317
+ {
318
+ :requires_gem => [],
319
+ :keep_retval => false,
320
+ :argument_required => false,
321
+ :interpolate => true,
322
+ :shellwords => true,
323
+ :listing => name,
324
+ :use_prefix => true
325
+ }
326
+ end
327
+
332
328
  def define_default_commands
333
329
 
334
330
  command "help", "This menu." do |cmd|
@@ -345,7 +341,7 @@ class Pry
345
341
  stagger_output(help_text)
346
342
  else
347
343
  if command = find_command(cmd)
348
- output.puts command.description
344
+ output.puts command.new.help
349
345
  else
350
346
  output.puts "No info for command: #{cmd}"
351
347
  end
@@ -355,10 +351,9 @@ class Pry
355
351
  command "install-command", "Install a disabled command." do |name|
356
352
  require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller
357
353
  command = find_command(name)
358
- stub_info = command.options[:stub_info]
359
354
 
360
- if !stub_info
361
- output.puts "Not a command stub. Nothing to do."
355
+ if command_dependencies_met?(command.options)
356
+ output.puts "Dependencies for #{command.name} are met. Nothing to do."
362
357
  next
363
358
  end
364
359
 
@@ -391,9 +386,31 @@ class Pry
391
386
  end
392
387
  next if gem_install_failed
393
388
 
394
- command.options.delete :stub_info
395
389
  output.puts "Installation of `#{name}` successful! Type `help #{name}` for information"
396
390
  end
397
391
  end
398
392
  end
393
+
394
+ # Wraps the return result of process_commands, indicates if the
395
+ # result IS a command and what kind of command (e.g void)
396
+ class Result
397
+ attr_reader :retval
398
+
399
+ def initialize(is_command, retval = nil)
400
+ @is_command, @retval = is_command, retval
401
+ end
402
+
403
+ # Is the result a command?
404
+ # @return [Boolean]
405
+ def command?
406
+ @is_command
407
+ end
408
+
409
+ # Is the result a command and if it is, is it a void command?
410
+ # (one that does not return a value)
411
+ # @return [Boolean]
412
+ def void_command?
413
+ retval == Command::VOID_VALUE
414
+ end
415
+ end
399
416
  end
@@ -3,124 +3,196 @@ class Pry
3
3
 
4
4
  Documentation = Pry::CommandSet.new do
5
5
 
6
- command "ri", "View ri documentation. e.g `ri Array#each`" do |*args|
7
- run ".ri", *args
6
+ command_class "ri", "View ri documentation. e.g `ri Array#each`" do
7
+ banner <<-BANNER
8
+ Usage: ri [spec]
9
+ e.g. ri Array#each
10
+
11
+ Relies on the ri executable being available. See also: show-doc.
12
+ BANNER
13
+
14
+ def process
15
+ run ".ri", *args
16
+ end
8
17
  end
9
18
 
10
- command "show-doc", "Show the comments above METH. Type `show-doc --help` for more info. Aliases: \?", :shellwords => false do |*args|
11
- opts, meth = parse_options!(args, :method_object) do |opt|
12
- opt.banner unindent <<-USAGE
13
- Usage: show-doc [OPTIONS] [METH]
14
- Show the comments above method METH. Tries instance methods first and then methods by default.
15
- e.g show-doc hello_method
16
- USAGE
19
+ command_class "show-doc", "Show the comments above METH. Type `show-doc --help` for more info. Aliases: \?", :shellwords => false do |*args|
20
+ banner <<-BANNER
21
+ Usage: show-doc [OPTIONS] [METH]
22
+ Show the comments above method METH. Tries instance methods first and then methods by default.
23
+ e.g show-doc hello_method
24
+ BANNER
17
25
 
26
+ def options(opt)
27
+ method_options(opt)
18
28
  opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
19
29
  end
20
30
 
21
- raise Pry::CommandError, "No documentation found." if meth.doc.nil? || meth.doc.empty?
22
-
23
- doc = process_comment_markup(meth.doc, meth.source_type)
24
- output.puts make_header(meth, doc)
25
- output.puts "#{text.bold("Owner:")} #{meth.owner || "N/A"}"
26
- output.puts "#{text.bold("Visibility:")} #{meth.visibility}"
27
- output.puts "#{text.bold("Signature:")} #{meth.signature}"
28
- output.puts
29
- render_output(opts.present?(:flood), false, doc)
31
+ def process
32
+ meth = method_object
33
+ raise Pry::CommandError, "No documentation found." if meth.doc.nil? || meth.doc.empty?
34
+
35
+ doc = process_comment_markup(meth.doc, meth.source_type)
36
+ output.puts make_header(meth, doc)
37
+ output.puts "#{text.bold("Owner:")} #{meth.owner || "N/A"}"
38
+ output.puts "#{text.bold("Visibility:")} #{meth.visibility}"
39
+ output.puts "#{text.bold("Signature:")} #{meth.signature}"
40
+ output.puts
41
+ render_output(opts.present?(:flood), false, doc)
42
+ end
30
43
  end
31
44
 
32
45
  alias_command "?", "show-doc"
33
46
 
34
- command "stat", "View method information and set _file_ and _dir_ locals. Type `stat --help` for more info.", :shellwords => false do |*args|
35
- target = target()
36
-
37
- opts, meth = parse_options!(args, :method_object) do |opt|
38
- opt.banner unindent <<-USAGE
47
+ command_class "stat", "View method information and set _file_ and _dir_ locals. Type `stat --help` for more info.", :shellwords => false do |*args|
48
+ banner <<-BANNER
39
49
  Usage: stat [OPTIONS] [METH]
40
50
  Show method information for method METH and set _file_ and _dir_ locals.
41
51
  e.g: stat hello_method
42
- USAGE
43
- end
44
-
45
- output.puts unindent <<-EOS
46
- Method Information:
47
- --
48
- Name: #{meth.name}
49
- Owner: #{meth.owner ? meth.owner : "Unknown"}
50
- Visibility: #{meth.visibility}
51
- Type: #{meth.is_a?(::Method) ? "Bound" : "Unbound"}
52
- Arity: #{meth.arity}
53
- Method Signature: #{meth.signature}
54
- Source Location: #{meth.source_location ? meth.source_location.join(":") : "Not found."}
55
- EOS
52
+ BANNER
53
+
54
+ def options(opt)
55
+ method_options(opt)
56
+ end
57
+
58
+ def process
59
+ meth = method_object
60
+ output.puts unindent <<-EOS
61
+ Method Information:
62
+ --
63
+ Name: #{meth.name}
64
+ Owner: #{meth.owner ? meth.owner : "Unknown"}
65
+ Visibility: #{meth.visibility}
66
+ Type: #{meth.is_a?(::Method) ? "Bound" : "Unbound"}
67
+ Arity: #{meth.arity}
68
+ Method Signature: #{meth.signature}
69
+ Source Location: #{meth.source_location ? meth.source_location.join(":") : "Not found."}
70
+ EOS
71
+ end
56
72
  end
57
73
 
58
- command "gist", "Gist a method or expression history to github. Type `gist --help` for more info.", :requires_gem => "gist", :shellwords => false do |*args|
59
- require 'gist'
74
+ command_class "gist", "Gist a method or expression history to github. Type `gist --help` for more info.", :requires_gem => "gist", :shellwords => false do
75
+ attr_accessor :content
76
+ attr_accessor :code_type
77
+ attr_accessor :input_ranges
60
78
 
61
- target = target()
79
+ def setup
80
+ require 'gist'
81
+ end
62
82
 
63
- opts = parse_options!(args) do |opt|
83
+ def options(opt)
64
84
  opt.banner unindent <<-USAGE
65
- Usage: gist [OPTIONS] [METH]
66
- Gist method (doc or source) or input expression to github.
67
- Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
68
- e.g: gist -m my_method
69
- e.g: gist -d my_method
70
- e.g: gist -i 1..10
71
- USAGE
85
+ Usage: gist [OPTIONS] [METH]
86
+ Gist method (doc or source) or input expression to github.
87
+ Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
88
+ e.g: gist -m my_method
89
+ e.g: gist -d my_method
90
+ e.g: gist -i 1..10
91
+ USAGE
72
92
 
73
93
  opt.on :d, :doc, "Gist a method's documentation.", true
74
94
  opt.on :m, :method, "Gist a method's source.", true
95
+ opt.on :f, :file, "Gist a file.", true
75
96
  opt.on :p, :public, "Create a public gist (default: false)", :default => false
76
- opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional => true, :as => Range, :default => -5..-1
97
+ opt.on :l, :lines, "Only gist a subset of lines (only works with -m and -f)", :optional => true, :as => Range, :default => 1..-1
98
+ opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional => true,
99
+ :as => Range, :default => -5..-1 do |range|
100
+ input_ranges << absolute_index_range(range, _pry_.input_array.length)
101
+ end
77
102
  end
78
103
 
79
- type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
80
- if opts.present?(:in)
81
- code_type = :ruby
82
- content = ""
83
- normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length)
84
- input_items = _pry_.input_array[normalized_range] || []
85
-
86
- input_items.each_with_index.map do |code, index|
87
- corrected_index = index + normalized_range.first
88
- if code && code != ""
89
- content << code
90
- content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}" if code !~ /;\Z/
104
+ def process
105
+ if opts.present?(:in)
106
+ in_option
107
+ elsif opts.present?(:file)
108
+ file_option
109
+ elsif opts.present?(:doc)
110
+ doc_option
111
+ elsif opts.present?(:method)
112
+ method_option
113
+ end
114
+
115
+ perform_gist
116
+ end
117
+
118
+ def in_option
119
+ self.code_type = :ruby
120
+ self.content = ""
121
+
122
+ input_ranges.each do |range|
123
+ input_expressions = _pry_.input_array[range] || []
124
+ input_expressions.each_with_index.map do |code, index|
125
+ corrected_index = index + range.first
126
+ if code && code != ""
127
+ self.content << code
128
+ if code !~ /;\Z/
129
+ self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}"
130
+ end
131
+ end
91
132
  end
92
133
  end
93
- elsif opts.present?(:doc)
134
+ end
135
+
136
+ def file_option
137
+ whole_file = File.read(File.expand_path(opts[:f]))
138
+ if opts.present?(:lines)
139
+ self.content = restrict_to_lines(whole_file, opts[:l])
140
+ else
141
+ self.content = whole_file
142
+ end
143
+ end
144
+
145
+ def doc_option
94
146
  meth = get_method_or_raise(opts[:d], target, {})
95
- content = meth.doc
96
- code_type = meth.source_type
147
+ self.content = meth.doc
148
+ self.code_type = meth.source_type
97
149
 
98
150
  text.no_color do
99
- content = process_comment_markup(content, code_type)
151
+ self.content = process_comment_markup(self.content, self.code_type)
100
152
  end
101
- code_type = :plain
102
- elsif opts.present?(:method)
153
+ self.code_type = :plain
154
+ end
155
+
156
+ def method_option
103
157
  meth = get_method_or_raise(opts[:m], target, {})
104
- content = meth.source
105
- code_type = meth.source_type
158
+ method_source = meth.source
159
+ if opts.present?(:lines)
160
+ self.content = restrict_to_lines(method_source, opts[:l])
161
+ else
162
+ self.content = method_source
163
+ end
164
+
165
+ self.code_type = meth.source_type
106
166
  end
107
167
 
108
- # prevent Gist from exiting the session on error
109
- begin
110
- link = Gist.write([:extension => ".#{type_map[code_type]}",
111
- :input => content],
112
- !opts[:p])
113
- rescue SystemExit
168
+ def perform_gist
169
+ type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
170
+
171
+ # prevent Gist from exiting the session on error
172
+ begin
173
+ extname = opts.present?(:file) ? ".#{gist_file_extension(opts[:f])}" : ".#{type_map[self.code_type]}"
174
+
175
+ link = Gist.write([:extension => extname,
176
+ :input => self.content],
177
+ !opts[:p])
178
+ rescue SystemExit
179
+ end
180
+
181
+ if link
182
+ Gist.copy(link)
183
+ output.puts "Gist created at #{link} and added to clipboard."
184
+ end
114
185
  end
115
186
 
116
- if link
117
- Gist.copy(link)
118
- output.puts "Gist created at #{link} and added to clipboard."
187
+ def restrict_to_lines(content, lines)
188
+ line_range = one_index_range(lines)
189
+ content.lines.to_a[line_range].join
119
190
  end
120
- end
121
191
 
192
+ def gist_file_extension(file_name)
193
+ file_name.split(".").last
194
+ end
122
195
 
123
- helpers do
124
196
  def comment_expression_result_for_gist(result)
125
197
  content = ""
126
198
  result.lines.each_with_index do |line, index|
@@ -133,8 +205,7 @@ class Pry
133
205
  content
134
206
  end
135
207
  end
136
-
137
-
138
208
  end
139
209
  end
140
210
  end
211
+