From da3439af026a76d008d7cd49206cb1081da7b316 Mon Sep 17 00:00:00 2001 From: Ryan Tomayko Date: Mon, 14 Apr 2008 16:34:10 -0400 Subject: [PATCH 01/11] Sinatra::Application as a sandbox for the DSL Moves DSL methods previously defined on (main) to Sinatra::Application and begins documenting them. This unifies a bunch of method names and will allow individual Application instances to be used as a sandbox for DSL execution. The Sinatra::Application::FORWARD_METHODS array is a list of method names that should be available at the top-level. A constant was created so that its easy to tell what methods are available from top-level when reading the API documentation. When one of the methods is invoked on (main), the message is forwarded to the default application (Sinatra::application). This is in preparation for some configuration related changes and multi-app support. --- lib/sinatra.rb | 194 ++++++++++++++++++++++++++++++++++------------- test/app_test.rb | 9 ++- 2 files changed, 150 insertions(+), 53 deletions(-) diff --git a/lib/sinatra.rb b/lib/sinatra.rb index 1b4fdec6..d3635c15 100644 --- a/lib/sinatra.rb +++ b/lib/sinatra.rb @@ -830,14 +830,43 @@ module Sinatra end end - + + class Application - - attr_reader :events, :errors, :templates, :filters - attr_reader :clearables, :reloading - - attr_writer :options - + + # Hash of event handlers with request method keys and + # arrays of potential handlers as values. + attr_reader :events + + # Hash of error handlers with error status codes as keys and + # handlers as values. + attr_reader :errors + + # Hash of template name mappings. + attr_reader :templates + + # Hash of filters with event name keys (:before) and arrays of + # handlers as values. + attr_reader :filters + + # Truthful when the application is in the process of being reloaded. + attr_reader :reloading + + # Array of objects to clear during reload. The objects in this array + # must respond to :clear. + attr_reader :clearables + + # Application options. + attr_accessor :options + + # List of methods available from top-level scope. When invoked from + # top-level the method is forwarded to the default application + # (Sinatra::application). + FORWARD_METHODS = %w[ + get put post delete head + template layout before error not_found + ] + def self.default_options root = File.expand_path(File.dirname($0)) @@default_options ||= { @@ -888,21 +917,108 @@ module Sinatra load_default_events! end - def define_event(method, path, options = {}, &b) - events[method] << event = Event.new(path, options, &b) - event + # Define an event handler for the given request method and path + # spec. The block is executed when a request matches the method + # and spec. + # + # NOTE: The #get, #post, #put, and #delete helper methods should + # be used to define events when possible. + def event(method, path, options = {}, &b) + events[method].push(Event.new(path, options, &b)).last end - - def define_template(name=:layout, &b) + + # Define an event handler for GET requests. + def get(path, options={}, &b) + event(:get, path, options, &b) + end + + # Define an event handler for POST requests. + def post(path, options={}, &b) + event(:post, path, options, &b) + end + + # Define an event handler for HEAD requests. + def head(path, options={}, &b) + event(:head, path, options, &b) + end + + # Define an event handler for PUT requests. + # + # NOTE: PUT events are triggered when the HTTP request method is + # PUT and also when the request method is POST and the body includes a + # "_method" parameter set to "PUT". + def put(path, options={}, &b) + event(:put, path, options, &b) + end + + # Define an event handler for DELETE requests. + # + # NOTE: DELETE events are triggered when the HTTP request method is + # DELETE and also when the request method is POST and the body includes a + # "_method" parameter set to "DELETE". + def delete(path, options={}, &b) + event(:delete, path, options, &b) + end + + # Define a named template. The template may be referenced from + # event handlers by passing the name as a Symbol to rendering + # methods. The block is executed each time the template is rendered + # and the resulting object is passed to the template handler. + # + # The following example defines a HAML template named hello and + # invokes it from an event handler: + # + # template :hello do + # "h1 Hello World!" + # end + # + # get '/' do + # haml :foo + # end + # + def template(name, &b) templates[name] = b end - - def define_error(code, options = {}, &b) - errors[code] = Error.new(code, &b) + + # Define a layout template. + def layout(name=:layout, &b) + template(name, &b) end - - def define_filter(type, &b) - filters[:before] << b + + # Define a custom error handler for the exception class +type+. The block + # is invoked when the specified exception type is raised from an error + # handler and can manipulate the response as needed: + # + # error MyCustomError do + # status 500 + # 'So what happened was...' + request.env['sinatra.error'].message + # end + # + # The Sinatra::ServerError handler is used by default when an exception + # occurs and no matching error handler is found. + def error(type=ServerError, options = {}, &b) + errors[type] = Error.new(type, &b) + end + + # Define a custom error handler for '404 Not Found' responses. This is a + # shorthand for: + # error NotFound do + # .. + # end + def not_found(options={}, &b) + error NotFound, options, &b + end + + # Define a request filter. When +type+ is +:before+, execute the block + # in the context of each request before matching event handlers. + def filter(type, &b) + filters[type] << b + end + + # Invoke the block in the context of each request before invoking + # matching event handlers. + def before(&b) + filter :before, &b end # Visits and invokes each handler registered for the +request_method+ in @@ -1091,46 +1207,20 @@ end
   
 end
 
-def get(path, options ={}, &b)
-  Sinatra.application.define_event(:get, path, options, &b)
-end
-
-def post(path, options ={}, &b)
-  Sinatra.application.define_event(:post, path, options, &b)
-end
-
-def put(path, options ={}, &b)
-  Sinatra.application.define_event(:put, path, options, &b)
-end
-
-def delete(path, options ={}, &b)
-  Sinatra.application.define_event(:delete, path, options, &b)
-end
-
-def before(&b)
-  Sinatra.application.define_filter(:before, &b)
+# Delegate DSLish methods to the currently active Sinatra::Application
+# instance.
+Sinatra::Application::FORWARD_METHODS.each do |method|
+  eval(<<-EOS, binding, '(__DSL__)', 1)
+    def #{method}(*args, &b)
+      Sinatra.application.#{method}(*args, &b)
+    end
+  EOS
 end
 
 def helpers(&b)
   Sinatra::EventContext.class_eval(&b)
 end
 
