Skip to content
Browse files

+ rewrite to render in the context of a view object. API breaking

change: #env is no longer available in the layout. use #request_path and
 #query_string instead.

Now you can define your own view helpers in your config.ru.
  • Loading branch information...
1 parent accb323 commit 6fe2da510f2298fe9474d573722debe9ce0f01af @niko committed Mar 8, 2011
Showing with 169 additions and 111 deletions.
  1. +49 −28 lib/tiny_site.rb
  2. +120 −83 spec/tiny_site_spec.rb
View
77 lib/tiny_site.rb
@@ -1,4 +1,5 @@
require 'open-uri'
+require 'forwardable'
require 'RedCloth'
require 'yaml'
require 'haml'
@@ -68,37 +69,56 @@ def bust
end
class TinySite
+ class View
+ extend Forwardable
+ def_delegators :@app, :request_path, :query_string, :global, :page, :file_url_for, :image_url_for
+
+ def initialize(app) ; @app = app ; end
+ end
+end
+
+class TinySite
+ attr_reader :file_path, :file_path_postfix, :file_extension, :image_path, :request_path, :query_string,
+ :request_path, :query_string
+
def initialize(opts)
@file_path = opts[:file_path]
@file_path_postfix = opts[:file_path_postfix] || ''
- @file_extension = opts[:file_extension] || 'textile'
- @image_path = opts[:image_path] || File.join(@file_path, 'images')
- @cache_buster = opts[:cache_buster] || 'bust'
+ @file_extension = opts[:file_extension] || 'textile'
+ @image_path = opts[:image_path] || File.join(@file_path, 'images')
+ @cache_buster = opts[:cache_buster] || 'bust'
end
- def remote_file_url_for(filename)
- File.join @file_path, "#{filename}.#{@file_extension}#{@file_path_postfix}"
+ def global
+ _, global_file = CachedHttpFile.get page_content_url_for('__global')
+ TextileParts.parse global_file, image_path, file_path_postfix
end
- def render
- global_file_fetch_tread = Thread.new{
- _, @global_file = CachedHttpFile.get remote_file_url_for('__global') } # get __global in background
- @status, page_file = CachedHttpFile.get remote_file_url_for(@path)
- _, page_file = CachedHttpFile.get remote_file_url_for(@status) if @status != 200
- global_file_fetch_tread.join
-
- global = TextileParts.parse @global_file, @image_path, @file_path_postfix
- page = TextileParts.parse page_file, @image_path, @file_path_postfix
-
- render_layout :global => global, :page => page, :env => {:path => @path, :query_string => @query_string}
+ def page
+ @status, page_file = CachedHttpFile.get page_content_url_for(@request_path)
+ _, page_file = CachedHttpFile.get page_content_url_for(@status) if @status != 200
+ TextileParts.parse page_file, image_path, file_path_postfix
end
- def render_layout(params)
- layout = params[:page][:layout] || params[:global][:layout] || 'layout'
-
- puts "rendering layout '#{layout}'"
- haml = Haml::Engine.new File.open("#{layout}.haml").read, :format => :html5
- haml.render Object.new, params
+ def status
+ @status or page && @status
+ end
+
+ def image_url_for(img_name)
+ file_url_for File.join(@image_path, img_name)
+ end
+ def file_url_for(filename)
+ File.join file_path, "#{filename}#{file_path_postfix}"
+ end
+ def page_content_url_for(filename)
+ file_url_for "#{filename.gsub(%r{^/$},'/index')}.#{file_extension}"
+ end
+
+ def layout
+ page[:layout] || global[:layout] || 'layout'
+ end
+ def body
+ Haml::Engine.new(File.open("#{layout}.haml").read, :format => :html5).render view
end
def caching_header
@@ -107,14 +127,15 @@ def caching_header
CachedHttpFile.bust and return { 'Cache-Control' => 'no-cache' }
end
+ def view
+ @view ||= View.new self
+ end
+
def call(env)
- @path, @query_string = env['PATH_INFO'], env['QUERY_STRING']
- @path = '/index' if @path == '/'
-
- return [301, {'Location' => @path}, ''] if @path.gsub!(/\/$/,'')
+ @request_path, @query_string = env['PATH_INFO'], env['QUERY_STRING']
- body = render # render the body first to set @status
- [@status, caching_header, body]
+ return [301, {'Location' => @request_path}, ''] if @request_path.gsub!(/(.)\/$/,'\\1')
+ [status, caching_header, body]
rescue => e
puts "#{e.class}: #{e.message} #{e.backtrace}"
[500, {}, 'Sorry, but something went wrong']
View
203 spec/tiny_site_spec.rb
@@ -91,98 +91,80 @@
end
describe TinySite do
- describe "#render" do
+ describe "#body" do
before(:each) do
@app = TinySite.new :file_path => 'http://foo/bar'
- CachedHttpFile.stub! :get
- TextileParts.stub! :parse
- @app.stub! :render_layout
- @app.stub! :remote_file_url_for
- end
- it 'gets the __global file' do
- @app.should_receive(:remote_file_url_for).with('__global').and_return('__global')
- CachedHttpFile.should_receive(:get).with('__global')
- @app.render
- end
- it 'gets the actual page file' do
- @app.instance_variable_set '@path', 'some/path'
- @app.should_receive(:remote_file_url_for).with('some/path').and_return('some/path')
- CachedHttpFile.should_receive(:get).with('some/path')
- @app.render
- end
- describe "when the actual page file doesn't exist" do
- it 'gets the 404 file' do
- @app.instance_variable_set '@path', 'some/path'
- @app.should_receive(:remote_file_url_for).with('some/path').and_return('some/path')
- CachedHttpFile.should_receive(:get).with('some/path').and_return([404, 'foobar'])
-
- @app.should_receive(:remote_file_url_for).with(404).and_return('404')
- CachedHttpFile.should_receive(:get).with('404')
- @app.render
- end
+ @app.stub! :page => {:layout => 'some_page_layout'}
+ @layout_file = StringIO.new('foo')
+ File.stub! :open => @layout_file
end
- describe 'after getting the files' do
- before(:each) do
- @app.instance_variable_set '@path', 'some/path'
- @app.should_receive(:remote_file_url_for).with('some/path').and_return('some/path')
- @app.should_receive(:remote_file_url_for).with('__global').and_return('__global')
- CachedHttpFile.should_receive(:get).with('some/path').and_return([200, 'some page related stuff'])
- CachedHttpFile.should_receive(:get).with('__global').and_return([200, 'some global stuff'])
- end
- it 'parses the global file' do
- TextileParts.should_receive(:parse).with('some global stuff', 'http://foo/bar/images', '')
- @app.render
+ it "reads the layout" do
+ File.should_receive(:open).with('some_page_layout.haml').and_return(StringIO.new('foo'))
+ @app.body
+ end
+ it "renders the layout" do
+ haml_stub = stub(:haml_stub, :render => 'foo')
+ Haml::Engine.should_receive(:new).with('foo', :format => :html5).and_return(haml_stub)
+ @app.body
+ end
+ it "uses the view" do
+ the_view = stub(:the_view)
+ @app.stub! :view => the_view
+ haml_stub = stub(:haml_stub)
+ Haml::Engine.stub!(:new => haml_stub)
+ haml_stub.should_receive(:render).with(the_view)
+ @app.body
+ end
+ end
+ describe "#layout" do
+ before(:each) do
+ @app = TinySite.new :file_path => 'http://foo/bar'
+ @app.instance_variable_set '@request_path', '/foo'
+ end
+ describe "with layout defined in page" do
+ it "returns the page-layout" do
+ @app.should_receive(:page).and_return({:layout => 'page_layout'})
+ @app.layout.should == 'page_layout'
end
- it 'parses the actual page file' do
- TextileParts.should_receive(:parse).with('some page related stuff', 'http://foo/bar/images', '')
- @app.render
+ end
+ describe "with layout defined in global" do
+ it "returns the global-layout" do
+ @app.stub! :page => {}
+ @app.should_receive(:global).and_return({:layout => 'global_layout'})
+ @app.layout.should == 'global_layout'
end
- describe "after parsing" do
- before(:each) do
- TextileParts.should_receive(:parse).with('some global stuff', 'http://foo/bar/images', '').and_return(:global)
- TextileParts.should_receive(:parse).with('some page related stuff', 'http://foo/bar/images', '').and_return(:page)
- end
- it 'renders the layout' do
- @app.should_receive(:render_layout).with({:global=>:global, :page =>:page, :env=>{:path=>"some/path", :query_string=>nil}})
- @app.render
- end
+ end
+ describe "with layout not defined" do
+ it "returns the default layout" do
+ @app.stub! :page => {}, :global => {}
+ @app.layout.should == 'layout'
end
end
end
- describe "#remote_file_url_for" do
+ describe "#image_url_for" do
before(:each) do
- @app = TinySite.new :file_path => 'http://foo/bar', :file_extension => 'ext', :file_path_postfix => '?download'
+ @app = TinySite.new :file_path => 'http://foo/bar', :image_path => 'imgs'
end
- it "concatenates (more or less) the different path components" do
- @app.remote_file_url_for('my_file').should == 'http://foo/bar/my_file.ext?download'
+ it "prepends the image_path" do
+ @app.should_receive(:file_url_for).with('imgs/beautyful.png').and_return('http://foo/imgs/beautyful.png')
+ @app.image_url_for('beautyful.png').should == 'http://foo/imgs/beautyful.png'
end
end
- describe "#render_layout" do
+ describe "#file_url_for" do
before(:each) do
- @app = TinySite.new :file_path => 'http://foo/bar'
- @params = { :global=>{}, :page=>{} }
- File.stub! :open => StringIO.new('foo')
+ @app = TinySite.new :file_path => 'http://foo/bar', :file_path_postfix => '?download'
end
- describe "determining the layout file" do
- it "uses the page var :layout if given" do
- File.should_receive(:open).with('some_layout.haml').and_return(StringIO.new('foo'))
- @app.render_layout @params.update(:page => {:layout => 'some_layout'})
- end
- it "uses the global var :layout if given no page var :layout is given" do
- File.should_receive(:open).with('some_global_layout.haml').and_return(StringIO.new('foo'))
- @app.render_layout @params.update(:global => {:layout => 'some_global_layout'})
- end
- it "uses the default if no layout given explicitly" do
- File.should_receive(:open).with('layout.haml').and_return(StringIO.new('foo'))
- @app.render_layout @params
- end
+ it "concatenates (more or less) the different path components" do
+ @app.file_url_for('my_file').should == 'http://foo/bar/my_file?download'
+ end
+ end
+ describe "#page_content_url_for" do
+ before(:each) do
+ @app = TinySite.new :file_path => 'http://foo/bar', :file_extension => 'ext', :file_path_postfix => '?download'
end
- it "uses haml to render" do
- File.stub! :open => StringIO.new('foo')
- haml_engine = Haml::Engine.new 'foo'
- haml_engine.should_receive(:render)
- Haml::Engine.should_receive(:new).with('foo', :format => :html5).and_return(haml_engine)
- @app.render_layout @params
+ it "calls #file_url_for with filename and extension" do
+ @app.should_receive(:file_url_for).with('some_content_url.ext')
+ @app.page_content_url_for('some_content_url')
end
end
describe "#caching_header" do
@@ -210,14 +192,10 @@
describe "#call" do
before(:each) do
@app = TinySite.new :file_path => 'http://foo/bar'
- @app.stub :render => 'foo'
+ @app.stub :body => 'foo'
@app.stub :caching_header => {'cache' => 'no cache'}
@app.instance_variable_set '@status', 123
end
- it "adds 'index' to the root path" do
- @app.call('PATH_INFO' => '/')
- @app.instance_variable_get('@path').should == '/index'
- end
it "sanitizes paths ending in '/' by a redirect" do
@app.call('PATH_INFO' => '/foo/bar/').should == [301, {'Location' => '/foo/bar'}, '']
end
@@ -227,8 +205,67 @@
it "catches errors nicely" do
error = StandardError.new 'an_error'
error.stub!(:backtrace => ['one'])
- @app.should_receive(:render).and_raise(error)
+ @app.should_receive(:status).and_raise(error)
@app.call('PATH_INFO' => '/foo').should == [500, {}, 'Sorry, but something went wrong']
end
+ it "sets request_path" do
+ @app.call('PATH_INFO' => '/foo')
+ @app.request_path.should == '/foo'
+ end
+ it "sets query_string" do
+ @app.call('PATH_INFO' => '/foo', 'QUERY_STRING' => 'brabbel')
+ @app.query_string.should == 'brabbel'
+ end
+ end
+ describe "#status" do
+ before(:each) do
+ @app = TinySite.new :file_path => 'http://foo/bar'
+ end
+ describe "when status is not set alread" do
+ it "calls #page to get it" do
+ @app.should_receive(:page)
+ @app.status
+ end
+ end
+ describe "when status is already set" do
+ it "just returns it" do
+ @app.instance_variable_set('@status', 123)
+ @app.should_receive(:page).never
+ @app.status.should == 123
+ end
+ end
+ end
+end
+
+describe TinySite::View do
+ before(:each) do
+ @app = stub(:app)
+ @view = TinySite::View.new @app
+ end
+ describe "delegated methods" do
+ it "delegates #request_path to the app" do
+ @app.should_receive :request_path
+ @view.request_path
+ end
+ it "delegates #query_string to the app" do
+ @app.should_receive :query_string
+ @view.query_string
+ end
+ it "delegates #global to the app" do
+ @app.should_receive :global
+ @view.global
+ end
+ it "delegates #page to the app" do
+ @app.should_receive :page
+ @view.page
+ end
+ it "delegates #file_url_for to the app" do
+ @app.should_receive :file_url_for
+ @view.file_url_for
+ end
+ it "delegates #image_url_for to the app" do
+ @app.should_receive :image_url_for
+ @view.image_url_for
+ end
end
end

0 comments on commit 6fe2da5

Please sign in to comment.
Something went wrong with that request. Please try again.