Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added YARD docs (100% Yardstick coverage)

  • Loading branch information...
commit bbdea40e36533365c4581b0491138da4ba27016f 1 parent db49bad
@dkubb authored
View
10 README.rdoc
@@ -1,9 +1,15 @@
= rack-conneg
-Description goes here.
+Negotiate the best static file for a request based on client preferences.
+
+== Usage
+
+Usage is the same as Rack::Static, eg:
+
+ use Rack::Conneg::Static, :urls => %w[ /css /images ], :root => 'public'
== Note on Patches/Pull Requests
-
+
* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a
View
4 Rakefile
@@ -9,8 +9,8 @@ begin
Jeweler::Tasks.new do |gem|
gem.name = 'rack-conneg'
- gem.summary = %Q{TODO: one-line summary of your gem}
- gem.description = %Q{TODO: longer description of your gem}
+ gem.summary = 'Rack middleware for static file content negotiation'
+ gem.description = 'Negotiate the best static file for a request based on client preferences'
gem.email = 'dan.kubb@gmail.com'
gem.homepage = 'http://github.com/dkubb/rack-conneg'
gem.authors = [ 'Dan Kubb' ]
View
19 lib/rack/conneg/file.rb
@@ -1,10 +1,29 @@
module Rack
class Conneg
class File
+
+ # Initialize a new File
+ #
+ # @param [#call] app
+ # the Rack app to handle static requests
+ #
+ # @return [File]
+ # returns a new File instance
+ #
+ # @api private
def initialize(file_server)
@file_server = file_server
end
+ # Negotiate a response using the request headers
+ #
+ # @param [Hash] env
+ # the Rack request environment
+ #
+ # @return [Array(Integer, Hash, #each)]
+ # returns a Rack response
+ #
+ # @api private
def call(env)
Negotiator.negotiate(@file_server, env)
end
View
98 lib/rack/conneg/negotiator.rb
@@ -7,56 +7,142 @@ class Negotiator
"406 Not Acceptable\n",
].freeze
+ # Negotiate a response using the request headers
+ #
+ # @param [#call] app
+ # the Rack app to handle static requests
+ # @param [Hash] env
+ # the Rack request environment
+ #
+ # @return [Array(Integer, Hash, #each)]
+ # returns a Rack response
+ #
+ # @api private
def self.negotiate(app, env)
negotiator = new(app, env)
negotiator.pass? ? negotiator.pass : negotiator.call
end
+ # Initialize a new Negotiator
+ #
+ # @param [#call] app
+ # the Rack app to handle static requests
+ # @param [Hash] env
+ # the Rack request environment
+ #
+ # @return [Negotiator]
+ # returns a new Negotiator instance
+ #
+ # @api private
def initialize(app, env)
@app = app
- @env = env
- @request = Acceptable::Request.new(@env)
+ @request = Acceptable::Request.new(env)
@path = Path.new(app.root, @request.path_info)
end
+ # Test if the request should be passed to the next Rack app
+ #
+ # @return [Boolean]
+ # true if the request should be passed through
+ #
+ # @api private
def pass?
not @path.variants?
end
- def pass(env = @env)
+ # The response from the next Rack app
+ #
+ # @param [Hash] env
+ # optional Rack request environment
+ #
+ # @return [Array(Integer, Hash, #each)]
+ # returns a Rack response
+ #
+ # @api private
+ def pass(env = @request.env)
@app.call(env)
end
+ # Negotiate the optimal response based on client preferences
+ #
+ # @return [Array(Integer, Hash, #each)]
+ # returns a negotiated Rack response
+ #
+ # @api private
def call
variant = select_variant
- response = variant ? serve_variant(variant.path_info) : NOT_ACCEPTABLE
+ response = variant ? serve_path(variant) : NOT_ACCEPTABLE
self.class.append_vary_header(response[1], 'Accept')
response
end
private
+ # Select the best variant based on client preferences
+ #
+ # @return [Path, nil]
+ # the negotiated variant, nil if none available
+ #
+ # @api private
def select_variant
variants = @path.variants
variants[@request.preferred_media_from(*variants.keys)]
end
- def serve_variant(path_info)
- status, headers, body = pass(@env.merge('PATH_INFO' => path_info))
+ # Serve the path to the client
+ #
+ # @param [Path] path
+ # the path to serve
+ #
+ # @return [Array(Integer, Hash, #each)]
+ # returns a negotiated Rack response
+ #
+ # @api private
+ def serve_path(path)
+ path_info = path.path_info
+ status, headers, body = pass(@request.env.merge('PATH_INFO' => path_info))
headers['Content-Location'] = path_info if (200..299).include?(status)
[ status, headers, body ]
end
+ # Append client header names used to negotiate the response to Vary
+ #
+ # @param [Hash] headers
+ # the Rack response headers
+ # @param [Array<String>] *names
+ # the client headers used to negotiate the response
+ #
+ # @return [undefined]
+ #
+ # @api private
def self.append_vary_header(headers, *names)
vary = split_header(headers['Vary'])
return if vary.include?('*')
headers['Vary'] = join_header(vary | names)
end
+ # Split the header value into an Array
+ #
+ # @param [#to_s] header
+ # the header value to split
+ #
+ # @return [Array<String>]
+ # the header values
+ #
+ # @api private
def self.split_header(header)
header.to_s.delete(' ').split(',')
end
+ # Join the header values into a String
+ #
+ # @param [Array<String>] values
+ # the header values to join
+ #
+ # @return [String]
+ # the header value
+ #
+ # @api private
def self.join_header(values)
values.join(',')
end
View
90 lib/rack/conneg/path.rb
@@ -5,26 +5,57 @@ class Conneg
class Path
EXTENSION_REGEXP = Regexp.union(*Mime::MIME_TYPES.keys.map { |ext| ext[1..-1] }).freeze
+ # Return the request path
+ #
+ # @return [String]
+ # the request path
+ #
+ # @api private
attr_reader :path_info
+ # Initialize a new Path
+ #
+ # @param [String, Pathname] root
+ # the server document root
+ # @param [String] path_info
+ # the request path
+ #
+ # @return [Path]
+ # returns a new Path instance
+ #
+ # @api private
def initialize(root, path_info)
@root = Pathname(root)
@path_info = self.class.normalize_path_info(path_info)
@path = @root + @path_info[1..-1]
end
+ # Return the mime type for the request path
+ #
+ # @return [String, nil]
+ # the mime type if known
+ #
+ # @api private
def mime_type
Mime.mime_type(extname, nil)
end
- def exist?
- @path.exist?
- end
-
+ # Test if there are variants for the request path
+ #
+ # @return [Boolean]
+ # true if there are variants
+ #
+ # @api private
def variants?
variants.any?
end
+ # Return the Hash of media types and variants
+ #
+ # @return [Hash]
+ # the variants for the request path
+ #
+ # @api private
def variants
return @variants if @variants
variants = {}
@@ -34,34 +65,79 @@ def variants
private
+ # Return the variant paths
+ #
+ # @return [Array<Path>]
+ # the variant paths
+ #
+ # @api private
def variant_paths
- paths.select { |path| variant?(path) && path.exist? }
- rescue SystemCallError
- []
+ paths.select { |path| variant?(path) }
end
+ # Return all the paths in the request path directory
+ #
+ # @return [Array<Path>]
+ # the paths in the current directory
+ #
+ # @api private
def paths
directory.map do |path|
self.class.new(@root, path.relative_path_from(@root))
end
end
+ # Return all the children in the request path directory
+ #
+ # @return [Array<Pathname>]
+ # the children in the current directory
+ #
+ # @api private
def directory
@path.dirname.children
+ rescue SystemCallError
+ []
end
+ # Return the request path extension
+ #
+ # @return [String, nil]
+ # the request path extension
+ #
+ # @api private
def extname
@path.extname
end
+ # Test if the other Path is a variant
+ #
+ # @param [Path] other
+ # the other Path to test
+ #
+ # @return [Boolean]
+ # true if the other Path is a variant
+ #
+ # @api private
def variant?(other)
pattern === other.path_info
end
+ # Return a Regexp to match the request path and known extensions
+ #
+ # @return [Regexp]
+ # match the request path
+ #
+ # @api private
def pattern
@pattern ||= /\A#{Regexp.escape(path_info)}\.#{EXTENSION_REGEXP}\z/.freeze
end
+ # Normalize the request path info
+ #
+ # @return [String]
+ # the request path
+ #
+ # @api private
def self.normalize_path_info(path_info)
"/#{Utils.unescape(path_info.to_s).gsub(/\A\//, '')}"
end
View
17 lib/rack/conneg/static.rb
@@ -1,7 +1,22 @@
module Rack
class Conneg
class Static < Rack::Static
- def initialize(*)
+
+ # Initialize middleware for static file content negotiation
+ #
+ # @example
+ # use Rack::Conneg::Static, :urls => %w[ /css /images ], :root => 'public'
+ #
+ # @param [#call] app
+ # the Rack app to handle static requests
+ # @param [Hash] options
+ # optional middleware configuration
+ #
+ # @return [Static]
+ # returns a new Static instance
+ #
+ # @api public
+ def initialize(app, options = {})
super
@file_server = File.new(@file_server)
end
Please sign in to comment.
Something went wrong with that request. Please try again.