Class: YardUtils
Overview
Utility class for YARD operations
Instance Attribute Summary collapse
-
#libraries ⇒ Object
readonly
Returns the value of attribute libraries.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#object_to_gem ⇒ Object
readonly
Returns the value of attribute object_to_gem.
Instance Method Summary collapse
-
#ancestors(path) ⇒ Array<String>
Returns the full ancestor chain (superclasses and included modules) for a class or module.
-
#children(path) ⇒ Array<String>
Lists the children (constants, classes, modules, methods, etc.) under a namespace.
-
#code_snippet(path) ⇒ String?
Fetches the code snippet for a YARD object from installed gems.
-
#ensure_yardoc_loaded_for_object!(object_path) ⇒ Object
Ensures the correct .yardoc is loaded for the given object path.
-
#get_doc(path, gem_name = nil) ⇒ Hash
Fetches documentation and metadata for a YARD object (class/module/method).
-
#hierarchy(path) ⇒ Hash
Returns inheritance and inclusion information for a class or module.
-
#initialize ⇒ YardUtils
constructor
A new instance of YardUtils.
-
#list_classes(gem_name) ⇒ Array<String>
Lists all classes and modules in the loaded YARD registry.
-
#list_gems ⇒ Array<String>
Lists all installed gems that have a .yardoc file available.
-
#load_yardoc_for_gem(gem_name) ⇒ Boolean
Loads the .yardoc file for a given gem into the YARD registry.
-
#methods_list(path) ⇒ Array<String>
Lists all methods for a class or module.
-
#related_objects(path) ⇒ Hash
Returns related objects: included modules, mixins, and subclasses.
-
#search(query) ⇒ Array<Hash>
Performs a fuzzy/full-text search in the YARD registry for objects whose path or docstring matches the query.
-
#source_location(path) ⇒ Hash
Returns the source file and line number for a YARD object (class/module/method).
Constructor Details
#initialize ⇒ YardUtils
Returns a new instance of YardUtils.
17 18 19 20 21 22 23 24 |
# File 'lib/yardmcp.rb', line 17 def initialize @libraries = {} @object_to_gem = {} @last_loaded_gem = nil @logger = Logger.new($stderr) @logger.level = Logger::INFO unless ENV['DEBUG'] build_index end |
Instance Attribute Details
#libraries ⇒ Object (readonly)
Returns the value of attribute libraries.
15 16 17 |
# File 'lib/yardmcp.rb', line 15 def libraries @libraries end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
15 16 17 |
# File 'lib/yardmcp.rb', line 15 def logger @logger end |
#object_to_gem ⇒ Object (readonly)
Returns the value of attribute object_to_gem.
15 16 17 |
# File 'lib/yardmcp.rb', line 15 def object_to_gem @object_to_gem end |
Instance Method Details
#ancestors(path) ⇒ Array<String>
Returns the full ancestor chain (superclasses and included modules) for a class or module.
172 173 174 175 176 177 178 179 180 |
# File 'lib/yardmcp.rb', line 172 def ancestors(path) ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return [] end obj.respond_to?(:inheritance_tree) ? obj.inheritance_tree(true).map(&:path) : [] end |
#children(path) ⇒ Array<String>
Lists the children (constants, classes, modules, methods, etc.) under a namespace.
124 125 126 127 128 129 130 131 132 |
# File 'lib/yardmcp.rb', line 124 def children(path) ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return [] end obj.respond_to?(:children) ? obj.children.map(&:path) : [] end |
#code_snippet(path) ⇒ String?
Fetches the code snippet for a YARD object from installed gems.
256 257 258 259 260 261 262 263 264 |
# File 'lib/yardmcp.rb', line 256 def code_snippet(path) ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return [] end obj.respond_to?(:source) ? obj.source : nil end |
#ensure_yardoc_loaded_for_object!(object_path) ⇒ Object
Ensures the correct .yardoc is loaded for the given object path
45 46 47 48 49 50 51 |
# File 'lib/yardmcp.rb', line 45 def ensure_yardoc_loaded_for_object!(object_path) # TODO: Handle multiple gems for the same object path, use some heuristic to determine the correct gem gem_name = @object_to_gem[object_path]&.first raise "No documentation found for #{object_path}" unless gem_name load_yardoc_for_gem(gem_name) end |
#get_doc(path, gem_name = nil) ⇒ Hash
Fetches documentation and metadata for a YARD object (class/module/method).
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/yardmcp.rb', line 73 def get_doc(path, gem_name = nil) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength if gem_name # Load the specific gem's yardoc load_yardoc_for_gem(gem_name) else ensure_yardoc_loaded_for_object!(path) end obj = YARD::Registry.at(path) raise 'Object not found' unless obj = obj..map do |tag| { tag_name: tag.tag_name, name: tag.respond_to?(:name) ? tag.name : nil, types: tag.respond_to?(:types) ? tag.types : nil, text: tag.text } end doc = { type: obj.type.to_s, name: obj.name.to_s, namespace: obj.namespace&.path, visibility: obj.respond_to?(:visibility) ? obj.visibility.to_s : nil, docstring: obj.docstring.to_s, parameters: obj.respond_to?(:parameters) ? obj.parameters : nil, return: if obj.respond_to?(:tag) && obj.tag('return') { types: obj.tag('return').types, text: obj.tag('return').text } end, source: obj.respond_to?(:source) ? obj.source : nil, tags: } # Add subclass-specific info doc[:attributes] = obj.attributes if obj.respond_to?(:attributes) && obj.attributes doc[:constants] = obj.constants.map(&:path) if obj.respond_to?(:constants) && obj.constants doc[:superclass] = obj.superclass&.path if obj.respond_to?(:superclass) && obj.superclass doc[:scope] = obj.scope if obj.respond_to?(:scope) && obj.scope doc[:overridden_method] = obj.overridden_method&.path if obj.respond_to?(:overridden_method) && obj.overridden_method doc end |
#hierarchy(path) ⇒ Hash
Returns inheritance and inclusion information for a class or module.
154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/yardmcp.rb', line 154 def hierarchy(path) # rubocop:disable Metrics/CyclomaticComplexity ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return [] end { superclass: obj.respond_to?(:superclass) && obj.superclass ? obj.superclass.path : nil, included_modules: obj.respond_to?(:included_modules) ? obj.included_modules.map(&:path) : [], mixins: obj.respond_to?(:mixins) ? obj.mixins.map(&:path) : [] } end |
#list_classes(gem_name) ⇒ Array<String>
Lists all classes and modules in the loaded YARD registry.
63 64 65 66 |
# File 'lib/yardmcp.rb', line 63 def list_classes(gem_name) load_yardoc_for_gem(gem_name) YARD::Registry.all(:class, :module).map(&:path).sort end |
#list_gems ⇒ Array<String>
Lists all installed gems that have a .yardoc file available.
56 57 58 |
# File 'lib/yardmcp.rb', line 56 def list_gems libraries.keys.sort end |
#load_yardoc_for_gem(gem_name) ⇒ Boolean
Loads the .yardoc file for a given gem into the YARD registry. Caches the last loaded gem to avoid unnecessary reloads.
31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/yardmcp.rb', line 31 def load_yardoc_for_gem(gem_name) return if @last_loaded_gem == gem_name spec = libraries[gem_name].first ver = "= #{spec.version}" dir = YARD::Registry.yardoc_file_for_gem(spec.name, ver) build_docs(gem_name) unless yardoc_exists?(dir) raise "Yardoc not found for #{gem_name}" unless yardoc_exists?(dir) YARD::Registry.load_yardoc(dir) @last_loaded_gem = gem_name end |
#methods_list(path) ⇒ Array<String>
Lists all methods for a class or module.
139 140 141 142 143 144 145 146 147 |
# File 'lib/yardmcp.rb', line 139 def methods_list(path) ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return [] end obj.respond_to?(:meths) ? obj.meths.map(&:path) : [] end |
#related_objects(path) ⇒ Hash
Returns related objects: included modules, mixins, and subclasses.
186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/yardmcp.rb', line 186 def (path) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return {} end subclasses = YARD::Registry.all(:class).select { |c| c.superclass && c.superclass.path == obj.path }.map(&:path) { included_modules: obj.respond_to?(:included_modules) ? obj.included_modules.map(&:path) : [], mixins: obj.respond_to?(:mixins) ? obj.mixins.map(&:path) : [], subclasses: } end |
#search(query) ⇒ Array<Hash>
Performs a fuzzy/full-text search in the YARD registry for objects whose path or docstring matches the query.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/yardmcp.rb', line 205 def search(query) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity require 'levenshtein' unless defined?(Levenshtein) results = [] YARD::Registry.all.each do |obj| path = obj.path.to_s doc = obj.docstring.to_s next if path.empty? score = nil if path == query score = 100 elsif path.start_with?(query) score = 90 elsif path.include?(query) score = 80 elsif doc.include?(query) score = 60 else # Fuzzy match: allow up to 2 edits for short queries, 3 for longer dist = Levenshtein.distance(path.downcase, query.downcase) score = 70 - dist if dist <= [2, query.length / 3].max end results << { path:, score: } if score end # Sort by score descending, then alphabetically results.sort_by { |r| [-r[:score], r[:path]] } end |
#source_location(path) ⇒ Hash
Returns the source file and line number for a YARD object (class/module/method).
238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/yardmcp.rb', line 238 def source_location(path) ensure_yardoc_loaded_for_object!(path) obj = YARD::Registry.at(path) unless obj logger.error "Object not found: #{path}" return [] end { file: obj.respond_to?(:file) ? obj.file : nil, line: obj.respond_to?(:line) ? obj.line : nil } end |