lib/yard/registry_store.rb
# frozen_string_literal: true require 'fileutils' module YARD # The data store for the {Registry}. # # @see Registry # @see Serializers::YardocSerializer class RegistryStore # @deprecated The registry no longer tracks proxy types attr_reader :proxy_types attr_reader :file, :checksums def initialize @file = nil @checksums = {} @store = {} @proxy_types = {} @object_types = {:root => [:root]} @notfound = {} @loaded_objects = 0 @available_objects = 0 @locales = {} @store[:root] = CodeObjects::RootObject.allocate @store[:root].send(:initialize, nil, :root) end # Gets a {CodeObjects::Base} from the store # # @param [String, Symbol] key the path name of the object to look for. # If it is empty or :root, returns the {#root} object. # @return [CodeObjects::Base, nil] a code object or nil if none is found def get(key) key = :root if key == '' key = key.to_sym return @store[key] if @store[key] return if @loaded_objects >= @available_objects # check disk return if @notfound[key] obj = @serializer.deserialize(key) if obj @loaded_objects += 1 put(key, obj) else @notfound[key] = true nil end end # Associates an object with a path # @param [String, Symbol] key the path name (:root or '' for root object) # @param [CodeObjects::Base] value the object to store # @return [CodeObjects::Base] returns +value+ def put(key, value) if key == '' @object_types[:root] = [:root] @store[:root] = value else @notfound.delete(key.to_sym) (@object_types[value.type] ||= []) << key.to_s if @store[key.to_sym] @object_types[@store[key.to_sym].type].delete(key.to_s) end @store[key.to_sym] = value end end alias [] get alias []= put # Deletes an object at a given path # @param [#to_sym] key the key to delete # @return [void] def delete(key) if @store[key.to_sym] @object_types[@store[key.to_sym].type].delete(key.to_s) @store.delete(key.to_sym) end end # Gets all path names from the store. Loads the entire database # if +reload+ is +true+ # # @param [Boolean] reload if false, does not load the entire database # before a lookup. # @return [Array<Symbol>] the path names of all the code objects def keys(reload = false) load_all if reload; @store.keys end # Gets all code objects from the store. Loads the entire database # if +reload+ is +true+ # # @param [Boolean] reload if false, does not load the entire database # before a lookup. # @return [Array<CodeObjects::Base>] all the code objects def values(reload = false) load_all if reload; @store.values end # @param [Symbol] type the type to look for # @return [Array<String>] a list of object paths with a given # {CodeObjects::Base#type} # @since 0.8.0 def paths_for_type(type, reload = false) load_all if reload @object_types[type] || [] end # @param [Symbol] type the type to look for # @return [Array<CodeObjects::Base>] a list of objects with a given # {CodeObjects::Base#type} # @since 0.8.0 def values_for_type(type, reload = false) load_all if reload paths_for_type(type).map {|t| @store[t.to_sym] } end # @return [CodeObjects::RootObject] the root object def root; @store[:root] end # @param [String] name the locale name. # @return [I18n::Locale] the locale object for +name+. # @since 0.8.3 def locale(name) @locales[name] ||= load_locale(name) end # @param [String, nil] file the name of the yardoc db to load # @return [Boolean] whether the database was loaded def load(file = nil) initialize @file = file @serializer = Serializers::YardocSerializer.new(@file) load_yardoc end # Loads the .yardoc file and loads all cached objects into memory # automatically. # # @param [String, nil] file the name of the yardoc db to load # @return [Boolean] whether the database was loaded # @see #load_all # @since 0.5.1 def load!(file = nil) if load(file) load_all true else false end end # Loads all cached objects into memory # @return [void] def load_all return unless @file return if @loaded_objects >= @available_objects log.debug "Loading entire database: #{@file} ..." objects = [] all_disk_objects.sort_by(&:size).each do |path| obj = @serializer.deserialize(path, true) objects << obj if obj end objects.each do |obj| put(obj.path, obj) end @loaded_objects += objects.size log.debug "Loaded database (file='#{@file}' count=#{objects.size} total=#{@available_objects})" end # Saves the database to disk # @param [Boolean] merge if true, merges the data in memory with the # data on disk, otherwise the data on disk is deleted. # @param [String, nil] file if supplied, the name of the file to save to # @return [Boolean] whether the database was saved def save(merge = true, file = nil) if file && file != @file @file = file @serializer = Serializers::YardocSerializer.new(@file) end destroy unless merge sdb = Registry.single_object_db if sdb == true || sdb.nil? @serializer.serialize(@store) else values(false).each do |object| @serializer.serialize(object) end end write_proxy_types write_object_types write_checksums write_complete_lock true end # (see Serializers::YardocSerializer#lock_for_writing) # @param file [String] if supplied, the path to the database def lock_for_writing(file = nil, &block) Serializers::YardocSerializer.new(file || @file).lock_for_writing(&block) end # (see Serializers::YardocSerializer#locked_for_writing?) # @param file [String] if supplied, the path to the database def locked_for_writing?(file = nil) Serializers::YardocSerializer.new(file || @file).locked_for_writing? end # Deletes the .yardoc database on disk # # @param [Boolean] force if force is not set to true, the file/directory # will only be removed if it ends with .yardoc. This helps with # cases where the directory might have been named incorrectly. # @return [Boolean] true if the .yardoc database was deleted, false # otherwise. def destroy(force = false) if (!force && file =~ /\.yardoc$/) || force if File.file?(@file) # Handle silent upgrade of old .yardoc format File.unlink(@file) elsif File.directory?(@file) FileUtils.rm_rf(@file) end true else false end end protected def objects_path @serializer.objects_path end # @deprecated The registry no longer tracks proxy types def proxy_types_path @serializer.proxy_types_path end def checksums_path @serializer.checksums_path end def object_types_path @serializer.object_types_path end def load_yardoc return false unless @file if File.directory?(@file) # new format @loaded_objects = 0 @available_objects = all_disk_objects.size load_proxy_types load_checksums load_root load_object_types true elsif File.file?(@file) # old format load_yardoc_old true else false end end private def load_yardoc_old @store, @proxy_types = *Marshal.load(File.read_binary(@file)) end # @deprecated The registry no longer tracks proxy types def load_proxy_types return unless File.file?(proxy_types_path) @proxy_types = Marshal.load(File.read_binary(proxy_types_path)) end def load_object_types if File.file?(object_types_path) @object_types = Marshal.load(File.read_binary(object_types_path)) else # migrate db without object_types values.each do |object| (@object_types[object.type] ||= []) << object.path end end end def load_checksums return unless File.file?(checksums_path) lines = File.readlines(checksums_path).map do |line| line.strip.rpartition(' ').tap { |p| p.delete_at(1) } end @checksums = Hash[lines] end def load_root root = @serializer.deserialize('root') return if root.nil? @loaded_objects += 1 if root.is_a?(Hash) # single object db log.debug "Loading single object DB from .yardoc" @loaded_objects += (root.keys.size - 1) @store = root else # just the root object @store[:root] = root end end def load_locale(name) locale = I18n::Locale.new(name) locale.load(Registry.po_dir) locale end def all_disk_objects Dir.glob(File.join(objects_path, '**/*')).select {|f| File.file?(f) } end # @deprecated The registry no longer tracks proxy types def write_proxy_types File.open!(proxy_types_path, 'wb') {|f| f.write(Marshal.dump(@proxy_types)) } end def write_object_types File.open!(object_types_path, 'wb') {|f| f.write(Marshal.dump(@object_types)) } end def write_checksums File.open!(checksums_path, 'w') do |f| @checksums.each {|k, v| f.puts("#{k} #{v}") } end end def write_complete_lock File.open!(@serializer.complete_lock_path, 'w') {} end end end