diff --git a/examples/hello/test.rb b/examples/hello/test.rb new file mode 100644 index 0000000000..b05e97d691 --- /dev/null +++ b/examples/hello/test.rb @@ -0,0 +1,20 @@ +$LOAD_PATH.unshift '../../lib/' +require 'sinatra' + +get '/' do + body <<-HTML +
+ HTML +end + +post '/' do + body "You entered #{params[:name]}" +end + +get '/erb' do + erb :index +end + +get '/test' do + erb "Hello <%= params[:name] %>" +end diff --git a/examples/hello/views/index.erb b/examples/hello/views/index.erb new file mode 100644 index 0000000000..cb7c473a21 --- /dev/null +++ b/examples/hello/views/index.erb @@ -0,0 +1 @@ +<%= 1 + 3 %> \ No newline at end of file diff --git a/files/default_index.erb b/files/default_index.erb new file mode 100644 index 0000000000..f929d7b157 --- /dev/null +++ b/files/default_index.erb @@ -0,0 +1 @@ +Default Index!!!!!!! \ No newline at end of file diff --git a/files/not_found.erb b/files/not_found.erb new file mode 100644 index 0000000000..e9194acb6a --- /dev/null +++ b/files/not_found.erb @@ -0,0 +1,52 @@ + + + + + + + Not Found :: Sinatra + + + + + + +
+
+
+

Sinatra doesn't know this diddy, but he's a quick study.

+

Add this to your lyrics:

