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