1
0
Fork 0
mirror of https://github.com/sinatra/sinatra synced 2023-03-27 23:18:01 -04:00

Merge branch 'master' into content-for

This commit is contained in:
Konstantin Haase 2011-03-27 17:53:15 +02:00
commit 36d2a73c9b
28 changed files with 1835 additions and 0 deletions

2
sinatra-contrib/Gemfile Normal file
View file

@ -0,0 +1,2 @@
source "http://rubygems.org" unless ENV['QUICK']
gemspec

View file

@ -0,0 +1 @@
TODO: More imports/rewrites, documentation.

View file

@ -0,0 +1,40 @@
require 'sinatra/base'
require 'yaml'
module Sinatra
module ConfigFile
def self.registered(base)
base.set :environments, %w[test production development]
end
def config_file(*paths)
Dir.chdir(root || '.') do
paths.each do |pattern|
Dir.glob(pattern) do |file|
$stderr.puts "loading config file '#{file}'" if logging?
yaml = config_for_env(YAML.load_file(file)) || {}
yaml.each_pair do |key, value|
for_env = config_for_env(value)
set key, for_env unless value and for_env.nil? and respond_to? key
end
end
end
end
end
private
def config_for_env(hash)
if hash.respond_to? :keys and hash.keys.all? { |k| environments.include? k.to_s }
hash = hash[environment.to_s] || hash[environment.to_sym]
end
if hash.respond_to? :to_hash
indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
indifferent_hash.merge hash.to_hash
else
hash
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'sinatra/contrib/setup'
module Sinatra
module Contrib
##
# Common middleware that doesn't bring run time overhead if not used
# or breaks if external dependencies are missing. Will extend
# Sinatra::Application by default.
module Common
register :ConfigFile
register :Decompile
register :Namespace
register :RespondWith
helpers :LinkHeader
end
##
# Other extensions you don't want to be loaded unless needed.
module Custom
end
##
# Stuff that aren't Sinatra extensions, technically.
autoload :Extension
autoload :TestHelpers
end
register Sinatra::Contrib::Common
end

View file

@ -0,0 +1,46 @@
require 'sinatra/base'
require 'sinatra/contrib/version'
require 'backports'
module Sinatra
module Contrib
module Loader
def extensions
@extensions ||= {:helpers => [], :register => []}
end
def register(name, path = nil)
autoload name, path, :register
end
def helpers(name, path = nil)
autoload name, path, :helpers
end
def autoload(name, path = nil, method = nil)
path ||= "sinatra/#{name.to_s.underscore}"
extensions[method] << name if method
Sinatra.autoload(name, path)
end
def registered(base)
@extensions.each do |meth, list|
base.send(meth, *list.map { |n| Sinatra.const_get n })
end
end
end
module Common
extend Loader
end
module Custom
extend Loader
end
extend Loader
def self.registered(base)
base.register Common, Custom
end
end
end

View file

@ -0,0 +1,44 @@
module Sinatra
module Contrib
def self.version
VERSION
end
module VERSION
extend Comparable
MAJOR = 1
MINOR = 2
TINY = 0
SIGNATURE = [MAJOR, MINOR, TINY]
STRING = SIGNATURE.join '.'
def self.major; MAJOR end
def self.minor; MINOR end
def self.tiny; TINY end
def self.to_s; STRING end
def self.hash
STRING.hash
end
def self.<=>(other)
other = other.split('.').map { |i| i.to_i } if other.respond_to? :split
SIGNATURE <=> Array(other)
end
def self.inspect
STRING.inspect
end
def self.respond_to?(meth, *)
meth.to_s !~ /^__|^to_str$/ and STRING.respond_to? meth unless super
end
def self.method_missing(meth, *args, &block)
return super unless STRING.respond_to?(meth)
STRING.send(meth, *args, &block)
end
end
end
end

View file

