tynn 2.0.0.beta3 → 2.0.0.beta4
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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +83 -660
- data/lib/tynn.rb +186 -50
- data/lib/tynn/environment.rb +1 -84
- data/lib/tynn/json.rb +24 -25
- data/lib/tynn/render.rb +72 -33
- data/lib/tynn/request.rb +2 -82
- data/lib/tynn/response.rb +4 -118
- data/lib/tynn/secure_headers.rb +2 -24
- data/lib/tynn/session.rb +4 -87
- data/lib/tynn/ssl.rb +12 -71
- data/lib/tynn/static.rb +1 -19
- data/lib/tynn/test.rb +2 -125
- data/lib/tynn/utils.rb +51 -8
- data/lib/tynn/version.rb +1 -1
- data/lib/tynn/x/versioning.rb +29 -0
- data/test/json_test.rb +197 -7
- data/test/render_test.rb +56 -8
- data/test/request_test.rb +2 -0
- data/test/response_test.rb +7 -3
- data/test/routing_test.rb +56 -4
- data/test/settings_test.rb +31 -0
- data/test/versioning_test.rb +201 -0
- metadata +10 -35
- data/lib/tynn/base.rb +0 -434
data/lib/tynn.rb
CHANGED
@@ -1,57 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "rack"
|
4
|
+
require "seg"
|
5
|
+
require_relative "tynn/request"
|
6
|
+
require_relative "tynn/response"
|
7
|
+
require_relative "tynn/utils"
|
4
8
|
require_relative "tynn/version"
|
5
9
|
|
6
10
|
class Tynn
|
7
|
-
# Loads given <tt>plugin</tt> into the application.
|
8
|
-
#
|
9
|
-
# [plugin]
|
10
|
-
# A module that can contain a <tt>ClassMethods</tt> or <tt>InstanceMethods</tt>
|
11
|
-
# module to extend Tynn. If <tt>plugin</tt> responds to <tt>setup</tt>, it will
|
12
|
-
# be called last, and should be used to set up the plugin.
|
13
|
-
#
|
14
|
-
# [*args]
|
15
|
-
# A list of arguments passed to <tt>plugin#setup</tt>.
|
16
|
-
#
|
17
|
-
# [&block]
|
18
|
-
# A block passed to <tt>plugin#setup</tt>.
|
19
|
-
#
|
20
|
-
# <tt></tt>
|
21
|
-
#
|
22
|
-
# # Using default plugins
|
23
|
-
# require "tynn"
|
24
|
-
# require "tynn/environment"
|
25
|
-
# require "tynn/static"
|
26
|
-
#
|
27
|
-
# Tynn.plugin(Tynn::Environment)
|
28
|
-
# Tynn.plugin(Tynn::Static, %(/css /js /images))
|
29
|
-
#
|
30
|
-
# # Using a custom plugin
|
31
|
-
# class MyAppNamePlugin
|
32
|
-
# def self.setup(app, name, &block)
|
33
|
-
# app.app_name = name
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# module ClassMethods
|
37
|
-
# def app_name
|
38
|
-
# @app_name
|
39
|
-
# end
|
40
|
-
#
|
41
|
-
# def app_name=(name)
|
42
|
-
# @app_name = name
|
43
|
-
# end
|
44
|
-
# end
|
45
|
-
#
|
46
|
-
# module InstanceMethods
|
47
|
-
# def app_name
|
48
|
-
# self.class.app_name
|
49
|
-
# end
|
50
|
-
# end
|
51
|
-
# end
|
52
|
-
#
|
53
|
-
# Tynn.plugin(MyAppNamePlugin, "MyApp")
|
54
|
-
#
|
55
11
|
def self.plugin(plugin, *args, &block)
|
56
12
|
if defined?(plugin::InstanceMethods)
|
57
13
|
include(plugin::InstanceMethods)
|
@@ -66,5 +22,185 @@ class Tynn
|
|
66
22
|
end
|
67
23
|
end
|
68
24
|
|
69
|
-
|
25
|
+
module ClassMethods
|
26
|
+
def define(&block)
|
27
|
+
build_app { |env| new(block).call(env) }
|
28
|
+
|
29
|
+
app.freeze
|
30
|
+
|
31
|
+
middleware.freeze
|
32
|
+
|
33
|
+
Tynn::Utils.deep_freeze!(settings)
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_app(&block)
|
37
|
+
@app = middleware.reverse.inject(block) { |a, e| e.call(a) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def use(middleware, *args, &block)
|
41
|
+
if self.middleware.frozen?
|
42
|
+
Tynn::Utils.raise_error("Application middleware is frozen")
|
43
|
+
else
|
44
|
+
self.middleware.push(proc { |app| middleware.new(app, *args, &block) })
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def middleware
|
49
|
+
@middleware ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(env)
|
53
|
+
app.call(env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def app
|
57
|
+
(defined?(@app) && @app) or
|
58
|
+
Tynn::Utils.raise_error("Application handler is missing")
|
59
|
+
end
|
60
|
+
|
61
|
+
def inherited(subclass)
|
62
|
+
subclass.instance_variable_set(:@settings, Tynn::Utils.deep_dup(settings))
|
63
|
+
end
|
64
|
+
|
65
|
+
def settings
|
66
|
+
@settings ||= {}
|
67
|
+
end
|
68
|
+
|
69
|
+
def set(option, value)
|
70
|
+
v = settings[option]
|
71
|
+
|
72
|
+
if Hash === v
|
73
|
+
value = v.merge(value)
|
74
|
+
end
|
75
|
+
|
76
|
+
set!(option, value)
|
77
|
+
end
|
78
|
+
|
79
|
+
def set!(option, value)
|
80
|
+
settings[option] = value
|
81
|
+
end
|
82
|
+
|
83
|
+
def default_headers
|
84
|
+
settings.fetch(:default_headers, {})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Copyright (c) 2016 Francesco Rodriguez
|
89
|
+
# Copyright (c) 2015-2016 Michel Martens (Portions of https://github.com/soveran/syro)
|
90
|
+
#
|
91
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
92
|
+
# of this software and associated documentation files (the "Software"), to deal
|
93
|
+
# in the Software without restriction, including without limitation the rights
|
94
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
95
|
+
# copies of the Software, and to permit persons to whom the Software is
|
96
|
+
# furnished to do so, subject to the following conditions:
|
97
|
+
#
|
98
|
+
# The above copyright notice and this permission notice shall be included in
|
99
|
+
# all copies or substantial portions of the Software.
|
100
|
+
#
|
101
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
102
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
103
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
104
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
105
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
106
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
107
|
+
# THE SOFTWARE.
|
108
|
+
|
109
|
+
module InstanceMethods
|
110
|
+
def initialize(code)
|
111
|
+
@__code = code
|
112
|
+
@__captures = []
|
113
|
+
end
|
114
|
+
|
115
|
+
def call(env)
|
116
|
+
@__env = env
|
117
|
+
@__req = Tynn::Request.new(env)
|
118
|
+
@__res = Tynn::Response.new(nil, Hash[self.class.default_headers])
|
119
|
+
@__seg = ::Seg.new(env["PATH_INFO"])
|
120
|
+
|
121
|
+
catch(:halt) do
|
122
|
+
instance_eval(&@__code)
|
123
|
+
|
124
|
+
@__res.finish
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def req
|
129
|
+
@__req
|
130
|
+
end
|
131
|
+
|
132
|
+
def res
|
133
|
+
@__res
|
134
|
+
end
|
135
|
+
|
136
|
+
def on(arg)
|
137
|
+
captures.clear
|
138
|
+
|
139
|
+
if match(arg)
|
140
|
+
yield(*captures)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private def match(arg)
|
145
|
+
case arg
|
146
|
+
when String then @__seg.consume(arg)
|
147
|
+
when Symbol then @__seg.capture(0, captures)
|
148
|
+
when Proc then arg.call
|
149
|
+
when true then true
|
150
|
+
else false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private def captures
|
155
|
+
@__captures
|
156
|
+
end
|
157
|
+
|
158
|
+
def halt(response)
|
159
|
+
throw(:halt, response)
|
160
|
+
end
|
161
|
+
|
162
|
+
def run(app, vars = nil)
|
163
|
+
path, script = @__env["PATH_INFO"], @__env["SCRIPT_NAME"]
|
164
|
+
|
165
|
+
@__env["PATH_INFO"] = @__seg.curr
|
166
|
+
@__env["SCRIPT_NAME"] = @__seg.prev
|
167
|
+
|
168
|
+
@__env.delete("tynn.vars")
|
169
|
+
@__env["tynn.vars"] = vars if vars
|
170
|
+
|
171
|
+
halt(app.call(@__env))
|
172
|
+
ensure
|
173
|
+
@__env["PATH_INFO"], @__env["SCRIPT_NAME"] = path, script
|
174
|
+
end
|
175
|
+
|
176
|
+
def vars
|
177
|
+
@__env.fetch("tynn.vars", {})
|
178
|
+
end
|
179
|
+
|
180
|
+
def get
|
181
|
+
root? && req.get?
|
182
|
+
end
|
183
|
+
|
184
|
+
def post
|
185
|
+
root? && req.post?
|
186
|
+
end
|
187
|
+
|
188
|
+
def patch
|
189
|
+
root? && req.patch?
|
190
|
+
end
|
191
|
+
|
192
|
+
def put
|
193
|
+
root? && req.put?
|
194
|
+
end
|
195
|
+
|
196
|
+
def delete
|
197
|
+
root? && req.delete?
|
198
|
+
end
|
199
|
+
|
200
|
+
def root?
|
201
|
+
@__seg.root?
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
plugin(self)
|
70
206
|
end
|
data/lib/tynn/environment.rb
CHANGED
@@ -1,115 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Tynn
|
4
|
-
# Adds helper methods to get and check the current environment.
|
5
|
-
# By default, the environment is based on <tt>ENV["RACK_ENV"]</tt>.
|
6
|
-
#
|
7
|
-
# require "tynn"
|
8
|
-
# require "tynn/environment"
|
9
|
-
#
|
10
|
-
# Tynn.plugin(Tynn::Environment)
|
11
|
-
#
|
12
|
-
# # Accessing the current environment.
|
13
|
-
# Tynn.environment # => :development
|
14
|
-
#
|
15
|
-
# # Checking the current environment.
|
16
|
-
# Tynn.development? # => true
|
17
|
-
# Tynn.production? # => false
|
18
|
-
# Tynn.test? # => false
|
19
|
-
# Tynn.staging? # => false
|
20
|
-
#
|
21
|
-
# # Changing the current environment.
|
22
|
-
# Tynn.set(:environment, :test)
|
23
|
-
#
|
24
|
-
# # Performing operations in specific environments.
|
25
|
-
# Tynn.configure(:development, :test) do
|
26
|
-
# # ...
|
27
|
-
# end
|
28
|
-
#
|
29
4
|
module Environment
|
30
|
-
def self.setup(app, env: ENV["RACK_ENV"])
|
5
|
+
def self.setup(app, env: ENV["RACK_ENV"])
|
31
6
|
app.set(:environment, (env || :development).to_sym)
|
32
7
|
end
|
33
8
|
|
34
9
|
module ClassMethods
|
35
|
-
# Yields if current environment matches one of the given environments.
|
36
|
-
#
|
37
|
-
# class MyApp < Tynn
|
38
|
-
# configure(:development, :staging) do
|
39
|
-
# use(BetterErrors::Middleware)
|
40
|
-
# end
|
41
|
-
#
|
42
|
-
# configure(:production) do
|
43
|
-
# plugin(Tynn::SSL)
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
10
|
def configure(*envs)
|
48
11
|
yield(self) if envs.include?(environment)
|
49
12
|
end
|
50
13
|
|
51
|
-
# Returns the current environment for the application.
|
52
|
-
#
|
53
|
-
# Tynn.environment
|
54
|
-
# # => :development
|
55
|
-
#
|
56
|
-
# Tynn.set(environment, :test)
|
57
|
-
#
|
58
|
-
# Tynn.environment
|
59
|
-
# # => :test
|
60
|
-
#
|
61
14
|
def environment
|
62
15
|
settings[:environment]
|
63
16
|
end
|
64
17
|
|
65
|
-
# Checks if current environment is development. Returns <tt>true</tt> if
|
66
|
-
# <tt>environment</tt> is <tt>:development</tt>. Otherwise, <tt>false</tt>.
|
67
|
-
#
|
68
|
-
# Tynn.set(:environment, :test)
|
69
|
-
# Tynn.development? # => false
|
70
|
-
#
|
71
|
-
# Tynn.set(:environment, :development)
|
72
|
-
# Tynn.development? # => true
|
73
|
-
#
|
74
18
|
def development?
|
75
19
|
environment == :development
|
76
20
|
end
|
77
21
|
|
78
|
-
# Checks if current environment is test. Returns <tt>true</tt> if
|
79
|
-
# <tt>environment</tt> is <tt>:test</tt>. Otherwise, <tt>false</tt>.
|
80
|
-
#
|
81
|
-
# Tynn.set(:environment, :development)
|
82
|
-
# Tynn.test? # => false
|
83
|
-
#
|
84
|
-
# Tynn.set(:environment, :test)
|
85
|
-
# Tynn.test? # => true
|
86
|
-
#
|
87
22
|
def test?
|
88
23
|
environment == :test
|
89
24
|
end
|
90
25
|
|
91
|
-
# Checks if current environment is production. Returns <tt>true</tt> if
|
92
|
-
# <tt>environment</tt> is <tt>:production</tt>. Otherwise, <tt>false</tt>.
|
93
|
-
#
|
94
|
-
# Tynn.set(:environment, :development)
|
95
|
-
# Tynn.production? # => false
|
96
|
-
#
|
97
|
-
# Tynn.set(:environment, :production)
|
98
|
-
# Tynn.production? # => true
|
99
|
-
#
|
100
26
|
def production?
|
101
27
|
environment == :production
|
102
28
|
end
|
103
29
|
|
104
|
-
# Checks if current environment is staging. Returns <tt>true</tt> if
|
105
|
-
# <tt>environment</tt> is <tt>:staging</tt>. Otherwise, <tt>false</tt>.
|
106
|
-
#
|
107
|
-
# Tynn.set(environment, :test)
|
108
|
-
# Tynn.staging? # => false
|
109
|
-
#
|
110
|
-
# Tynn.set(:environment, :staging)
|
111
|
-
# Tynn.staging? # => true
|
112
|
-
#
|
113
30
|
def staging?
|
114
31
|
environment == :staging
|
115
32
|
end
|
data/lib/tynn/json.rb
CHANGED
@@ -3,39 +3,38 @@
|
|
3
3
|
require "json"
|
4
4
|
|
5
5
|
class Tynn
|
6
|
-
# Adds helper methods for JSON generation.
|
7
|
-
#
|
8
|
-
# require "tynn"
|
9
|
-
# require "tynn/json"
|
10
|
-
#
|
11
|
-
# Tynn.plugin(Tynn::JSON)
|
12
|
-
#
|
13
6
|
module JSON
|
14
|
-
def self.setup(app, options = {})
|
7
|
+
def self.setup(app, options = {})
|
15
8
|
app.set(:json, {
|
16
|
-
content_type: "application/json"
|
9
|
+
content_type: "application/json",
|
10
|
+
on_parse_error: proc { halt([400, {}, []]) },
|
11
|
+
read_opts: { create_additions: false },
|
12
|
+
write_opts: {}
|
17
13
|
}.merge(options))
|
18
14
|
end
|
19
15
|
|
20
16
|
module InstanceMethods
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
def json_params
|
18
|
+
@__json_params ||= ::JSON.parse(req.body.read, json_opts[:read_opts])
|
19
|
+
rescue ::JSON::ParserError
|
20
|
+
rescue_json_parse_error
|
21
|
+
ensure
|
22
|
+
req.body.rewind
|
23
|
+
end
|
24
|
+
|
25
|
+
def rescue_json_parse_error
|
26
|
+
case (rescuer = json_opts[:on_parse_error])
|
27
|
+
when Symbol then send(rescuer)
|
28
|
+
when Proc then instance_eval(&rescuer)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private :rescue_json_parse_error
|
33
|
+
|
34
|
+
def json(data, opts = json_opts[:write_opts])
|
36
35
|
res.content_type = json_opts[:content_type]
|
37
36
|
|
38
|
-
res.write(
|
37
|
+
res.write(data.to_json(opts))
|
39
38
|
end
|
40
39
|
|
41
40
|
private
|
data/lib/tynn/render.rb
CHANGED
@@ -1,67 +1,106 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "utils"
|
4
4
|
|
5
5
|
class Tynn
|
6
6
|
module Render
|
7
|
-
def self.setup(app, options = {})
|
7
|
+
def self.setup(app, options = {})
|
8
8
|
app.set(:render, {
|
9
9
|
content_type: "text/html",
|
10
|
-
engine: "erb",
|
11
|
-
engine_opts: { escape_html: true },
|
12
10
|
layout: "layout",
|
13
11
|
root: Dir.pwd,
|
14
12
|
views: "views"
|
15
13
|
}.merge(options))
|
16
14
|
end
|
17
15
|
|
16
|
+
# Copyright (c) 2016 Francesco Rodriguez
|
17
|
+
# Copyright (c) 2011-2016 Michel Martens (https://github.com/soveran/mote)
|
18
|
+
#
|
19
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
20
|
+
# of this software and associated documentation files (the "Software"), to deal
|
21
|
+
# in the Software without restriction, including without limitation the rights
|
22
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
23
|
+
# copies of the Software, and to permit persons to whom the Software is
|
24
|
+
# furnished to do so, subject to the following conditions:
|
25
|
+
#
|
26
|
+
# The above copyright notice and this permission notice shall be included in
|
27
|
+
# all copies or substantial portions of the Software.
|
28
|
+
#
|
29
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
30
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
31
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
32
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
33
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
34
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
35
|
+
# THE SOFTWARE.
|
36
|
+
|
37
|
+
module Engine
|
38
|
+
PATTERN = /(<%)\s+(.*?)\s+%>(?:\n)|(<%==?)(.*?)%>/m
|
39
|
+
|
40
|
+
module_function
|
41
|
+
|
42
|
+
def parse(file, vars, context = self)
|
43
|
+
terms = File.read(file).split(PATTERN)
|
44
|
+
parts = "proc do |locals = {}, __o = ''|\n".dup
|
45
|
+
|
46
|
+
while var = vars.shift
|
47
|
+
parts << sprintf("%s = locals[%p]\n", var, var)
|
48
|
+
end
|
49
|
+
|
50
|
+
while term = terms.shift
|
51
|
+
parts << parse_expression(terms, term)
|
52
|
+
end
|
53
|
+
|
54
|
+
parts << "__o; end"
|
55
|
+
|
56
|
+
context.instance_eval(parts, file, -(vars.size.succ))
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_expression(terms, term)
|
60
|
+
case term
|
61
|
+
when "<%" then terms.shift << "\n"
|
62
|
+
when "<%=" then "__o << Tynn::Utils.h((" + terms.shift << ").to_s)\n"
|
63
|
+
when "<%==" then "__o << (" + terms.shift << ").to_s\n"
|
64
|
+
else "__o << " + term.dump << "\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
18
69
|
module InstanceMethods
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
#
|
23
|
-
# render("about", title: "About", name: "John Doe")
|
24
|
-
#
|
25
|
-
def render(template, locals = {})
|
26
|
-
res.content_type = render_opts[:content_type]
|
27
|
-
|
28
|
-
res.write(view(template, locals))
|
70
|
+
def render(template, content_type: nil, layout: render_layout, locals: {})
|
71
|
+
res.content_type = content_type || render_opts[:content_type]
|
72
|
+
res.write(view(template, layout: layout, locals: locals))
|
29
73
|
end
|
30
74
|
|
31
|
-
|
32
|
-
|
33
|
-
#
|
34
|
-
# res.write(view("about", title: "About", name: "John Doe"))
|
35
|
-
#
|
36
|
-
def view(template, locals = {})
|
37
|
-
partial(render_opts[:layout], locals.merge(content: partial(template, locals)))
|
75
|
+
def view(template, layout: render_layout, locals: {})
|
76
|
+
partial(layout, locals: locals.merge(content: partial(template, locals: locals)))
|
38
77
|
end
|
39
78
|
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
# res.write(partial("about", name: "John Doe"))
|
44
|
-
#
|
45
|
-
def partial(template, locals = {})
|
46
|
-
tilt(template_path(template), locals.merge(app: self), render_opts[:engine_opts])
|
79
|
+
def partial(template, locals: {})
|
80
|
+
render_file(template_path(template), locals.merge(app: self))
|
47
81
|
end
|
48
82
|
|
49
83
|
private
|
50
84
|
|
51
85
|
def template_path(template)
|
52
|
-
File.join(views_path, "#{ template }
|
86
|
+
File.join(views_path, "#{ template }.erb")
|
53
87
|
end
|
54
88
|
|
55
89
|
def views_path
|
56
90
|
File.expand_path(render_opts[:views], render_opts[:root])
|
57
91
|
end
|
58
92
|
|
59
|
-
def
|
60
|
-
|
93
|
+
def render_file(file, locals)
|
94
|
+
render_cache[file] ||= Engine.parse(file, locals.keys, TOPLEVEL_BINDING)
|
95
|
+
render_cache[file].call(locals)
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_cache
|
99
|
+
Thread.current[:render_cache] ||= {}
|
61
100
|
end
|
62
101
|
|
63
|
-
def
|
64
|
-
|
102
|
+
def render_layout
|
103
|
+
render_opts[:layout]
|
65
104
|
end
|
66
105
|
|
67
106
|
def render_opts
|