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`.

Instance Method Summary collapse

Constructor Details

#initialize(root_path) ⇒ DocExtractor

Returns a new instance of DocExtractor.



12
13
14
15
# File 'lib/pink_spoon/doc_extractor.rb', line 12

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.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/pink_spoon/doc_extractor.rb', line 95

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)
    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.



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

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

#find_constant_source(type) ⇒ Object

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



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

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.



19
20
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
# File 'lib/pink_spoon/doc_extractor.rb', line 19

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].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.



85
86
87
88
89
90
91
92
# File 'lib/pink_spoon/doc_extractor.rb', line 85

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