-def error(type = Sinatra::ServerError, options = {}, &b)
-  Sinatra.application.define_error(type, options, &b)
-end
-
-def not_found(options = {}, &b)
-  Sinatra.application.define_error(Sinatra::NotFound, options, &b)
-end
-
-def layout(name = :layout, &b)
-  Sinatra.application.define_template(name, &b)
-end
-
-def template(name, &b)
-  Sinatra.application.define_template(name, &b)
-end
-
 def use_in_file_templates!
   require 'stringio'
   templates = IO.read(caller.first.split(':').first).split('__FILE__').last
diff --git a/test/app_test.rb b/test/app_test.rb
index c7d2e51b..cc94839a 100644
--- a/test/app_test.rb
+++ b/test/app_test.rb
@@ -5,7 +5,14 @@ context "Sinatra" do
   setup do
     Sinatra.application = nil
   end
-  
+
+  specify "should put all DSL methods on (main)" do
+    object = Object.new
+    Sinatra::Application::FORWARD_METHODS.each do |method|
+      object.private_methods.should.include(method)
+    end
+  end
+
   specify "should handle result of nil" do
     get '/' do
       nil

From 38588af109a9ad0cfc422ae6e2cce3593445f07f Mon Sep 17 00:00:00 2001
From: Ryan Tomayko 
Date: Mon, 14 Apr 2008 19:59:31 -0400
Subject: [PATCH 02/11] Refactor options / configures into Sinatra::Application

This is a grab-bag of minor configuration related refactorings:

  * Moved the top-level #configures into Sinatra::Application and delegate from
    top-level to the default application.

  * Modified default options loading to parse command line arguments
    the first time Application::default_options is accessed only.

  * Removed the ability to reload default_options by setting an application's
    options to nil. We can add this back in fairly easily but it seems to lead
    to the bad practice of modifying default_options and then reloading instead
    of modifying the application options directly.
---
 lib/sinatra.rb           | 71 ++++++++++++++++++++++++----------------
 lib/sinatra/test/unit.rb |  2 +-
 test/application_test.rb | 33 ++++++++++++++++++-
 3 files changed, 76 insertions(+), 30 deletions(-)

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index d3635c15..981d1866 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -849,15 +849,13 @@ module Sinatra
     # handlers as values.
     attr_reader :filters
 
-    # Truthful when the application is in the process of being reloaded.
-    attr_reader :reloading
-
     # Array of objects to clear during reload. The objects in this array
     # must respond to :clear.
     attr_reader :clearables
 
-    # Application options.
-    attr_accessor :options
+    # Object including open attribute methods for modifying Application
+    # configuration.
+    attr_reader :options
 
     # List of methods available from top-level scope. When invoked from
     # top-level the method is forwarded to the default application
@@ -865,11 +863,21 @@ module Sinatra
     FORWARD_METHODS = %w[
       get put post delete head
       template layout before error not_found
+      configures configure
     ]
 
+    # Hash of default application configuration options. When a new
+    # Application is created, the #options object takes its initial values
+    # from here.
+    #
+    # Changes to the default_options Hash effect only Application objects
+    # created after the changes are made. For this reason, modifications to
+    # the default_options Hash typically occur at the very beginning of a
+    # file, before any DSL related functions are invoked.
     def self.default_options
+      return @default_options unless @default_options.nil?
       root = File.expand_path(File.dirname($0))
