fivepointssolutions-serve 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ module Serve
2
+ mattr_accessor :file_resolver
3
+
4
+ class FileResolver
5
+ cattr_accessor :alternate_extensions
6
+ self.alternate_extensions = %w(html txt text haml erb rhtml html.erb html.haml textile markdown email redirect)
7
+
8
+ def resolve(root, path)
9
+ return nil if path.nil?
10
+ path = File.join(path) # path may be array
11
+ return nil if path =~ /\.\./
12
+ path = path.sub(/\/\A/, '')
13
+ if path =~ /\.css\Z/ && !File.file?(File.join(root, path))
14
+ sass_path = path.sub(/\.css\Z/, '.sass')
15
+ sass_path if File.file?(File.join(root, sass_path))
16
+ elsif File.directory?(File.join(root, path))
17
+ resolve(root, File.join(path, 'index'))
18
+ else
19
+ resolve_with_extension(root, path)
20
+ end
21
+ end
22
+
23
+ def resolve_with_extension(root, path)
24
+ full_path = File.join(root, path)
25
+ if File.file?(full_path)
26
+ path
27
+ else
28
+ if extension = find_extension(alternate_extensions, full_path)
29
+ "#{path}.#{extension}"
30
+ end
31
+ end
32
+ end
33
+
34
+ def find_extension(extensions_to_try, full_path)
35
+ extensions_to_try.find do |ext|
36
+ File.file?("#{full_path}.#{ext}")
37
+ end
38
+ end
39
+ end
40
+
41
+ self.file_resolver = FileResolver.new
42
+
43
+ def self.resolve_file(root, path)
44
+ file_resolver.resolve(root, path)
45
+ end
46
+ end
@@ -0,0 +1,210 @@
1
+ module Serve #:nodoc:
2
+ class DynamicHandler < FileTypeHandler #:nodoc:
3
+ extension 'erb', 'html.erb', 'rhtml', 'haml', 'html.haml'
4
+
5
+ def process(request, response)
6
+ response.headers['content-type'] = content_type
7
+ response.body = parse(request, response)
8
+ end
9
+
10
+ def parse(request, response)
11
+ context = Context.new(@root_path, request, response)
12
+ install_view_helpers(context)
13
+ parser = Parser.new(context)
14
+ context.content << parser.parse_file(@script_filename)
15
+ layout = find_layout_for(@script_filename)
16
+ if layout
17
+ parser.parse_file(layout)
18
+ else
19
+ context.content
20
+ end
21
+ end
22
+
23
+ def find_layout_for(filename)
24
+ root = @root_path
25
+ path = filename[root.size..-1]
26
+ layout = nil
27
+ until layout or path == "/"
28
+ path = File.dirname(path)
29
+ possible_layouts = ['_layout.haml', '_layout.html.haml', '_layout.erb', '_layout.html.erb'].map do |l|
30
+ possible_layout = File.join(root, path, l)
31
+ File.file?(possible_layout) ? possible_layout : false
32
+ end
33
+ layout = possible_layouts.detect { |o| o }
34
+ end
35
+ layout
36
+ end
37
+
38
+ def install_view_helpers(context)
39
+ view_helpers_file_path = @root_path + '/view_helpers.rb'
40
+ if File.file?(view_helpers_file_path)
41
+ context.metaclass.module_eval(File.read(view_helpers_file_path) + "\ninclude ViewHelpers")
42
+ end
43
+ end
44
+
45
+ module ERB #:nodoc:
46
+ class Engine #:nodoc:
47
+ def initialize(string, options = {})
48
+ @erb = ::ERB.new(string, nil, '-', '@erbout')
49
+ @erb.filename = options[:filename]
50
+ end
51
+
52
+ def render(context, &block)
53
+ # we have to keep track of the old erbout variable for nested renders
54
+ # because ERB#result will set it to an empty string before it renders
55
+ old_erbout = context.instance_variable_get('@erbout')
56
+ result = @erb.result(context.instance_eval { binding })
57
+ context.instance_variable_set('@erbout', old_erbout)
58
+ result
59
+ end
60
+ end
61
+ end
62
+
63
+ class Parser #:nodoc:
64
+ attr_accessor :context, :script_filename
65
+
66
+ def initialize(context)
67
+ @context = context
68
+ @context.parser = self
69
+ end
70
+
71
+ def parse_file(filename)
72
+ old_script_filename = @script_filename
73
+ @script_filename = filename
74
+ lines = IO.read(filename)
75
+ engine = case File.extname(filename).sub(/^./, '').downcase
76
+ when 'haml'
77
+ require 'haml'
78
+ require 'sass'
79
+ require 'sass/plugin'
80
+ Haml::Engine.new(lines, :attr_wrapper => '"', :filename => filename)
81
+ when 'erb'
82
+ require 'erb'
83
+ ERB::Engine.new(lines, :filename => filename)
84
+ else
85
+ raise 'extension not supported'
86
+ end
87
+ result = engine.render(context) do |*args|
88
+ context.get_content_for(*args)
89
+ end
90
+ @script_filename = old_script_filename
91
+ result
92
+ end
93
+ end
94
+
95
+ class Context #:nodoc:
96
+ attr_accessor :content, :parser
97
+ attr_reader :request, :response
98
+
99
+ def initialize(root_path, request, response)
100
+ @root_path, @request, @response = root_path, request, response
101
+ @content = ''
102
+ end
103
+
104
+ def _erbout
105
+ @erbout
106
+ end
107
+
108
+ # This is extracted from Rails
109
+ def capture_erb(&block)
110
+ buffer = _erbout
111
+ pos = buffer.length
112
+ block.call
113
+
114
+ # extract the block
115
+ data = buffer[pos..-1]
116
+
117
+ # replace it in the original with empty string
118
+ buffer[pos..-1] = ''
119
+
120
+ data
121
+ end
122
+
123
+ # Content_for methods
124
+
125
+ def content_for(symbol, &block)
126
+ if @haml_buffer
127
+ set_content_for(symbol, capture_haml(&block))
128
+ else
129
+ set_content_for(symbol, capture_erb(&block))
130
+ end
131
+ end
132
+
133
+ def content_for?(symbol)
134
+ !(get_content_for(symbol)).nil?
135
+ end
136
+
137
+ def get_content_for(symbol = :content)
138
+ if symbol.to_s.intern == :content
139
+ @content
140
+ else
141
+ instance_variable_get("@content_for_#{symbol}")
142
+ end
143
+ end
144
+
145
+ # View helper methods
146
+
147
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
148
+ def html_escape(s)
149
+ s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
150
+ end
151
+ alias h html_escape
152
+
153
+ def set_content_for(symbol, value)
154
+ instance_variable_set("@content_for_#{symbol}", value)
155
+ end
156
+
157
+ def params
158
+ request.params
159
+ end
160
+
161
+ # Render methods
162
+
163
+ def render(options)
164
+ partial = options.delete(:partial)
165
+ template = options.delete(:template)
166
+ case
167
+ when partial
168
+ render_partial(partial)
169
+ when template
170
+ render_template(template)
171
+ else
172
+ raise "render options not supported #{options.inspect}"
173
+ end
174
+ end
175
+
176
+ def render_partial(partial)
177
+ render_template(partial, :partial => true)
178
+ end
179
+
180
+ def render_template(template, options={})
181
+ path = File.dirname(parser.script_filename)
182
+ if template =~ %r{^/}
183
+ template = template[1..-1]
184
+ path = @root_path
185
+ end
186
+ filename = template_filename(File.join(path, template), :partial => options.delete(:partial))
187
+ if File.file?(filename)
188
+ parser.parse_file(filename)
189
+ else
190
+ raise "File does not exist #{filename.inspect}"
191
+ end
192
+ end
193
+
194
+ private
195
+
196
+ def template_filename(name, options)
197
+ path = File.dirname(name)
198
+ template = File.basename(name)
199
+ template = "_" + template if options.delete(:partial)
200
+ template += extname(parser.script_filename) unless name =~ /\.[a-z]{3,4}$/
201
+ File.join(path, template)
202
+ end
203
+
204
+ def extname(filename)
205
+ /(\.[a-z]{3,4}\.[a-z]{3,4})$/.match(filename)
206
+ $1 || File.extname(filename) || ''
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,23 @@
1
+ module Serve #:nodoc:
2
+ class EmailHandler < FileTypeHandler #:nodoc:
3
+ extension 'email'
4
+
5
+ def parse(string)
6
+ title = "E-mail"
7
+ title = $1 + " #{title}" if string =~ /^Subject:\s*(\S.*?)$/im
8
+ head, body = string.split("\n\n", 2)
9
+ output = []
10
+ output << "<html><head><title>#{title}</title></head>"
11
+ output << '<body style="font-family: Arial; line-height: 1.2em; font-size: 90%; margin: 0; padding: 0">'
12
+ output << '<div id="head" style="background-color: #E9F2FA; padding: 1em">'
13
+ head.each do |line|
14
+ key, value = line.split(":", 2).map { |a| a.strip }
15
+ output << "<div><strong>#{key}:</strong> #{value}</div>"
16
+ end
17
+ output << '</div><pre id="body" style="font-size: 110%; padding: 1em">'
18
+ output << body
19
+ output << '</pre></body></html>'
20
+ output.join("\n")
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Serve #:nodoc:
2
+ class FileTypeHandler #:nodoc:
3
+ def self.handlers
4
+ @handlers ||= {}
5
+ end
6
+
7
+ def self.extension(*extensions)
8
+ extensions.each do |ext|
9
+ FileTypeHandler.handlers[ext] = self
10
+ end
11
+ end
12
+
13
+ def self.find(path)
14
+ if ext = File.extname(path)
15
+ handlers[ext.sub(/\A\./, '')]
16
+ end
17
+ end
18
+
19
+ def initialize(root_path, path)
20
+ @root_path = root_path
21
+ @script_filename = File.join(@root_path, path)
22
+ end
23
+
24
+ def process(request, response)
25
+ response.headers['content-type'] = content_type
26
+ response.body = parse(open(@script_filename){|io| io.read })
27
+ end
28
+
29
+ def content_type
30
+ 'text/html'
31
+ end
32
+
33
+ def parse(string)
34
+ string.dup
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ module Serve #:nodoc:
2
+ class MarkdownHandler < FileTypeHandler #:nodoc:
3
+ extension 'markdown'
4
+
5
+ def parse(string)
6
+ require 'bluecloth'
7
+ "<html><body>#{ BlueCloth.new(string).to_html }</body></html>"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Serve #:nodoc:
2
+ class RedirectHandler < FileTypeHandler #:nodoc:
3
+ extension 'redirect'
4
+
5
+ def process(request, response)
6
+ url = super.strip
7
+ unless url =~ %r{^\w[\w\d+.-]*:.*}
8
+ url = request.protocol + request.host_with_port + url
9
+ end
10
+ response.redirect(url, '302')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module Serve #:nodoc:
2
+ class SassHandler < FileTypeHandler #:nodoc:
3
+ extension 'sass', 'css'
4
+
5
+ def parse(string)
6
+ require 'sass'
7
+ engine = Sass::Engine.new(string,
8
+ :load_paths => [@root_path],
9
+ :style => :expanded,
10
+ :filename => @script_filename
11
+ )
12
+ engine.render
13
+ end
14
+
15
+ def content_type
16
+ 'text/css'
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Serve #:nodoc:
2
+ class TextileHandler < FileTypeHandler #:nodoc:
3
+ extension 'textile'
4
+
5
+ def parse(string)
6
+ require 'redcloth'
7
+ "<html><body>#{ RedCloth.new(string).to_html }</body></html>"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ require 'serve/rails/configuration'
2
+ require 'serve/rails/mount'
3
+ require 'serve/rails/routing'
@@ -0,0 +1,69 @@
1
+ module Serve # :nodoc:
2
+ module Rails
3
+ class Configuration # :nodoc:
4
+ attr_reader :mounts, :view_helpers
5
+
6
+ def initialize
7
+ Serve::ResponseCache.defaults.update(
8
+ :logger => ActionController::Base.logger,
9
+ :perform_caching => ActionController::Base.perform_caching
10
+ )
11
+
12
+ @mounts = []
13
+
14
+ define_find_cache do |request|
15
+ @default_cache ||= Serve::ResponseCache.new(
16
+ :directory => File.join(::Rails.root, 'tmp', 'serve_cache')
17
+ )
18
+ end
19
+ end
20
+
21
+ def define_find_cache(&block)
22
+ metaclass.module_eval do
23
+ define_method :find_cache, &block
24
+ end
25
+ end
26
+
27
+ def mount(route, root_path)
28
+ m = Mount.new(route, root_path)
29
+ @mounts << m
30
+ yield m if block_given?
31
+ m
32
+ end
33
+ end
34
+
35
+ # Answer the cache for the given request. Delegates to the 'find_cache'
36
+ # block, which defaults to a single cache.
37
+ #
38
+ def self.cache(request)
39
+ configuration.find_cache(request)
40
+ end
41
+
42
+ def self.configuration
43
+ @configuration ||= Configuration.new
44
+ end
45
+
46
+ # The most powerful way to configure Serve::Rails, the configuration is
47
+ # yielded to the provided block. Multiple calls are cumulative - there is
48
+ # only one configuration instance.
49
+ #
50
+ def self.configure
51
+ yield configuration
52
+ end
53
+
54
+ # Define the strategy for resolving the cache for a request. This allows
55
+ # applications to provide logic like cache-per-browser. The default is a
56
+ # single cache.
57
+ #
58
+ def self.find_cache(&block)
59
+ configuration.define_find_cache(&block)
60
+ end
61
+
62
+ # Mount a route on a directory. This allows an application to have
63
+ # multiple served directories, each connected to different routes.
64
+ #
65
+ def self.mount(route, root_path, &block)
66
+ configuration.mount(route, root_path, &block)
67
+ end
68
+ end
69
+ end