serve 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +18 -18
- data/Gemfile.lock +64 -61
- data/README.rdoc +14 -2
- data/VERSION +1 -1
- data/lib/serve.rb +2 -0
- data/lib/serve/application.rb +1 -1
- data/lib/serve/bootstrap/Gemfile +4 -1
- data/lib/serve/bootstrap/README.md +2 -7
- data/lib/serve/bootstrap/config.ru +6 -2
- data/lib/serve/handlers/coffee_handler.rb +18 -0
- data/lib/serve/handlers/dynamic_handler.rb +11 -65
- data/lib/serve/handlers/file_type_handler.rb +34 -14
- data/lib/serve/handlers/less_handler.rb +1 -1
- data/lib/serve/handlers/redirect_handler.rb +9 -5
- data/lib/serve/handlers/sass_handler.rb +6 -8
- data/lib/serve/pipeline.rb +119 -0
- data/lib/serve/rack.rb +3 -8
- data/lib/serve/router.rb +12 -8
- data/lib/serve/templates/default/views/view_helpers.rb +2 -2
- data/lib/serve/view_helpers.rb +14 -12
- data/spec/fixtures/directory/_layout.erb +3 -0
- data/spec/fixtures/directory/coffee.coffee +0 -0
- data/spec/fixtures/directory/markdown.html.markdown +0 -0
- data/spec/fixtures/directory/markdown.markdown +0 -0
- data/spec/fixtures/directory/markdown_erb.markdown.erb +0 -0
- data/spec/fixtures/directory/subdirectory/test.erb +1 -0
- data/spec/fixtures/file.erb +1 -0
- data/spec/pipeline_spec.rb +67 -0
- data/spec/router_spec.rb +4 -2
- data/spec/views/view_helpers.rb +7 -7
- metadata +259 -152
@@ -9,29 +9,49 @@ module Serve #:nodoc:
|
|
9
9
|
FileTypeHandler.handlers[ext] = self
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
13
|
-
def self.
|
14
|
-
|
15
|
-
handlers[ext.sub(/\A\./, '')]
|
16
|
-
end
|
12
|
+
|
13
|
+
def self.extensions
|
14
|
+
handlers.keys
|
17
15
|
end
|
18
|
-
|
19
|
-
def
|
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
|
-
@
|
37
|
+
@template_path = template_path
|
38
|
+
@extension = extension
|
22
39
|
end
|
23
40
|
|
24
|
-
def process(
|
25
|
-
|
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(
|
34
|
-
|
53
|
+
def parse(input, context)
|
54
|
+
input.dup
|
35
55
|
end
|
36
56
|
end
|
37
|
-
end
|
57
|
+
end
|
@@ -2,13 +2,17 @@ module Serve #:nodoc:
|
|
2
2
|
class RedirectHandler < FileTypeHandler #:nodoc:
|
3
3
|
extension 'redirect'
|
4
4
|
|
5
|
-
def process(
|
6
|
-
lines =
|
5
|
+
def process(input, context)
|
6
|
+
lines = input.strip.split("\n")
|
7
7
|
url = lines.last.strip
|
8
|
-
unless url =~ %r{^\w[\w
|
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
|
-
:
|
16
|
-
:syntax => syntax(@script_filename)
|
15
|
+
:syntax => syntax
|
17
16
|
)
|
18
17
|
engine.render
|
19
18
|
end
|
20
19
|
|
21
|
-
def syntax
|
22
|
-
|
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
|
data/lib/serve/rack.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|
data/lib/serve/router.rb
CHANGED
@@ -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
|
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
|
-
|
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 =
|
30
|
-
result.sub(/^#{root}
|
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
|
data/lib/serve/view_helpers.rb
CHANGED
@@ -35,8 +35,10 @@ module Serve #:nodoc:
|
|
35
35
|
end
|
36
36
|
|
37
37
|
module ContentHelpers
|
38
|
-
def content_for(symbol, &block)
|
39
|
-
|
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 =
|
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(
|
144
|
-
if File.file?(filename)
|
145
|
-
parser.
|
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(
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
File.join(path,
|
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)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
Test
|
@@ -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
|