-      @@default_options ||= {
+      @default_options = {
         :run => true,
         :port => 4567,
         :env => :development,
@@ -879,17 +887,15 @@ module Sinatra
         :sessions => false,
         :logging => true
       }
-    end
-    
-    def default_options
-      self.class.default_options
+      load_default_options_from_command_line!
+      @default_options
     end
 
-    
-    ##
-    # Load all options given on the command line
+    # Search ARGV for command line arguments and update the
+    # Sinatra::default_options Hash accordingly. This method is
+    # invoked the first time the default_options Hash is accessed.
     # NOTE:  Ignores --name so unit/spec tests can run individually
-    def load_options!
+    def self.load_default_options_from_command_line! #:nodoc:
       require 'optparse'
       OptionParser.new do |op|
         op.on('-p port') { |port| default_options[:port] = port }
@@ -907,16 +913,37 @@ module Sinatra
     end
 
     def initialize
+      @reloading = false
       @clearables = [
         @events = Hash.new { |hash, key| hash[key] = [] },
         @errors = Hash.new,
         @filters = Hash.new { |hash, key| hash[key] = [] },
         @templates = Hash.new
       ]
-      load_options!
+      @options = OpenStruct.new(self.class.default_options)
       load_default_events!
     end
 
+    # Determine whether the application is in the process of being
+    # reloaded.
+    def reloading?
+      @reloading == true
+    end
+
+    # Yield to the block for configuration if the current environment
+    # matches any included in the +envs+ list. Always yield to the block
+    # when no environment is specified.
+    #
+    # NOTE: configuration blocks are not executed during reloads.
+    def configures(*envs, &b)
+      return if reloading?
+      return unless envs.empty? || envs.include?(options.env)
+      yield self
+    end
+
+    alias :configure :configures
+
+
     # Define an event handler for the given request method and path
     # spec. The block is executed when a request matches the method
     # and spec.
@@ -1036,10 +1063,6 @@ module Sinatra
         errors[NotFound].invoke(request)
     end
 
-    def options
-      @options ||= OpenStruct.new(default_options)
-    end
-    
     def development?
       options.env == :development
     end
@@ -1052,7 +1075,7 @@ module Sinatra
       @reloading = false
       Environment.setup!
     end
-    
+
     def mutex
       @@mutex ||= Mutex.new
     end
@@ -1236,16 +1259,8 @@ def use_in_file_templates!
   end
 end
 
-def configures(*envs, &b)
-  yield if  !Sinatra.application.reloading && 
-            (envs.include?(Sinatra.application.options.env) ||
-            envs.empty?)
-end
-alias :configure :configures
-
 def set_options(opts)
   Sinatra::Application.default_options.merge!(opts)
-  Sinatra.application.options = nil
 end
 
 def set_option(key, value)
diff --git a/lib/sinatra/test/unit.rb b/lib/sinatra/test/unit.rb
index 92cc9bd3..7b25cbc1 100644
--- a/lib/sinatra/test/unit.rb
+++ b/lib/sinatra/test/unit.rb
@@ -10,4 +10,4 @@ Sinatra::Application.default_options.merge!(
   :logging => false
 )
 
-Sinatra.application.options = nil
+Sinatra.application = nil
diff --git a/test/application_test.rb b/test/application_test.rb
index 20a195eb..c9fe35a0 100644
--- a/test/application_test.rb
+++ b/test/application_test.rb
@@ -97,7 +97,38 @@ context "An app returns" do
   end
   
 end
-  
+
+context "Application#configure blocks" do
+
+  setup do
+    Sinatra.application = nil
+  end
+
+  specify "run when no environment specified" do
+    ref = false
+    configure { ref = true }
+    ref.should.equal true
+  end
+
+  specify "run when matching environment specified" do
+    ref = false
+    configure(:test) { ref = true }
+    ref.should.equal true
+  end
+
+  specify "do not run when no matching environment specified" do
+    configure(:foo) { flunk "block should not have been executed" }
+    configure(:development, :production, :foo) { flunk "block should not have been executed" }
+  end
+
+  specify "accept multiple environments" do
+    ref = false
+    configure(:foo, :test, :bar) { ref = true }
+    ref.should.equal true
+  end
+
+end
+
 context "Events in an app" do
   
   setup do

From 2413987830ca004ba2da9be48b199cb82a654146 Mon Sep 17 00:00:00 2001
From: Ryan Tomayko 
Date: Tue, 15 Apr 2008 03:42:33 -0400
Subject: [PATCH 03/11] Finish Sinatra::Application API documentation

No changes to code were made here. Remaining Sinatra::Application
methods are documented and some things were shifted around so that
configuration/option related methods, event related methods, and call
related methods are grouped together.
---
 lib/sinatra.rb | 113 ++++++++++++++++++++++++++++++++-----------------
 1 file changed, 75 insertions(+), 38 deletions(-)

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 981d1866..2baf5419 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -832,6 +832,16 @@ module Sinatra
   end
 
 
+  # The Application class represents the top-level working area of a
+  # Sinatra app. It provides the DSL for defining various aspects of the
+  # application and implements a Rack compatible interface for dispatching
+  # requests.
+  #
+  # Many of the instance methods defined in this class (#get, #post,
+  # #put, #delete, #layout, #before, #error, #not_found, etc.) are
+  # available at top-level scope. When invoked from top-level, the
+  # messages are forwarded to the "default application" (accessible
+  # at Sinatra::application).
   class Application
 
     # Hash of event handlers with request method keys and
@@ -866,6 +876,25 @@ module Sinatra
       configures configure
     ]
 
+    # Create a new Application with a default configuration taken
+    # from the default_options Hash.
+    #
+    # NOTE: A default Application is automatically created the first
+    # time any of Sinatra's DSL related methods is invoked so there
+    # is typically no need to create an instance explicitly. See
+    # Sinatra::application for more information.
+    def initialize
+      @reloading = false
+      @clearables = [
+        @events = Hash.new { |hash, key| hash[key] = [] },
+        @errors = Hash.new,
+        @filters = Hash.new { |hash, key| hash[key] = [] },
+        @templates = Hash.new
+      ]
+      @options = OpenStruct.new(self.class.default_options)
+      load_default_events!
+    end
+
     # Hash of default application configuration options. When a new
     # Application is created, the #options object takes its initial values
     # from here.
@@ -912,18 +941,6 @@ module Sinatra
       events[:get] << Static.new
     end
 
-    def initialize
-      @reloading = false
-      @clearables = [
-        @events = Hash.new { |hash, key| hash[key] = [] },
-        @errors = Hash.new,
-        @filters = Hash.new { |hash, key| hash[key] = [] },
-        @templates = Hash.new
-      ]
-      @options = OpenStruct.new(self.class.default_options)
-      load_default_events!
-    end
-
     # Determine whether the application is in the process of being
     # reloaded.
     def reloading?
@@ -987,6 +1004,22 @@ module Sinatra
       event(:delete, path, options, &b)
     end
 
+    # Visits and invokes each handler registered for the +request_method+ in
+    # definition order until a Result response is produced. If no handler
+    # responds with a Result, the NotFound error handler is invoked.
+    #
+    # When the request_method is "HEAD" and no valid Result is produced by
+    # the set of handlers registered for HEAD requests, an attempt is made to
+    # invoke the GET handlers to generate the response before resorting to the
+    # default error handler.
+    def lookup(request)
+      method = request.request_method.downcase.to_sym
+      events[method].eject(&[:invoke, request]) ||
+        (events[:get].eject(&[:invoke, request]) if method == :head) ||
+        errors[NotFound].invoke(request)
+    end
+
+
     # Define a named template. The template may be referenced from
     # event handlers by passing the name as a Symbol to rendering
     # methods. The block is executed each time the template is rendered
@@ -1000,7 +1033,7 @@ module Sinatra
     #   end
     #
     #   get '/' do
-    #     haml :foo
+    #     haml :hello
     #   end
     #
     def template(name, &b)
@@ -1048,25 +1081,13 @@ module Sinatra
       filter :before, &b
     end
 
