lib/yard/server/commands/base.rb in yard-0.6.3 vs lib/yard/server/commands/base.rb in yard-0.6.4

- old
+ new

@@ -1,42 +1,92 @@ require 'fileutils' module YARD module Server module Commands + # This is the base command class used to implement custom commands for + # a server. A command will be routed to by the {Router} class and return + # a Rack-style response. + # + # == Attribute Initializers + # All attributes can be initialized via options passed into the {#initialize} + # method. When creating a custom command, the {Adapter#options} will + # automatically be mapped to attributes by the same name on your class. + # + # class MyCommand < Base + # attr_accessor :myattr + # end + # + # Adapter.new(libs, {:myattr => 'foo'}).start + # + # # when a request comes in, cmd.myattr == 'foo' + # + # == Subclassing Notes + # To implement a custom command, override the {#run} method, not {#call}. + # In your implementation, you should set the body and status for requests. + # See details in the +#run+ method documentation. + # + # Note that if your command deals directly with libraries, you should + # consider subclassing the more specific {LibraryCommand} class instead. + # + # @abstract + # @see #run class Base + # @group Basic Command and Adapter Options + # @return [Hash] the options passed to the command's constructor attr_accessor :command_options + # @return [Adapter] the server adapter + attr_accessor :adapter + + # @return [Boolean] whether to cache + attr_accessor :caching + + # @group Attributes Set Per Request + # @return [Request] request object attr_accessor :request # @return [String] the path after the command base URI attr_accessor :path # @return [Hash{String => String}] response headers attr_accessor :headers - # @return [Numeric] status code + # @return [Numeric] status code. Defaults to 200 per request attr_accessor :status - # @return [String] the response body + # @return [String] the response body. Defaults to empty string. attr_accessor :body - # @return [Adapter] the server adapter - attr_accessor :adapter - - # @return [Boolean] whether to cache - attr_accessor :caching + # @group Instance Method Summary + # Creates a new command object, setting attributes named by keys + # in the options hash. After initialization, the options hash + # is saved in {#command_options} for further inspection. + # + # @example Creating a Command + # cmd = DisplayObjectCommand.new(:caching => true, :library => mylib) + # cmd.library # => mylib + # cmd.command_options # => {:caching => true, :library => mylib} + # @param [Hash] opts the options hash, saved to {#command_options} + # after initialization. def initialize(opts = {}) opts.each do |key, value| send("#{key}=", value) if respond_to?("#{key}=") end self.command_options = opts end + # The main method called by a router with a request object. + # + # @note This command should not be overridden by subclasses. Implement + # the callback method {#run} instead. + # @param [Adapter Dependent] request the request object + # @return [Array(Number,Hash,Array<String>)] a Rack-style response + # of status, headers, and body wrapped in an array. def call(request) self.request = request self.path ||= request.path[1..-1] self.headers = {'Content-Type' => 'text/html'} self.body = '' @@ -49,24 +99,65 @@ self.status = 404 end not_found if status == 404 [status, headers, body.is_a?(Array) ? body : [body]] end + + # @group Abstract Methods + # Subclass this method to implement a custom command. This method + # should set the {#status} and {#body}, and optionally modify the + # {#headers}. Note that +#status+ defaults to 200. + # + # @example A custom command + # class ErrorCommand < Base + # def run + # self.body = 'ERROR! The System is down!' + # self.status = 500 + # self.headers['Conten-Type'] = 'text/plain' + # end + # end + # + # @abstract + # @return [void] def run raise NotImplementedError end + + protected - def not_found - return unless body.empty? - self.body = "Not found: #{request.path}" - self.headers['Content-Type'] = 'text/plain' - self.headers['X-Cascade'] = 'pass' + # @group Helper Methods + + # Renders a specific object if provided, or a regular template rendering + # if object is not provided. + # + # @todo This method is dependent on +#options+, it should be in {LibraryCommand}. + # @param [CodeObjects::Base, nil] object calls {CodeObjects::Base#format} if + # an object is provided, or {Templates::Engine.render} if object is nil. Both + # receive +#options+ as an argument. + # @return [String] the resulting output to display + def render(object = nil) + case object + when CodeObjects::Base + cache object.format(options) + when nil + cache Templates::Engine.render(options) + else + cache object + end end - protected - + # Override this method to implement custom caching mechanisms for + # + # @example Caching to memory + # $memory_cache = {} + # def cache(data) + # $memory_cache[path] = data + # end + # @param [String] data the data to cache + # @return [String] the same cached data (for chaining) + # @see StaticCaching def cache(data) if caching && adapter.document_root path = File.join(adapter.document_root, request.path.sub(/\.html$/, '') + '.html') path = path.sub(%r{/\.html$}, '.html') FileUtils.mkdir_p(File.dirname(path)) @@ -74,20 +165,23 @@ File.open(path, 'wb') {|f| f.write(data) } end self.body = data end - def render(object = nil) - case object - when CodeObjects::Base - cache object.format(options) - when nil - cache Templates::Engine.render(options) - else - cache object - end + # Sets the body and headers (but not status) for a 404 response. Does + # nothing if the body is already set. + # + # @return [void] + def not_found + return unless body.empty? + self.body = "Not found: #{request.path}" + self.headers['Content-Type'] = 'text/plain' + self.headers['X-Cascade'] = 'pass' end + # Sets the headers and status code for a redirection to a given URL + # @param [String] url the URL to redirect to + # @raise [FinishRequest] causes the request to terminate. def redirect(url) headers['Location'] = url self.status = 302 raise FinishRequest end