pry 0.9.11.4 → 0.9.12pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.travis.yml +2 -0
  2. data/Rakefile +4 -0
  3. data/lib/pry.rb +1 -1
  4. data/lib/pry/cli.rb +14 -8
  5. data/lib/pry/code.rb +3 -3
  6. data/lib/pry/command.rb +20 -5
  7. data/lib/pry/command_set.rb +3 -3
  8. data/lib/pry/commands.rb +1 -1
  9. data/lib/pry/commands/disabled_commands.rb +2 -0
  10. data/lib/pry/commands/ls.rb +1 -2
  11. data/lib/pry/commands/reload_code.rb +8 -1
  12. data/lib/pry/commands/show_info.rb +27 -4
  13. data/lib/pry/commands/show_source.rb +2 -1
  14. data/lib/pry/commands/whereami.rb +87 -19
  15. data/lib/pry/completion.rb +7 -4
  16. data/lib/pry/helpers/base_helpers.rb +5 -2
  17. data/lib/pry/helpers/command_helpers.rb +3 -1
  18. data/lib/pry/helpers/documentation_helpers.rb +18 -7
  19. data/lib/pry/helpers/table.rb +4 -4
  20. data/lib/pry/indent.rb +2 -7
  21. data/lib/pry/method.rb +89 -129
  22. data/lib/pry/method/disowned.rb +53 -0
  23. data/lib/pry/method/weird_method_locator.rb +186 -0
  24. data/lib/pry/module_candidate.rb +3 -3
  25. data/lib/pry/pager.rb +13 -11
  26. data/lib/pry/pry_class.rb +10 -3
  27. data/lib/pry/terminal.rb +73 -0
  28. data/lib/pry/version.rb +1 -1
  29. data/lib/pry/wrapped_module.rb +51 -1
  30. data/spec/command_helpers_spec.rb +21 -1
  31. data/spec/commands/ls_spec.rb +4 -0
  32. data/spec/commands/show_doc_spec.rb +28 -28
  33. data/spec/commands/show_source_spec.rb +70 -22
  34. data/spec/commands/whereami_spec.rb +60 -11
  35. data/spec/documentation_helper_spec.rb +73 -0
  36. data/spec/fixtures/whereami_helper.rb +6 -0
  37. data/spec/helpers/table_spec.rb +19 -0
  38. data/spec/method_spec.rb +24 -7
  39. metadata +13 -7
  40. data/lib/pry/commands/deprecated_commands.rb +0 -2
  41. data/lib/pry/terminal_info.rb +0 -48
