pry-doc 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pry-doc.rb CHANGED
@@ -20,20 +20,41 @@ class Pry
20
20
  end
21
21
 
22
22
  module MethodInfo
23
- @doc_cache = {}
24
- class << self; attr_reader :doc_cache; end
25
23
 
26
24
  # Convert a method object into the `Class#method` string notation.
27
25
  # @param [Method, UnboundMethod] meth
28
26
  # @return [String] The method in string receiver notation.
29
27
  def self.receiver_notation_for(meth)
30
- if meth.owner.ancestors.first == meth.owner
31
- "#{meth.owner.name}##{meth.name}"
32
- else
28
+ if is_singleton?(meth)
33
29
  "#{meth.owner.to_s[/#<.+?:(.+?)>/, 1]}.#{meth.name}"
30
+ else
31
+ "#{meth.owner.name}##{meth.name}"
34
32
  end
35
33
  end
36
34
 
35
+ # Retrives aliases of a method
36
+ # @param [Method, UnboundMethod] meth The method object.
37
+ # @return [Array] The aliases of a method if it exists
38
+ # otherwise, return empty array
39
+ def self.aliases(meth)
40
+ host = method_host(meth)
41
+ method_type = is_singleton?(meth) ? :method : :instance_method
42
+
43
+ methods = Pry::Method.send(:all_from_common, host, method_type, false).
44
+ map { |m| m.instance_variable_get(:@method) }
45
+
46
+ methods.select { |m| host.send(method_type,m.name) == host.send(method_type,meth.name) }.
47
+ reject { |m| m.name == meth.name }.
48
+ map { |m| host.send(method_type,m.name) }
49
+ end
50
+
51
+ # Checks whether method is a singleton (i.e class method)
52
+ # @param [Method, UnboundMethod] meth
53
+ # @param [Boolean] true if singleton
54
+ def self.is_singleton?(meth)
55
+ meth.owner.ancestors.first != meth.owner
56
+ end
57
+
37
58
  # Check whether the file containing the method is already cached.
38
59
  # @param [Method, UnboundMethod] meth The method object.
39
60
  # @return [Boolean] Whether the method is cached.
@@ -44,10 +65,14 @@ class Pry
44
65
  def self.registry_lookup(meth)
45
66
  obj = YARD::Registry.at(receiver_notation_for(meth))
46
67
 
47
- # YARD thinks that some methods are on Object when
48
- # they're actually on Kernel; so try again on Object if Kernel fails.
49
- if obj.nil? && meth.owner == Kernel
50
- obj = YARD::Registry.at("Object##{meth.name}")
68
+ if obj.nil?
69
+ if !(aliases = aliases(meth)).empty?
70
+ obj = YARD::Registry.at(receiver_notation_for(aliases.first))
71
+ elsif meth.owner == Kernel
72
+ # YARD thinks that some methods are on Object when
73
+ # they're actually on Kernel; so try again on Object if Kernel fails.
74
+ obj = YARD::Registry.at("Object##{meth.name}")
75
+ end
51
76
  end
52
77
  obj
53
78
  end
@@ -71,16 +96,116 @@ class Pry
71
96
  end
72
97
  end
73
98
 
