class Deas::Runner

Constants

DEFAULT_CHARSET
DEFAULT_MIME_TYPE
SplatParseError

Attributes

handler[R]
handler_class[R]
logger[R]
params[R]
request[R]
route_path[R]
router[R]
template_source[R]

Public Class Methods

body_value(value) click to toggle source
# File lib/deas/runner.rb, line 14
def self.body_value(value)
  # http://www.rubydoc.info/github/rack/rack/master/file/SPEC#The_Body
  # "The Body must respond to each and must only yield String values"
  # String#each is a thing in 1.8.7, so account for it here
  return nil if value.to_s.empty?
  !value.respond_to?(:each) || value.kind_of?(String) ? [*value.to_s] : value
end
new(handler_class, args = nil) click to toggle source
# File lib/deas/runner.rb, line 26
def initialize(handler_class, args = nil)
  @handler_class = handler_class
  @status, @body = nil, nil
  @headers = Rack::Utils::HeaderHash.new.merge(@handler_class.default_headers)
  @handler = @handler_class.new(self)

  args ||= {}
  @request         = args[:request]
  @route_path      = args[:route_path].to_s
  @params          = args[:params]          || {}
  @logger          = args[:logger]          || Deas::NullLogger.new
  @router          = args[:router]          || Deas::Router.new
  @template_source = args[:template_source] || Deas::NullTemplateSource.new
end

Public Instance Methods

body(value = nil) click to toggle source
# File lib/deas/runner.rb, line 66
def body(value = nil)
  if !value.nil?
    @body = self.class.body_value(value)
  end
  @body
end
content_type(extname, params = nil) click to toggle source
# File lib/deas/runner.rb, line 73
def content_type(extname, params = nil)
  self.headers['Content-Type'] = get_content_type(extname, params)
end
halt(*args) click to toggle source
# File lib/deas/runner.rb, line 85
def halt(*args)
  self.status(args.shift)  if args.first.instance_of?(::Fixnum)
  self.headers(args.shift) if args.first.kind_of?(::Hash)
  self.body(args.shift)    if !args.first.to_s.empty?
  throw :halt
end
headers(value = nil) click to toggle source
# File lib/deas/runner.rb, line 61
def headers(value = nil)
  @headers.merge!(value) if !value.nil?
  @headers
end
partial(template_name, locals = nil) click to toggle source
# File lib/deas/runner.rb, line 139
def partial(template_name, locals = nil)
  source_partial(self.template_source, template_name, locals)
end
redirect(location, *halt_args) click to toggle source
# File lib/deas/runner.rb, line 92
def redirect(location, *halt_args)
  self.status(302)
  self.headers['Location'] = get_absolute_url(location)
  halt(*halt_args)
end
render(template_name, locals = nil) click to toggle source
# File lib/deas/runner.rb, line 127
def render(template_name, locals = nil)
  source_render(self.template_source, template_name, locals)
end
run() click to toggle source
# File lib/deas/runner.rb, line 45
def run
  raise NotImplementedError
end
send_file(file_path, opts = nil) click to toggle source
# File lib/deas/runner.rb, line 98
def send_file(file_path, opts = nil)
  path_name = Pathname.new(file_path)
  self.halt(404, []) if !path_name.exist?

  env   = self.request.env
  mtime = path_name.mtime.httpdate.to_s
  self.halt(304, []) if env['HTTP_IF_MODIFIED_SINCE'] == mtime
  self.headers['Last-Modified'] ||= mtime

  self.headers['Content-Type'] ||= get_content_type(path_name.extname)

  opts ||= {}
  disposition = opts[:disposition]
  filename    = opts[:filename]
  disposition ||= 'attachment' if !filename.nil?
  filename    ||= path_name.basename
  if !disposition.nil?
    self.headers['Content-Disposition'] ||= "#{disposition};filename=\"#{filename}\""
  end

  sfb = SendFileBody.new(env, path_name)
  self.body(sfb)
  self.headers['Content-Length'] ||= sfb.size.to_s
  self.headers['Content-Range']  ||= sfb.content_range if sfb.partial?
  self.status(sfb.partial? ? 206 : 200)

  self.halt # be consistent with halts above - `send_file` always halts
end
source_partial(source, template_name, locals = nil) click to toggle source
# File lib/deas/runner.rb, line 143
def source_partial(source, template_name, locals = nil)
  source.partial(template_name, locals || {})
end
source_render(source, template_name, locals = nil) click to toggle source
# File lib/deas/runner.rb, line 131
def source_render(source, template_name, locals = nil)
  self.headers['Content-Type'] ||= get_content_type(
    File.extname(template_name),
    'charset' => DEFAULT_CHARSET
  )
  self.body(source.render(template_name, self.handler, locals || {}))
end
splat() click to toggle source
# File lib/deas/runner.rb, line 41
def splat
  @splat ||= parse_splat_value
end
status(value = nil) click to toggle source
# File lib/deas/runner.rb, line 56
def status(value = nil)
  @status = value if !value.nil?
  @status
end
to_rack() click to toggle source
# File lib/deas/runner.rb, line 49
def to_rack
  [ self.status || @handler_class.default_status,
    self.headers.to_hash,
    self.body   || @handler_class.default_body
  ]
end

Private Instance Methods

get_absolute_url(url) click to toggle source
# File lib/deas/runner.rb, line 155
def get_absolute_url(url)
  return url if url =~ /\A[A-z][A-z0-9\+\.\-]*:/
  File.join("#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}", url)
end
get_content_type(extname, params = nil) click to toggle source
# File lib/deas/runner.rb, line 149
def get_content_type(extname, params = nil)
  [ Rack::Mime.mime_type(extname, DEFAULT_MIME_TYPE),
    params ? params.map{ |k,v| "#{k}=#{v}" }.join(',') : nil
  ].compact.join(';')
end
parse_splat_value() click to toggle source
# File lib/deas/runner.rb, line 160
def parse_splat_value
  return nil if request.nil? || (path_info = request.env['PATH_INFO']).nil?

  regex = splat_value_parse_regex
  match = regex.match(path_info)
  if match.nil?
    raise SplatParseError, "could not parse the splat value: " \
                           "the PATH_INFO, `#{path_info.inspect}`, " \
                           "doesn't match " \
                           "the route, `#{self.route_path.inspect}`, " \
                           "using the route regex, `#{regex.inspect}`"
  end
  match.captures.first
end
route_path_with_regex_placeholders() click to toggle source
# File lib/deas/runner.rb, line 183
def route_path_with_regex_placeholders
  # Replace param values with regex placeholders b/c we don't care what
  # the values are just that "a param value goes here".  Use gsub in the odd
  # case a placeholder is used more than once in a route.  This is to help
  # ensure matching and capturing splats even on nonsense paths.
  sorted_param_names.inject(self.route_path) do |path, name|
    path.gsub(":#{name}", '.+?')
  end
end
sorted_param_names() click to toggle source
# File lib/deas/runner.rb, line 193
def sorted_param_names
  # Sort longer key names first so they will be processed first.  This
  # ensures that shorter param names that compose longer param names won't
  # be subbed in the longer param name's place.
  self.params.keys.sort{ |a, b| b.size <=> a.size }
end
splat_value_parse_regex() click to toggle source
# File lib/deas/runner.rb, line 177
def splat_value_parse_regex
  # `sub` should suffice as only a single trailing splat is allowed. Replace
  # any splat with a capture placholder so we can extract the splat value.
  /^#{route_path_with_regex_placeholders.sub('*', '(.+?)')}$/
end