Class: PinkSpoon::DocExtractor

Inherits:
Object
  • Object
show all
Defined in:
lib/pink_spoon/doc_extractor.rb

Overview

Fetches YARD/RDoc comments from installed gem source files for a given type + method. Handles both regular ‘def` and DSL-style definitions like `define_example_method :it` or `define_example_group_method :describe`.

Gem lookup uses the downcased first namespace component so that ‘RSpec::Core::ExampleGroup` correctly finds the `rspec-core` gem rather than trying the broken underscore form `r_spec`.

Defined Under Namespace

Classes: ConstantDefinitionFinder

Instance Method Summary collapse

Constructor Details

#initialize(root_path) ⇒ DocExtractor

Returns a new instance of DocExtractor.



14
15
16
17
# File 'lib/pink_spoon/doc_extractor.rb', line 14

def initialize(root_path)
  @root_path = root_path
  @cache     = {}
end

Instance Method Details

#extract(type, method_name) ⇒ Object

Returns a markdown string with the doc comment, or nil.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/pink_spoon/doc_extractor.rb', line 113

def extract(type, method_name)
  cache_key = "doc:#{type}##{method_name}"
  return @cache[cache_key] if @cache.key?(cache_key)

  @cache[cache_key] = begin
    loc = find_location(type, method_name)
    if loc
      raw = read_comments(loc[:file], loc[:line])
      raw.empty? ? nil : render(raw)
    else
      extract_via_ri(type, method_name)
    end
  end
rescue
  nil
end

#extract_for_constant(type) ⇒ Object

Returns markdown docs for a class/module/constant. When there is a YARD comment above the definition, renders it. When there is no comment, falls back to showing the declaration line itself.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/pink_spoon/doc_extractor.rb', line 65

def extract_for_constant(type)
  cache_key = "const_doc:#{type}"
  return @cache[cache_key] if @cache.key?(cache_key)

  @cache[cache_key] = begin
    loc = find_constant_location(type)
    if loc
      raw = read_comments(loc[:file], loc[:line])

      if raw.empty?
        decl = declaration_line(loc[:file], loc[:line])
        decl ? "```ruby\n#{decl}\n```" : nil
      else
        render(raw)
      end
    end
  end
rescue
  nil
end

#extract_for_location(loc) ⇒ Object

Returns hover markdown for a known file+line (used as fallback when find_constant_location cannot locate the constant via gem-hint scanning).



88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/pink_spoon/doc_extractor.rb', line 88

def extract_for_location(loc)
  return nil unless loc

  raw = read_comments(loc[:file], loc[:line])
  if raw.empty?
    decl = declaration_line(loc[:file], loc[:line])
    decl ? "```ruby\n#{decl}\n```" : nil
  else
    render(raw)
  end
rescue
  nil
end

#find_constant_source(type) ⇒ Object

Returns { file: absolute_path, line: integer } for a class/module definition.



53
54
55
56
57
58
59
60
# File 'lib/pink_spoon/doc_extractor.rb', line 53

def find_constant_source(type)
  cache_key = "const_loc:#{type}"
  return @cache[cache_key] if @cache.key?(cache_key)

  @cache[cache_key] = find_constant_location(type)
rescue
  nil
end

#find_ivar_in_type(type, ivar_name) ⇒ Object

Returns { file:, line: } for the first assignment of ivar_name (@foo = …) in any source file belonging to the given type’s gem or project.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/pink_spoon/doc_extractor.rb', line 21

def find_ivar_in_type(type, ivar_name)
  cache_key = "ivar:#{type}##{ivar_name}"
  return @cache[cache_key] if @cache.key?(cache_key)

  @cache[cache_key] = begin
    escaped = Regexp.escape(ivar_name)
    pattern = /#{escaped}\s*=/
    hint    = type.split("::").first.to_s.downcase

    gem_dirs_for(hint).each do |gem_dir|
      Dir.glob("#{gem_dir}/lib/**/*.rb").sort.each do |file|
        line = first_line_matching(file, pattern)
        return { file: file, line: line } if line
      end
    end

    %w[lib app spec test].each do |subdir|
      dir = File.join(@root_path, subdir)
      next unless File.directory?(dir)
      Dir.glob("#{dir}/**/*.rb").sort.each do |file|
        line = first_line_matching(file, pattern)
        return { file: file, line: line } if line
      end
    end

    nil
  end
rescue
  nil
end

#find_source(type, method_name) ⇒ Object

Returns { file: absolute_path, line: integer } or nil.



103
104
105
106
107
108
109
110
# File 'lib/pink_spoon/doc_extractor.rb', line 103

def find_source(type, method_name)
  cache_key = "loc:#{type}##{method_name}"
  return @cache[cache_key] if @cache.key?(cache_key)

  @cache[cache_key] = find_location(type, method_name)
rescue
  nil
end