@@ -0,0 +1,186 @@
1
+ class Pry
2
+ class Method
3
+
4
+ # This class is responsible for locating the *real* `Pry::Method`
5
+ # object captured by a binding.
6
+ #
7
+ # Given a `Binding` from inside a method and a 'seed' Pry::Method object,
8
+ # there are primarily two situations where the seed method doesn't match
9
+ # the Binding:
10
+ # 1. The Pry::Method is from a subclass 2. The Pry::Method represents a method of the same name
11
+ # while the original was renamed to something else. For 1. we
12
+ # search vertically up the inheritance chain,
13
+ # and for 2. we search laterally along the object's method table.
14
+ #
15
+ # When we locate the method that matches the Binding we wrap it in
16
+ # Pry::Method and return it, or return nil if we fail.
17
+ class WeirdMethodLocator
18
+ class << self
19
+
20
+ # Whether the given method object matches the associated binding.
21
+ # If the method object does not match the binding, then it's
22
+ # most likely not the method captured by the binding, and we
23
+ # must commence a search.
24
+ #
25
+ # @param [Pry::Method] method
26
+ # @param [Binding] b
27
+ # @return [Boolean]
28
+ def normal_method?(method, b)
29
+ method && (method.source_file && method.source_range rescue false) &&
30
+ File.expand_path(method.source_file) == File.expand_path(b.eval('__FILE__')) &&
31
+ method.source_range.include?(b.eval('__LINE__'))
32
+ end
33
+
34
+ def weird_method?(method, b)
35
+ !normal_method?(method, b)
36
+ end
37
+ end
38
+
39
+ attr_accessor :method
40
+ attr_accessor :target
41
+
42
+ # @param [Pry::Method] method The seed method.
43
+ # @param [Binding] target The Binding that captures the method
44
+ # we want to locate.
45
+ def initialize(method, target)
46
+ @method, @target = method, target
47
+ end
48
+
49
+ # @return [Pry::Method, nil] The Pry::Method that matches the
50
+ # given binding.
51
+ def get_method
52
+ find_method_in_superclass || find_renamed_method
53
+ end
54
+
55
+ # @return [Boolean] Whether the Pry::Method is unrecoverable
56
+ # This usually happens when the method captured by the Binding
57
+ # has been subsequently deleted.
58
+ def lost_method?
59
+ !!(get_method.nil? && renamed_method_source_location)
60
+ end
61
+
62
+ private
63
+
64
+ def normal_method?(method)
65
+ self.class.normal_method?(method, target)
66
+ end
67
+
68
+ def target_self
69
+ target.eval('self')
70
+ end
71
+
72
+ def target_file
73
+ pry_file? ? target.eval('__FILE__') : File.expand_path(target.eval('__FILE__'))
74
+ end
75
+
76
+ def target_line
77
+ target.eval('__LINE__')
78
+ end
79
+
80
+ def pry_file?
81
+ Pry.eval_path == target.eval('__FILE__')
82
+ end
83
+
84
+ # it's possible in some cases that the method we find by this approach is a sub-method of
85
+ # the one we're currently in, consider:
86
+ #
87
+ # class A; def b; binding.pry; end; end
88
+ # class B < A; def b; super; end; end
89
+ #
90
+ # Given that we can normally find the source_range of methods, and that we know which
91
+ # __FILE__ and __LINE__ the binding is at, we can hope to disambiguate these cases.
92
+ #
93
+ # This obviously won't work if the source is unavaiable for some reason, or if both
94
+ # methods have the same __FILE__ and __LINE__, or if we're in rbx where b.eval('__LINE__')
95
+ # is broken.
96
+ #
97
+ # @return [Pry::Method, nil] The Pry::Method representing the
98
+ # superclass method.
99
+ def find_method_in_superclass
100
+ guess = method
101
+
102
+ while guess
103
+ # needs rescue if this is a Disowned method or a C method or something...
104
+ # TODO: Fix up the exception handling so we don't need a bare rescue
105
+ if normal_method?(guess)
106
+ return guess
107
+ else
108
+ guess = guess.super
109
+ end
110
+ end
111
+
112
+ # Uhoh... none of the methods in the chain had the right __FILE__ and __LINE__
113
+ # This may be caused by rbx https://github.com/rubinius/rubinius/issues/953,
114
+ # or other unknown circumstances (TODO: we should warn the user when this happens)
115
+ nil
116
+ end
117
+
118
+ # This is the case where the name of a method has changed
119
+ # (via alias_method) so we locate the Method object for the
120
+ # renamed method.
121
+ #
122
+ # @return [Pry::Method, nil] The Pry::Method representing the
123
+ # renamed method
124
+ def find_renamed_method
125
+ return if !valid_file?(target_file)
126
+ alias_name = all_methods_for(target_self).find do |v|
127
+ expanded_source_location(target_self.method(v).source_location) == renamed_method_source_location
128
+ end
129
+
130
+ alias_name && Pry::Method(target_self.method(alias_name))
131
+ end
132
+
133
+ def expanded_source_location(sl)
134
+ return if !sl
135
+
136
+ if pry_file?
137
+ sl
138
+ else
139
+ [File.expand_path(sl.first), sl.last]
140
+ end
141
+ end
142
+
143
+ # Use static analysis to locate the start of the method definition.
144
+ # We have the `__FILE__` and `__LINE__` from the binding and the
145
+ # original name of the method so we search up until we find a
146
+ # def/define_method, etc defining a method of the appropriate name.
147
+ #
148
+ # @return [Array<String, Fixnum>] The `source_location` of the
149
+ # renamed method
150
+ def renamed_method_source_location
151
+ return @original_method_source_location if defined?(@original_method_source_location)
152
+
153
+ source_index = lines_for_file(target_file)[0..(target_line - 1)].rindex do |v|
154
+ Pry::Method.method_definition?(method.name, v)
155
+ end
156
+
157
+ @original_method_source_location = source_index &&
158
+ [target_file, index_to_line_number(source_index)]
159
+ end
160
+
161
+ def index_to_line_number(index)
162
+ # Pry.line_buffer is 0-indexed
163
+ pry_file? ? index : index + 1
164
+ end
165
+
166
+ def valid_file?(file)
167
+ File.exists?(file) || Pry.eval_path == file
168
+ end
169
+
170
+ def lines_for_file(file)
171
+ @lines_for_file ||= {}
172
+ @lines_for_file[file] ||= if Pry.eval_path == file
173
+ Pry.line_buffer
174
+ else
175
+ File.readlines(file)
176
+ end
177
+ end
178
+
179
+ def all_methods_for(obj)
180
+ obj.public_methods(false) +
181
+ obj.private_methods(false) +
182
+ obj.protected_methods(false)
183
+ end
184
+ end
185
+ end
186
+ end
@@ -52,8 +52,8 @@ class Pry
52
52
  # @return [String] The source for the candidate, i.e the
