diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..882fce76 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2007 Blake Mizerany + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Manifest b/Manifest index e6128b31..8b04bcdd 100644 --- a/Manifest +++ b/Manifest @@ -1,7 +1,4 @@ CHANGELOG -RakeFile -README -x.rb examples/hello/hello.rb examples/hello/views/hello.erb examples/todo/todo.rb @@ -9,8 +6,15 @@ files/default_index.erb files/error.erb files/logo.png files/not_found.erb -lib/sinatra.rb +lib/sinatra/context/renderer.rb lib/sinatra/context.rb +lib/sinatra/core_ext/array.rb +lib/sinatra/core_ext/class.rb +lib/sinatra/core_ext/hash.rb +lib/sinatra/core_ext/kernel.rb +lib/sinatra/core_ext/metaid.rb +lib/sinatra/core_ext/module.rb +lib/sinatra/core_ext/symbol.rb lib/sinatra/dispatcher.rb lib/sinatra/dsl.rb lib/sinatra/environment.rb @@ -19,29 +23,28 @@ lib/sinatra/irb.rb lib/sinatra/loader.rb lib/sinatra/logger.rb lib/sinatra/options.rb -lib/sinatra/pretty_url.rb +lib/sinatra/rack_ext/request.rb lib/sinatra/route.rb lib/sinatra/server.rb +lib/sinatra/sessions.rb lib/sinatra/test_methods.rb -lib/sinatra/context/renderer.rb -lib/sinatra/core_ext/array.rb -lib/sinatra/core_ext/class.rb -lib/sinatra/core_ext/hash.rb -lib/sinatra/core_ext/kernel.rb -lib/sinatra/core_ext/metaid.rb -lib/sinatra/core_ext/module.rb -lib/sinatra/core_ext/symbol.rb +lib/sinatra.rb +LICENSE +RakeFile +README +sinatra.gemspec +site/index.htm site/index.html site/logo.png test/helper.rb test/sinatra/dispatcher_test.rb test/sinatra/event_test.rb -test/sinatra/pretty_url_test.rb test/sinatra/renderer_test.rb +test/sinatra/request_test.rb test/sinatra/route_test.rb +test/sinatra/static_files/foo.txt test/sinatra/static_files_test.rb test/sinatra/url_test.rb -test/sinatra/static_files/foo.txt vendor/erb/init.rb vendor/erb/lib/erb.rb vendor/haml/init.rb diff --git a/README b/README index 1b32308c..05516492 100644 --- a/README +++ b/README @@ -1,43 +1,57 @@ -Copyright (c) +Sinatra (C) 2007 By Blake Mizerany -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: += Classy web-development dressed in a DSL -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +== Install! -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. + sudo gem install sinatra -y +== Use! -Using Sinatra is EASY! +I'm going to move quick. I'll let you drool at your own pace. -WARNING: Keep a fresh pair of underwear nearby. You may need them when you see this. +- Create a file called lyrics.rb (or any name you like) -Get running without a file! +- Add + require 'rubygems' + require 'sinatra' -ruby -e 'require "sinatra"' # HIT http://localhost:4567/ and BAM! +- Run (yes, with just ruby) + % ruby lyrics.rb + == Sinata has taken the stage on port 4567! -now create a file called test.rb (or whatever you fancy) and enter this: +- Take a moment and view the default page http://localhost:4567. Go ahead and bask in it's glory. -require 'sinatra' +* Notice: + * It didn't create any page to show you that default page (just a cool thing to see, that's all) + * There was nothing generated other than a log file + * Sinatra is a really cool name for a web-framework that's a DSL -get '/' do - body 'Hello World!' -end +- Modify lyrics.rb by adding: -now run 'ruby test.rb' and refresh your browser + get '/' do + 'Hello World' + end + +- Refresh (no need to restart Sinatra): -Oh yeah! + http://localhost:4567 + +- Modify again (then refresh): + + get '/' do + <<-HTML +
+ + +
+ HTML + end + + post '/' do + "Hello #{params[:name] || 'World'}!" + end + +- Homework: + +Use the Sinatra::Erb::EventContext or Sinatra::Haml::EventContext to do the same. Do them inline and as template files. diff --git a/RakeFile b/RakeFile index f88404b5..3f6a10a6 100644 --- a/RakeFile +++ b/RakeFile @@ -20,6 +20,7 @@ begin p.email = "blake.mizerany@gmail.com" p.test_pattern = 'test/**/*_test.rb' p.include_rakefile = true + p.rdoc_pattern = ['README', 'LICENSE'] << Dir.glob('lib/**/*.rb') << Dir.glob('vendor/**/*.rb') end rescue LoadError diff --git a/lib/sinatra/context.rb b/lib/sinatra/context.rb index 63b88387..103c4d14 100644 --- a/lib/sinatra/context.rb +++ b/lib/sinatra/context.rb @@ -9,22 +9,33 @@ module Sinatra include Sinatra::Renderer - def initialize(request) + def initialize(request) #:nodoc: @request = request @headers = {} end + # Sets or returns the status def status(value = nil) @status = value if value @status || 200 end + # Sets or returns the body + # *Usage* + # body 'test' + # or + # body do + # 'test' + # end + # both are the same + # def body(value = nil, &block) @body = value if value @body = block.call if block @body end - + + # Renders an exception to +body+ and sets status to 500 def error(value = nil) if value status 500 @@ -34,12 +45,14 @@ module Sinatra @error end - # This allows for: - # header 'Content-Type' => 'text/html' - # header 'Foo' => 'Bar' + # Sets or returns response headers + # + # *Usage* + # header 'Content-Type' => 'text/html' + # header 'Foo' => 'Bar' # or - # headers 'Content-Type' => 'text/html', - # 'Foo' => 'Bar' + # headers 'Content-Type' => 'text/html', + # 'Foo' => 'Bar' # # Whatever blows your hair back def headers(value = nil) @@ -48,21 +61,24 @@ module Sinatra end alias :header :headers + # Returns a Hash of session data. Keys are symbolized def session request.env['rack.session'] end + # Returns a Hash of params. Keys are symbolized def params @params ||= @request.params.symbolize_keys end + # Redirect to a url def redirect(path) logger.info "Redirecting to: #{path}" status 302 header 'Location' => path end - def log_event + def log_event #:nodoc: logger.info "#{request.request_method} #{request.path_info} | Status: #{status} | Params: #{params.inspect}" logger.exception(error) if error end diff --git a/lib/sinatra/context/renderer.rb b/lib/sinatra/context/renderer.rb index a703c7ab..56cbb4bf 100644 --- a/lib/sinatra/context/renderer.rb +++ b/lib/sinatra/context/renderer.rb @@ -1,7 +1,12 @@ -Layouts = Hash.new +Layouts = Hash.new # :nodoc: module Sinatra + # The magic or rendering happens here. This is included in Sinatra::EventContext on load. + # + # These methods are the foundation for Sinatra::Erb and Sinatra::Haml and allow you to quickly + # create custom wrappers for your favorite rendering engines outside of erb and haml. + module Renderer DEFAULT_OPTIONS = { @@ -9,6 +14,38 @@ module Sinatra :layout => :layout } + + # Renders templates from a string or file and handles their layouts: + # + # Example: + # module MyRenderer + # def my(template, options, &layout) + # render(template, :my, options, &layout) + # end + # + # def render_my(template) + # template.capitalize # It capitalizes templates!!!!! WOW! + # end + # end + # Sinatra::EventContext.send :include, MyRenderer + # + # get '/' do + # my "something" + # end + # + # get_it '/' # => 'Something' + # + # The second method is named render_extname. render will call this dynamicly + # + # paramaters: + # * +template+ If String, renders the string. If Symbol, reads from file with the basename of the Symbol; uses +renderer+ for extension. + # * +renderer+ A symbol defining the render_ method to call and the extension append to +template+ when looking in the +views_directory+ + # * +options+ An optional Hash of options (see next section) + # + # options: + # * +:views_directory+ Allows you to override the default 'views' directory an look for the template in another + # * +:layout+ Which layout to use (see Sinatra::Dsl). false to force a render with no layout. Defaults to :default layout + # def render(template, renderer, options = {}) options = DEFAULT_OPTIONS.merge(options) diff --git a/lib/sinatra/core_ext/metaid.rb b/lib/sinatra/core_ext/metaid.rb index f701081b..415ca17a 100644 --- a/lib/sinatra/core_ext/metaid.rb +++ b/lib/sinatra/core_ext/metaid.rb @@ -1,3 +1,6 @@ +# Compliments to why for this: +# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html + class Object # The hidden singleton lurks behind everyone def metaclass; class << self; self; end; end diff --git a/lib/sinatra/dsl.rb b/lib/sinatra/dsl.rb index 0601623b..9aeea407 100644 --- a/lib/sinatra/dsl.rb +++ b/lib/sinatra/dsl.rb @@ -1,42 +1,163 @@ -module Kernel - %w( get post put delete ).each do |verb| - eval <<-end_eval - def #{verb}(path, &block) - Sinatra::Event.new(:#{verb}, path, &block) - end - end_eval - end - - def after_attend(filter_name = nil, &block) - Sinatra::Event.after_attend(filter_name, &block) - end - - def helpers(&block) - Sinatra::EventContext.class_eval(&block) - end +module Sinatra - def static(path, root) - Sinatra::StaticEvent.new(path, root) - end - - %w(test development production).each do |env| - module_eval <<-end_eval - def #{env} - yield if Sinatra::Options.environment == :#{env} - end - end_eval - end - - def layout(name = :layout, options = {}) - Layouts[name] = unless block_given? - File.read("%s/%s" % [options[:views_directory] || 'views', name]) - else - yield + module Dsl + + # Define an Event that responds to a +path+ on GET method + # + # The +path+ can be a template (i.e. '/:foo/bar/:baz'). When recognized, it will add :foo and :baz to +params+ with their values. + # + # Example: + # # Going RESTful + # + # get '/' do + # .. show stuff .. + # end + # + # post '/' do + # .. add stuff .. + # redirect '/' + # end + # + # put '/:id' do + # .. update params[:id] .. + # redirect '/' + # end + # + # delete '/:id' do + # .. delete params[:id] .. + # redirect '/' + # end + # + # BIG NOTE: PUT and DELETE are trigged when POSTing to their paths with a _method param whose value is PUT or DELETE + # + def get(path, &block) + Sinatra::Event.new(:get, path, &block) + end + + # Same as get but responds to POST + def post(path, &block) + Sinatra::Event.new(:post, path, &block) + end + + # Same as get but responds to PUT + # + # BIG NOTE: PUT and DELETE are trigged when POSTing to their paths with a _method param whose value is PUT or DELETE + def put(path, &block) + Sinatra::Event.new(:put, path, &block) + end + + # Same as get but responds to DELETE + # + # BIG NOTE: PUT and DELETE are trigged when POSTing to their paths with a _method param whose value is PUT or DELETE + def delete(path, &block) + Sinatra::Event.new(:delete, path, &block) + end + + # Run given block after each Event's execution + # Example: + # after_attend do + # logger.debug "After event attend!" + # end + def after_attend(filter_name = nil, &block) + Sinatra::Event.after_attend(filter_name, &block) end - end - def sessions(on_off) - Sinatra::Session::Cookie.use = on_off + # Add methods to each event for use during execution + # + # Example: + # helpers do + # def foo + # 'foo!' + # end + # end + # + # get '/bar' do + # foo + # end + # + # get_it '/bar' # => 'foo!' + # + def helpers(&block) + Sinatra::EventContext.class_eval(&block) + end + + # Maps a path to a physical directory containing static files + # + # Example: + # static '/p', 'public' + # + def static(path, root) + Sinatra::StaticEvent.new(path, root) + end + + # Execute block if in development mode (Used for configuration) + def development + yield if Sinatra::Options.environment == :development + end + + # Execute block if in production mode (Used for configuration) + def production + yield if Sinatra::Options.environment == :production + end + + # Execute block if in test mode (Used for configuration) + def test + yield if Sinatra::Options.environment == :test + end + + # Define named layouts (default name is :layout) + # + # Examples: + # # Default layout in Erb + # layout do + # '-- <%= yield %> --' + # end + # + # # Named layout in Haml + # layout :for_haml do + # '== XXXX #{yield} XXXX' + # end + # + # # Loads layout named :"foo.erb" from file (default behaviour if block is omitted) + # layout 'foo.erb' # looks for foo.erb. This is odd an is being re-thought + # + # def layout(name = :layout, options = {}) + # Layouts[name] = unless block_given? + # File.read("%s/%s" % [options[:views_directory] || 'views', name]) + # else + # yield + # end + # end + # + # Cool trick: + # + # # Send a one-time layout to renderer method + # get '/cooltrick' do + # erb 'wicked' do + # 'Cool <%= yield %> Trick' + # end + # end + # + # get_it '/cooltrick' # => 'Cool wicked Trick' + # + def layout(name = :layout, options = {}) + Layouts[name] = unless block_given? + File.read("%s/%s" % [options[:views_directory] || 'views', name]) + else + yield + end + end + + # Turn sessions :on or :off + # + # NOTE: There is currently no way to turn it on or off per Event... patches anyone?) + def sessions(on_off) + Sinatra::Session::Cookie.use = on_off + end + end + end + +include Sinatra::Dsl diff --git a/lib/sinatra/event.rb b/lib/sinatra/event.rb index 4aa5954d..4c2c6d6d 100644 --- a/lib/sinatra/event.rb +++ b/lib/sinatra/event.rb @@ -1,6 +1,6 @@ module Sinatra - module EventManager + module EventManager # :nodoc: extend self def reset! @@ -39,7 +39,7 @@ module Sinatra end - class Event + class Event # :nodoc: cattr_accessor :logger cattr_accessor :after_filters @@ -88,7 +88,7 @@ module Sinatra end - class StaticEvent < Event + class StaticEvent < Event # :nodoc: def initialize(path, root, register = true) @root = root diff --git a/lib/sinatra/irb.rb b/lib/sinatra/irb.rb index 1a229412..76d0473c 100644 --- a/lib/sinatra/irb.rb +++ b/lib/sinatra/irb.rb @@ -1,17 +1,24 @@ module Sinatra + + # Sinatra Irb is entered via ruby myapp.rb -c (replace myapp.rb with your app filename) + # + # Be sure to also check out Sinatra::TestMethods for more cool stuff when your in Irb + # module Irb extend self # taken from merb - def start! + def start! #:nodoc: Object.send(:include, TestMethods) # added to allow post_to in console Object.class_eval do + # Reload all Sinatra and App specific files def reload! Loader.reload! end + # Show the +body+ with result info in your text editor!!! Great Job! def show!(editor = nil) editor = editor || ENV['EDITOR'] IO.popen(editor, 'w') do |f| @@ -24,7 +31,7 @@ module Sinatra end alias :mate :show! - def result_info + def result_info #:nodoc: info = <<-end_info # Status: #{status} # Headers: #{headers.inspect} diff --git a/lib/sinatra/rack_ext/request.rb b/lib/sinatra/rack_ext/request.rb index e2349917..f0c928c0 100644 --- a/lib/sinatra/rack_ext/request.rb +++ b/lib/sinatra/rack_ext/request.rb @@ -1,6 +1,6 @@ -module Rack +module Rack #:nodoc: - class Request + class Request #:nodoc: def request_method if @env['REQUEST_METHOD'] == 'POST' && %w(PUT DELETE).include?(params['_method']) diff --git a/lib/sinatra/test_methods.rb b/lib/sinatra/test_methods.rb index 90207533..2273c8d0 100644 --- a/lib/sinatra/test_methods.rb +++ b/lib/sinatra/test_methods.rb @@ -1,9 +1,20 @@ require 'uri' module Sinatra - + + # These methods are for integration testing without an internet connection. They are available in Test::Unit::TestCase and when in Irb. + module TestMethods + # get_it, post_it, put_it, delete_it + # Executes the method and returns the result of the body + # + # options: + # +:params+ a hash of name parameters + # + # Example: + # get_it '/', :name => 'Blake' # => 'Hello Blake!' + # %w(get post put delete).each do |verb| module_eval <<-end_eval def #{verb}_it(path, params = {}) diff --git a/test/sinatra/renderer_test.rb b/test/sinatra/renderer_test.rb index a538b14c..51abe066 100644 --- a/test/sinatra/renderer_test.rb +++ b/test/sinatra/renderer_test.rb @@ -1,6 +1,6 @@ require File.dirname(__FILE__) + '/../helper' -class Sinatra::EventContext +class Sinatra::EventContext # :nodoc: def render_foo(template) require 'erb' diff --git a/vendor/erb/lib/erb.rb b/vendor/erb/lib/erb.rb index f56c1871..baa8c9b3 100644 --- a/vendor/erb/lib/erb.rb +++ b/vendor/erb/lib/erb.rb @@ -1,14 +1,36 @@ module Sinatra - module Erb + module Erb # :nodoc: module EventContext + # Renders raw erb in within the events context. + # + # This can be use to if you already have the template on hand and don't + # need a layout. This is speedier than using erb + # def render_erb(content) require 'erb' body ERB.new(content).result(binding) end - + + # Renders erb within an event. + # + # Inline example: + # + # get '/foo' do + # erb 'The time is <%= Time.now %>' + # end + # + # Template example: + # + # get '/foo' do + # erb :foo #=> reads and renders view/foo.erb + # end + # + # For options, see Sinatra::Renderer + # + # See also: Sinatra::Renderer def erb(template, options = {}, &layout) render(template, :erb, options, &layout) end diff --git a/vendor/haml/lib/haml.rb b/vendor/haml/lib/haml.rb index 5376a715..f2621041 100644 --- a/vendor/haml/lib/haml.rb +++ b/vendor/haml/lib/haml.rb @@ -1,14 +1,36 @@ module Sinatra - module Haml + module Haml # :nodoc: module EventContext - def render_haml(content) + # Renders raw haml in within the events context. + # + # This can be use to if you already have the template on hand and don't + # need a layout. This is speedier than using haml + # + def render_haml(template) require 'haml' - body ::Haml::Engine.new(content).render(self) + body ::Haml::Engine.new(template).render(self) end + # Renders Haml within an event. + # + # Inline example: + # + # get '/foo' do + # haml '== The time is #{Time.now}' + # end + # + # Template example: + # + # get '/foo' do + # haml :foo #=> reads and renders view/foo.haml + # end + # + # For options, see Sinatra::Renderer + # + # See also: Sinatra::Renderer def haml(template, options = {}, &layout) render(template, :haml, options, &layout) end