-    # Visits and invokes each handler registered for the +request_method+ in
-    # definition order until a Result response is produced. If no handler
-    # responds with a Result, the NotFound error handler is invoked.
-    #
-    # When the request_method is "HEAD" and no valid Result is produced by
-    # the set of handlers registered for HEAD requests, an attempt is made to
-    # invoke the GET handlers to generate the response before resorting to the
-    # default error handler.
-    def lookup(request)
-      method = request.request_method.downcase.to_sym
-      events[method].eject(&[:invoke, request]) ||
-        (events[:get].eject(&[:invoke, request]) if method == :head) ||
-        errors[NotFound].invoke(request)
-    end
-
     def development?
       options.env == :development
     end
 
+    # Clear all events, templates, filters, and error handlers
+    # and then reload the application source file. This occurs
+    # automatically before each request is processed in development.
     def reload!
       @reloading = true
       clearables.each(&:clear)
@@ -1076,10 +1097,18 @@ module Sinatra
       Environment.setup!
     end
 
+    # Determine whether the application is in the process of being
+    # reloaded.
+    def reloading?
+      @reloading == true
+    end
+
+    # Mutex instance used for thread synchronization.
     def mutex
       @@mutex ||= Mutex.new
     end
-    
+
+    # Yield to the block with thread synchronization
     def run_safely
       if options.mutex
         mutex.synchronize { yield }
@@ -1087,16 +1116,24 @@ module Sinatra
         yield
       end
     end
-    
+
+    # Rack compatible request invocation interface. Requests are processed
+    # as follows:
+    #
+    # 1. Create Rack::Request, Rack::Response helper objects.
+    # 2. Lookup event handler based on request method and path.
+    # 3. Create new EventContext to house event handler evaluation.
+    # 4. Invoke each #before filter in context of EventContext object.
+    # 5. Invoke event handler in context of EventContext object.
+    # 6. Return response to Rack.
+    #
+    # See the Rack specification for detailed information on the
+    # +env+ argument and return value.
     def call(env)
       reload! if development?
       request = Rack::Request.new(env)
       result = lookup(request)
-      context = EventContext.new(
-        request, 
-        Rack::Response.new,
-        result.params
-      )
+      context = EventContext.new(request, Rack::Response.new, result.params)
       context.status(result.status)
       begin
         returned = run_safely do
@@ -1122,10 +1159,10 @@ module Sinatra
       context.body = body.kind_of?(String) ? [*body] : body
       context.finish
     end
-    
+
   end
-  
-  
+
+
   module Environment
     extend self
     

From fd0150da29d097fe1b4efc9f398c60caa4b380b2 Mon Sep 17 00:00:00 2001
From: Ryan Tomayko 
Date: Tue, 15 Apr 2008 04:25:09 -0400
Subject: [PATCH 04/11] Move set_option(s) to Application, export to (main)

Move top-level set_option/set_options to Sinatra::Application#set
and plumb in delegates from (main).

NOTE: options set via these methods are no longer set in the
default_options Hash but directly on the current application's
options object.
---
 lib/sinatra.rb           | 35 ++++++++++++++++++++++++++---------
 test/application_test.rb | 34 ++++++++++++++++++++++++++++++++++
 test/sessions_test.rb    | 11 +++++------
 3 files changed, 65 insertions(+), 15 deletions(-)

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 2baf5419..48b7b85d 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -873,7 +873,7 @@ module Sinatra
     FORWARD_METHODS = %w[
       get put post delete head
       template layout before error not_found
-      configures configure
+      configures configure set_options set_option
     ]
 
     # Create a new Application with a default configuration taken
@@ -960,6 +960,31 @@ module Sinatra
 
     alias :configure :configures
 
+    # When both +option+ and +value+ arguments are provided, set the option
+    # specified. With a single Hash argument, set all options specified in
+    # Hash. Options are available via the Application#options object.
+    #
+    # Setting individual options:
+    #   set :port, 80
+    #   set :env, :production
+    #   set :views, '/path/to/views'
+    #
+    # Setting multiple options:
+    #   set :port  => 80,
+    #       :env   => :production,
+    #       :views => '/path/to/views'
+    #
+    def set(option, value=self)
+      if value == self && option.kind_of?(Hash)
+        option.each { |key,val| set(key, val) }
+      else
+        options.send("#{option}=", value)
+      end
+    end
+
+    alias :set_option :set
+    alias :set_options :set
+
 
     # Define an event handler for the given request method and path
     # spec. The block is executed when a request matches the method
@@ -1296,14 +1321,6 @@ def use_in_file_templates!
   end
 end
 
-def set_options(opts)
-  Sinatra::Application.default_options.merge!(opts)
-end
-
-def set_option(key, value)
-  set_options(key => value)
-end
-
 def mime(ext, type)
   Rack::File::MIME_TYPES[ext.to_s] = type
 end
diff --git a/test/application_test.rb b/test/application_test.rb
index c9fe35a0..5cfaef91 100644
--- a/test/application_test.rb
+++ b/test/application_test.rb
@@ -204,3 +204,37 @@ context "Events in an app" do
 end
 
 
+context "Options in an app" do
+
+  setup do
+    Sinatra.application = nil
+    @app = Sinatra::application
+  end
+
+  specify "can be set singly on app" do
+    @app.set :foo, 1234
+    @app.options.foo.should.equal 1234
+  end
+
+  specify "can be set singly from top-level" do
+    set_option :foo, 1234
+    @app.options.foo.should.equal 1234
+  end
+
+  specify "can be set multiply on app" do
+    @app.options.foo.should.be.nil
+    @app.set :foo => 1234,
+      :bar => 'hello, world'
+    @app.options.foo.should.equal 1234
+    @app.options.bar.should.equal 'hello, world'
+  end
+
+  specify "can be set multiply from top-level" do
+    @app.options.foo.should.be.nil
+    set_options :foo => 1234,
+      :bar => 'hello, world'
+    @app.options.foo.should.equal 1234
+    @app.options.bar.should.equal 'hello, world'
+  end
+
+end
diff --git a/test/sessions_test.rb b/test/sessions_test.rb
index bda96e99..37912304 100644
--- a/test/sessions_test.rb
+++ b/test/sessions_test.rb
@@ -1,10 +1,10 @@
 require File.dirname(__FILE__) + '/helper'
 
 context "Sessions" do