@ -0,0 +1,61 @@
require 'sinatra/base'
require 'backports'
module Sinatra
##
# Can be used as extension or stand-alone:
#
# Sinatra::Decompile.decompile(...)
module Decompile
extend self
##
# Regenerates a string pattern for a given route
#
# Example:
#
# class Sinatra::Application
# routes.each do |verb, list|
# puts "#{verb}:"
# list.each do |data|
# puts "\t" << decompile(data)
# end
# end
# end
#
# Will return the internal Regexp if unable to reconstruct the pattern,
# which likely indicates that a Regexp was used in the first place.
#
# You can also use this to check whether you could actually use a string
# pattern instead of your regexp:
#
# decompile /^/foo$/ # => '/foo'
def decompile(pattern, keys = nil, *)
# Everything in here is basically just the reverse of
# Sinatra::Base#compile
pattern, keys = pattern if pattern.respond_to? :to_ary
keys, str = keys.try(:dup), pattern.inspect
return pattern unless str.start_with? '/' and str.end_with? '/'
str.gsub! /^\/\^?|\$?\/$/, ''
return pattern if str =~ /^[\.\+]/
str.gsub! /\([^\(]*\)/ do |part|
case part
when '(.*?)'
return pattern if keys.shift != 'splat'
'*'
when '([^\/?#]+)'
return pattern if keys.empty?
":" << keys.shift
else
return pattern
end
end
str.gsub /(.)([\.\+\(\)\/])/ do
return pattern if $1 != "\\"
$2
end
end
end
register Decompile
end

View file

@ -0,0 +1,50 @@
require 'sinatra/base'
require 'backports/basic_object' unless defined? BasicObject
module Sinatra
module Extension
def self.new(&block)
ext = Module.new.extend(self)
ext.class_eval(&block)
ext
end
def settings
self
end
def configure(*args, &block)
record(:configure, *args) { |c| c.instance_exec(c, &block) }
end
def registered(base = nil, &block)
base ? replay(base) : record(:class_eval, &block)
end
private
def record(method, *args, &block)
recorded_methods << [method, args, block]
end
def replay(object)
recorded_methods.each { |m, a, b| object.send(m, *a, &b) }
end
def recorded_methods
@recorded_methods ||= []
end
def method_missing(method, *args, &block)
return super unless Sinatra::Base.respond_to? method
record(method, *args, &block)
DontCall.new(method)
end
class DontCall < BasicObject
def initialize(method) @method = method end
def method_missing(*) fail "not supposed to use result of #@method!" end
def inspect; "#<#{self.class}: #{@method}>" end
end
end
end

View file

@ -0,0 +1,84 @@
require 'sinatra/base'
module Sinatra
##
# Helper methods for generating Link HTTP headers and HTML tags.
module LinkHeader
##
# Set Link HTTP header and returns HTML tags for telling the browser to
# prefetch given resources (only supported by Opera and Firefox at the
# moment).
def prefetch(*urls)
link(:prefetch, *urls)
end
##
# Sets Link HTTP header and returns HTML tags for using stylesheets.
def stylesheet(*urls)
urls << {} unless urls.last.respond_to? :to_hash
urls.last[:type] ||= mime_type(:css)
link(:stylesheet, *urls)
end
##
# Sets Link HTTP header and returns corresponding HTML tags.
#
# Example:
#
# # Sets header:
# # Link: </foo>; rel="next"
# # Returns String:
# # '<link href="/foo" rel="next" />'
# link '/foo', :rel => :next
#
# # Multiple URLs
# link :stylesheet, '/a.css', '/b.css'
def link(*urls)
opts = urls.last.respond_to?(:to_hash) ? urls.pop : {}
opts[:rel] = urls.shift unless urls.first.respond_to? :to_str
options = opts.map { |k, v| " #{k}=#{v.to_s.inspect}" }
html_pattern = "<link href=\"%s\"#{options.join} />"
http_pattern = ["<%s>", *options].join ";"
link = (response["Link"] ||= "")
urls.map do |url|
link << "\n" unless link.empty?
link << (http_pattern % url)
html_pattern % url
end.join "\n"
end
##
# Takes the current value of th Link header(s) and generates HTML tags
# from it.
#
# Example:
#
# get '/' do
# # You can of course use fancy helpers like #link, #stylesheet
# # or #prefetch
# response["Link"] = '</foo>; rel="next"'
# haml :some_page
# end
#
# __END__
#
# @@ layout
# %head= link_headers
# %body= yield
def link_headers
yield if block_given?
return "" unless response.include? "Link"
response["Link"].lines.map do |line|
url, *opts = line.split(';').map(&:strip)
"<link href=\"#{url[1..-2]}\" #{opts.join " "} />"
end.join "\n"
end
def self.registered(base)
puts "WARNING: #{self} is a helpers module, not an extension."
end
end
helpers LinkHeader
end

View file

@ -0,0 +1,137 @@
require 'sinatra/base'
require 'sinatra/decompile'
module Sinatra
module Namespace
def self.new(base, pattern, conditions = {}, &block)
Module.new do
extend NamespacedMethods
@base, @extensions = base, []
@pattern, @conditions = compile(pattern, conditions)
namespace = self
before { extend namespace }
define_method(:error_block!) do |*keys|
if block = keys.inject(nil) { |b,k| b ||= namespace.errors[k] }
instance_eval(&block)
else
super(*keys)
end
end
class_eval(&block)
end
end
module SharedMethods
def namespace(pattern, conditions = {}, &block)
Sinatra::Namespace.new(self, pattern, conditions, &block)
end
end
module NamespacedMethods
include SharedMethods
include Sinatra::Decompile
attr_reader :base
def self.prefixed(*names)
names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }}
end
prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put
def helpers(*extensions, &block)
class_eval(&block) if block_given?
include(*extensions) if extensions.any?
end
def register(*extensions, &block)
extensions << Module.new(&block) if block_given?
@extensions += extensions
extensions.each do |extension|
extend extension
extension.registered(self) if extension.respond_to?(:registered)
end
end
def invoke_hook(name, *args)
@extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
end
def errors
@errors ||= {}
end
def not_found(&block)
error(404, &block)
end
def error(codes = Exception, &block)
[*codes].each { |c| errors[c] = block }
end
def respond_to(*args)
return @conditions[:provides] || base.respond_to if args.empty?
@conditions[:provides] = args
end
private
def app
base.respond_to?(:base) ? base.base : base
end
def compile(pattern, conditions, default_pattern = nil)
if pattern.respond_to? :to_hash
conditions = conditions.merge pattern.to_hash
pattern = nil
end
base_pattern, base_conditions = @pattern, @conditions
pattern ||= default_pattern
base_pattern ||= base.pattern if base.respond_to? :pattern
base_conditions ||= base.conditions if base.respond_to? :conditions
[ prefixed_path(base_pattern, pattern),
(base_conditions || {}).merge(conditions) ]
end
def prefixed_path(a, b)
return a || b || // unless a and b
a, b = decompile(a), decompile(b) unless a.class == b.class
a, b = regexpify(a), regexpify(b) unless a.class == b.class
path = a.class.new "#{a}#{b}"
path = /^#{path}$/ if path.is_a? Regexp and base == app
path
end
def regexpify(pattern)
pattern = Sinatra::Base.send(:compile, pattern).first.inspect
pattern.gsub! /^\/(\^|\\A)?|(\$|\\Z)?\/$/, ''
Regexp.new pattern
end
def prefixed(method, pattern = nil, conditions = {}, &block)
default = '*' if method == :before or method == :after
pattern, conditions = compile pattern, conditions, default
result = base.send(method, pattern, conditions, &block)
invoke_hook :route_added, method.to_s.upcase, pattern, block
result
end
def method_missing(meth, *args, &block)
return super if args.any? or block or not base.respond_to? meth
base.send meth
end
end
module BaseMethods
include SharedMethods
end
def self.extend_object(base)
base.extend BaseMethods
end
end
register Sinatra::Namespace
Delegator.delegate :namespace
end