+
<%= request.request_method.downcase %> '<%= request.path_info %>' do
+	html "Replace this with your code."
+end
+
+
+
+ + + + \ No newline at end of file diff --git a/lib/sinatra.rb b/lib/sinatra.rb new file mode 100644 index 0000000000..8fe526a0db --- /dev/null +++ b/lib/sinatra.rb @@ -0,0 +1,36 @@ +%w(rubygems rack).each do |library| + begin + require library + rescue LoadError + raise "== Sinatra cannot run without #{library} installed" + end +end + +require File.dirname(__FILE__) + '/sinatra/core_ext/class' +require File.dirname(__FILE__) + '/sinatra/core_ext/hash' + +require File.dirname(__FILE__) + '/sinatra/logger' +require File.dirname(__FILE__) + '/sinatra/event' +require File.dirname(__FILE__) + '/sinatra/dispatcher' +require File.dirname(__FILE__) + '/sinatra/server' +require File.dirname(__FILE__) + '/sinatra/dsl' + +SINATRA_LOGGER = Sinatra::Logger.new(STDOUT) + +def set_logger(logger = SINATRA_LOGGER) + [Sinatra::Server, Sinatra::EventContext, Sinatra::Event].each do |klass| + klass.logger = logger + end +end + +set_logger + +SINATRA_ROOT = File.dirname(__FILE__) + '/..' + +Dir.glob(SINATRA_ROOT + '/vendor/*/init.rb').each do |plugin| + require plugin +end + +at_exit do + Sinatra::Server.new.start unless Sinatra::Server.running +end diff --git a/lib/sinatra/core_ext/class.rb b/lib/sinatra/core_ext/class.rb new file mode 100644 index 0000000000..79247e00cb --- /dev/null +++ b/lib/sinatra/core_ext/class.rb @@ -0,0 +1,48 @@ +# Extends the class object with class and instance accessors for class attributes, +# just like the native attr* accessors for instance attributes. +class Class # :nodoc: + def cattr_reader(*syms) + syms.flatten.each do |sym| + next if sym.is_a?(Hash) + class_eval(<<-EOS, __FILE__, __LINE__) + unless defined? @@#{sym} + @@#{sym} = nil + end + + def self.#{sym} + @@#{sym} + end + + def #{sym} + @@#{sym} + end + EOS + end + end + + def cattr_writer(*syms) + options = syms.last.is_a?(Hash) ? syms.pop : {} + syms.flatten.each do |sym| + class_eval(<<-EOS, __FILE__, __LINE__) + unless defined? @@#{sym} + @@#{sym} = nil + end + + def self.#{sym}=(obj) + @@#{sym} = obj + end + + #{" + def #{sym}=(obj) + @@#{sym} = obj + end + " unless options[:instance_writer] == false } + EOS + end + end + + def cattr_accessor(*syms) + cattr_reader(*syms) + cattr_writer(*syms) + end +end diff --git a/lib/sinatra/core_ext/hash.rb b/lib/sinatra/core_ext/hash.rb new file mode 100644 index 0000000000..e71044079a --- /dev/null +++ b/lib/sinatra/core_ext/hash.rb @@ -0,0 +1,7 @@ +class Hash + + def symbolize_keys + self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h } + end + +end diff --git a/lib/sinatra/dispatcher.rb b/lib/sinatra/dispatcher.rb new file mode 100644 index 0000000000..173eeeff32 --- /dev/null +++ b/lib/sinatra/dispatcher.rb @@ -0,0 +1,42 @@ +module Sinatra + + DEFAULT_HEADERS = { 'Content-Type' => 'text/html' } + + class Dispatcher + + def headers + DEFAULT_HEADERS + end + + def call(env) + @request = Rack::Request.new(env) + + event = EventManager.events.detect(lambda { not_found }) do |e| + e.path == @request.path_info && e.verb == @request.request_method.downcase.intern + end + + result = event.attend(@request) + + [result.status, headers.merge(result.headers), result.body] + rescue => e + puts "#{e.message}:\n\t#{e.backtrace.join("\n\t")}" + end + + private + + def not_found + Event.new(:get, nil) do + status 404 + views_dir SINATRA_ROOT + '/files' + + if request.path_info == '/' + erb :default_index + else + erb :not_found + end + end + end + + end + +end diff --git a/lib/sinatra/dsl.rb b/lib/sinatra/dsl.rb new file mode 100644 index 0000000000..75499e3493 --- /dev/null +++ b/lib/sinatra/dsl.rb @@ -0,0 +1,11 @@ +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 + +end diff --git a/lib/sinatra/event.rb b/lib/sinatra/event.rb new file mode 100644 index 0000000000..216ececcf1 --- /dev/null +++ b/lib/sinatra/event.rb @@ -0,0 +1,94 @@ +module Sinatra + + module EventManager + + extend self + + def events + @events || [] + end + + def register_event(event) + (@events ||= []) << event + end + + end + + class EventContext + + cattr_accessor :logger + + attr_reader :request + + def initialize(request) + @request = request + @headers = {} + end + + def status(value = nil) + @status = value if value + @status || 200 + end + + def body(value = nil) + @body = value if value + @body || '' + end + + # This allows for: + # header 'Content-Type' => 'text/html' + # header 'Foo' => 'Bar' + # or + # headers 'Content-Type' => 'text/html', + # 'Foo' => 'Bar' + # + # Whatever blows your hair back + def headers(value = nil) + @headers.merge!(value) if value + @headers + end + alias :header :headers + + def params + @params ||= @request.params.symbolize_keys + end + + end + + class Event + + cattr_accessor :logger + + attr_reader :path, :verb + + def initialize(verb, path, &block) + @verb = verb + @path = path + @block = block + EventManager.register_event(self) + end + + def attend(request) + begin + context = EventContext.new(request) + context.instance_eval(&@block) if @block + log_event(request, context, nil) + context + rescue => e + context.status 500 + log_event(request, context, e) + context + end + end + alias :call :attend + + private + + def log_event(request, context, e) + logger.info "#{request.request_method} #{request.path_info} | Status: #{context.status} | Params: #{context.params.inspect}" + logger.exception(e) if e + end + + end + +end diff --git a/lib/sinatra/logger.rb b/lib/sinatra/logger.rb new file mode 100644 index 0000000000..e2dbbbd862 --- /dev/null +++ b/lib/sinatra/logger.rb @@ -0,0 +1,21 @@ +module Sinatra + + class Logger + + def initialize(steam) + @stream = steam + end + + %w(info debug error warn).each do |n| + define_method n do |message| + @stream.puts message + end + end + + def exception(e) + error "#{e.message}:\n\t#{e.backtrace.join("\n\t")}" + end + + end + +end \ No newline at end of file diff --git a/lib/sinatra/server.rb b/lib/sinatra/server.rb new file mode 100644 index 0000000000..3d13111198 --- /dev/null +++ b/lib/sinatra/server.rb @@ -0,0 +1,26 @@ +module Sinatra + + class Server + + cattr_accessor :logger + cattr_accessor :running + + def start + begin + Rack::Handler::Mongrel.run(Sinatra::Dispatcher.new, :Port => 4567) do |server| + logger.info "== Sinatra has taken the stage on port #{server.port}!" + trap("INT") do + server.stop + self.class.running = false + logger.info "\n== Sinatra has ended his set (crowd applauds)" + end + end + self.class.running = true + rescue => e + logger.exception e + end + end + + end + +end diff --git a/test/helper.rb b/test/helper.rb new file mode 100644 index 0000000000..a1cd5e3d1f --- /dev/null +++ b/test/helper.rb @@ -0,0 +1,41 @@ +require File.dirname(__FILE__) + '/../lib/sinatra' + +%w(mocha test/spec).each do |library| + begin + require library + rescue + STDERR.puts "== Sinatra's tests need #{library} to run." + end +end + +Sinatra::Server.running = true + +class Test::Unit::TestCase + + def get_it(path) + request = Rack::MockRequest.new(Sinatra::Dispatcher.new) + @response = request.get path + end + + def post_it(path) + request = Rack::MockRequest.new(Sinatra::Dispatcher.new) + @response = request.post path + end + + def response + @response + end + + def status + @response.status + end + + def text + @response.body + end + + def headers + @response.headers + end + +end diff --git a/test/sinatra/dispatcher_test.rb b/test/sinatra/dispatcher_test.rb new file mode 100644 index 0000000000..fc96fb4b2f --- /dev/null +++ b/test/sinatra/dispatcher_test.rb @@ -0,0 +1,30 @@ +require File.dirname(__FILE__) + '/../helper' + +describe "When a dispatcher receives a request" do + + it "should attend to the event" do + + Sinatra::Event.new(:get, '/') do + body 'this is the index as a get' + end + + get_it "/" + + status.should.equal 200 + text.should.equal 'this is the index as a get' + headers['Content-Type'].should.equal 'text/html' + + post_it "/test" + + status.should.equal 404 + text.scan("Not Found :: Sinatra").size.should.equal 1 + headers['Content-Type'].should.equal 'text/html' + + get_it '/foo' + + status.should.equal 404 + text.scan("Not Found :: Sinatra").size.should.equal 1 + + end + +end \ No newline at end of file diff --git a/test/sinatra/event_test.rb b/test/sinatra/event_test.rb new file mode 100644 index 0000000000..c7efda562a --- /dev/null +++ b/test/sinatra/event_test.rb @@ -0,0 +1,17 @@ +require File.dirname(__FILE__) + '/../helper' + +describe "Event" do + + it "should return 500 if exception thrown" do + set_logger stub_everything + + event = Sinatra::Event.new(:get, nil) do + raise 'whaaaa!' + end + + result = event.attend(stub_everything) + + result.status.should.equal 500 + end + +end diff --git a/vendor/erb/init.rb b/vendor/erb/init.rb new file mode 100644 index 0000000000..0ea9d0f9e7 --- /dev/null +++ b/vendor/erb/init.rb @@ -0,0 +1,3 @@ +require File.dirname(__FILE__) + '/lib/erb' + +Sinatra::EventContext.send(:include, Sinatra::Erb::InstanceMethods) diff --git a/vendor/erb/lib/erb.rb b/vendor/erb/lib/erb.rb new file mode 100644 index 0000000000..220f4936aa --- /dev/null +++ b/vendor/erb/lib/erb.rb @@ -0,0 +1,27 @@ +require 'erb' + +module Sinatra + + module Erb + + module InstanceMethods + + def erb(content) + s = if content.is_a?(Symbol) + open("%s/%s.erb" % [views_dir, content]).read + else + content + end + body ERB.new(s).result(binding) + end + + def views_dir(value = nil) + @views_dir = value if value + @views_dir || File.dirname($0) + '/views' + end + + end + + end + +end