53
53
  # complete module/class definition.
54
54
  def source
55
+ return nil if file.nil?
55
56
  return @source if @source
56
- raise CommandError, "Could not locate source for #{wrapped}!" if file.nil?
57
57
 
58
58
  @source = strip_leading_whitespace(Pry::Code.from_file(file).expression_at(line, number_of_lines_in_first_chunk))
59
59
  end
@@ -61,10 +61,10 @@ class Pry
61
61
  # @raise [Pry::CommandError] If documentation cannot be found.
62
62
  # @return [String] The documentation for the candidate.
63
63
  def doc
64
+ return nil if file.nil?
64
65
  return @doc if @doc
65
- raise CommandError, "Could not locate doc for #{wrapped}!" if file.nil?
66
66
 
67
- @doc = strip_leading_hash_and_whitespace_from_ruby_comments(Pry::Code.from_file(file).comment_describing(line))
67
+ @doc = get_comment_content(Pry::Code.from_file(file).comment_describing(line))
68
68
  end
69
69
 
70
70
  # @return [Array, nil] A `[String, Fixnum]` pair representing the
@@ -12,8 +12,11 @@ class Pry::Pager
12
12
  case pager
13
13
  when nil
14
14
  no_pager = !SystemPager.available?
15
- is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
16
- (is_jruby || no_pager) ? SimplePager.new(text).page : SystemPager.new(text).page
15
+ if no_pager || Pry::Helpers::BaseHelpers.jruby?
16
+ SimplePager.new(text).page
17
+ else
18
+ SystemPager.new(text).page
19
+ end
17
20
  when :simple
18
21
  SimplePager.new(text).page
19
22
  when :system
@@ -24,12 +27,8 @@ class Pry::Pager
24
27
  end
25
28
 
26
29
  def self.page_size
27
- @page_size ||= begin
28
- require 'io/console'
29
- $stdout.winsize.first
30
- rescue LoadError
31
- 27
32
- end
30
+ rows = Pry::Terminal.screen_size
31
+ @page_size ||= (rows && rows.first || 27)
33
32
  end
34
33
 
35
34
  def initialize(text)
@@ -38,11 +37,14 @@ class Pry::Pager
38
37
 
39
38
  class SimplePager < Pry::Pager
40
39
  def page
40
+ # The pager size minus the number of lines used by the simple pager info bar.
41
+ page_size = Pry::Pager.page_size - 3
41
42
  text_array = @text.lines.to_a
42
- text_array.each_slice(Pry::Pager.page_size) do |chunk|
43
+
44
+ text_array.each_slice(page_size) do |chunk|
43
45
  puts chunk.join
44
- break if chunk.size < Pry::Pager.page_size
45
- if text_array.size > Pry::Pager.page_size
46
+ break if chunk.size < page_size
47
+ if text_array.size > page_size
46
48
  puts "\n<page break> --- Press enter to continue ( q<enter> to break ) --- <page break>"
