serve 1.5.1 → 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,29 +9,49 @@ module Serve #:nodoc:
9
9
  FileTypeHandler.handlers[ext] = self
10
10
  end
11
11
  end
12
-
13
- def self.find(path)
14
- if ext = File.extname(path)
15
- handlers[ext.sub(/\A\./, '')]
16
- end
12
+
13
+ def self.extensions
14
+ handlers.keys
17
15
  end
18
-
19
- def initialize(root_path, path)
16
+
17
+ def self.handlers_for(path)
18
+ extensions = File.basename(path).split(".")[1..-1]
19
+ extensions.collect{|e| [handlers[e], e] if handlers[e]}.compact
20
+ end
21
+
22
+ def self.configure(extension, options)
23
+ extension_options[extension.to_sym].merge!(options)
24
+ end
25
+
26
+ def self.extension_options
27
+ @extension_options ||= Hash.new{|h,k| h[k] = {}}
28
+ end
29
+
30
+ def self.options_for(extension)
31
+ extension_options[extension.to_sym]
32
+ end
33
+
34
+ attr_reader :extension
35
+ def initialize(root_path, template_path, extension)
20
36
  @root_path = root_path
21
- @script_filename = File.join(@root_path, path)
37
+ @template_path = template_path
38
+ @extension = extension
22
39
  end
23
40
 
24
- def process(request, response)
25
- response.headers['content-type'] = content_type
26
- response.body = parse(open(@script_filename){|io| io.read })
41
+ def process(input, context)
42
+ parse(input, context)
27
43
  end
28
44
 
29
45
  def content_type
30
46
  'text/html'
31
47
  end
48
+
49
+ def layout?
50
+ true
51
+ end
32
52
 
33
- def parse(string)
34
- string.dup
53
+ def parse(input, context)
54
+ input.dup
35
55
  end
36
56
  end
37
- end
57
+ end
@@ -7,7 +7,7 @@ module Serve #:nodoc:
7
7
  class LessHandler < FileTypeHandler #:nodoc:
8
8
  extension 'less'
9
9
 
10
- def parse(string)
10
+ def parse(string, context)
11
11
  require 'less'
12
12
  Less.parse(string)
13
13
  end
@@ -2,13 +2,17 @@ module Serve #:nodoc:
2
2
  class RedirectHandler < FileTypeHandler #:nodoc:
3
3
  extension 'redirect'
4
4
 
5
- def process(request, response)
6
- lines = super.strip.split("\n")
5
+ def process(input, context)
6
+ lines = input.strip.split("\n")
7
7
  url = lines.last.strip
8
- unless url =~ %r{^\w[\w\d+.-]*:.*}
9
- url = request.protocol + request.host_with_port + url
8
+ unless url =~ %r{^\w[\w+.-]*:.*}
9
+ url = context.request.protocol + context.request.host_with_port + url
10
10
  end
11
- response.redirect(url, '302')
11
+ context.response.redirect(url, '302')
12
+ end
13
+
14
+ def layout?
15
+ false
12
16
  end
13
17
  end
14
18
  end
@@ -7,20 +7,18 @@ module Serve #:nodoc:
7
7
  class SassHandler < FileTypeHandler #:nodoc:
8
8
  extension 'sass', 'scss'
9
9
 
10
- def parse(string)
10
+ def parse(string, context)
11
11
  require 'sass'
12
12
  engine = Sass::Engine.new(string,
13
- :load_paths => [@root_path],
13
+ :load_paths => [@root_path] + Sass::Engine::DEFAULT_OPTIONS[:load_paths],
14
14
  :style => :expanded,
15
- :filename => @script_filename,
16
- :syntax => syntax(@script_filename)
15
+ :syntax => syntax
17
16
  )
18
17
  engine.render
19
18
  end
20
19
 
21
- def syntax(filename)
22
- ext = File.extname(@script_filename)
23
- if ext == '.scss'
20
+ def syntax
21
+ if extension == 'scss'
24
22
  :scss
25
23
  else
26
24
  :sass
@@ -31,4 +29,4 @@ module Serve #:nodoc:
31
29
  'text/css'
32
30
  end
33
31
  end
