Class: YardUtils
Overview
Utility class for YARD operations
Defined Under Namespace
Classes: AmbiguousObjectError, DocumentationError
Constant Summary collapse
- MAX_SOURCE_CHARS =
20_000
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, gem_name = nil) ⇒ Array<String>
Returns the full ancestor chain (superclasses and included modules) for a class or module.
- #build_docs(gem_name) ⇒ Object
-
#children(path, gem_name = nil) ⇒ Array<String>
Lists the children (constants, classes, modules, methods, etc.) under a namespace.
-
#code_snippet(path, gem_name = nil, max_chars: MAX_SOURCE_CHARS) ⇒ 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.
- #gem_candidates(gem_names) ⇒ Object
-
#get_doc(path, gem_name = nil) ⇒ Hash
Fetches documentation and metadata for a YARD object (class/module/method).
-
#hierarchy(path, gem_name = nil) ⇒ 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.
- #list_installed_gems ⇒ Object
-
#load_yardoc_for_gem(gem_name) ⇒ Boolean
Loads the .yardoc file for a given gem into the YARD registry.
-
#methods_list(path, gem_name = nil) ⇒ Array<String>
Lists all methods for a class or module.
-
#related_objects(path, gem_name = nil) ⇒ Hash
Returns related objects: included modules, mixins, and subclasses.
-
#search(query, gem_name = nil, limit: 25, offset: 0) ⇒ Array<Hash>
Performs a fuzzy/full-text search in the YARD registry for objects whose path or docstring matches the query.
-
#source_location(path, gem_name = nil) ⇒ 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.
32 33 34 35 36 37 38 39 40 |
# File 'lib/yardmcp.rb', line 32 def initialize @libraries = {} @object_to_gem = {} @last_loaded_gem = nil @class_cache = {} @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.
30 31 32 |
# File 'lib/yardmcp.rb', line 30 def libraries @libraries end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
30 31 32 |
# File 'lib/yardmcp.rb', line 30 def logger @logger end |
#object_to_gem ⇒ Object (readonly)
Returns the value of attribute object_to_gem.
30 31 32 |
# File 'lib/yardmcp.rb', line 30 def object_to_gem @object_to_gem end |
Instance Method Details
#ancestors(path, gem_name = nil) ⇒ Array<String>
Returns the full ancestor chain (superclasses and included modules) for a class or module.
179 180 181 182 |
# File 'lib/yardmcp.rb', line 179 def ancestors(path, gem_name = nil) obj = object_for!(path, gem_name) obj.respond_to?(:inheritance_tree) ? obj.inheritance_tree(true).map(&:path) : [] end |
#build_docs(gem_name) ⇒ Object
234 235 236 237 238 239 240 241 242 243 |
# File 'lib/yardmcp.rb', line 234 def build_docs(gem_name) gem_spec!(gem_name) logger.info "Building docs for #{gem_name}..." YARD::CLI::Gems.new.run(gem_name) @class_cache.delete(gem_name) @last_loaded_gem = nil load_yardoc_for_gem(gem_name) merge_gem_results([collect_current_gem_objects(gem_name)]) true end |
#children(path, gem_name = nil) ⇒ Array<String>
Lists the children (constants, classes, modules, methods, etc.) under a namespace.
146 147 148 149 |
# File 'lib/yardmcp.rb', line 146 def children(path, gem_name = nil) obj = object_for!(path, gem_name) obj.respond_to?(:children) ? obj.children.map(&:path) : [] end |
#code_snippet(path, gem_name = nil, max_chars: MAX_SOURCE_CHARS) ⇒ String?
Fetches the code snippet for a YARD object from installed gems.
229 230 231 232 |
# File 'lib/yardmcp.rb', line 229 def code_snippet(path, gem_name = nil, max_chars: MAX_SOURCE_CHARS) obj = object_for!(path, gem_name) capped_source(obj.respond_to?(:source) ? obj.source : nil, max_chars:) end |
#ensure_yardoc_loaded_for_object!(object_path) ⇒ Object
Ensures the correct .yardoc is loaded for the given object path
59 60 61 62 63 64 65 |
# File 'lib/yardmcp.rb', line 59 def ensure_yardoc_loaded_for_object!(object_path) gem_names = @object_to_gem[object_path] raise DocumentationError, "No indexed documentation contains '#{object_path}'. Pass gem_name if you know the gem." if gem_names.nil? || gem_names.empty? raise AmbiguousObjectError.new(object_path, gem_candidates(gem_names)) if gem_names.uniq.size > 1 load_yardoc_for_gem(gem_names.first) end |
#gem_candidates(gem_names) ⇒ Object
80 81 82 83 84 85 86 87 |
# File 'lib/yardmcp.rb', line 80 def gem_candidates(gem_names) gem_names.uniq.sort.map do |gem_name| { gem_name:, versions: Array(libraries[gem_name]).map { |library| library.version.to_s }.uniq.sort } end end |
#get_doc(path, gem_name = nil) ⇒ Hash
Fetches documentation and metadata for a YARD object (class/module/method).
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/yardmcp.rb', line 102 def get_doc(path, gem_name = nil) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/MethodLength obj = object_for!(path, gem_name) = 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: capped_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, gem_name = nil) ⇒ Hash
Returns inheritance and inclusion information for a class or module.
166 167 168 169 170 171 172 173 |
# File 'lib/yardmcp.rb', line 166 def hierarchy(path, gem_name = nil) obj = object_for!(path, gem_name) { superclass: obj.respond_to?(:superclass) && obj.superclass ? obj.superclass.path : nil, included_modules: obj.respond_to?(:mixins) ? obj.mixins.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.
92 93 94 95 |
# File 'lib/yardmcp.rb', line 92 def list_classes(gem_name) load_yardoc_for_gem(gem_name) @class_cache[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.
70 71 72 73 74 |
# File 'lib/yardmcp.rb', line 70 def list_gems libraries.keys.select do |name| yardoc_exists?(yardoc_path_for(gem_spec!(name))) end.sort end |
#list_installed_gems ⇒ Object
76 77 78 |
# File 'lib/yardmcp.rb', line 76 def list_installed_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.
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/yardmcp.rb', line 47 def load_yardoc_for_gem(gem_name) return if @last_loaded_gem == gem_name spec = gem_spec!(gem_name) dir = yardoc_path_for(spec) raise DocumentationError, "YARD documentation is not indexed for gem '#{gem_name}'" unless yardoc_exists?(dir) YARD::Registry.load!(dir) @last_loaded_gem = gem_name end |
#methods_list(path, gem_name = nil) ⇒ Array<String>
Lists all methods for a class or module.
156 157 158 159 |
# File 'lib/yardmcp.rb', line 156 def methods_list(path, gem_name = nil) obj = object_for!(path, gem_name) obj.respond_to?(:meths) ? obj.meths.map(&:path) : [] end |
#related_objects(path, gem_name = nil) ⇒ Hash
Returns related objects: included modules, mixins, and subclasses.
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/yardmcp.rb', line 188 def (path, gem_name = nil) obj = object_for!(path, gem_name) subclasses = YARD::Registry.all(:class).select { |c| c.superclass && c.superclass.path == obj.path }.map(&:path) mixins_list = obj.respond_to?(:mixins) ? obj.mixins.map(&:path) : [] { included_modules: mixins_list, mixins: mixins_list, subclasses: } end |
#search(query, gem_name = nil, limit: 25, offset: 0) ⇒ Array<Hash>
Performs a fuzzy/full-text search in the YARD registry for objects whose path or docstring matches the query.
203 204 205 206 207 208 209 |
# File 'lib/yardmcp.rb', line 203 def search(query, gem_name = nil, limit: 25, offset: 0) require 'levenshtein' unless defined?(Levenshtein) candidates = gem_name ? loaded_objects_for_search(gem_name) : indexed_paths_for_search results = candidates.filter_map { |candidate| score_search_candidate(candidate, query) } # Sort by score descending, then alphabetically results.sort_by { |r| [-r[:score], r[:path]] }.slice(offset, limit) || [] end |
#source_location(path, gem_name = nil) ⇒ Hash
Returns the source file and line number for a YARD object (class/module/method).
216 217 218 219 220 221 222 |
# File 'lib/yardmcp.rb', line 216 def source_location(path, gem_name = nil) obj = object_for!(path, gem_name) { file: obj.respond_to?(:file) ? obj.file : nil, line: obj.respond_to?(:line) ? obj.line : nil } end |