View file

@ -0,0 +1,154 @@
require 'sinatra/base'
require 'json' unless String.method_defined? :to_json
module Sinatra
module RespondWith
class Format
def initialize(app)
@app, @map, @generic, @default = app, {}, {}, nil
end
def on(type, &block)
@app.settings.mime_types(type).each do |mime|
case mime
when '*/*' then @default = block
when /^([^\/]+)\/\*$/ then @generic[$1] = block
else @map[mime] = block
end
end
end
def finish
yield self if block_given?
mime_type = @app.content_type ||
@app.request.preferred_type(@map.keys) ||
@app.request.preferred_type ||
'text/html'
type = mime_type.split(/\s*;\s*/, 2).first
handlers = [@map[type], @generic[type[/^[^\/]+/]], @default].compact
handlers.each do |block|
if result = block.call(type)
@app.content_type mime_type
@app.halt result
end
end
@app.halt 406
end
def method_missing(meth, *args, &block)
return super if args.any? or block.nil? or not @app.mime_type(meth)
on(meth, &block)
end
end
module Helpers
def respond_with(template, object = nil, &block)
object, template = template, nil unless Symbol === template
format = Format.new(self)
format.on "*/*" do |type|
exts = settings.ext_map[type]
exts << :xml if type.end_with? '+xml'
if template
args = template_cache.fetch(type, template) { template_for(template, exts) }
if args.any?
locals = { :object => object }
locals.merge! object.to_hash if object.respond_to? :to_hash
args << { :locals => locals }
halt send(*args)
end
end
if object
exts.each do |ext|
next unless meth = "to_#{ext}" and object.respond_to? meth
halt(*object.send(meth))
end
end
false
end
format.finish(&block)
end
def respond_to(&block)
Format.new(self).finish(&block)
end
private
def template_for(name, exts)
# in production this is cached, so don't worry to much about runtime
possible = []
settings.template_engines[:all].each do |engine|
exts.each { |ext| possible << [engine, "#{name}.#{ext}"] }
end
exts.each do |ext|
settings.template_engines[ext].each { |e| possible << [e, name] }
end
possible.each do |engine, template|
find_template(settings.views, template, Tilt[engine]) do |file|
next unless File.exist? file
return settings.rendering_method(engine) << template.to_sym
end
end
[] # nil or false would not be cached
end
end
attr_accessor :ext_map
def remap_extensions
ext_map.clear
Rack::Mime::MIME_TYPES.each { |e,t| ext_map[t] << e[1..-1].to_sym }
ext_map['text/javascript'] << 'js'
ext_map['text/xml'] << 'xml'
end
def mime_type(*)
result = super
remap_extensions
result
end
def respond_to(*formats)
if formats.any?
@respond_to ||= []
@respond_to.concat formats
elsif @respond_to.nil? and superclass.respond_to? :respond_to
superclass.respond_to
else
@respond_to
end
end
def rendering_method(engine)
return [engine] if Sinatra::Templates.method_defined? engine
return [:mab] if engine.to_sym == :markaby
[:render, :engine]
end
private
def compile!(verb, path, block, options = {})
options[:provides] ||= respond_to if respond_to
super
end
ENGINES = {
:css => [:less, :sass, :scss],
:xml => [:builder, :nokogiri],
:js => [:coffee],
:html => [:erb, :erubis, :haml, :slim, :liquid, :radius, :mab, :markdown,
:textile, :rdoc],
:all => Sinatra::Templates.instance_methods.map(&:to_sym) + [:mab] -
[:find_template, :markaby]
}
ENGINES.default = []
def self.registered(base)
base.ext_map = Hash.new { |h,k| h[k] = [] }
base.set :template_engines, ENGINES.dup
base.remap_extensions
base.helpers Helpers
end
end
end

View file