-  
-  specify "should be off by default" do
-    Sinatra.application = nil
 
+  setup { Sinatra.application = nil }
+
+  specify "should be off by default" do
     get '/asdf' do
       session[:test] = true
       "asdf"
@@ -18,10 +18,9 @@ context "Sessions" do
     assert ok?
     assert !include?('Set-Cookie')
   end
-  
+
   specify "should be able to store data accross requests" do
-    set_options(:sessions => true)
-    Sinatra.application = nil
+    set_option :sessions, true
 
     get '/foo' do
       session[:test] = true

From a54d7cc321cb81308d23f68084bcb406d2e27c46 Mon Sep 17 00:00:00 2001
From: Ryan Tomayko 
Date: Tue, 15 Apr 2008 22:57:25 -0400
Subject: [PATCH 05/11] Move Environment.setup! into Application; simplify
 reload

Merges Application#load_default_events! and Environment#setup!
into a single Application#load_default_configuration! method. This
method is called during Application#initialize and Application#reload!
before the app code is executed.
---
 lib/sinatra.rb           | 46 +++++++++++++++++-----------------------
 test/application_test.rb | 15 +++++++++++++
 2 files changed, 34 insertions(+), 27 deletions(-)

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 48b7b85d..427b90b6 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -96,19 +96,15 @@ module Sinatra
   def options
     application.options
   end
-  
+
   def application
-    unless @app 
-      @app = Application.new
-      Sinatra::Environment.setup!
-    end
-    @app
+    @app ||= Application.new
   end
-  
+
   def application=(app)
     @app = app
   end
-  
+
   def port
     application.options.port
   end
@@ -892,7 +888,7 @@ module Sinatra
         @templates = Hash.new
       ]
       @options = OpenStruct.new(self.class.default_options)
-      load_default_events!
+      load_default_configuration!
     end
 
     # Hash of default application configuration options. When a new
@@ -914,7 +910,8 @@ module Sinatra
         :views => root + '/views',
         :public => root + '/public',
         :sessions => false,
-        :logging => true
+        :logging => true,
+        :raise_errors => false
       }
       load_default_options_from_command_line!
       @default_options
@@ -934,13 +931,6 @@ module Sinatra
       end.parse!(ARGV.dup.select { |o| o !~ /--name/ })
     end
 
-    # Called immediately after the application is initialized or reloaded to
-    # register default events. Events added here have dibs on requests since
-    # they appear first in the list.
-    def load_default_events!
-      events[:get] << Static.new
-    end
-
     # Determine whether the application is in the process of being
     # reloaded.
     def reloading?
@@ -954,8 +944,7 @@ module Sinatra
     # NOTE: configuration blocks are not executed during reloads.
     def configures(*envs, &b)
       return if reloading?
-      return unless envs.empty? || envs.include?(options.env)
-      yield self
+      yield self if envs.empty? || envs.include?(options.env)
     end
 
     alias :configure :configures
@@ -1116,10 +1105,9 @@ module Sinatra
     def reload!
       @reloading = true
       clearables.each(&:clear)
-      load_default_events!
+      load_default_configuration!
       Kernel.load $0
       @reloading = false
-      Environment.setup!
     end
 
     # Determine whether the application is in the process of being
@@ -1185,13 +1173,14 @@ module Sinatra
       context.finish
     end
 
-  end
+    # Called immediately after the application is initialized or reloaded to
+    # register default events, templates, and error handlers.
+    def load_default_configuration!
 
+      # The static event is always executed first.
+      events[:get] << Static.new
 
-  module Environment
-    extend self
-    
-    def setup!
+      # Default configuration for all environments.
       configure do
         error do
           raise request.env['sinatra.error'] if Sinatra.options.raise_errors
@@ -1288,8 +1277,11 @@ end
         end
       end
     end
+
+    private :load_default_configuration!
+
   end
-  
+
 end
 
 # Delegate DSLish methods to the currently active Sinatra::Application
diff --git a/test/application_test.rb b/test/application_test.rb
index 5cfaef91..508cd101 100644
--- a/test/application_test.rb
+++ b/test/application_test.rb
@@ -129,6 +129,21 @@ context "Application#configure blocks" do
 
 end
 
+context "Default Application Configuration" do
+
+  specify "includes 404 and 500 error handlers" do
+    Sinatra.application.errors.should.include(Sinatra::ServerError)
+    Sinatra.application.errors[Sinatra::ServerError].should.not.be.nil
+    Sinatra.application.errors.should.include(Sinatra::NotFound)
+    Sinatra.application.errors[Sinatra::NotFound].should.not.be.nil
+  end
+
+  specify "includes Static event" do
+    assert Sinatra.application.events[:get].any? { |e| Sinatra::Static === e }
+  end
+
+end
+
 context "Events in an app" do
   
   setup do

From 64a2dee4f035aa876b40d826741004e9c0965c73 Mon Sep 17 00:00:00 2001
From: Ryan Tomayko 
Date: Thu, 17 Apr 2008 00:06:25 -0400
Subject: [PATCH 06/11] Add enable/disable methods to Application

These methods take multiple option name arguments and set their
values to true (enable) or false (disable). Purely sugar.
---
 lib/sinatra.rb           | 18 +++++++++++++++---
 test/application_test.rb | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 427b90b6..e7722205 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -867,9 +867,8 @@ module Sinatra
     # top-level the method is forwarded to the default application
     # (Sinatra::application).
     FORWARD_METHODS = %w[
-      get put post delete head
-      template layout before error not_found
-      configures configure set_options set_option
+      get put post delete head template layout before error not_found
+      configures configure set_options set_option enable disable
     ]
 
     # Create a new Application with a default configuration taken
@@ -974,6 +973,19 @@ module Sinatra
     alias :set_option :set
     alias :set_options :set
 
