Skip to content

Commit

Permalink
Add handler pipelining.
Browse files Browse the repository at this point in the history
Pipelining allows an arbitrary number of renderers to get a shot
at a template before it is returned, so template.html.markdown.erb
will be passed through ERb->Markdown and then finally wrapped in a
layout. This also includes an initial refactoring of template
handling to DRY up some of the functionality, in particular the
handling of layouts.
  • Loading branch information
ntalbott committed Apr 6, 2012
1 parent f0dcb3a commit 322251e
Show file tree
Hide file tree
Showing 20 changed files with 274 additions and 111 deletions.
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ group :development do
gem 'compass', '~> 0.11.1'
gem 'slim', '~> 0.9.4'
gem 'rdiscount', '~> 1.6.8'
gem 'RedCloth', '~> 4.2.7'
gem 'RedCloth', '~> 4.2.9'
gem 'erubis', '~> 2.7.0'
gem 'less', '~> 1.2.21'
gem 'radius', '~> 0.6.1'
gem 'radius', '~> 0.7.3'
gem 'coffee-script', '~> 2.2.0'
end

Expand Down
8 changes: 4 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GIT
GEM
remote: http://rubygems.org/
specs:
RedCloth (4.2.7)
RedCloth (4.2.9)
activesupport (3.0.9)
chunky_png (1.2.1)
coffee-script (2.2.0)
Expand Down Expand Up @@ -48,7 +48,7 @@ GEM
rack (1.3.2)
rack-test (0.6.1)
rack (>= 1.0)
radius (0.6.1)
radius (0.7.3)
rake (0.9.2)
rdiscount (1.6.8)
rdoc (3.8)
Expand Down Expand Up @@ -76,7 +76,7 @@ PLATFORMS
ruby

DEPENDENCIES
RedCloth (~> 4.2.7)
RedCloth (~> 4.2.9)
activesupport (~> 3.0)
coffee-script (~> 2.2.0)
compass (~> 0.11.1)
Expand All @@ -89,7 +89,7 @@ DEPENDENCIES
maruku (~> 0.6.0)
rack (~> 1.2)
rack-test (~> 0.5)
radius (~> 0.6.1)
radius (~> 0.7.3)
rake (~> 0.9.0)
rdiscount (~> 1.6.8)
rdoc (~> 3.8.0)
Expand Down
1 change: 1 addition & 0 deletions lib/serve.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def singleton_class

require 'serve/version'
require 'serve/router'
require 'serve/pipeline'
require 'serve/handlers/file_type_handler'
require 'serve/handlers/dynamic_handler'
require 'serve/handlers/sass_handler'
Expand Down
8 changes: 6 additions & 2 deletions lib/serve/handlers/coffee_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ module Serve #:nodoc:
class CoffeeHandler < FileTypeHandler #:nodoc:
extension 'coffee'

def parse(string)
engine = Tilt::CoffeeScriptTemplate.new { string }
def parse(input, context)
engine = Tilt::CoffeeScriptTemplate.new { input }
engine.render
end

def content_type
'text/javascript'
end

def layout?
false
end
end
end
76 changes: 11 additions & 65 deletions lib/serve/handlers/dynamic_handler.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
require 'serve/view_helpers'
require 'tilt'

module Serve #:nodoc:
Expand All @@ -13,64 +12,25 @@ def extensions
self.class.extensions
end

extension *extensions
extension(*extensions)

def process(request, response)
response.headers['content-type'] = content_type
response.body = parse(request, response)
end

def parse(request, response)
context = Context.new(@root_path, request, response)
install_view_helpers(context)
parser = Parser.new(context)
context.content << parser.parse_file(@script_filename)
layout = find_layout_for(@script_filename)
if layout
parser.parse_file(layout)
else
context.content
end
end

def find_layout_for(filename)
root = @root_path
path = filename[root.size..-1]
layout = nil
until layout or path == "/"
path = File.dirname(path)
possible_layouts = extensions.map do |ext|
l = "_layout.#{ext}"
possible_layout = File.join(root, path, l)
File.file?(possible_layout) ? possible_layout : false
end
layout = possible_layouts.detect { |o| o }
end
layout
end

def install_view_helpers(context)
view_helpers_file_path = @root_path + '/view_helpers.rb'
if File.file?(view_helpers_file_path)
context.singleton_class.module_eval(File.read(view_helpers_file_path) + "\ninclude ViewHelpers", view_helpers_file_path)
end
def parse(input, context)
parser = Parser.new(context, @template_path)
parser.parse(input, extension)
end