@ -0,0 +1,86 @@
require 'sinatra/base'
require 'rack/test'
require 'rack'
require 'forwardable'
module Sinatra
Base.set :environment, :test
module TestHelpers
class Session < Rack::Test::Session
def global_env
@global_env ||= {}
end
private
def default_env
super.merge global_env
end
end
include Rack::Test::Methods
extend Forwardable
attr_accessor :settings
def_delegators :last_response, :body, :headers, :status, :errors
def_delegators :app, :configure, :set, :enable, :disable, :use, :helpers, :register
def_delegators :current_session, :env_for
def mock_app(base = Sinatra::Base, &block)
inner = nil
@app = Sinatra.new(base) do
inner = self
class_eval(&block)
end
@settings = inner
app
end
def app=(base)
@app = base
end
alias set_app app=
def app
@app ||= Class.new Sinatra::Base
Rack::Lint.new @app
end
unless method_defined? :options
def options(uri, params = {}, env = {}, &block)
env = env_for(uri, env.merge(:method => "OPTIONS", :params => params))
current_session.send(:process_request, uri, env, &block)
end
end
unless method_defined? :patch
def patch(uri, params = {}, env = {}, &block)
env = env_for(uri, env.merge(:method => "PATCH", :params => params))
current_session.send(:process_request, uri, env, &block)
end
end
def last_request?
last_request
true
rescue Rack::Test::Error
false
end
def session
return {} unless last_request?
raise Rack::Test:Error, "session not enabled for app" unless last_env["rack.session"] or app.session?
last_request.session
end
def last_env
last_request.env
end
def build_rack_test_session(name) # :nodoc:
Session.new rack_mock_session(name)
end
end
end

View file

@ -0,0 +1,17 @@
Gem::Specification.new do |s|
s.name = "sinatra-contrib"
s.version = "1.2.0"
s.description = "Collection of useful Sinatra extensions"
s.authors = ["Konstantin Haase"]
s.email = "konstantin.mailinglists@googlemail.com"
s.files = Dir["**/*.{rb,md}"] << "LICENSE"
s.has_rdoc = 'yard'
s.homepage = "http://github.com/rkh/#{s.name}"
s.require_paths = ["lib"]
s.summary = s.description
s.add_dependency "sinatra", "~> 1.2.2"
s.add_dependency "backports", ">= 2.0"
s.add_development_dependency "rspec", "~> 2.3"
end

View file

@ -0,0 +1,6 @@
---
foo: bar
something: 42
nested:
a: 1
b: 2

View file

@ -0,0 +1,4 @@
---
foo:
production: 10
development: 20

View file

@ -0,0 +1,7 @@
---
development:
foo: development
production:
foo: production
test:
foo: test

View file

@ -0,0 +1,11 @@
---
database:
production:
adapter: postgresql
database: foo_production
development:
adapter: sqlite
database: db/development.db
test:
adapter: sqlite
database: db/test.db

View file

@ -0,0 +1,44 @@
require 'backports'
require_relative 'spec_helper'
describe Sinatra::ConfigFile do
def config_file(*args, &block)
mock_app do
register Sinatra::ConfigFile
set :root, File.expand_path('../config_file', __FILE__)
instance_eval(&block) if block
config_file(*args)
end
end
it 'should set options from a simple config_file' do
config_file 'key_value.yml'
settings.foo.should == 'bar'
settings.something.should == 42
end
it 'should create indifferent hashes' do
config_file 'key_value.yml'
settings.nested['a'].should == 1
settings.nested[:a].should == 1
end
it 'should recognize env specific settings per file' do
config_file 'with_envs.yml'
settings.foo.should == 'test'
end
it 'should recognize env specific settings per setting' do
config_file 'with_nested_envs.yml'
settings.database[:adapter].should == 'sqlite'
end
it 'should not set present values to nil if the current env is missing' do
# first let's check the test is actually working properly
config_file('missing_env.yml') { set :foo => 42, :environment => :production }
settings.foo.should == 10
# now test it
config_file('missing_env.yml') { set :foo => 42, :environment => :test }
settings.foo.should == 42
end
end

View file

@ -0,0 +1,42 @@
require 'backports'
require_relative 'spec_helper'
RSpec::Matchers.define :decompile do |path|
match do |app|
@compiled, @keys = app.send :compile, path
@decompiled = app.decompile(@compiled, @keys)
@decompiled.should == path
end
failure_message_for_should do |app|
values = [app, @compiled, @keys, path, @decompiled].map(&:inspect)
"expected %s to decompile %s with %s to %s, but was %s" % values
end
end
describe Sinatra::Decompile do
subject { Sinatra::Application }
it { should decompile("") }
it { should decompile("/") }
it { should decompile("/?") }
it { should decompile("/foo") }
it { should decompile("/:name") }
it { should decompile("/:name?") }
it { should decompile("/:foo/:bar") }
it { should decompile("/page/:id/edit") }
it { should decompile("/hello/*") }
it { should decompile("/*/foo/*") }
it { should decompile("*") }
it { should decompile(":name.:format") }
it { should decompile(/./) }
it { should decompile(/f(oo)/) }
it { should decompile(/ba+r/) }
it 'just returns strings' do
subject.decompile('/foo').should == '/foo'
end
it 'just decompile simple regexps without keys' do
subject.decompile(%r{/foo}).should == '/foo'
end
end

View file

@ -0,0 +1,33 @@
require 'backports'
require_relative 'spec_helper'
describe Sinatra::Extension do
module ExampleExtension
extend Sinatra::Extension
set :foo, :bar
settings.set :bar, :blah
configure :test, :production do
set :reload_stuff, false
end
configure :development do
set :reload_stuff, true
end
get '/' do
"from extension, yay"
end
end
before { mock_app { register ExampleExtension }}
it('allows using set') { settings.foo.should == :bar }
it('implements configure') { settings.reload_stuff.should be_false }
it 'allows defing routes' do
get('/').should be_ok
body.should == "from extension, yay"
end
end