+    # Enable the options specified by setting their values to true. For
+    # example, to enable sessions and logging:
+    #   enable :sessions, :logging
+    def enable(*opts)
+      opts.each { |key| set(key, true) }
+    end
+
+    # Disable the options specified by setting their values to false. For
+    # example, to disable logging and automatic run:
+    #   disable :logging, :run
+    def disable(*opts)
+      opts.each { |key| set(key, false) }
+    end
 
     # Define an event handler for the given request method and path
     # spec. The block is executed when a request matches the method
diff --git a/test/application_test.rb b/test/application_test.rb
index 508cd101..57a67bbf 100644
--- a/test/application_test.rb
+++ b/test/application_test.rb
@@ -252,4 +252,36 @@ context "Options in an app" do
     @app.options.bar.should.equal 'hello, world'
   end
 
+  specify "can be enabled on app" do
+    @app.options.foo.should.be.nil
+    @app.enable :sessions, :foo, :bar
+    @app.options.sessions.should.equal true
+    @app.options.foo.should.equal true
+    @app.options.bar.should.equal true
+  end
+
+  specify "can be enabled from top-level" do
+    @app.options.foo.should.be.nil
+    enable :sessions, :foo, :bar
+    @app.options.sessions.should.equal true
+    @app.options.foo.should.equal true
+    @app.options.bar.should.equal true
+  end
+
+  specify "can be disabled on app" do
+    @app.options.foo.should.be.nil
+    @app.disable :sessions, :foo, :bar
+    @app.options.sessions.should.equal false
+    @app.options.foo.should.equal false
+    @app.options.bar.should.equal false
+  end
+
+  specify "can be enabled from top-level" do
+    @app.options.foo.should.be.nil
+    disable :sessions, :foo, :bar
+    @app.options.sessions.should.equal false
+    @app.options.foo.should.equal false
+    @app.options.bar.should.equal false
+  end
+
 end

From c1aa6663a55cf10e38f548f48743837e88581b2b Mon Sep 17 00:00:00 2001
From: Ryan Tomayko 
Date: Tue, 15 Apr 2008 06:55:22 -0400
Subject: [PATCH 07/11] Rack pipelines leading to app; 'use' DSL method.

Moved Rack middleware management into Sinatra::Application and
enabled support for assembling custom Rack middleware pipelines
before an app.

  require 'sinatra'
  use Rack::Lint
  use Rack::Authentication
  get '/' do
    "Hello, World"
  end

Assuming a default set of options, this would result in the
following pipeline leading to the application:

  CommonLogger -> Lint -> Authentication -> Application

(The Rack::CommonLogger middleware is created automatically when the
:logging option is enabled.)

Also worth noting is that the pipeline leading to the application is
reassembled on each request during reload in :development mode.
---
 lib/sinatra.rb        | 82 +++++++++++++++++++++++++++++++++----------
 test/pipeline_test.rb | 66 ++++++++++++++++++++++++++++++++++
 2 files changed, 130 insertions(+), 18 deletions(-)
 create mode 100644 test/pipeline_test.rb

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index e7722205..6ff54e6d 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -112,14 +112,10 @@ module Sinatra
   def env
     application.options.env
   end
-  
-  def build_application
-    app = application
-    app = Rack::Session::Cookie.new(app) if Sinatra.options.sessions == true
-    app = Rack::CommonLogger.new(app) if Sinatra.options.logging == true
-    app
-  end
-  
+
+  # Deprecated: use application instead of build_application.
+  alias :build_application :application
+
   def server
     @server ||= case options.server
     when "mongrel"
@@ -141,11 +137,10 @@ module Sinatra
   end
   
   def run
-    
     begin
       puts "== Sinatra has taken the stage on port #{port} for #{env} with backup by #{server.name}"
       require 'pp'
-      server.run(build_application, :Port => port) do |server|
+      server.run(application, :Port => port) do |server|
         trap(:INT) do
           server.stop
           puts "\n== Sinatra has ended his set (crowd applauds)"
@@ -154,9 +149,8 @@ module Sinatra
     rescue Errno::EADDRINUSE => e
       puts "== Someone is already performing on port #{port}!"
     end
-    
   end
-      
+
   class Event
 
     URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
@@ -868,7 +862,7 @@ module Sinatra
     # (Sinatra::application).
     FORWARD_METHODS = %w[
       get put post delete head template layout before error not_found
-      configures configure set_options set_option enable disable
+      configures configure set_options set_option enable disable use
     ]
 
     # Create a new Application with a default configuration taken
@@ -884,7 +878,8 @@ module Sinatra
         @events = Hash.new { |hash, key| hash[key] = [] },
         @errors = Hash.new,
         @filters = Hash.new { |hash, key| hash[key] = [] },
-        @templates = Hash.new
+        @templates = Hash.new,
+        @middleware = []
       ]
       @options = OpenStruct.new(self.class.default_options)
       load_default_configuration!
@@ -1116,6 +1111,7 @@ module Sinatra
     # automatically before each request is processed in development.
     def reload!
       @reloading = true
+      @pipeline = nil
       clearables.each(&:clear)
       load_default_configuration!
       Kernel.load $0
@@ -1142,8 +1138,59 @@ module Sinatra
       end
     end
 
