pry-doc 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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