View file

@ -0,0 +1,100 @@
require 'backports'
require_relative 'spec_helper'
describe Sinatra::LinkHeader do
before do
mock_app do
helpers Sinatra::LinkHeader
before('/') { link 'something', :rel => 'from-filter', :foo => :bar }
get '/' do
link :something, 'booyah'
end
get '/style' do
stylesheet '/style.css'
end
get '/prefetch' do
prefetch '/foo'
end
get '/link_headers' do
response['Link'] = "<foo> ;bar=\"baz\""
stylesheet '/style.css'
prefetch '/foo'
link_headers
end
end
end
describe :link do
it "sets link headers" do
get '/'
headers['Link'].lines.should include('<booyah>; rel="something"')
end
it "returns link html tags" do
get '/'
body.should == '<link href="booyah" rel="something" />'
end
it "takes an options hash" do
get '/'
elements = ["<something>", "foo=\"bar\"", "rel=\"from-filter\""]
headers['Link'].lines.first.strip.split('; ').sort.should == elements
end
end
describe :stylesheet do
it 'sets link headers' do
get '/style'
headers['Link'].should match(%r{^</style\.css>;})
end
it 'sets type to text/css' do
get '/style'
headers['Link'].should include('type="text/css"')
end
it 'sets rel to stylesheet' do
get '/style'
headers['Link'].should include('rel="stylesheet"')
end
it 'returns html tag' do
get '/style'
body.should match(%r{^<link href="/style\.css"})
end
end
describe :prefetch do
it 'sets link headers' do
get '/prefetch'
headers['Link'].should match(%r{^</foo>;})
end
it 'sets rel to prefetch' do
get '/prefetch'
headers['Link'].should include('rel="prefetch"')
end
it 'returns html tag' do
get '/prefetch'
body.should == '<link href="/foo" rel="prefetch" />'
end
end
describe :link_headers do
it 'generates html for all link headers' do
get '/link_headers'
body.should include('<link href="/foo" rel="prefetch" />')
body.should include('<link href="/style.css" ')
end
it "respects Link headers not generated on its own" do
get '/link_headers'
body.should include('<link href="foo" bar="baz" />')
end
end
end

View file