34
- end
32
+ end
@@ -0,0 +1,119 @@
1
+ require 'serve/view_helpers'
2
+
3
+ module Serve
4
+ class Pipeline
5
+ def self.handles?(path)
6
+ !FileTypeHandler.handlers_for(path).empty?
7
+ end
8
+
9
+ def self.build(root, path)
10
+ return nil unless handles?(path)
11
+ Pipeline.new(root, path, extensions_for(path))
12
+ end
13
+
14
+ attr_reader :template, :layout
15
+ def initialize(root_path, path)
16
+ @root_path = root_path
17
+ @template = Template.new(File.join(@root_path, path))
18
+ @layout = find_layout_for(@template.path)
19
+ end
20
+
21
+ def find_layout_for(template_path)
22
+ return Template::Passthrough.new(@template) unless @template.layout?
23
+ root = @root_path
24
+ layout = nil
25
+ search = File.split(template_path[root.size..-1])
26
+ until(layout || search.empty?)
27
+ possible_layouts = FileTypeHandler.extensions.map do |ext|
28
+ l = "_layout.#{ext}"
29
+ possible_layout = File.join(File.join(root, *search), l)
30
+ File.file?(possible_layout) ? possible_layout : false
31
+ end
32
+ layout = possible_layouts.detect { |o| o }
33
+ search.pop
34
+ end
35
+ if layout
36
+ Template.new(layout)
37
+ else
38
+ Template::Passthrough.new(@template)
39
+ end
40
+ end
41
+
42
+ def process(request, response)
43
+ response.headers['Content-Type'] = @layout.content_type
44
+ context = Context.new(@root_path, request, response)
45
+ @template.process(context)
46
+ @layout.process(context)
47
+ response.body = context.content
48
+ end
49
+
50
+ class Template
51
+ attr_reader :file, :path, :handlers
52
+ def initialize(file)
53
+ @file = File.basename(file)
54
+ @path = File.dirname(file)
55
+ @raw = File.read(file)
56
+ @handlers = FileTypeHandler.handlers_for(file).collect{|h, extension| h.new(@root_path, @path, extension)}
57
+ end
58
+
59
+ def content_type
60
+ @handlers.first.content_type
61
+ end
62
+
63
+ def process(context)
64
+ context.content = @handlers.reverse.inject(@raw.dup) do |body, handler|
65
+ handler.process(body, context)
66
+ end
67
+ end
68
+
69
+ def layout?
70
+ @handlers.first.layout?
71
+ end
72
+
73
+ def passthrough?
74
+ false
75
+ end
76
+
77
+ class Passthrough
78
+ def initialize(template)
79
+ @template = template
80
+ end
81
+
82
+ def process(context)
83
+ end
84
+
85
+ def layout?
86
+ false
87
+ end
88
+
89
+ def content_type
90
+ @template.content_type
91
+ end
92
+
93
+ def passthrough?
94
+ true
95
+ end
96
+ end
97
+ end
98
+
99
+ class Context #:nodoc:
100
+ attr_accessor :content, :parser
101
+ attr_reader :request, :response
102
+
103
+ def initialize(root_path, request, response)
104
+ @root_path, @request, @response = root_path, request, response
105
+ @content = ''
106
+ install_view_helpers
107
+ end
108
+
109
+ def install_view_helpers
110
+ view_helpers_file_path = @root_path + '/view_helpers.rb'
111
+ if File.file?(view_helpers_file_path)
112
+ singleton_class.module_eval(File.read(view_helpers_file_path) + "\ninclude ViewHelpers", view_helpers_file_path)
113
+ end
114
+ end
115
+
116
+ include Serve::ViewHelpers
117
+ end
118
+ end
119
+ end
@@ -61,8 +61,6 @@ module Serve
61
61
  # A specialized hash for the environment variables on a request.
62
62
  # Borrowed from ActionDispatch in Rails.
63
63
  class Headers < Hash
64
- extend ActiveSupport::Memoizable
65
-
66
64
  def initialize(*args)
67
65
  if args.size == 1 && args[0].is_a?(Hash)
68
66
  super()
@@ -83,9 +81,8 @@ module Serve
83
81
  private
84
82
  # Converts a HTTP header name to an environment variable name.
85
83
  def env_name(header_name)