47
49
  break if $stdin.gets.chomp == "q"
48
50
  end
@@ -263,9 +263,16 @@ class Pry
263
263
 
264
264
  def self.auto_resize!
265
265
  trap :WINCH do
266
- size = `stty size`.split(/\s+/).map &:to_i
267
- Readline.set_screen_size *size
268
- Readline.refresh_line
266
+ begin
267
+ Readline.set_screen_size *Terminal.size!
268
+ rescue => e
269
+ warn "\nPry.auto_resize!'s Readline.set_screen_size failed: #{e}"
270
+ end
271
+ begin
272
+ Readline.refresh_line
273
+ rescue => e
274
+ warn "\nPry.auto_resize!'s Readline.refresh_line failed: #{e}"
275
+ end
269
276
  end
270
277
  end
271
278
 
@@ -0,0 +1,73 @@
1
+ class Pry::Terminal
2
+ class << self
3
+ # Return a pair of [rows, columns] which gives the size of the window.
4
+ #
5
+ # If the window size cannot be determined, return nil.
6
+ def screen_size
7
+ rows, cols = actual_screen_size
8
+ if rows && cols
9
+ [rows.to_i, cols.to_i]
10
+ else
11
+ nil
12
+ end
13
+ end
14
+
15
+ # Return a screen size or a default if that fails.
16
+ def size! default = [25, 80]
17
+ screen_size || default
18
+ end
19
+
20
+ # Return a screen width or the default if that fails.
21
+ def width! default = 80
22
+ size![1]
23
+ end
24
+
25
+ def actual_screen_size
26
+ # The best way, if possible (requires non-jruby ≥1.9 or io-console gem)
27
+ screen_size_according_to_io_console or
28
+ # Fall back to the old standby, though it might be stale:
29
+ screen_size_according_to_env or
30
+ # Fall further back, though this one is also out of date without something
31
+ # calling Readline.set_screen_size
32
+ screen_size_according_to_readline or
33
+ # Windows users can otherwise run ansicon and get a decent answer:
34
+ screen_size_according_to_ansicon_env
35
+ end
36
+
37
+ def screen_size_according_to_io_console
38
+ return if Pry::Helpers::BaseHelpers.jruby?
39
+ require 'io/console'
40
+ $stdout.winsize if $stdout.tty? and $stdout.respond_to?(:winsize)
41
+ rescue LoadError
42
+ # They're probably on 1.8 without the io-console gem. We'll keep trying.
43
+ end
44
+
45
+ def screen_size_according_to_env
46
+ size = [ENV['LINES'] || ENV['ROWS'], ENV['COLUMNS']]
47
+ size if nonzero_column?(size)
48
+ end
49
+
50
+ def screen_size_according_to_readline
51
+ if Readline.respond_to?(:get_screen_size)
52
+ size = Readline.get_screen_size
53
+ size if nonzero_column?(size)
54
+ end
55
+ rescue Java::JavaLang::NullPointerException
56
+ # This rescue won't happen on jrubies later than:
57
+ # https://github.com/jruby/jruby/pull/436
58
+ nil
59
+ end
60
+
61
+ def screen_size_according_to_ansicon_env
62
+ return unless ENV['ANSICON'] =~ /\((.*)x(.*)\)/
63
+ size = [$2, $1]
64
+ size if nonzero_column?(size)
65
+ end
66
+
67
+ private
68
+
69
+ def nonzero_column?(size)
70
+ size[1].to_i > 0
71
+ end
72
+ end
73
+ end
@@ -1,3 +1,3 @@
1
1
  class Pry
2
- VERSION = "0.9.11.4"
2
+ VERSION = "0.9.12pre1"
3
3
  end
@@ -65,6 +65,30 @@ class Pry
65
65
  @doc = nil
66
66
  end
67
67
 