-    # Rack compatible request invocation interface. Requests are processed
-    # as follows:
+    # Add a piece of Rack middleware to the pipeline leading to the
+    # application.
+    def use(klass, *args, &block)
+      fail "#{klass} must respond to 'new'" unless klass.respond_to?(:new)
+      @pipeline = nil
+      @middleware.push([ klass, args, block ]).last
+    end
+
+  private
+
+    # Rack middleware derived from current state of application options.
+    # These components are plumbed in at the very beginning of the
+    # pipeline.
+    def optional_middleware
+      [
+        ([ Rack::CommonLogger,    [], nil ] if options.logging),
+        ([ Rack::Session::Cookie, [], nil ] if options.sessions)
+      ].compact
+    end
+
+    # Rack middleware explicitly added to the application with #use. These
+    # components are plumbed into the pipeline downstream from
+    # #optional_middle.
+    def explicit_middleware
+      @middleware
+    end
+
+    # All Rack middleware used to construct the pipeline.
+    def middleware
+      optional_middleware + explicit_middleware
+    end
+
+  public
+
+    # An assembled pipeline of Rack middleware that leads eventually to
+    # the Application#invoke method. The pipeline is built upon first
+    # access. Defining new middleware with Application#use or manipulating
+    # application options may cause the pipeline to be rebuilt.
+    def pipeline
+      @pipeline ||=
+        middleware.inject(method(:dispatch)) do |app,(klass,args,block)|
+          klass.new(app, *args, &block)
+        end
+    end
+
+    # Rack compatible request invocation interface.
+    def call(env)
+      reload! if development?
+      pipeline.call(env)
+    end
+
+    # Request invocation handler - called at the end of the Rack pipeline
+    # for each request.
     #
     # 1. Create Rack::Request, Rack::Response helper objects.
     # 2. Lookup event handler based on request method and path.
@@ -1154,8 +1201,7 @@ module Sinatra
     #
     # See the Rack specification for detailed information on the
     # +env+ argument and return value.
-    def call(env)
-      reload! if development?
+    def dispatch(env)
       request = Rack::Request.new(env)
       result = lookup(request)
       context = EventContext.new(request, Rack::Response.new, result.params)
diff --git a/test/pipeline_test.rb b/test/pipeline_test.rb
new file mode 100644
index 00000000..67f08501
--- /dev/null
+++ b/test/pipeline_test.rb
@@ -0,0 +1,66 @@
+require File.dirname(__FILE__) + '/helper'
+
+class UpcaseMiddleware
+  def initialize(app, *args, &block)
+    @app = app
+    @args = args
+    @block = block
+  end
+  def call(env)
+    env['PATH_INFO'] = env['PATH_INFO'].to_s.upcase
+    @app.call(env)
+  end
+end
+
+context "Middleware Pipelines" do
+
+  setup do
+    Sinatra.application = nil
+    @app = Sinatra.application
+  end
+
+  teardown do
+    Sinatra.application = nil
+  end
+
+  specify "includes default middleware with options set" do
+    @app.set_options :sessions => true, :logging => true
+    @app.send(:optional_middleware).should.include([Rack::Session::Cookie, [], nil])
+    @app.send(:optional_middleware).should.include([Rack::CommonLogger, [], nil])
+  end
+
+  specify "does not include default middleware with options unset" do
+    @app.set_options :sessions => false, :logging => false
+    @app.send(:optional_middleware).should.not.include([Rack::Session::Cookie, [], nil])
+    @app.send(:optional_middleware).should.not.include([Rack::CommonLogger, [], nil])
+  end
+
+  specify "includes only optional middleware when no explicit middleware added" do
+    @app.set_options :sessions => true, :logging => true
+    @app.send(:middleware).should.equal @app.send(:optional_middleware)
+  end
+
+  specify "should clear middleware before reload" do
+    @app.clearables.should.include(@app.send(:explicit_middleware))
+  end
+
+  specify "should add middleware with use" do
+    block = Proc.new { |env| }
+    @app.use UpcaseMiddleware
+    @app.use UpcaseMiddleware, "foo", "bar"
+    @app.use UpcaseMiddleware, "foo", "bar", &block
+    @app.send(:middleware).should.include([UpcaseMiddleware, [], nil])
+    @app.send(:middleware).should.include([UpcaseMiddleware, ["foo", "bar"], nil])
+    @app.send(:middleware).should.include([UpcaseMiddleware, ["foo", "bar"], block])
+  end
+
+  specify "should run middleware added with use" do
+    get('/foo') { "FAIL!" }
+    get('/FOO') { "PASS!" }
+    use UpcaseMiddleware
+    get_it '/foo'
+    should.be.ok
+    body.should.equal "PASS!"
+  end
+
+end

From f71330e6598bba00bb26735a6077cda00f3f7ef4 Mon Sep 17 00:00:00 2001
From: bmizerany 
Date: Wed, 7 May 2008 14:18:43 -0700
Subject: [PATCH 08/11] quick doc fix

---
 README.rdoc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.rdoc b/README.rdoc
index 0f3bbe7e..f1dc1013 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -88,7 +88,7 @@ If a file exists that maps to the REQUEST_PATH then it is served and the request
 
 = Views (if you need MVC)
 
-All views are looked up in:
+All file-based views are looked up in:
 
   root
     | - views/
@@ -150,12 +150,12 @@ This one is cool:
   
   __END__
   
-  ## layout
+  @@ layout
   X
   = yield
   X
   
-  ## index
+  @@ index
   %div.title Hello world!!!!!
   
 Try it!

From 08b1452a3762a1e7f443352c78f6e3d373cea1d4 Mon Sep 17 00:00:00 2001
From: bmizerany 
Date: Wed, 7 May 2008 18:51:02 -0700
Subject: [PATCH 09/11] getting ready for 0.2.3

---
 .gitignore     |  1 +
 Manifest       | 81 ++++++++++++++++++++++++++++++++++++++++++++++++--
 lib/sinatra.rb |  2 +-
 3 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 3b42519e..1eabcf7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ doc
 pkg
 *.log
 .DS_Store
+Manifest
\ No newline at end of file
diff --git a/Manifest b/Manifest
index f09f9445..c2e30aed 100644
--- a/Manifest
+++ b/Manifest
@@ -1,11 +1,13 @@
 CHANGELOG
 images/404.png
 images/500.png
-index.html
 lib/sinatra/test/methods.rb
 lib/sinatra/test/spec.rb
 lib/sinatra/test/unit.rb
 lib/sinatra.rb
+LICENSE
+Manifest
+Rakefile
 README.rdoc
 test/app_test.rb
 test/application_test.rb