86
- "HTTP_#{header_name.upcase.gsub(/-/, '_')}"
84
+ @env_name ||= "HTTP_#{header_name.upcase.gsub(/-/, '_')}"
87
85
  end
88
- memoize :env_name
89
86
  end
90
87
 
91
88
  class RackAdapter
@@ -111,11 +108,9 @@ module Serve
111
108
  path = Serve::Router.resolve(@root, request.path_info)
112
109
  if path
113
110
  # Fetch the file handler for a file with a given extension/
114
- ext = File.extname(path)[1..-1]
115
- handler = Serve::FileTypeHandler.handlers[ext]
116
- if handler
111
+ if Serve::Pipeline.handles?(path)
117
112
  # Handler exists? Process the request and response.
118
- handler.new(@root, path).process(request, response)
113
+ Serve::Pipeline.new(@root, path).process(request, response)
119
114
  response
120
115
  else
121
116
  # Handler doesn't exist? Rewrite the request to use the new path.
@@ -11,23 +11,23 @@ module Serve
11
11
  full_path = File.join(root, path)
12
12
 
13
13
  case
14
- when File.file?(full_path)
14
+ when File.file?(best_match(full_path))
15
15
  # A file exists! Return the matching path.
16
16
  path
17
- when File.directory?(full_path)
17
+ when File.directory?(best_match(full_path))
18
18
  # It's a directory? Try a directory index.
19
19
  resolve(root, File.join(path, 'index'))
20
- when path.ends_with?('.css')
20
+ when path =~ /\.css\Z/i
21
21
  # CSS not found? Try SCSS or Sass.
22
22
  alternates = %w{.scss .sass}.map { |ext| path.sub(/\.css\Z/, ext) }
23
- sass_path = alternates.find do |p|
24
- File.file?(File.join(root, p))
25
- end
23
+ alternates.find do |p|
24
+ File.file?(best_match(File.join(root, p)))
25
+ end
26
26
  else
27
27
  # Still no luck? Check to see if a file with an extension exists by that name.
28
28
  # TODO: Return a path with an extension based on priority, not just the first found.