68
+ # Returns an array of the names of the constants accessible in the wrapped
69
+ # module. This provides a consistent interface between 1.8 and 1.9 and also
70
+ # avoids the problem of accidentally calling the singleton method
71
+ # `Module.constants`.
72
+ # @param [Boolean] inherit (true) Include the names of constants from
73
+ # included modules?
74
+ def constants(inherit = true)
75
+ method = Module.instance_method(:constants).bind(@wrapped)
76
+
77
+ # If we're on 1.8, we have to manually remove ancestors' constants. If
78
+ # we're on 1.9, though, it's better to use the built-in `inherit` param,
79
+ # since it doesn't do things like incorrectly remove Pry::Config.
80
+ if method.arity == 0
81
+ consts = method.call
82
+ if !inherit
83
+ consts -= (@wrapped.ancestors - [@wrapped]).map(&:constants).flatten
84
+ end
85
+ else
86
+ consts = method.call(inherit)
87
+ end
88
+
89
+ consts
90
+ end
91
+
68
92
  # The prefix that would appear before methods defined on this class.
69
93
  #
70
94
  # i.e. the "String." or "String#" in String.new and String#initialize.
@@ -205,13 +229,27 @@ class Pry
205
229
  @memoized_candidates[rank] ||= Candidate.new(self, rank)
206
230
  end
207
231
 
208
-
209
232
  # @return [Fixnum] The number of candidate definitions for the
210
233
  # current module.
211
234
  def number_of_candidates
212
235
  method_candidates.count
213
236
  end
214
237
 
238
+ # @note On JRuby 1.9 and higher, in certain conditions, this method chucks
239
+ # away its ability to be quick (when there are lots of monkey patches,
240
+ # like in Rails). However, it should be efficient enough on other rubies.
241
+ # @see https://github.com/jruby/jruby/issues/525
242
+ # @return [Enumerator, Array] on JRuby 1.9 and higher returns Array, on
243
+ # other rubies returns Enumerator
244
+ def candidates
245
+ enum = generator.new do |y|
246
+ (0...number_of_candidates).each do |num|
247
+ y.yield candidate(num)
248
+ end
249
+ end
250
+ Pry::Helpers::BaseHelpers.jruby_19? ? enum.to_a : enum
251
+ end
252
+
215
253
  # @return [Boolean] Whether YARD docs are available for this module.
216
254
  def yard_docs?
217
255
  !!(defined?(YARD) && YARD::Registry.at(name))
@@ -237,6 +275,18 @@ class Pry
237
275
 
238
276
  private
239
277
 
278
+ # Ruby 1.8 doesn't support `Enumerator` (it's called Generator instead)
279
+ #
280
+ # @return [Object] Return the appropriate generator class.
281
+ def generator
282
+ @generator ||= if defined?(Enumerator)
283
+ Enumerator
284
+ else
285
+ require 'generator'
286
+ Generator
287
+ end
288
+ end
289
+
240
290
  # @return [Pry::WrappedModule::Candidate] The candidate of rank 0,
241
291
  # that is the 'monkey patch' of this module with the highest
242
292
  # number of methods. It is considered the 'canonical' definition
@@ -5,5 +5,25 @@ describe Pry::Helpers::CommandHelpers do
5
5
  @helper = Pry::Helpers::CommandHelpers
6
6
  end
7
7
 
8
- # FIXME: currently no tests
8
+ describe "unindent" do
9
+ it "should remove the same prefix from all lines" do
10
+ @helper.unindent(" one\n two\n").should == "one\ntwo\n"
11
+ end
12
+
13
+ it "should not be phased by empty lines" do
14
+ @helper.unindent(" one\n\n two\n").should == "one\n\ntwo\n"
15
+ end
16
+
17
+ it "should only remove a common prefix" do
18
+ @helper.unindent(" one\n two\n").should == " one\ntwo\n"
19
+ end
20
+
21
+ it "should also remove tabs if present" do
22
+ @helper.unindent("\tone\n\ttwo\n").should == "one\ntwo\n"
23
+ end
24
+
25
+ it "should ignore lines starting with --" do
26
+ @helper.unindent(" one\n--\n two\n").should == "one\n--\ntwo\n"
27
+ end
28
+ end
9
29
  end