class Parser #:nodoc:
attr_accessor :context, :script_filename, :script_extension, :engine
attr_accessor :context, :script_extension, :engine, :template_path

def initialize(context)
def initialize(context, template_path)
@context = context
@context.parser = self
@template_path = template_path
end

def parse_file(filename, locals={})
old_script_filename, old_script_extension, old_engine = @script_filename, @script_extension, @engine

@script_filename = filename

ext = File.extname(filename).sub(/^\.html\.|^\./, '').downcase

def parse(input, ext, locals={})
old_script_extension, old_engine = @script_extension, @engine

if ext == 'slim' # Ugly, but works
if Thread.list.size > 1
warn "WARN: serve autoloading 'slim' in a non thread-safe way; " +
Expand All @@ -81,31 +41,17 @@ def parse_file(filename, locals={})

@script_extension = ext

@engine = Tilt[ext].new(filename, nil, :outvar => '@_out_buf')
@engine = Tilt[ext].new(nil, nil, :outvar => '@_out_buf'){input}

raise "#{ext} extension not supported" if @engine.nil?

@engine.render(context, locals) do |*args|
context.get_content_for(*args)
end
ensure
@script_filename = old_script_filename
@script_extension = old_script_extension
@engine = old_engine
end

end

class Context #:nodoc:
attr_accessor :content, :parser
attr_reader :request, :response

def initialize(root_path, request, response)
@root_path, @request, @response = root_path, request, response
@content = ''
end

include Serve::ViewHelpers
end
end
end
34 changes: 21 additions & 13 deletions lib/serve/handlers/file_type_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,37 @@ def self.extension(*extensions)
FileTypeHandler.handlers[ext] = self
end
end

def self.find(path)
if ext = File.extname(path)
handlers[ext.sub(/\A\./, '')]
end

def self.extensions
handlers.keys
end

def initialize(root_path, path)

def self.handlers_for(path)
extensions = File.basename(path).split(".")[1..-1]
extensions.collect{|e| [handlers[e], e] if handlers[e]}.compact
end

attr_reader :extension
def initialize(root_path, template_path, extension)
@root_path = root_path
@script_filename = File.join(@root_path, path)
@template_path = template_path
@extension = extension
end

def process(request, response)
response.headers['content-type'] = content_type
response.body = parse(open(@script_filename){|io| io.read })
def process(input, context)
parse(input, context)
end

def content_type
'text/html'
end

def layout?
true
end

def parse(string)
string.dup
def parse(input, context)
input.dup
end
end
end
2 changes: 1 addition & 1 deletion lib/serve/handlers/less_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Serve #:nodoc:
class LessHandler < FileTypeHandler #:nodoc:
extension 'less'

def parse(string)
def parse(string, context)
require 'less'
Less.parse(string)
end
Expand Down
14 changes: 9 additions & 5 deletions lib/serve/handlers/redirect_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ module Serve #:nodoc:
class RedirectHandler < FileTypeHandler #:nodoc:
extension 'redirect'

def process(request, response)
lines = super.strip.split("\n")
def process(input, context)
lines = input.strip.split("\n")
url = lines.last.strip
unless url =~ %r{^\w[\w\d+.-]*:.*}
url = request.protocol + request.host_with_port + url
unless url =~ %r{^\w[\w+.-]*:.*}
url = context.request.protocol + context.request.host_with_port + url
end
response.redirect(url, '302')
context.response.redirect(url, '302')
end

def layout?
false
end
end
end
10 changes: 4 additions & 6 deletions lib/serve/handlers/sass_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@ module Serve #:nodoc:
class SassHandler < FileTypeHandler #:nodoc:
extension 'sass', 'scss'

def parse(string)
def parse(string, context)
require 'sass'
engine = Sass::Engine.new(string,
:load_paths => [@root_path],
:style => :expanded,
:filename => @script_filename,
:syntax => syntax(@script_filename)
:syntax => syntax
)
engine.render
end

def syntax(filename)
ext = File.extname(@script_filename)
if ext == '.scss'
def syntax
if extension == 'scss'
:scss
else
:sass
Expand Down
Loading

2 comments on commit 322251e

@duff
Copy link

@duff duff commented on 322251e Apr 7, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's this commit or not, but when I use the latest released version of the gem, it works in that it picks up a a layout that's in a parent directory.

When I use what's currently in master, my parent layout isn't picked up.

@ntalbott
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you open an issue for this? Much easier to track that way.

Please sign in to comment.