@ -0,0 +1,546 @@
require 'backports'
require_relative 'spec_helper'
describe Sinatra::Namespace do
verbs = [:get, :head, :post, :put, :delete, :options]
verbs << :patch if Sinatra::VERSION >= '1.3'
def mock_app(&block)
super do
register Sinatra::Namespace
class_eval(&block)
end
end
def namespace(*args, &block)
mock_app { namespace(*args, &block) }
end
verbs.each do |verb|
describe "HTTP #{verb.to_s.upcase}" do
describe 'pattern generation' do
it "should add routes including prefix to the base app" do
namespace("/foo") { send(verb, "/bar") { "baz" }}
send(verb, "/foo/bar").should be_ok
body.should == "baz" unless verb == :head
send(verb, "/foo/baz").should_not be_ok
end
it "should allows adding routes with no path" do
namespace("/foo") { send(verb) { "bar" } }
send(verb, "/foo").should be_ok
body.should == "bar" unless verb == :head
end
it "allows unsing regular expressions" do
namespace("/foo") { send(verb, /\/\d\d/) { "bar" }}
send(verb, "/foo/12").should be_ok
body.should == "bar" unless verb == :head
send(verb, "/foo/123").should_not be_ok
end
it "allows using regular expressions for the prefix" do
namespace(/\/\d\d/) { send(verb, /\/\d\d/) { "foo" }}
send(verb, "/23/12").should be_ok
body.should == "foo" unless verb == :head
send(verb, "/123/12").should_not be_ok
end
it "sets params correctly from namespace" do
namespace("/:foo") { send(verb, "/bar") { params[:foo] }}
send(verb, "/foo/bar").should be_ok
body.should == "foo" unless verb == :head
send(verb, "/foo/baz").should_not be_ok
send(verb, "/fox/bar").should be_ok
body.should == "fox" unless verb == :head
end
it "sets params correctly from route" do
namespace("/foo") { send(verb, "/:bar") { params[:bar] }}
send(verb, "/foo/bar").should be_ok
body.should == "bar" unless verb == :head
send(verb, "/foo/baz").should be_ok
body.should == "baz" unless verb == :head
end
it "allows splats to be combined from namespace and route" do
namespace("/*") { send(verb, "/*") { params[:splat].join " - " }}
send(verb, '/foo/bar').should be_ok
body.should == "foo - bar" unless verb == :head
end
it "sets params correctly from namespace if simple regexp is used for route" do
namespace("/:foo") { send(verb, %r{/bar}) { params[:foo] }}
send(verb, "/foo/bar").should be_ok
body.should == "foo" unless verb == :head
send(verb, "/foo/baz").should_not be_ok
send(verb, "/fox/bar").should be_ok
body.should == "fox" unless verb == :head
end
it "sets params correctly from route if simple regexp is used for namespace" do
namespace(%r{/foo}) { send(verb, "/:bar") { params[:bar] }}
send(verb, "/foo/bar").should be_ok
body.should == "bar" unless verb == :head
send(verb, "/foo/baz").should be_ok
body.should == "baz" unless verb == :head
end
it 'allows defining routes without a pattern' do
namespace(%r{/foo}) { send(verb) { 'bar' } }
send(verb, '/foo').should be_ok
body.should == 'bar' unless verb == :head
end
end
describe 'conditions' do
it 'allows using conditions for namespaces' do
mock_app do
namespace(:host_name => 'example.com') { send(verb) { 'yes' }}
send(verb, '/') { 'no' }
end
send(verb, '/', {}, 'HTTP_HOST' => 'example.com')
last_response.should be_ok
body.should == 'yes' unless verb == :head
send(verb, '/', {}, 'HTTP_HOST' => 'example.org')
last_response.should be_ok
body.should == 'no' unless verb == :head
end
it 'allows using conditions for before filters' do
namespace '/foo' do
before(:host_name => 'example.com') { @yes = "yes" }
send(verb) { @yes || "no" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com')
last_response.should be_ok
body.should == 'yes' unless verb == :head
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org')
last_response.should be_ok
body.should == 'no' unless verb == :head
end
it 'allows using conditions for after filters' do
ran = false
namespace '/foo' do
before(:host_name => 'example.com') { ran = true }
send(verb) { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com')
ran.should be_true
end
it 'allows using conditions for routes' do
namespace '/foo' do
send(verb, :host_name => 'example.com') { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com').should be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org').should_not be_ok
end
it 'allows using conditions for before filters and the namespace' do
ran = false
namespace '/', :provides => :txt do
before(:host_name => 'example.com') { ran = true }
send(verb) { "ok" }
end
send(verb, '/', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
ran.should be_false
send(verb, '/', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_true
end
it 'allows using conditions for routes and the namespace' do
namespace '/foo', :host_name => 'example.com' do
send(verb, :provides => :txt) { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain').should be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html').should_not be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain').should_not be_ok
end
it 'allows combining conditions with a prefix for namespaces' do
namespace '/', :host_name => 'example.com' do
send(verb) { "ok" }
end
send(verb, '/', {}, 'HTTP_HOST' => 'example.com').should be_ok
send(verb, '/', {}, 'HTTP_HOST' => 'example.org').should_not be_ok
end
it 'allows combining conditions with a prefix for before filters' do
ran = false
namespace :provides => :txt do
before('/foo', :host_name => 'example.com') { ran = true }
send(verb, '/*') { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
ran.should be_false
send(verb, '/bar', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_true
end
it 'allows combining conditions with a prefix for after filters' do
ran = false
namespace :provides => :txt do
after('/foo', :host_name => 'example.com') { ran = true }
send(verb, '/*') { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
ran.should be_false
send(verb, '/bar', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_true
end
it 'allows combining conditions with a prefix for routes' do
namespace :host_name => 'example.com' do
send(verb, '/foo', :provides => :txt) { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain').should be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html').should_not be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain').should_not be_ok
end
it 'allows combining conditions with a prefix for filters and the namespace' do
ran = false
namespace '/f', :provides => :txt do
before('oo', :host_name => 'example.com') { ran = true }
send(verb, '/*') { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')
ran.should be_false
send(verb, '/far', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_false
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')
ran.should be_true
end
it 'allows combining conditions with a prefix for routes and the namespace' do
namespace '/f', :host_name => 'example.com' do
send(verb, 'oo', :provides => :txt) { "ok" }
end
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain').should be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html').should_not be_ok
send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain').should_not be_ok
end
end
describe 'filters' do
it 'should trigger before filters for namespaces' do
ran = false
namespace('/foo') { before { ran = true }}
send(verb, '/foo')
ran.should be_true
end
it 'should trigger after filters for namespaces' do
ran = false
namespace('/foo') { after { ran = true }}
send(verb, '/foo')
ran.should be_true
end
it 'should not trigger before filter for different namespaces' do
ran = false
namespace('/foo') { before { ran = true }}
send(verb, '/fox')
ran.should be_false
end
it 'should not trigger after filter for different namespaces' do
ran = false
namespace('/foo') { after { ran = true }}
send(verb, '/fox')
ran.should be_false
end
end
describe 'helpers' do
it "allows defining helpers with the helpers method" do
namespace '/foo' do
helpers do
def magic
42
end
end
send verb, '/bar' do
magic.to_s
end
end
send(verb, '/foo/bar').should be_ok
body.should == '42' unless verb == :head
end
it "allows defining helpers without the helpers method" do
namespace '/foo' do
def magic
42
end
send verb, '/bar' do
magic.to_s
end
end
send(verb, '/foo/bar').should be_ok
body.should == '42' unless verb == :head
end
it "allows using helper mixins with the helpers method" do
mixin = Module.new do
def magic
42
end
end
namespace '/foo' do
helpers mixin
send verb, '/bar' do
magic.to_s
end
end
send(verb, '/foo/bar').should be_ok
body.should == '42' unless verb == :head
end
it "makes helpers defined inside a namespace not available to routes outside that namespace" do
mock_app do
namespace '/foo' do
def magic
42
end
send verb, '/bar' do
magic.to_s
end
end
send verb, '/' do
magic.to_s
end
end
proc { send verb, '/' }.should raise_error(NameError)
end
it "makes helper mixins used inside a namespace not available to routes outside that namespace" do
mixin = Module.new do
def magic
42
end
end
mock_app do
namespace '/foo' do
helpers mixin
send verb, '/bar' do
magic.to_s
end
end
send verb, '/' do
magic.to_s
end
end
proc { send verb, '/' }.should raise_error(NameError)
end
it "allows accessing helpers defined outside the namespace" do
mock_app do
helpers do
def magic
42
end
end
namespace '/foo' do
send verb, '/bar' do
magic.to_s
end
end
end
send(verb, '/foo/bar').should be_ok
body.should == '42' unless verb == :head
end
it "allows calling super in helpers overwritten inside a namespace" do
mock_app do
helpers do
def magic
42
end
end
namespace '/foo' do
def magic
super - 19
end
send verb, '/bar' do
magic.to_s
end
end
end
send(verb, '/foo/bar').should be_ok
body.should == '23' unless verb == :head
end
end
describe 'nesting' do
it 'routes to nested namespaces' do
namespace '/foo' do
namespace '/bar' do
send(verb, '/baz') { 'OKAY!!11!'}
end
end
send(verb, '/foo/bar/baz').should be_ok
body.should == 'OKAY!!11!' unless verb == :head
end
it 'exposes helpers to nested namespaces' do
namespace '/foo' do
helpers do
def magic
42
end
end
namespace '/bar' do
send verb, '/baz' do
magic.to_s
end
end
end
send(verb, '/foo/bar/baz').should be_ok
body.should == '42' unless verb == :head
end
it 'does not use helpers of nested namespaces outside that namespace' do
namespace '/foo' do
namespace '/bar' do
def magic
42
end
send verb, '/baz' do
magic.to_s
end
end
send verb do
magic.to_s
end
end
proc { send verb, '/foo' }.should raise_error(NameError)
end
it 'sets params correctly' do
namespace('/:a') { namespace('/:b') { send(verb) { params[:a] }}}
send(verb, '/foo/bar').should be_ok
body.should == 'foo' unless verb == :head
end
end
describe 'error handlers' do
it "should allow custom error handlers with not found" do
namespace('/de') do
not_found { 'nicht gefunden' }
end
send(verb, '/foo').status.should == 404
last_response.body.should_not == 'nicht gefunden' unless verb == :head
get('/en/foo').status.should == 404
last_response.body.should_not == 'nicht gefunden' unless verb == :head
get('/de/foo').status.should == 404
last_response.body.should == 'nicht gefunden' unless verb == :head
end
it "should allow custom error handlers with error" do
namespace('/de') do
error(404) { 'nicht gefunden' }
end
send(verb, '/foo').status.should == 404
last_response.body.should_not == 'nicht gefunden' unless verb == :head
get('/en/foo').status.should == 404
last_response.body.should_not == 'nicht gefunden' unless verb == :head
get('/de/foo').status.should == 404
last_response.body.should == 'nicht gefunden' unless verb == :head
end
end
describe 'templates' do
it "allows to define nested templates"
it "allows to define nested layouts"
it "allows setting a different views directory"
end
describe 'extensions' do
it 'allows read access to settings' do
value = nil
mock_app do
set :foo, 42
namespace '/foo' do
value = foo
end
end
value.should == 42
end
it 'allows registering extensions for a namespace only' do
a = b = nil
extension = Module.new { define_method(:views) { "CUSTOM!!!" } }
mock_app do
namespace '/' do
register extension
a = views
end
b = views
end
a.should == 'CUSTOM!!!'
b.should_not == 'CUSTOM!!!'
end
it 'triggers route_added hook' do
route = nil
extension = Module.new
extension.singleton_class.class_eval do
define_method(:route_added) { |*r| route = r }
end
mock_app do
namespace '/f' do
register extension
get('oo') { }
end
get('/bar') { }
end
route[1].should == '/foo'
end
it 'prevents changing app global settings' do
proc { namespace('/') { set :foo, :bar }}.should raise_error
end
end
end
end
end

View file

@ -0,0 +1 @@
Girl! I wanna take you to a ... bar!

View file

@ -0,0 +1 @@
json!

View file

@ -0,0 +1 @@
Hello <%= name %>!

View file

@ -0,0 +1,2 @@
body
color: red

View file

@ -0,0 +1,280 @@
require 'backports'
require_relative 'spec_helper'
describe Sinatra::RespondWith do
def provides(*args)
@provides = args
end
def respond_app(&block)
types = @provides
mock_app do
set :app_file, __FILE__
set :views, root + '/respond_with'
register Sinatra::RespondWith
respond_to(*types) if types
class_eval(&block)
end
end
def respond_to(*args, &block)
respond_app { get('/') { respond_to(*args, &block) } }
end
def respond_with(*args, &block)
respond_app { get('/') { respond_with(*args, &block) } }
end
def req(*types)
p = types.shift if types.first.is_a? String and types.first.start_with? '/'
accept = types.map { |t| Sinatra::Base.mime_type(t).to_s }.join ','
get (p || '/'), {}, 'HTTP_ACCEPT' => accept
end
describe "Helpers#respond_to" do
it 'allows defining handlers by file extensions' do
respond_to do |format|
format.html { "html!" }
format.json { "json!" }
end
req(:html).body.should == "html!"
req(:json).body.should == "json!"
end
it 'respects quality' do
respond_to do |format|
format.html { "html!" }
format.json { "json!" }
end
req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
end
it 'allows using mime types' do
respond_to do |format|
format.on('text/html') { "html!" }
format.json { "json!" }
end
req(:html).body.should == "html!"
end
it 'allows using wildcards in format matchers' do
respond_to do |format|
format.on('text/*') { "text!" }
format.json { "json!" }
end
req(:html).body.should == "text!"
end
it 'allows using catch all wildcards in format matchers' do
respond_to do |format|
format.on('*/*') { "anything!" }
format.json { "json!" }
end
req(:html).body.should == "anything!"
end
it 'prefers concret over generic' do
respond_to do |format|
format.on('text/*') { "text!" }
format.on('*/*') { "anything!" }
format.json { "json!" }
end
req(:json).body.should == "json!"
req(:html).body.should == "text!"
end
it 'does not set up default handlers' do
respond_to
req.should_not be_ok
status.should == 406
end
end
describe "Helpers#respond_with" do
describe "matching" do
it 'allows defining handlers by file extensions' do
respond_with(:ignore) do |format|
format.html { "html!" }
format.json { "json!" }
end
req(:html).body.should == "html!"
req(:json).body.should == "json!"
end
it 'respects quality' do
respond_with(:ignore) do |format|
format.html { "html!" }
format.json { "json!" }
end
req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
end
it 'allows using mime types' do
respond_with(:ignore) do |format|
format.on('text/html') { "html!" }
format.json { "json!" }
end
req(:html).body.should == "html!"
end
it 'allows using wildcards in format matchers' do
respond_with(:ignore) do |format|
format.on('text/*') { "text!" }
format.json { "json!" }
end
req(:html).body.should == "text!"
end
it 'allows using catch all wildcards in format matchers' do
respond_with(:ignore) do |format|
format.on('*/*') { "anything!" }
format.json { "json!" }
end
req(:html).body.should == "anything!"
end
it 'prefers concret over generic' do
respond_with(:ignore) do |format|
format.on('text/*') { "text!" }
format.on('*/*') { "anything!" }
format.json { "json!" }
end
req(:json).body.should == "json!"
req(:html).body.should == "text!"
end
end
describe "default behavior" do
it 'converts objects to json out of the box' do
respond_with 'a' => 'b'
req(:json).body.should == {'a' => 'b'}.to_json
end
it 'handles multiple routes correctly' do
respond_app do
get('/') { respond_with 'a' => 'b' }
get('/:name') { respond_with 'a' => params[:name] }
end
req('/', :json).body.should == {'a' => 'b'}.to_json
req('/b', :json).body.should == {'a' => 'b'}.to_json
req('/c', :json).body.should == {'a' => 'c'}.to_json
end
it "calls to_EXT if available" do
respond_with Struct.new(:to_pdf).new("hello")
req(:pdf).body.should == "hello"
end
it 'results in a 406 if format cannot be produced' do
respond_with({})
req(:html).should_not be_ok
status.should == 406
end
end
describe 'templates' do
it 'looks for templates with name.target.engine' do
respond_with :foo, :name => 'World'
req(:html).should be_ok
body.should == "Hello World!"
end
it 'looks for templates with name.engine for specific engines' do
respond_with :bar
req(:html).should be_ok
body.should == "Girl! I wanna take you to a ... bar!"
end
it 'does not use name.engine for engines producing other formats' do
respond_with :not_html
req(:html).should_not be_ok
status.should == 406
body.should be_empty
end
it 'falls back to to_EXT if no template is found' do
respond_with :foo, :name => 'World'
req(:json).should be_ok
body.should == {:name => 'World'}.to_json
end
it 'favors templates over to_EXT' do
respond_with :bar, :name => 'World'
req(:json).should be_ok
body.should == 'json!'
end
end
describe 'customizing' do
it 'allows customizing' do
respond_with(:foo, :name => 'World') { |f| f.html { 'html!' }}
req(:html).should be_ok
body.should == "html!"
end
it 'falls back to default behavior if none matches' do
respond_with(:foo, :name => 'World') { |f| f.json { 'json!' }}
req(:html).should be_ok
body.should == "Hello World!"
end
it 'favors generic rule over default behavior' do
respond_with(:foo, :name => 'World') { |f| f.on('*/*') { 'generic!' }}
req(:html).should be_ok
body.should == "generic!"
end
end
end
describe :respond_to do
it 'acts as global provides condition' do
respond_app do
respond_to :json, :html
get('/a') { 'ok' }
get('/b') { 'ok' }
end
req('/b', :xml).should_not be_ok
req('/b', :html).should be_ok
end
it 'still allows provides' do
respond_app do
respond_to :json, :html
get('/a') { 'ok' }
get('/b', :provides => :json) { 'ok' }
end
req('/b', :html).should_not be_ok
req('/b', :json).should be_ok
end
it 'plays well with namespaces' do
respond_app do
register Sinatra::Namespace
namespace '/a' do
respond_to :json
get { 'json' }
end
get('/b') { 'anything' }
end
req('/a', :html).should_not be_ok
req('/b', :html).should be_ok
end
end
end

View file

@ -0,0 +1,6 @@
require 'sinatra/contrib'
RSpec.configure do |config|
config.expect_with :rspec, :stdlib
config.include Sinatra::TestHelpers
end