@@ -42,4 +44,79 @@ test/views/layout_test/layout.haml
 test/views/layout_test/layout.sass
 test/views/no_layout/no_layout.builder
 test/views/no_layout/no_layout.haml
-Manifest
+vendor/rack/AUTHORS
+vendor/rack/bin/rackup
+vendor/rack/contrib/rack_logo.svg
+vendor/rack/COPYING
+vendor/rack/example/lobster.ru
+vendor/rack/example/protectedlobster.rb
+vendor/rack/example/protectedlobster.ru
+vendor/rack/KNOWN-ISSUES
+vendor/rack/lib/rack/adapter/camping.rb
+vendor/rack/lib/rack/auth/abstract/handler.rb
+vendor/rack/lib/rack/auth/abstract/request.rb
+vendor/rack/lib/rack/auth/basic.rb
+vendor/rack/lib/rack/auth/digest/md5.rb
+vendor/rack/lib/rack/auth/digest/nonce.rb
+vendor/rack/lib/rack/auth/digest/params.rb
+vendor/rack/lib/rack/auth/digest/request.rb
+vendor/rack/lib/rack/auth/openid.rb
+vendor/rack/lib/rack/builder.rb
+vendor/rack/lib/rack/cascade.rb
+vendor/rack/lib/rack/commonlogger.rb
+vendor/rack/lib/rack/file.rb
+vendor/rack/lib/rack/handler/cgi.rb
+vendor/rack/lib/rack/handler/fastcgi.rb
+vendor/rack/lib/rack/handler/lsws.rb
+vendor/rack/lib/rack/handler/mongrel.rb
+vendor/rack/lib/rack/handler/scgi.rb
+vendor/rack/lib/rack/handler/webrick.rb
+vendor/rack/lib/rack/lint.rb
+vendor/rack/lib/rack/lobster.rb
+vendor/rack/lib/rack/mock.rb
+vendor/rack/lib/rack/recursive.rb
+vendor/rack/lib/rack/reloader.rb
+vendor/rack/lib/rack/request.rb
+vendor/rack/lib/rack/response.rb
+vendor/rack/lib/rack/session/abstract/id.rb
+vendor/rack/lib/rack/session/cookie.rb
+vendor/rack/lib/rack/session/memcache.rb
+vendor/rack/lib/rack/session/pool.rb
+vendor/rack/lib/rack/showexceptions.rb
+vendor/rack/lib/rack/showstatus.rb
+vendor/rack/lib/rack/static.rb
+vendor/rack/lib/rack/urlmap.rb
+vendor/rack/lib/rack/utils.rb
+vendor/rack/lib/rack.rb
+vendor/rack/Rakefile
+vendor/rack/README
+vendor/rack/test/cgi/lighttpd.conf
+vendor/rack/test/cgi/test
+vendor/rack/test/cgi/test.fcgi
+vendor/rack/test/cgi/test.ru
+vendor/rack/test/spec_rack_auth_basic.rb
+vendor/rack/test/spec_rack_auth_digest.rb
+vendor/rack/test/spec_rack_builder.rb
+vendor/rack/test/spec_rack_camping.rb
+vendor/rack/test/spec_rack_cascade.rb
+vendor/rack/test/spec_rack_cgi.rb
+vendor/rack/test/spec_rack_commonlogger.rb
+vendor/rack/test/spec_rack_fastcgi.rb
+vendor/rack/test/spec_rack_file.rb
+vendor/rack/test/spec_rack_lint.rb
+vendor/rack/test/spec_rack_lobster.rb
+vendor/rack/test/spec_rack_mock.rb
+vendor/rack/test/spec_rack_mongrel.rb
+vendor/rack/test/spec_rack_recursive.rb
+vendor/rack/test/spec_rack_request.rb
+vendor/rack/test/spec_rack_response.rb
+vendor/rack/test/spec_rack_session_cookie.rb
+vendor/rack/test/spec_rack_session_memcache.rb
+vendor/rack/test/spec_rack_session_pool.rb
+vendor/rack/test/spec_rack_showexceptions.rb
+vendor/rack/test/spec_rack_showstatus.rb
+vendor/rack/test/spec_rack_static.rb
+vendor/rack/test/spec_rack_urlmap.rb
+vendor/rack/test/spec_rack_utils.rb
+vendor/rack/test/spec_rack_webrick.rb
+vendor/rack/test/testrequest.rb
diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 6ff54e6d..7afce4d2 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -82,7 +82,7 @@ module Sinatra
   module Version
     MAJOR = '0'
     MINOR = '2'
-    REVISION = '1'
+    REVISION = '3'
     def self.combined
       [MAJOR, MINOR, REVISION].join('.')
     end

From ce673fa4d9e724b017290e85f3da40cfddf068a2 Mon Sep 17 00:00:00 2001
From: bmizerany 
Date: Sun, 18 May 2008 14:53:19 -0700
Subject: [PATCH 10/11] Fixes problem in development where reloading overrides
 custom errors

---
 lib/sinatra.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/sinatra.rb b/lib/sinatra.rb
index 7afce4d2..39a47dc8 100644
--- a/lib/sinatra.rb
+++ b/lib/sinatra.rb
@@ -1110,10 +1110,10 @@ module Sinatra
     # and then reload the application source file. This occurs
     # automatically before each request is processed in development.
     def reload!
-      @reloading = true
-      @pipeline = nil
       clearables.each(&:clear)
       load_default_configuration!
+      @pipeline = nil
+      @reloading = true
       Kernel.load $0
       @reloading = false
     end

From d2cfe06c3b177a351f5f16d186620ab2995759c9 Mon Sep 17 00:00:00 2001
From: bmizerany 
Date: Sun, 18 May 2008 16:01:16 -0700
Subject: [PATCH 11/11] ignore test file

---
 .gitignore | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index 1eabcf7a..582f3cc8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ doc
 pkg
 *.log
 .DS_Store
-Manifest
\ No newline at end of file
+Manifest
+x.rb