serve 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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