99
+ # Attempts to find the c source files if method belongs to a gem
100
+ # and use YARD to parse and cache the source files for display
101
+ #
102
+ # @param [Method, UnboundMethod] meth The method object.
103
+ def self.parse_and_cache_if_gem_cext(meth)
104
+ if gem_dir = find_gem_dir(meth)
105
+ if c_files_found?(gem_dir)
106
+ warn "Scanning and caching *.c files..."
107
+ YARD.parse("#{gem_dir}/**/*.c")
108
+ end
109
+ end
110
+ end
111
+
112
+ # @param [String] root directory path of gem that method belongs to
113
+ # @return [Boolean] true if c files exist?
114
+ def self.c_files_found?(gem_dir)
115
+ Dir.glob("#{gem_dir}/**/*.c").count > 0
116
+ end
117
+
118
+ # @return [Object] The host of the method (receiver or owner).
119
+ def self.method_host(meth)
120
+ is_singleton?(meth) ? meth.receiver : meth.owner
121
+ end
122
+
123
+ # FIXME: this is unnecessarily limited to ext/ and lib/ folders
124
+ # @return [String] The root folder of a given gem directory.
125
+ def self.gem_root(dir)
126
+ dir.split(/\/(?:lib|ext)(?:\/|$)/).first
127
+ end
128
+
129
+ # @param [Method, UnboundMethod] meth The method object.
130
+ # @return [String] root directory path of gem that method belongs to,
131
+ # nil if could not be found
132
+ def self.find_gem_dir(meth)
133
+ host = method_host(meth)
134
+
135
+ begin
136
+ host_source_location, _ = WrappedModule.new(host).source_location
137
+ break if host_source_location != nil
138
+ host = eval(host.namespace_name)
139
+ end while host
140
+
141
+ # we want to exclude all source_locations that aren't gems (i.e
142
+ # stdlib)
143
+ if host_source_location && host_source_location =~ %r{/gems/}
144
+ gem_root(host_source_location)
145
+ else
146
+
147
+ # the WrappedModule approach failed, so try our backup approach
148
+ gem_dir_from_method(meth)
149
+ end
150
+ end
151
+
152
+ # Try to guess what the gem name will be based on the name of the module.
153
+ # We try a few approaches here depending on the `guess` parameter.
154
+ # @param [String] name The name of the module.
155
+ # @param [Fixnum] guess The current guessing approach to use.
156
+ # @return [String, nil] The guessed gem name, or `nil` if out of guesses.
157
+ def self.guess_gem_name_from_module_name(name, guess)
158
+ case guess
159
+ when 0
160
+ name.downcase
161
+ when 1
162
+ name.scan(/[A-Z][a-z]+/).map(&:downcase).join('_')
163
+ when 2
164
+ name.scan(/[A-Z][a-z]+/).map(&:downcase).join('_').sub("_", "-")
165
+ when 3
166
+ name.scan(/[A-Z][a-z]+/).map(&:downcase).join('-')
167
+ when 4
168
+ name
169
+ else
170
+ nil
171
+ end
172
+ end
173
+
174
+ # Try to recover the gem directory of a gem based on a method object.
175
+ # @param [Method, UnboundMethod] meth The method object.
176
+ # @return [String, nil] The located gem directory.
177
+ def self.gem_dir_from_method(meth)
178
+ guess = 0
179
+
180
+ host = method_host(meth)
181
+ root_module_name = host.name.split("::").first
182
+ while gem_name = guess_gem_name_from_module_name(root_module_name, guess)
183
+ matches = $LOAD_PATH.grep %r{/gems/#{gem_name}} if !gem_name.empty?
184
+ if matches && matches.any?
185
+ return gem_root(matches.first)
186
+ else
187
+ guess += 1
188
+ end
189
+ end
190
+
191
+ nil
192
+ end
193
+
74
194
  # Cache the file that holds the method or return immediately if file is
75
195
  # already cached. Return if the method cannot be cached -
76
- # i.e is a C method.
196
+ # i.e is a C stdlib method.
77
197
  # @param [Method, UnboundMethod] meth The method object.
78
198
  def self.cache(meth)
79
199
  file, _ = meth.source_location
80
- return if !file
200
+
81
201
  return if is_eval_method?(meth)
82
202
  return if cached?(meth)
83
203
 
204
+ if !file
205
+ parse_and_cache_if_gem_cext(meth)
206
+ return
207
+ end
208
+
84
209
  log.enter_level(Logger::FATAL) do
85
210
  YARD.parse(file)
86
211
  end
@@ -88,4 +213,3 @@ class Pry
88
213
  end
89
214
  end
90
215
 
91
-
@@ -0,0 +1,225 @@
1
+ # pry-doc.rb
2
+ # (C) John Mair (banisterfiend); MIT license
3
+
4
+ direc = File.dirname(__FILE__)
5
+
6
+ require "#{direc}/pry-doc/version"
7
+ require "yard"
8
+
9
+ if RUBY_VERSION =~ /1.9/
10
+ YARD::Registry.load_yardoc("#{File.dirname(__FILE__)}/pry-doc/core_docs_19")
11
+ else
12
+ YARD::Registry.load_yardoc("#{File.dirname(__FILE__)}/pry-doc/core_docs_18")
13
+ end
14
+
15
+ class Pry
16
+
17
+ # do not use pry-doc if rbx is active
18
+ if !Object.const_defined?(:RUBY_ENGINE) || RUBY_ENGINE !~ /rbx/
19
+ self.config.has_pry_doc = true
20
+ end
21
+
22
+ module MethodInfo
23
+
24
+ # Convert a method object into the `Class#method` string notation.
25
+ # @param [Method, UnboundMethod] meth
26
+ # @return [String] The method in string receiver notation.
27
+ def self.receiver_notation_for(meth)
28
+ if is_singleton?(meth)
29
+ "#{meth.owner.to_s[/#<.+?:(.+?)>/, 1]}.#{meth.name}"
30
+ else
31
+ "#{meth.owner.name}##{meth.name}"
32
+ end
33
+ end
34
+
35
+ # Retrives aliases of a method
36
+ # @param [Method, UnboundMethod] meth The method object.
37
+ # @return [Array] The aliases of a method if it exists
38
+ # otherwise, return empty array
39
+ def self.aliases(meth)
40
+ host = method_host(meth)
41
+ method_type = is_singleton?(meth) ? :method : :instance_method
42
+
43
+ methods = Pry::Method.send(:all_from_common, host, method_type, false).
44
+ map { |m| m.instance_variable_get(:@method) }
45
+
46
+ methods.select { |m| host.send(method_type,m.name) == host.send(method_type,meth.name) }.
47
+ reject { |m| m.name == meth.name }.
48
+ map { |m| host.send(method_type,m.name) }
49
+ end
50
+
51
+ # Checks whether method is a singleton (i.e class method)
52
+ # @param [Method, UnboundMethod] meth
53
+ # @param [Boolean] true if singleton
54
+ def self.is_singleton?(meth)
55
+ meth.owner.ancestors.first != meth.owner
56
+ end
57
+
58
+ # Check whether the file containing the method is already cached.
59
+ # @param [Method, UnboundMethod] meth The method object.
60
+ # @return [Boolean] Whether the method is cached.
61
+ def self.cached?(meth)
62
+ !!registry_lookup(meth)
63
+ end
64
+
65
+ def self.registry_lookup(meth)
66
+ obj = YARD::Registry.at(receiver_notation_for(meth))
67
+
68
+ if obj.nil?
69
+ if !(aliases = aliases(meth)).empty?
70
+ obj = YARD::Registry.at(receiver_notation_for(aliases.first))
71
+ elsif meth.owner == Kernel
72
+ # YARD thinks that some methods are on Object when
73
+ # they're actually on Kernel; so try again on Object if Kernel fails.
74
+ obj = YARD::Registry.at("Object##{meth.name}")
75
+ end
76
+ end
77
+ obj
78
+ end
79
+
80
+ # Retrieve the YARD object that contains the method data.
81
+ # @param [Method, UnboundMethod] meth The method object.
82
+ # @return [YARD::CodeObjects::MethodObject] The YARD data for the method.
83
+ def self.info_for(meth)
84
+ cache(meth)
85
+ registry_lookup(meth)
86
+ end
87
+
88
+ # Determine whether a method is an eval method.
89
+ # @return [Boolean] Whether the method is an eval method.
90
+ def self.is_eval_method?(meth)
91
+ file, _ = meth.source_location
92
+ if file =~ /(\(.*\))|<.*>/
93
+ true
94
+ else
95
+ false
96
+ end
97
+ end
98
+
99
+ # Attempts to find the c source files if method belongs to a gem
100
+ # and use YARD to parse and cache the source files for display
101
+ #
102
+ # @param [Method, UnboundMethod] meth The method object.
103
+ def self.parse_and_cache_if_gem_cext(meth)
104
+ if gem_dir = find_gem_dir(meth)
105
+ if c_files_found?(gem_dir)
106
+ warn "Scanning and caching *.c files..."
107
+ YARD.parse("#{gem_dir}/ext/**/*.c")
108
+ end
109
+ end
110
+ end
111
+
112
+ # @param [String] root directory path of gem that method belongs to
113
+ # @return [Boolean] true if c files exist?
114
+ def self.c_files_found?(gem_dir)
115
+ Dir.glob("#{gem_dir}/ext/**/*.c").count > 0
116
+ end
117
+
118
+ # @return [Object] The host of the method (receiver or owner).
119
+ def self.method_host(meth)
120
+ is_singleton?(meth) ? meth.receiver : meth.owner
121
+ end
122
+
123
+ # FIXME: this is unnecessarily limited to ext/ and lib/ folders
124
+ # @return [String] The root folder of a given gem directory.
125
+ def self.gem_root(dir)
126
+ dir.split(/\/(?:lib|ext)(?:\/|$)/).first
127
+ end
128
+
129
+ # @param [Method, UnboundMethod] meth The method object.
130
+ # @return [String] root directory path of gem that method belongs to,
131
+ # nil if could not be found
132
+ def self.find_gem_dir(meth)
133
+ host = method_host(meth)
134
+
135
+ begin
136
+ <<<<<<< HEAD
137
+ host_source_location, _ = WrappedModule.new(host).source_location
138
+ break if host_source_location != nil
139
+ =======
140
+ host_location, _ = WrappedModule.new(host).source_location
141
+ break if host_location
142
+ >>>>>>> 7f4d702f529aba494ca935a89409d579e3b7146c
143
+ host = eval(host.namespace_name)
144
+ end while host
145
+
146
+ <<<<<<< HEAD
147
+ # we want to exclude all source_locations that aren't gems (i.e
148
+ # stdlib)
149
+ if host_source_location && host_source_location =~ %r{/gems/}
150
+ gem_root(host_source_location)
151
+ =======
152
+ if host_location
153
+ gem_root(host_location)
154
+ >>>>>>> 7f4d702f529aba494ca935a89409d579e3b7146c
155
+ else
156
+
157
+ # the WrappedModule approach failed, so try our backup approach
158
+ gem_dir_from_method(meth)
159
+ end
160
+ end
161
+
162
+ # Try to guess what the gem name will be based on the name of the module.
163
+ # We try a few approaches here depending on the `guess` parameter.
164
+ # @param [String] name The name of the module.
165
+ # @param [Fixnum] guess The current guessing approach to use.
166
+ # @return [String, nil] The guessed gem name, or `nil` if out of guesses.
167
+ def self.guess_gem_name_from_module_name(name, guess)
168
+ case guess
169
+ when 0
170
+ name.downcase
171
+ when 1
172
+ name.scan(/[A-Z][a-z]+/).map(&:downcase).join('_')
173
+ when 2
174
+ name.scan(/[A-Z][a-z]+/).map(&:downcase).join('_').sub("_", "-")
175
+ when 3
176
+ name.scan(/[A-Z][a-z]+/).map(&:downcase).join('-')
177
+ when 4
178
+ name
179
+ else
180
+ nil
181
+ end
182
+ end
183
+
184
+ # Try to recover the gem directory of a gem based on a method object.
185
+ # @param [Method, UnboundMethod] meth The method object.
186
+ # @return [String, nil] The located gem directory.
187
+ def self.gem_dir_from_method(meth)
188
+ guess = 0
189
+
190
+ host = method_host(meth)
191
+ root_module_name = host.name.split("::").first
192
+ while gem_name = guess_gem_name_from_module_name(root_module_name, guess)
193
+ matches = $LOAD_PATH.grep %r{/gems/#{gem_name}} if !gem_name.empty?
194
+ if matches && matches.any?
195
+ return gem_root(matches.first)
196
+ else
197
+ guess += 1
198
+ end
199
+ end
200
+
201
+ nil
202
+ end
203
+
204
+ # Cache the file that holds the method or return immediately if file is
205
+ # already cached. Return if the method cannot be cached -
206
+ # i.e is a C stdlib method.
207
+ # @param [Method, UnboundMethod] meth The method object.
208
+ def self.cache(meth)
209
+ file, _ = meth.source_location
210
+
211
+ return if is_eval_method?(meth)
212
+ return if cached?(meth)
213
+
214
+ if !file
215
+ parse_and_cache_if_gem_cext(meth)
216
+ return
217
+ end
218
+
219
+ log.enter_level(Logger::FATAL) do
220
+ YARD.parse(file)
221
+ end
222
+ end
223
+ end
224
+ end
225
+
@@ -1,3 +1,3 @@
1
1
  module PryDoc
2
- VERSION = "0.4.2"
2
+ VERSION = "0.4.3"
3
3
  end
data/test/test.rb CHANGED
@@ -4,13 +4,16 @@ require 'rubygems'
4
4
  require 'pry'
5
5
  require "#{direc}/../lib/pry-doc"
6
6
  require "#{direc}/test_helper"
7
+ require "#{direc}/gem_with_cext/gems/sample"
7
8
  require 'bacon'
8
9
  require 'set'
10
+ require 'fileutils'
9
11
 
10
12
  puts "Testing pry-doc version #{PryDoc::VERSION}..."
11
13
  puts "Ruby version: #{RUBY_VERSION}"
12
14
 
13
15
  describe PryDoc do
16
+
14
17
  describe "core C methods" do
15
18
  it 'should look up core (C) methods' do
16
19
  obj = Pry::MethodInfo.info_for(method(:puts))
@@ -66,11 +69,82 @@ describe PryDoc do
66
69
  end
67
70
  end
68
71
 
72
+ describe "C ext methods" do
73
+
74
+ it "should lookup C ext methods" do
75
+ obj = Pry::MethodInfo.info_for(Sample.instance_method(:unlink))
76
+ obj.should.not == nil
77
+ end
78
+
79
+ it "should lookup aliased C ext methods" do
80
+ obj = Pry::MethodInfo.info_for(Sample.instance_method(:remove))
81
+ obj.should.not == nil
82
+ end
83
+
84
+ it "should lookup C ext instance methods even when its owners don't have any ruby methods" do
85
+ obj = Pry::MethodInfo.info_for(Sample::A::B.instance_method(:unlink))
86
+ obj.should.not == nil
87
+ end
88
+
89
+ it "should lookup C ext class methods even when its owners don't have any ruby methods" do
90
+ obj = Pry::MethodInfo.info_for(Sample::A::B.method(:unlink))
91
+ obj.should.not == nil
92
+ end
93
+ end
94
+
69
95
  describe "C stdlib methods" do
70
96
  it "should return nil for C stdlib methods" do
71
97
  obj = Pry::MethodInfo.info_for(Readline.method(:readline))
72
98
  obj.should == nil
73
99
  end
74
100
  end
101
+
102
+ describe ".aliases" do
103
+ it "should return empty array if method does not have any alias" do
104
+ aliases = Pry::MethodInfo.aliases(Sample.instance_method(:some_meth))
105
+ aliases.should == []
106
+ end
107
+
108
+ it "should return aliases of a (C) method" do
109
+ orig = Sample.instance_method(:unlink)
110
+ copy = Sample.instance_method(:remove)
111
+
112
+ aliases = Pry::MethodInfo.aliases(orig)
113
+ aliases.should == [copy]
114
+
115
+ aliases = Pry::MethodInfo.aliases(copy)
116
+ aliases.should == [orig]
117
+ end
118
+
119
+ it "should return aliases of a ruby method" do
120
+ C.class_eval { alias msg message }
121
+
122
+ orig = C.instance_method(:message)
123
+ copy = C.instance_method(:msg)
124
+
125
+ aliases = Pry::MethodInfo.aliases(orig)
126
+ aliases.should == [copy]
127
+
128
+ aliases = Pry::MethodInfo.aliases(copy)
129
+ aliases.should == [orig]
130
+ end
131
+
132
+ it "should return aliases of protected method" do
133
+ orig = Sample.instance_method(:unlink_1)
134
+ copy = Sample.instance_method(:remove_1)
135
+
136
+ aliases = Pry::MethodInfo.aliases(orig)
137
+ aliases.should == [copy]
138
+ end
139
+
140
+ it "should return aliases of private method" do
141
+ orig = Sample.instance_method(:unlink_2)
142
+ copy = Sample.instance_method(:remove_2)
143
+
144
+ aliases = Pry::MethodInfo.aliases(orig)
145
+ aliases.should == [copy]
146
+ end
147
+ end
148
+
75
149
  end
76
150
 
data/test/test_helper.rb CHANGED
@@ -1,3 +1,11 @@
1
+ direc = File.dirname(__FILE__)
2
+
1
3
  class C
2
4
  def message; end
3
5
  end
6
+
7
+ puts
8
+ puts "Building Sample Gem with C Extensions for testing.."
9
+ system("cd #{direc}/gem_with_cext/gems/ext/ && ruby extconf.rb && make")
10
+ puts
11
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pry-doc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-17 00:00:00.000000000 Z
12
+ date: 2012-07-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: yard
16
- requirement: &70101923678800 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 0.8.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70101923678800
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.8.1
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: pry
27
- requirement: &70101923689680 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 0.9.0
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *70101923689680
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.0
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: bacon
38
- requirement: &70101923687600 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,7 +53,12 @@ dependencies:
43
53
  version: 1.1.0
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70101923687600
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.1.0
47
62
  description: Provides YARD and extended documentation support for Pry
48
63
  email: jrmair@gmail.com
49
64
  executables: []
@@ -3083,6 +3098,7 @@ files:
3083
3098
  - lib/pry-doc/core_docs_19/proxy_types
3084
3099
  - lib/pry-doc/version.rb
3085
3100
  - lib/pry-doc.rb
3101
+ - lib/pry-doc.rb.orig
3086
3102
  - test/test.rb
3087
3103
  - test/test_helper.rb
3088
3104
  - HISTORY
@@ -3109,7 +3125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
3109
3125
  version: '0'
3110
3126
  requirements: []
3111
3127
  rubyforge_project:
3112
- rubygems_version: 1.8.16
3128
+ rubygems_version: 1.8.24
3113
3129
  signing_key:
3114
3130
  specification_version: 3
3115
3131
  summary: Provides YARD and extended documentation support for Pry