29
- result = Dir.glob(full_path + ".*", File::FNM_CASEFOLD).first
30
- result.sub(/^#{root}/, '').sub(/^\//, '') if result && File.file?(result)
29
+ result = best_match(full_path + ".*")
30
+ result.sub(/^#{root}/i, '').sub(/^\//, '') if result && File.file?(result)
31
31
  end
32
32
  end
33
33
 
@@ -38,6 +38,10 @@ module Serve
38
38
  path = path.sub(%r{/\Z}, '') # remove trailing slash
39
39
  path unless path =~ /\.\./ # guard against evil paths
40
40
  end
41
+
42
+ def self.best_match(path)
43
+ (Dir.glob(path, File::FNM_CASEFOLD).first || path)
44
+ end
41
45
 
42
46
  end
43
47
  end
@@ -9,9 +9,9 @@ module ViewHelpers
9
9
  def copyright_years(start_year)
10
10
  end_year = Date.today.year
11
11
  if start_year == end_year
12
- "\#{start_year}"
12
+ "#{start_year}"
13
13
  else
14
- "\#{start_year}&#8211;\#{end_year}"
14
+ "#{start_year}&#8211;#{end_year}"
15
15
  end
16
16
  end
17
17
 
@@ -35,8 +35,10 @@ module Serve #:nodoc:
35
35
  end
36
36
 
37
37
  module ContentHelpers
38
- def content_for(symbol, &block)
39
- set_content_for(symbol, capture(&block))
38
+ def content_for(symbol, content = nil, &block)
39
+ content = capture(&block) if block_given?
40
+ set_content_for(symbol, content) if content
41
+ get_content_for(symbol) unless content
40
42
  end
41
43
 
42
44
  def content_for?(symbol)
@@ -135,14 +137,14 @@ module Serve #:nodoc:
135
137
  end
136
138
 
137
139
  def render_template(template, options={})
138
- path = File.dirname(parser.script_filename)
140
+ path = parser.template_path
139
141
  if template =~ %r{^/}
140
142
  template = template[1..-1]
141
143
  path = @root_path
142
144
  end
143
- filename = template_filename(File.join(path, template), :partial => options[:partial])
144
- if File.file?(filename)
145
- parser.parse_file(filename, options[:locals])
145
+ filename = template_filename(path, template, :partial => options[:partial])
146
+ if filename && File.file?(filename)
147
+ parser.parse(File.read(filename), File.extname(filename).split(".").last, options[:locals])
146
148
  else
147
149
  raise "File does not exist #{filename.inspect}"
148
150
  end
@@ -150,12 +152,12 @@ module Serve #:nodoc:
150
152
 
151
153
  private
152
154
 
153
- def template_filename(name, options)
154
- path = File.dirname(name)
155
- template = File.basename(name)
156
- template = "_" + template if options[:partial]
157
- template += extname(parser.script_filename) unless name =~ /\.[a-z]+$/
158
- File.join(path, template)
155
+ def template_filename(path, template, options)
156
+ template_path = File.dirname(template)
157
+ template_file = File.basename(template)
158
+ template_file = "_" + template_file if options[:partial]
159
+ route = Serve::Router.resolve(path, File.join(template_path, template_file))
160
+ (route && File.join(path, route))
159
161
  end
160
162
 
161
163
  def extname(filename)
@@ -0,0 +1,3 @@
1
+ <h1>Awesomesacue</h1>
2
+
3
+ <%= yield %>
File without changes
@@ -0,0 +1 @@
1
+ Test
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require 'serve/pipeline'
3
+
4
+ describe Serve::Pipeline do
5
+ before :each do
6
+ @root = File.expand_path("../fixtures", __FILE__)
7
+ end
8
+
9
+ describe "self.handles?" do
10
+ it "should not handle .html" do
11
+ Serve::Pipeline.handles?("dir/file.html").should be_false
12
+ end
13
+
14
+ it "should handle .markdown" do
15
+ Serve::Pipeline.handles?("dir/file.markdown").should be_true
16
+ end
17
+
18
+ it "should handle .html.markdown" do
19
+ Serve::Pipeline.handles?("dir/file.html.markdown").should be_true
20
+ end
21
+
22
+ it "should handle .coffee" do
23
+ Serve::Pipeline.handles?("dir/file.coffee").should be_true
24
+ end
25
+
26
+ it "should handle .markdown.erb" do
27
+ Serve::Pipeline.handles?("dir/file.markdown.erb").should be_true
28
+ end
29
+ end
30
+
31
+ describe "initialize" do
32
+ it "should build a pipeline for .markdown" do
33
+ pipeline = Serve::Pipeline.new(@root, "directory/markdown.markdown")
34
+ pipeline.template.handlers.collect{|h| h.extension}.should == %w(markdown)
35
+ end
36
+
37
+ it "should build a pipeline for .html.markdown" do
38
+ pipeline = Serve::Pipeline.new(@root, "directory/markdown.html.markdown")
39
+ pipeline.template.handlers.collect{|h| h.extension}.should == %w(markdown)
40
+ end
41
+
42
+ it "should build a pipeline for .coffee" do
43
+ pipeline = Serve::Pipeline.new(@root, "directory/coffee.coffee")
44
+ pipeline.template.handlers.collect{|h| h.extension}.should == %w(coffee)
45
+ end
46
+
47
+ it "should build a pipeline for .markdown.erb" do
48
+ pipeline = Serve::Pipeline.new(@root, "directory/markdown_erb.markdown.erb")
49
+ pipeline.template.handlers.collect{|h| h.extension}.should == %w(markdown erb)
50
+ end
51
+ end
52
+
53
+ describe "layout search" do
54
+ it "should return no layout for a file at the root" do
55
+ pipeline = Serve::Pipeline.new(@root, "file.erb")
56
+ pipeline.layout.should be_passthrough
57
+ end
58
+
59
+ it "should search up the directory tree for layouts" do
60
+ root = File.join(@root, "directory")
61
+ pipeline = Serve::Pipeline.new(root, "subdirectory/test.erb")
62
+ pipeline.layout.should_not be_passthrough
63
+ pipeline.layout.path.should == root
64
+ pipeline.layout.file.should == "_layout.erb"
65
+ end
66
+ end
67
+ end