fivepointssolutions-serve 0.9.11

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.
@@ -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