lib/yard/server/library_version.rb in yard-0.9.16 vs lib/yard/server/library_version.rb in yard-0.9.17

- old
+ new

@@ -1,277 +1,277 @@ -# frozen_string_literal: true -require 'fileutils' -require 'thread' - -module YARD - module Server - # This exception is raised when {LibraryVersion#prepare!} fails, or discovers - # that the library is not "prepared" to be served by - class LibraryNotPreparedError < RuntimeError; end - - # A library version encapsulates a library's documentation at a specific version. - # Although the version is optional, this allows for creating multiple documentation - # points for a specific library, each representing a unique version. The term - # "library" used in other parts of the YARD::Server documentation refers to - # objects of this class unless otherwise noted. - # - # A library points to a location where a {#yardoc_file} is located so that - # its documentation may be loaded and served. Optionally, a {#source_path} is - # given to point to a location where any extra files (and {YARD::CLI::Yardoc .yardopts}) - # should be loaded from. Both of these methods may not be known immediately, - # since the yardoc file may not be built until later. Resolving the yardoc - # file and source path are dependent on the specific library "source type" used. - # Source types (known as "library source") are discussed in detail below. - # - # == Using with Adapters - # A list of libraries need to be passed into adapters upon creation. In - # most cases, you will never do this manually, but if you use a {RackMiddleware}, - # you will need to pass in this list yourself. To build this list of libraries, - # you should create a hash of library names mapped to an *Array* of LibraryVersion - # objects. For example: - # - # {'mylib' => [LibraryVersion.new('mylib', '1.0', ...), - # LibraryVersion.new('mylib', '2.0', ...)]} - # - # Note that you can also use {Adapter#add_library} for convenience. - # - # The "array" part is required, even for just one library version. - # - # == Library Sources - # The {#source} method represents the library source type, ie. where the - # library "comes from". It might come from "disk", or it might come from a - # "gem" (technically the disk, but a separate type nonetheless). In these - # two cases, the yardoc file sits somewhere on your filesystem, though - # it may also be built dynamically if it does not yet exist. This behaviour - # is controlled through the {#prepare!} method, which prepares the yardoc file - # given a specific library source. We will see how this works in detail in - # the following section. - # - # == Implementing a Custom Library Source - # YARD can be extended to support custom library sources in order to - # build or retrieve a yardoc file at runtime from many different locations. - # - # To implement this behaviour, 3 methods can be added to the +LibraryVersion+ - # class, +#load_yardoc_from_SOURCE+, +#yardoc_file_for_SOURCE+, and - # +#source_path_for_SOURCE+. In all cases, "SOURCE" represents the source - # type used in {#source} when creating the library object. The - # +#yardoc_file_for_SOURCE+ and +#source_path_for_SOURCE+ methods are called upon - # creation and should return the location where the source code for the library - # lives. The load method is called from {#prepare!} if there is no yardoc file - # and should set {#yardoc_file}. Below is a full example for - # implementing a custom library source, +:http+, which reads packaged .yardoc - # databases from zipped archives off of an HTTP server. - # - # Note that only +#load_yardoc_from_SOURCE+ is required. The other two - # methods are optional and can be set manually (via {#source_path=} and - # {#yardoc_file=}) on the object at any time. - # - # @example Implementing a Custom Library Source - # # Adds the source type "http" for .yardoc files zipped on HTTP servers - # class LibraryVersion - # def load_yardoc_from_http - # Thread.new do - # # zip/unzip method implementations are not shown - # download_zip_file("http://mysite.com/yardocs/#{self}.zip") - # unzip_file_to("/path/to/yardocs/#{self}") - # end - # - # # tell the server it's not ready yet (but it might be next time) - # raise LibraryNotPreparedError - # end - # - # def yardoc_file_for_http - # "/path/to/yardocs/#{self}/.yardoc" - # end - # - # def source_path_for_http - # File.dirname(yardoc_file) - # end - # end - # - # # Creating a library of this source type: - # LibraryVersion.new('name', '1.0', nil, :http) - # - class LibraryVersion - # @return [String] the name of the library - attr_accessor :name - - # @return [String] the version of the specific library - attr_accessor :version - - # @return [String] the location of the yardoc file used to load the object - # information from. - # @return [nil] if no yardoc file exists yet. In this case, {#prepare!} will - # be called on this library to build the yardoc file. - # @note To implement a custom yardoc file getter, implement - def yardoc_file - @yardoc_file ||= load_yardoc_file - end - attr_writer :yardoc_file - - # @return [Symbol] the source type representing where the yardoc should be - # loaded from. Defaults are +:disk+ and +:gem+, though custom sources - # may be implemented. This value is used to inform {#prepare!} about how - # to load the necessary data in order to display documentation for an object. - # @see LibraryVersion LibraryVersion documentation for "Implementing a Custom Library Source" - attr_accessor :source - - # @return [String] the location of the source code for a library. This - # value is filled by calling +#source_path_for_SOURCE+ on this class. - # @return [nil] if there is no source code - # @see LibraryVersion LibraryVersion documentation for "Implementing a Custom Library Source" - def source_path - @source_path ||= load_source_path - end - attr_writer :source_path - - # @param [String] name the name of the library - # @param [String] version the specific (usually, but not always, numeric) library - # version - # @param [String] yardoc the location of the yardoc file, or nil if it is - # generated later - # @param [Symbol] source the location of the files used to build the yardoc. - # Builtin source types are +:disk+ or +:gem+. - def initialize(name, version = nil, yardoc = nil, source = :disk) - self.name = name - self.yardoc_file = yardoc - self.version = version - self.source = source - end - - # @param [Boolean] url_format if true, returns the string in a URI-compatible - # format (for appending to a URL). Otherwise, it is given in a more human - # readable format. - # @return [String] the string representation of the library. - def to_s(url_format = true) - version ? "#{name}#{url_format ? '/' : '-'}#{version}" : name.to_s - end - - # @return [Fixnum] used for Hash mapping. - def hash; to_s.hash end - - # @return [Boolean] whether another LibraryVersion is equal to this one - def eql?(other) - other.is_a?(LibraryVersion) && other.name == name && - other.version == version && other.yardoc_file == yardoc_file - end - alias == eql? - alias equal? eql? - - # @return [Boolean] whether the library has been completely processed - # and is ready to be served - def ready? - return false if yardoc_file.nil? - serializer.complete? - end - - # @note You should not directly override this method. Instead, implement - # +load_yardoc_from_SOURCENAME+ when implementing loading for a specific - # source type. See the {LibraryVersion} documentation for "Implementing - # a Custom Library Source" - # - # Prepares a library to be displayed by the server. This callback is - # performed before each request on a library to ensure that it is loaded - # and ready to be viewed. If any steps need to be performed prior to loading, - # they are performed through this method (though they should be implemented - # through the +load_yardoc_from_SOURCE+ method). - # - # @raise [LibraryNotPreparedError] if the library is not ready to be - # displayed. Usually when raising this error, you would simultaneously - # begin preparing the library for subsequent requests, although this - # is not necessary. - def prepare! - return if ready? - meth = "load_yardoc_from_#{source}" - send(meth) if respond_to?(meth, true) - end - - # @return [Gem::Specification] a gemspec object for a given library. Used - # for :gem source types. - # @return [nil] if there is no installed gem for the library - def gemspec - ver = version ? "= #{version}" : ">= 0" - YARD::GemIndex.find_all_by_name(name, ver).last - end - - protected - - @@chdir_mutex = Mutex.new - - # Called when a library of source type "disk" is to be prepared. In this - # case, the {#yardoc_file} should already be set, but the library may not - # be prepared. Run preparation if not done. - # - # @raise [LibraryNotPreparedError] if the yardoc file has not been - # prepared. - def load_yardoc_from_disk - return if ready? - - @@chdir_mutex.synchronize do - Dir.chdir(source_path_for_disk) do - Thread.new do - CLI::Yardoc.run('--no-stats', '-n', '-b', yardoc_file) - end - end - end - - raise LibraryNotPreparedError - end - - # Called when a library of source type "gem" is to be prepared. In this - # case, the {#yardoc_file} needs to point to the correct location for - # the installed gem. The yardoc file is built if it has not been done. - # - # @raise [LibraryNotPreparedError] if the gem does not have an existing - # yardoc file. - def load_yardoc_from_gem - return if ready? - ver = version ? "= #{version}" : ">= 0" - - @@chdir_mutex.synchronize do - Thread.new do - # Build gem docs on demand - log.debug "Building gem docs for #{to_s(false)}" - CLI::Gems.run(name, ver) - log.debug "Done building gem docs for #{to_s(false)}" - end - end - - raise LibraryNotPreparedError - end - - # @return [String] the source path for a disk source - def source_path_for_disk - File.dirname(yardoc_file) if yardoc_file - end - - # @return [String] the source path for a gem source - def source_path_for_gem - gemspec.full_gem_path if gemspec - end - - # @return [String] the yardoc file for a gem source - def yardoc_file_for_gem - require 'rubygems' - ver = version ? "= #{version}" : ">= 0" - Registry.yardoc_file_for_gem(name, ver) - end - - private - - def load_source_path - meth = "source_path_for_#{source}" - send(meth) if respond_to?(meth, true) - end - - def load_yardoc_file - meth = "yardoc_file_for_#{source}" - send(meth) if respond_to?(meth, true) - end - - def serializer - return if yardoc_file.nil? - Serializers::YardocSerializer.new(yardoc_file) - end - end - end -end +# frozen_string_literal: true +require 'fileutils' +require 'thread' + +module YARD + module Server + # This exception is raised when {LibraryVersion#prepare!} fails, or discovers + # that the library is not "prepared" to be served by + class LibraryNotPreparedError < RuntimeError; end + + # A library version encapsulates a library's documentation at a specific version. + # Although the version is optional, this allows for creating multiple documentation + # points for a specific library, each representing a unique version. The term + # "library" used in other parts of the YARD::Server documentation refers to + # objects of this class unless otherwise noted. + # + # A library points to a location where a {#yardoc_file} is located so that + # its documentation may be loaded and served. Optionally, a {#source_path} is + # given to point to a location where any extra files (and {YARD::CLI::Yardoc .yardopts}) + # should be loaded from. Both of these methods may not be known immediately, + # since the yardoc file may not be built until later. Resolving the yardoc + # file and source path are dependent on the specific library "source type" used. + # Source types (known as "library source") are discussed in detail below. + # + # == Using with Adapters + # A list of libraries need to be passed into adapters upon creation. In + # most cases, you will never do this manually, but if you use a {RackMiddleware}, + # you will need to pass in this list yourself. To build this list of libraries, + # you should create a hash of library names mapped to an *Array* of LibraryVersion + # objects. For example: + # + # {'mylib' => [LibraryVersion.new('mylib', '1.0', ...), + # LibraryVersion.new('mylib', '2.0', ...)]} + # + # Note that you can also use {Adapter#add_library} for convenience. + # + # The "array" part is required, even for just one library version. + # + # == Library Sources + # The {#source} method represents the library source type, ie. where the + # library "comes from". It might come from "disk", or it might come from a + # "gem" (technically the disk, but a separate type nonetheless). In these + # two cases, the yardoc file sits somewhere on your filesystem, though + # it may also be built dynamically if it does not yet exist. This behaviour + # is controlled through the {#prepare!} method, which prepares the yardoc file + # given a specific library source. We will see how this works in detail in + # the following section. + # + # == Implementing a Custom Library Source + # YARD can be extended to support custom library sources in order to + # build or retrieve a yardoc file at runtime from many different locations. + # + # To implement this behaviour, 3 methods can be added to the +LibraryVersion+ + # class, +#load_yardoc_from_SOURCE+, +#yardoc_file_for_SOURCE+, and + # +#source_path_for_SOURCE+. In all cases, "SOURCE" represents the source + # type used in {#source} when creating the library object. The + # +#yardoc_file_for_SOURCE+ and +#source_path_for_SOURCE+ methods are called upon + # creation and should return the location where the source code for the library + # lives. The load method is called from {#prepare!} if there is no yardoc file + # and should set {#yardoc_file}. Below is a full example for + # implementing a custom library source, +:http+, which reads packaged .yardoc + # databases from zipped archives off of an HTTP server. + # + # Note that only +#load_yardoc_from_SOURCE+ is required. The other two + # methods are optional and can be set manually (via {#source_path=} and + # {#yardoc_file=}) on the object at any time. + # + # @example Implementing a Custom Library Source + # # Adds the source type "http" for .yardoc files zipped on HTTP servers + # class LibraryVersion + # def load_yardoc_from_http + # Thread.new do + # # zip/unzip method implementations are not shown + # download_zip_file("http://mysite.com/yardocs/#{self}.zip") + # unzip_file_to("/path/to/yardocs/#{self}") + # end + # + # # tell the server it's not ready yet (but it might be next time) + # raise LibraryNotPreparedError + # end + # + # def yardoc_file_for_http + # "/path/to/yardocs/#{self}/.yardoc" + # end + # + # def source_path_for_http + # File.dirname(yardoc_file) + # end + # end + # + # # Creating a library of this source type: + # LibraryVersion.new('name', '1.0', nil, :http) + # + class LibraryVersion + # @return [String] the name of the library + attr_accessor :name + + # @return [String] the version of the specific library + attr_accessor :version + + # @return [String] the location of the yardoc file used to load the object + # information from. + # @return [nil] if no yardoc file exists yet. In this case, {#prepare!} will + # be called on this library to build the yardoc file. + # @note To implement a custom yardoc file getter, implement + def yardoc_file + @yardoc_file ||= load_yardoc_file + end + attr_writer :yardoc_file + + # @return [Symbol] the source type representing where the yardoc should be + # loaded from. Defaults are +:disk+ and +:gem+, though custom sources + # may be implemented. This value is used to inform {#prepare!} about how + # to load the necessary data in order to display documentation for an object. + # @see LibraryVersion LibraryVersion documentation for "Implementing a Custom Library Source" + attr_accessor :source + + # @return [String] the location of the source code for a library. This + # value is filled by calling +#source_path_for_SOURCE+ on this class. + # @return [nil] if there is no source code + # @see LibraryVersion LibraryVersion documentation for "Implementing a Custom Library Source" + def source_path + @source_path ||= load_source_path + end + attr_writer :source_path + + # @param [String] name the name of the library + # @param [String] version the specific (usually, but not always, numeric) library + # version + # @param [String] yardoc the location of the yardoc file, or nil if it is + # generated later + # @param [Symbol] source the location of the files used to build the yardoc. + # Builtin source types are +:disk+ or +:gem+. + def initialize(name, version = nil, yardoc = nil, source = :disk) + self.name = name + self.yardoc_file = yardoc + self.version = version + self.source = source + end + + # @param [Boolean] url_format if true, returns the string in a URI-compatible + # format (for appending to a URL). Otherwise, it is given in a more human + # readable format. + # @return [String] the string representation of the library. + def to_s(url_format = true) + version ? "#{name}#{url_format ? '/' : '-'}#{version}" : name.to_s + end + + # @return [Fixnum] used for Hash mapping. + def hash; to_s.hash end + + # @return [Boolean] whether another LibraryVersion is equal to this one + def eql?(other) + other.is_a?(LibraryVersion) && other.name == name && + other.version == version && other.yardoc_file == yardoc_file + end + alias == eql? + alias equal? eql? + + # @return [Boolean] whether the library has been completely processed + # and is ready to be served + def ready? + return false if yardoc_file.nil? + serializer.complete? + end + + # @note You should not directly override this method. Instead, implement + # +load_yardoc_from_SOURCENAME+ when implementing loading for a specific + # source type. See the {LibraryVersion} documentation for "Implementing + # a Custom Library Source" + # + # Prepares a library to be displayed by the server. This callback is + # performed before each request on a library to ensure that it is loaded + # and ready to be viewed. If any steps need to be performed prior to loading, + # they are performed through this method (though they should be implemented + # through the +load_yardoc_from_SOURCE+ method). + # + # @raise [LibraryNotPreparedError] if the library is not ready to be + # displayed. Usually when raising this error, you would simultaneously + # begin preparing the library for subsequent requests, although this + # is not necessary. + def prepare! + return if ready? + meth = "load_yardoc_from_#{source}" + send(meth) if respond_to?(meth, true) + end + + # @return [Gem::Specification] a gemspec object for a given library. Used + # for :gem source types. + # @return [nil] if there is no installed gem for the library + def gemspec + ver = version ? "= #{version}" : ">= 0" + YARD::GemIndex.find_all_by_name(name, ver).last + end + + protected + + @@chdir_mutex = Mutex.new + + # Called when a library of source type "disk" is to be prepared. In this + # case, the {#yardoc_file} should already be set, but the library may not + # be prepared. Run preparation if not done. + # + # @raise [LibraryNotPreparedError] if the yardoc file has not been + # prepared. + def load_yardoc_from_disk + return if ready? + + @@chdir_mutex.synchronize do + Dir.chdir(source_path_for_disk) do + Thread.new do + CLI::Yardoc.run('--no-stats', '-n', '-b', yardoc_file) + end + end + end + + raise LibraryNotPreparedError + end + + # Called when a library of source type "gem" is to be prepared. In this + # case, the {#yardoc_file} needs to point to the correct location for + # the installed gem. The yardoc file is built if it has not been done. + # + # @raise [LibraryNotPreparedError] if the gem does not have an existing + # yardoc file. + def load_yardoc_from_gem + return if ready? + ver = version ? "= #{version}" : ">= 0" + + @@chdir_mutex.synchronize do + Thread.new do + # Build gem docs on demand + log.debug "Building gem docs for #{to_s(false)}" + CLI::Gems.run(name, ver) + log.debug "Done building gem docs for #{to_s(false)}" + end + end + + raise LibraryNotPreparedError + end + + # @return [String] the source path for a disk source + def source_path_for_disk + File.dirname(yardoc_file) if yardoc_file + end + + # @return [String] the source path for a gem source + def source_path_for_gem + gemspec.full_gem_path if gemspec + end + + # @return [String] the yardoc file for a gem source + def yardoc_file_for_gem + require 'rubygems' + ver = version ? "= #{version}" : ">= 0" + Registry.yardoc_file_for_gem(name, ver) + end + + private + + def load_source_path + meth = "source_path_for_#{source}" + send(meth) if respond_to?(meth, true) + end + + def load_yardoc_file + meth = "yardoc_file_for_#{source}" + send(meth) if respond_to?(meth, true) + end + + def serializer + return if yardoc_file.nil? + Serializers::YardocSerializer.new(yardoc_file) + end + end + end +end