lib/yard/server/library_version.rb in yard-0.6.3 vs lib/yard/server/library_version.rb in yard-0.6.4

- old
+ new

@@ -1,56 +1,195 @@ require 'fileutils' 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, two methods must be added to the +LibraryVersion+ + # class, +#load_yardoc_from_SOURCE+ and +#source_path_for_SOURCE+. In both + # cases, "SOURCE" represents the source type used in {#source} when creating + # the library object. The +#source_path_for_SOURCE+ method is 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. + # + # @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 + # return if yardoc_file # we have the library + # + # # otherwise download it in a thread and return immediately + # 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}") + # self.yardoc_file = "/path/to/yardocs/#{self}/.yardoc" + # self.source_path = self.yardoc_file + # end + # + # # tell the server it's not ready yet (but it might be next time) + # raise LibraryNotPreparedError + # end + # + # # we set this later + # def source_path_for_http; nil 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. attr_accessor :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" attr_accessor :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 self.source_path = load_source_path 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}" 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? + # @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 yardoc_file meth = "load_yardoc_from_#{source}" send(meth) if respond_to?(meth) 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" Gem.source_index.find_name(name, ver).first end protected + # Called when a library of source type "disk" is to be prepared. In this + # case, the {#yardoc_file} should already be set, so nothing needs to be + # done. def load_yardoc_from_disk nil 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 require 'rubygems' ver = version ? "= #{version}" : ">= 0" self.yardoc_file = Registry.yardoc_file_for_gem(name, ver) unless yardoc_file && File.directory?(yardoc_file) @@ -65,13 +204,15 @@ unless yardoc_file && File.exist?(File.join(yardoc_file, 'complete')) raise LibraryNotPreparedError end 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 private \ No newline at end of file