fiasco-template 0.0.1
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 +7 -0
- data/lib/fiasco/template/compiler.rb +79 -0
- data/lib/fiasco/template/render_context.rb +171 -0
- data/lib/fiasco/template.rb +8 -0
- data/test/macros/fields.html +12 -0
- data/test/templates/base.html +10 -0
- data/test/templates/child.html +9 -0
- data/test/templates/grandchild.html +5 -0
- data/test/templates/uses_macros.html +9 -0
- data/test/templates/with_locals.html +1 -0
- data/test/test_compiler.rb +105 -0
- data/test/test_render_context.rb +95 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11cbe3ece994f98ca8bde7971960d447f6014756
|
4
|
+
data.tar.gz: a49d13f4c731a7f29cd7fac9b4da6968a2fe693b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 559985260dd577af330558e26e0b6c9a851598b751342a3c436d3624a92b8424df7cfb4135a6ff7a32b0c7f8994839a0808884a355e425db5c92dc520f0aef60
|
7
|
+
data.tar.gz: 9a5d95d6bfaa1914d370fcb06fec9bac3c7c0f9b14dfb3a31647c754bdcaca32b9ed00bf362fbd6b57bf87a1044621b47588151aa015c673814e68efb0866672
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Fiasco::Template
|
4
|
+
class Compiler
|
5
|
+
OPENERS = /(.*?)(^[ \t]*%|\{%-?|\{\{-?|\{#-?|\z)/m
|
6
|
+
DEFAULT_DISPLAY_VALUE = ->(outvar, literal){"#{outvar} << (#{literal}).to_s"}
|
7
|
+
DEFAULT_DISPLAY_TEXT = ->(text){text.dump}
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@output_var = options.fetch(:output_var, '@render_output')
|
11
|
+
@display_value = options.fetch(:display_value, DEFAULT_DISPLAY_VALUE)
|
12
|
+
@display_text = options.fetch(:display_text, DEFAULT_DISPLAY_TEXT)
|
13
|
+
end
|
14
|
+
|
15
|
+
def closer_for(tag)
|
16
|
+
case tag
|
17
|
+
when /\{%-?/ then /(.*?)(-?%\}|\z)/m
|
18
|
+
when /\{\{-?/ then /(.*?)(-?}\}|\z)/m
|
19
|
+
when /\{#-?/ then /(.*?)(-?#\}|\z)/m
|
20
|
+
when '%' then /(.*?)($)/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def scan(body)
|
25
|
+
scanner = StringScanner.new(body)
|
26
|
+
open_tag = nil
|
27
|
+
|
28
|
+
until scanner.eos?
|
29
|
+
if open_tag
|
30
|
+
scanner.scan(closer_for(open_tag))
|
31
|
+
inner, close_tag = scanner[1], scanner[2]
|
32
|
+
|
33
|
+
case open_tag
|
34
|
+
when '{{', '{{-' then yield [:display, inner]
|
35
|
+
when '{%', '{%-' then yield [:code, inner]
|
36
|
+
when '{#', '{#-' then yield [:comment, inner]
|
37
|
+
when '%' then yield [:code_line, inner]
|
38
|
+
end
|
39
|
+
|
40
|
+
open_tag = nil
|
41
|
+
else
|
42
|
+
scanner.scan(OPENERS)
|
43
|
+
before, open_tag = scanner[1], scanner[2]
|
44
|
+
newlines_count = before.count("\n")
|
45
|
+
open_tag.lstrip! # for % which captures preceeding whitespace
|
46
|
+
|
47
|
+
text = before
|
48
|
+
text.lstrip! if close_tag && close_tag[0] == '-'
|
49
|
+
text.rstrip! if open_tag[-1] == '-'
|
50
|
+
text.chomp! if open_tag == '%'
|
51
|
+
|
52
|
+
yield [:text, text]
|
53
|
+
yield [:newlines, newlines_count]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def compile(body)
|
59
|
+
src = []
|
60
|
+
|
61
|
+
scan(body) do |command, data|
|
62
|
+
case command
|
63
|
+
when :newlines
|
64
|
+
src << "\n" * data unless data == 0
|
65
|
+
when :text
|
66
|
+
src << "#{@output_var} << #{@display_text.(data)}" unless data.empty?
|
67
|
+
when :code, :code_line
|
68
|
+
src << data
|
69
|
+
when :display
|
70
|
+
src << @display_value.(@output_var, data)
|
71
|
+
when :comment
|
72
|
+
# skip
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
src.join(';')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'set'
|
2
|
+
require_relative 'compiler'
|
3
|
+
|
4
|
+
module Fiasco::Template
|
5
|
+
class RenderContext
|
6
|
+
class NullOutput; def <<(value); end; end
|
7
|
+
|
8
|
+
Entry = Struct.new(:body, :filename)
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@content_blocks = Hash.new {|h,k| h[k] = [] }
|
12
|
+
@template_locals = Hash.new {|h,k| h[k] = [] }
|
13
|
+
@templates = {}
|
14
|
+
@compiled = Set.new
|
15
|
+
display_value = lambda do |outvar, literal|
|
16
|
+
"__tmp = (#{literal}); #{outvar} << display_value(__tmp)"
|
17
|
+
end
|
18
|
+
@compiler = Compiler.new(display_value: display_value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def block(key, &b)
|
22
|
+
@content_blocks[key.to_s] << b
|
23
|
+
end
|
24
|
+
|
25
|
+
def superblock(*args)
|
26
|
+
# for a blocklevel N, we can find the superblock (block with the
|
27
|
+
# same name defined on a parent template) in N+1
|
28
|
+
@blocklevel += 1
|
29
|
+
@content_blocks[@blockname][@blocklevel].call(*args)
|
30
|
+
ensure
|
31
|
+
@blocklevel -= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def yield_block(key, *args, &b)
|
35
|
+
key = key.to_s
|
36
|
+
@content_blocks[key] << b if b
|
37
|
+
|
38
|
+
if @content_blocks[key].length > 0
|
39
|
+
begin
|
40
|
+
old_blocklevel, @blocklevel = @blocklevel, -1
|
41
|
+
old_blockname, @blockname = @blockname, key
|
42
|
+
|
43
|
+
# @blocklevel starts at -1, making this call render
|
44
|
+
# the block at level 0 (the last one defined in the
|
45
|
+
# inheritance chain)
|
46
|
+
superblock(*args)
|
47
|
+
ensure
|
48
|
+
@blocklevel, @blockname = old_blocklevel, old_blockname
|
49
|
+
end
|
50
|
+
else
|
51
|
+
''
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extends(base, *args)
|
56
|
+
# Store render output remporarily because we don't want to
|
57
|
+
# render any output of a child template until we render the parent
|
58
|
+
@render_output, @original_render_output = NullOutput.new, @render_output
|
59
|
+
@extends = [base, args]
|
60
|
+
end
|
61
|
+
|
62
|
+
def _compile(name, entry, locals = [])
|
63
|
+
src = "params ||= {}; @render_output ||= ''; "
|
64
|
+
locals.each {|var| src += "#{var} = params[:#{var}]; "}
|
65
|
+
src << "\n"
|
66
|
+
src << @compiler.compile(entry.body)
|
67
|
+
src << "\n@render_output"
|
68
|
+
|
69
|
+
meth = <<-EOS
|
70
|
+
#coding:UTF-8
|
71
|
+
define_singleton_method(:'__view__#{name}') do |params|
|
72
|
+
#{src}
|
73
|
+
end
|
74
|
+
EOS
|
75
|
+
eval(meth, binding, entry.filename || "(TEMPLATE:#{name})", -2)
|
76
|
+
@compiled << name
|
77
|
+
end
|
78
|
+
|
79
|
+
def _process_locals(name, locals)
|
80
|
+
seen_variables = @template_locals[name]
|
81
|
+
diff = locals.keys - seen_variables
|
82
|
+
|
83
|
+
unless diff.empty?
|
84
|
+
seen_variables += diff
|
85
|
+
@compiled.delete(name)
|
86
|
+
end
|
87
|
+
|
88
|
+
seen_variables
|
89
|
+
end
|
90
|
+
|
91
|
+
def _declare(options)
|
92
|
+
contents = options[:path] ? File.read(options[:path]) : options[:contents]
|
93
|
+
|
94
|
+
if contents.nil?
|
95
|
+
raise ArgumentError.new("Need either path or contents")
|
96
|
+
end
|
97
|
+
|
98
|
+
entry = Entry.new(contents, options[:path])
|
99
|
+
|
100
|
+
yield(entry)
|
101
|
+
end
|
102
|
+
|
103
|
+
def declare(name, options = {})
|
104
|
+
name = name.to_s
|
105
|
+
_declare(options) {|e| @templates[name] = e}
|
106
|
+
end
|
107
|
+
|
108
|
+
def _render(name, locals = {})
|
109
|
+
name = name.to_s
|
110
|
+
variables = _process_locals(name, locals)
|
111
|
+
|
112
|
+
unless @compiled.include?(name)
|
113
|
+
entry = @templates.fetch(name) do
|
114
|
+
raise ArgumentError, "template `#{name}` not declared"
|
115
|
+
end
|
116
|
+
|
117
|
+
_compile(name, entry, variables)
|
118
|
+
end
|
119
|
+
|
120
|
+
send("__view__#{name}", locals)
|
121
|
+
|
122
|
+
if @extends
|
123
|
+
parent, pargs = @extends
|
124
|
+
@extends = nil
|
125
|
+
@render_output, @original_render_output = @original_render_output, nil
|
126
|
+
_render(parent, *pargs)
|
127
|
+
end
|
128
|
+
|
129
|
+
@render_output
|
130
|
+
end
|
131
|
+
|
132
|
+
def render(name, locals = {})
|
133
|
+
_render(name, locals)
|
134
|
+
ensure
|
135
|
+
@content_blocks.clear
|
136
|
+
@render_output = nil
|
137
|
+
end
|
138
|
+
|
139
|
+
alias_method :[], :render
|
140
|
+
|
141
|
+
def display_value(value)
|
142
|
+
str = value.to_s
|
143
|
+
value.tainted? ? Rack::Utils.escape_html(str) : str
|
144
|
+
end
|
145
|
+
|
146
|
+
def macro(mname, defaults = {}, &b)
|
147
|
+
arguments = b.parameters
|
148
|
+
define_singleton_method mname do |named = defaults, &block|
|
149
|
+
args = arguments.select{|t| t[0] != :block}.map do |type, name|
|
150
|
+
named.fetch(name) do
|
151
|
+
defaults.fetch(name) do
|
152
|
+
msg = "Macro invocation '#{mname}' is missing a required argument: #{name}"
|
153
|
+
raise ArgumentError, msg, caller(10)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
b.call(*args, &block)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def load_macros(options)
|
162
|
+
@render_output, tmp = NullOutput.new, @render_output
|
163
|
+
b = binding
|
164
|
+
_declare(options) do |e|
|
165
|
+
eval(@compiler.compile(e.body), b, e.filename || 'MACROS')
|
166
|
+
end
|
167
|
+
ensure
|
168
|
+
@render_output = tmp
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
% macro :input, type: 'text', value: '', size: 20 do |name, type, value, size|
|
2
|
+
<input type="{{type}}" name="{{name}}" value="{{value}}" size="{{size}}">
|
3
|
+
% end
|
4
|
+
% macro :label, required: false do |text, required|
|
5
|
+
<label>{{text}}{% if required %}<span class=required>*</span>{% end %}</label>
|
6
|
+
% end
|
7
|
+
% macro :field, type: 'text', required: false, lbl: nil do |type, name, lbl, required|
|
8
|
+
<div class=field>
|
9
|
+
% label text: lbl || name.gsub(/[-_]/, ' ').capitalize, required: required
|
10
|
+
% input name: name, type: type
|
11
|
+
</div>
|
12
|
+
% end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html>
|
3
|
+
<head><title>{% yield_block('title') do %}My Site{% end %}</title></head>
|
4
|
+
<body class="{% yield_block('body_classes') %}">
|
5
|
+
<div id=wrapper>
|
6
|
+
<h1>{% yield_block('title') %}</h1>
|
7
|
+
% yield_block('contents')
|
8
|
+
</div>
|
9
|
+
</body>
|
10
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
{{ local1 }} {{ local2 }}
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../lib/fiasco/template'
|
3
|
+
|
4
|
+
class TestCompiler < Test::Unit::TestCase
|
5
|
+
def compile_and_run(body, binding)
|
6
|
+
@__result = ''
|
7
|
+
s = Fiasco::Template::Compiler.new(output_var: "@__result")
|
8
|
+
src = s.compile(body)
|
9
|
+
eval src, binding, '(template)', 1
|
10
|
+
@__result
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_complete
|
14
|
+
body = <<-EOT
|
15
|
+
<head><title>{{title}}</title></head>
|
16
|
+
<body>
|
17
|
+
{% 3.times do |n| %}-{{n}}-{% end %}
|
18
|
+
{% 3.times do |n| -%}
|
19
|
+
-{{n}}-
|
20
|
+
{#- comment #}
|
21
|
+
{%- end %}
|
22
|
+
|
23
|
+
% val = '-' * 10
|
24
|
+
{{val}}
|
25
|
+
</body>
|
26
|
+
EOT
|
27
|
+
|
28
|
+
expected = <<-EOT
|
29
|
+
<head><title>The Title</title></head>
|
30
|
+
<body>
|
31
|
+
-0--1--2-
|
32
|
+
-0--1--2-
|
33
|
+
|
34
|
+
----------
|
35
|
+
</body>
|
36
|
+
EOT
|
37
|
+
|
38
|
+
title = 'The Title'
|
39
|
+
result = compile_and_run(body, binding)
|
40
|
+
|
41
|
+
assert_equal expected, result
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_expression_substitution
|
45
|
+
value = 'value'
|
46
|
+
result = compile_and_run('**{{value}} {{ value + value }}**', binding)
|
47
|
+
|
48
|
+
assert_equal '**value valuevalue**', result
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_code_blocks
|
52
|
+
body = '{% value = 10 %}{% 3.times do %}{{ value * 100 }}{% end %}'
|
53
|
+
result = compile_and_run(body, binding)
|
54
|
+
|
55
|
+
assert_equal "100010001000", result
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_code_lines
|
59
|
+
body = <<-EOT
|
60
|
+
% 3.times do |n|
|
61
|
+
Iteration: {{n}}
|
62
|
+
% end
|
63
|
+
EOT
|
64
|
+
result = compile_and_run(body, binding)
|
65
|
+
|
66
|
+
assert_equal "\nIteration: 0\nIteration: 1\nIteration: 2\n", result
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_whitespace_handling
|
70
|
+
value = '|value|'
|
71
|
+
|
72
|
+
body = " \n {{ value }} \n "
|
73
|
+
assert_equal " \n |value| \n ", compile_and_run(body, binding)
|
74
|
+
|
75
|
+
body = " \n {{- value }} \n "
|
76
|
+
assert_equal "|value| \n ", compile_and_run(body, binding)
|
77
|
+
|
78
|
+
body = " \n {{ value -}} \n "
|
79
|
+
assert_equal " \n |value|", compile_and_run(body, binding)
|
80
|
+
|
81
|
+
body = " \n {{- value -}} \n "
|
82
|
+
assert_equal "|value|", compile_and_run(body, binding)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_comment
|
86
|
+
body = "123{# comment #}456"
|
87
|
+
assert_equal "123456", compile_and_run(body, binding)
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_error_line_mapping
|
91
|
+
body = <<-EOT
|
92
|
+
Line 1
|
93
|
+
Line {{ 1 + 1 }}
|
94
|
+
Line {{- 1 + 2 -}}
|
95
|
+
Line {{ invalid }}
|
96
|
+
Line 5
|
97
|
+
EOT
|
98
|
+
begin
|
99
|
+
compile_and_run(body, binding)
|
100
|
+
rescue StandardError => e
|
101
|
+
e.backtrace[0] =~ /\(template\):(\d+):.*/
|
102
|
+
assert_equal "Error in line: 4", "Error in line: #{$1}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../lib/fiasco/template'
|
3
|
+
|
4
|
+
$renderer = Fiasco::Template::RenderContext.new
|
5
|
+
|
6
|
+
Dir['./test/templates/*.html'].each do |path|
|
7
|
+
name = path.gsub(/\.html$/, '').gsub('./test/templates/', '')
|
8
|
+
$renderer.declare(name, path: path)
|
9
|
+
end
|
10
|
+
|
11
|
+
$renderer.load_macros(path: './test/macros/fields.html')
|
12
|
+
|
13
|
+
class TestRender < Test::Unit::TestCase
|
14
|
+
def test_simple_render
|
15
|
+
expected = <<-EOS
|
16
|
+
<!doctype html>
|
17
|
+
<html>
|
18
|
+
<head><title>My Site</title></head>
|
19
|
+
<body class="">
|
20
|
+
<div id=wrapper>
|
21
|
+
<h1>My Site</h1>
|
22
|
+
</div>
|
23
|
+
</body>
|
24
|
+
</html>
|
25
|
+
EOS
|
26
|
+
|
27
|
+
assert_equal expected, $renderer.render('base')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_inheritance
|
31
|
+
expected = <<-EOS
|
32
|
+
<!doctype html>
|
33
|
+
<html>
|
34
|
+
<head><title>My Site -- Homepage</title></head>
|
35
|
+
<body class="">
|
36
|
+
<div id=wrapper>
|
37
|
+
<h1>My Site -- Homepage</h1>
|
38
|
+
<div class=main>
|
39
|
+
<h2>This is the homepage</h2>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</body>
|
43
|
+
</html>
|
44
|
+
EOS
|
45
|
+
|
46
|
+
assert_equal expected, $renderer.render('child')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_nested_inheritance
|
50
|
+
expected = <<-EOS
|
51
|
+
<!doctype html>
|
52
|
+
<html>
|
53
|
+
<head><title>My Site -- Homepage</title></head>
|
54
|
+
<body class="">
|
55
|
+
<div id=wrapper>
|
56
|
+
<h1>My Site -- Homepage</h1>
|
57
|
+
Grandchild
|
58
|
+
</div>
|
59
|
+
</body>
|
60
|
+
</html>
|
61
|
+
EOS
|
62
|
+
|
63
|
+
assert_equal expected, $renderer.render('grandchild')
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_locals
|
67
|
+
assert_equal "a b\n", $renderer.render('with_locals',
|
68
|
+
local1: 'a', local2: 'b')
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_macros
|
72
|
+
expected = <<-EOS
|
73
|
+
<form method=post>
|
74
|
+
<fieldset>
|
75
|
+
<div class=field>
|
76
|
+
<label>Username</label>
|
77
|
+
<input type="text" name="username" value="" size="20">
|
78
|
+
</div>
|
79
|
+
<div class=field>
|
80
|
+
<label>Password</label>
|
81
|
+
<input type="password" name="password" value="" size="20">
|
82
|
+
</div>
|
83
|
+
<div class=field>
|
84
|
+
<label>Password confirm</label>
|
85
|
+
<input type="password" name="password_confirm" value="" size="20">
|
86
|
+
</div>
|
87
|
+
</fieldset>
|
88
|
+
|
89
|
+
<button>Submit</button>
|
90
|
+
</form>
|
91
|
+
EOS
|
92
|
+
|
93
|
+
assert_equal expected, $renderer.render('uses_macros', user_name: 'Admin')
|
94
|
+
end
|
95
|
+
end
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fiasco-template
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bruno Deferrari
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Templating engine inspired by Jinja2.
|
14
|
+
email:
|
15
|
+
- utizoc@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/fiasco/template/compiler.rb
|
21
|
+
- lib/fiasco/template/render_context.rb
|
22
|
+
- lib/fiasco/template.rb
|
23
|
+
- test/test_compiler.rb
|
24
|
+
- test/test_render_context.rb
|
25
|
+
- test/macros/fields.html
|
26
|
+
- test/templates/base.html
|
27
|
+
- test/templates/child.html
|
28
|
+
- test/templates/grandchild.html
|
29
|
+
- test/templates/uses_macros.html
|
30
|
+
- test/templates/with_locals.html
|
31
|
+
homepage: http://github.com/tizoc/fiasco-template
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.0.3
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: Templating engine inspired by Jinja2.
|
55
|
+
test_files:
|
56
|
+
- test/test_compiler.rb
|
57
|
+
- test/test_render_context.rb
|
58
|
+
- test/macros/fields.html
|
59
|
+
- test/templates/base.html
|
60
|
+
- test/templates/child.html
|
61
|
+
- test/templates/grandchild.html
|
62
|
+
- test/templates/uses_macros.html
|
63
|
+
- test/templates/with_locals.html
|