lm_docstache 1.1.0
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/.gitignore +5 -0
- data/CHANGELOG.md +58 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +176 -0
- data/README.md +72 -0
- data/Rakefile +7 -0
- data/lib/lm_docstache.rb +9 -0
- data/lib/lm_docstache/block.rb +62 -0
- data/lib/lm_docstache/data_scope.rb +67 -0
- data/lib/lm_docstache/document.rb +157 -0
- data/lib/lm_docstache/renderer.rb +76 -0
- data/lib/lm_docstache/version.rb +3 -0
- data/lm_docstache.gemspec +25 -0
- data/spec/data_scope_spec.rb +56 -0
- data/spec/empty_data_scope_spec.rb +10 -0
- data/spec/example_input/ExampleTemplate.docx +0 -0
- data/spec/example_input/word/document.xml +1122 -0
- data/spec/integration_spec.rb +78 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/template_processor_spec.rb +222 -0
- metadata +129 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support/core_ext/object/blank.rb'
|
3
|
+
|
4
|
+
module LMDocstache
|
5
|
+
module TestData
|
6
|
+
DATA = {
|
7
|
+
teacher: 'Johhny Bissel',
|
8
|
+
building: 'Building #14',
|
9
|
+
classroom: 'Rm 202'.to_sym,
|
10
|
+
district: 'San Deigo Unified School District',
|
11
|
+
seniority: 12.25,
|
12
|
+
roster: [
|
13
|
+
{ name: 'Sally', age: 12, attendance: '100%' },
|
14
|
+
{ name: :Xiao, age: 10, attendance: '94%' },
|
15
|
+
{ name: 'Bryan', age: 13, attendance: '100%' },
|
16
|
+
{ name: 'Larry', age: 11, attendance: '90%' },
|
17
|
+
{ name: 'Kumar', age: 12, attendance: '76%' },
|
18
|
+
{ name: 'Amber', age: 11, attendance: '100%' },
|
19
|
+
{ name: 'Isaiah', age: 12, attendance: '89%' },
|
20
|
+
{ name: 'Omar', age: 12, attendance: '99%' },
|
21
|
+
{ name: 'Xi', age: 11, attendance: '20%' },
|
22
|
+
{ name: 'Noushin', age: 12, attendance: '100%' }
|
23
|
+
],
|
24
|
+
event_reports: [
|
25
|
+
{ name: 'Science Museum Field Trip', notes: 'PTA sponsored event. Spoke to Astronaut with HAM radio.' },
|
26
|
+
{ name: 'Wilderness Center Retreat', notes: '2 days hiking for charity:water fundraiser, $10,200 raised.' }
|
27
|
+
],
|
28
|
+
created_at: '11-12-20 02:01',
|
29
|
+
true_cond: true,
|
30
|
+
false_cond: false
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'integration test', integration: true do
|
36
|
+
let(:data) { LMDocstache::TestData::DATA }
|
37
|
+
let(:base_path) { SPEC_BASE_PATH.join('example_input') }
|
38
|
+
let(:input_file) { "#{base_path}/ExampleTemplate.docx" }
|
39
|
+
let(:output_dir) { "#{base_path}/tmp" }
|
40
|
+
let(:output_file) { "#{output_dir}/IntegrationTestOutput.docx" }
|
41
|
+
let(:document) { LMDocstache::Document.new(input_file) }
|
42
|
+
before do
|
43
|
+
FileUtils.rm_rf(output_dir) if File.exist?(output_dir)
|
44
|
+
Dir.mkdir(output_dir)
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'should process that incoming docx' do
|
48
|
+
it 'loads the input file' do
|
49
|
+
expect(document).to_not be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'generates output file with the same contents as the input file' do
|
53
|
+
input_entries = Zip::File.open(input_file) { |z| z.map(&:name) }
|
54
|
+
document.save(output_file)
|
55
|
+
output_entries = Zip::File.open(output_file) { |z| z.map(&:name) }
|
56
|
+
|
57
|
+
expect(input_entries - output_entries).to be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fixes nested xml errors breaking tags' do
|
61
|
+
expect(document.send(:problem_paragraphs)).to_not be_empty
|
62
|
+
document.fix_errors
|
63
|
+
expect(document.send(:problem_paragraphs)).to be_empty
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'has the expected amount of usable tags' do
|
67
|
+
expect(document.usable_tags.count).to be(27)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'has the expected amount of unique tag names' do
|
71
|
+
expect(document.usable_tag_names.count).to be(16)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'renders file using data' do
|
75
|
+
document.render_file(output_file, data)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require_relative '../lib/lm_docstache'
|
3
|
+
|
4
|
+
SPEC_BASE_PATH = Pathname.new(File.expand_path(File.dirname(__FILE__)))
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
[:expect_with, :mock_with].each do |method|
|
8
|
+
config.send(method, :rspec) do |c|
|
9
|
+
c.syntax = :expect
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# require 'spec_helper'
|
2
|
+
# require 'nokogiri'
|
3
|
+
#
|
4
|
+
# module LMDocstache
|
5
|
+
# module TestData
|
6
|
+
# DATA = {
|
7
|
+
# teacher: 'Priya Vora',
|
8
|
+
# building: 'Building #14',
|
9
|
+
# classroom: 'Rm 202'.to_sym,
|
10
|
+
# district: 'Washington County Public Schools',
|
11
|
+
# senority: 12.25,
|
12
|
+
# roster: [
|
13
|
+
# { name: 'Sally', age: 12, attendence: '100%' },
|
14
|
+
# { name: :Xiao, age: 10, attendence: '94%' },
|
15
|
+
# { name: 'Bryan', age: 13, attendence: '100%' },
|
16
|
+
# { name: 'Larry', age: 11, attendence: '90%' },
|
17
|
+
# { name: 'Kumar', age: 12, attendence: '76%' },
|
18
|
+
# { name: 'Amber', age: 11, attendence: '100%' },
|
19
|
+
# { name: 'Isaiah', age: 12, attendence: '89%' },
|
20
|
+
# { name: 'Omar', age: 12, attendence: '99%' },
|
21
|
+
# { name: 'Xi', age: 11, attendence: '20%' },
|
22
|
+
# { name: 'Noushin', age: 12, attendence: '100%' }
|
23
|
+
# ],
|
24
|
+
# event_reports: [
|
25
|
+
# { name: 'Science Museum Field Trip', notes: 'PTA sponsored event. Spoke to Astronaut with HAM radio.' },
|
26
|
+
# { name: 'Wilderness Center Retreat', notes: '2 days hiking for charity:water fundraiser, $10,200 raised.' }
|
27
|
+
# ],
|
28
|
+
# created_at: '11-12-03 02:01'
|
29
|
+
# }
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# describe LMDocstache::Render do
|
34
|
+
# let(:data) { Marshal.load(Marshal.dump(DocxTemplater::TestData::DATA)) } # deep copy
|
35
|
+
# let(:base_path) { SPEC_BASE_PATH.join('example_input') }
|
36
|
+
# let(:xml) { File.read("#{base_path}/word/document.xml") }
|
37
|
+
# let(:parser) { DocxTemplater::TemplateProcessor.new(data) }
|
38
|
+
#
|
39
|
+
# context 'valid xml' do
|
40
|
+
# it 'should render and still be valid XML' do
|
41
|
+
# expect(Nokogiri::XML.parse(xml)).to be_xml
|
42
|
+
# out = parser.render(xml)
|
43
|
+
# expect(Nokogiri::XML.parse(out)).to be_xml
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# it 'should accept non-ascii characters' do
|
47
|
+
# data[:teacher] = '老师'
|
48
|
+
# out = parser.render(xml)
|
49
|
+
# expect(out).to include('老师')
|
50
|
+
# expect(Nokogiri::XML.parse(out)).to be_xml
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# it 'should escape as necessary invalid xml characters, if told to' do
|
54
|
+
# data[:building] = '23rd & A #1 floor'
|
55
|
+
# data[:classroom] = '--> 201 <!--'
|
56
|
+
# data[:roster][0][:name] = '<#Ai & Bo>'
|
57
|
+
# out = parser.render(xml)
|
58
|
+
#
|
59
|
+
# expect(Nokogiri::XML.parse(out)).to be_xml
|
60
|
+
# expect(out).to include('23rd & A #1 floor')
|
61
|
+
# expect(out).to include('--> 201 <!--')
|
62
|
+
# expect(out).to include('<#Ai & Bo>')
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# context 'not escape xml' do
|
66
|
+
# let(:parser) { DocxTemplater::TemplateProcessor.new(data, false) }
|
67
|
+
# it 'does not escape the xml attributes' do
|
68
|
+
# data[:building] = '23rd <p>&</p> #1 floor'
|
69
|
+
# out = parser.render(xml)
|
70
|
+
# expect(Nokogiri::XML.parse(out)).to be_xml
|
71
|
+
# expect(out).to include('23rd <p>&</p> #1 floor')
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# context 'unmatched begin and end row templates' do
|
77
|
+
# it 'should not raise' do
|
78
|
+
# xml = <<EOF
|
79
|
+
# <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
80
|
+
# <w:body>
|
81
|
+
# <w:tbl>
|
82
|
+
# <w:tr><w:tc>
|
83
|
+
# <w:p>
|
84
|
+
# <w:r><w:t>#BEGIN_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
|
85
|
+
# </w:p>
|
86
|
+
# </w:tc></w:tr>
|
87
|
+
# <w:tr><w:tc>
|
88
|
+
# <w:p>
|
89
|
+
# <w:r><w:t>#END_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
|
90
|
+
# </w:p>
|
91
|
+
# </w:tc></w:tr>
|
92
|
+
# <w:tr><w:tc>
|
93
|
+
# <w:p>
|
94
|
+
# <w:r><w:t>#BEGIN_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
|
95
|
+
# </w:p>
|
96
|
+
# </w:tc></w:tr>
|
97
|
+
# <w:tr><w:tc>
|
98
|
+
# <w:p>
|
99
|
+
# <w:r><w:t>#END_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
|
100
|
+
# </w:p>
|
101
|
+
# </w:tc></w:tr>
|
102
|
+
# </w:tbl>
|
103
|
+
# </w:body>
|
104
|
+
# </xml>
|
105
|
+
# EOF
|
106
|
+
# expect { parser.render(xml) }.to_not raise_error
|
107
|
+
# end
|
108
|
+
#
|
109
|
+
# it 'should raise an exception' do
|
110
|
+
# xml = <<EOF
|
111
|
+
# <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
112
|
+
# <w:body>
|
113
|
+
# <w:tbl>
|
114
|
+
# <w:tr><w:tc>
|
115
|
+
# <w:p>
|
116
|
+
# <w:r><w:t>#BEGIN_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
|
117
|
+
# </w:p>
|
118
|
+
# </w:tc></w:tr>
|
119
|
+
# <w:tr><w:tc>
|
120
|
+
# <w:p>
|
121
|
+
# <w:r><w:t>#END_ROW:#{:roster.to_s.upcase}#</w:t></w:r>
|
122
|
+
# </w:p>
|
123
|
+
# </w:tc></w:tr>
|
124
|
+
# <w:tr><w:tc>
|
125
|
+
# <w:p>
|
126
|
+
# <w:r><w:t>#BEGIN_ROW:#{:event_reports.to_s.upcase}#</w:t></w:r>
|
127
|
+
# </w:p>
|
128
|
+
# </w:tc></w:tr>
|
129
|
+
# </w:tbl>
|
130
|
+
# </w:body>
|
131
|
+
# </xml>
|
132
|
+
# EOF
|
133
|
+
# expect { parser.render(xml) }.to raise_error(/#END_ROW:EVENT_REPORTS# nil: true/)
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# it 'should enter no text for a nil value' do
|
138
|
+
# xml = <<EOF
|
139
|
+
# <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
140
|
+
# <w:body>
|
141
|
+
# <w:p>Before.$KEY$After</w:p>
|
142
|
+
# </w:body>
|
143
|
+
# </xml>
|
144
|
+
# EOF
|
145
|
+
# actual = DocxTemplater::TemplateProcessor.new(key: nil).render(xml)
|
146
|
+
# expected_xml = <<EOF
|
147
|
+
# <w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
148
|
+
# <w:body>
|
149
|
+
# <w:p>Before.After</w:p>
|
150
|
+
# </w:body>
|
151
|
+
# </xml>
|
152
|
+
# EOF
|
153
|
+
# expect(actual).to eq(expected_xml)
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# it 'should replace all simple keys with values' do
|
157
|
+
# non_array_keys = data.reject { |_, v| v.is_a?(Array) }
|
158
|
+
# non_array_keys.keys.each do |key|
|
159
|
+
# expect(xml).to include("$#{key.to_s.upcase}$")
|
160
|
+
# expect(xml).not_to include(data[key].to_s)
|
161
|
+
# end
|
162
|
+
# out = parser.render(xml)
|
163
|
+
#
|
164
|
+
# non_array_keys.each do |key|
|
165
|
+
# expect(out).not_to include("$#{key}$")
|
166
|
+
# expect(out).to include(data[key].to_s)
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# it 'should replace all array keys with values' do
|
171
|
+
# expect(xml).to include('#BEGIN_ROW:')
|
172
|
+
# expect(xml).to include('#END_ROW:')
|
173
|
+
# expect(xml).to include('$EACH:')
|
174
|
+
#
|
175
|
+
# out = parser.render(xml)
|
176
|
+
#
|
177
|
+
# expect(out).not_to include('#BEGIN_ROW:')
|
178
|
+
# expect(out).not_to include('#END_ROW:')
|
179
|
+
# expect(out).not_to include('$EACH:')
|
180
|
+
#
|
181
|
+
# [:roster, :event_reports].each do |key|
|
182
|
+
# data[key].each do |row|
|
183
|
+
# row.values.map(&:to_s).each do |row_value|
|
184
|
+
# expect(out).to include(row_value)
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# it 'shold render students names in the same order as the data' do
|
191
|
+
# out = parser.render(xml)
|
192
|
+
# expect(out).to include('Sally')
|
193
|
+
# expect(out).to include('Kumar')
|
194
|
+
# expect(out.index('Kumar')).to be > out.index('Sally')
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# it 'shold render event reports names in the same order as the data' do
|
198
|
+
# out = parser.render(xml)
|
199
|
+
# expect(out).to include('Science Museum Field Trip')
|
200
|
+
# expect(out).to include('Wilderness Center Retreat')
|
201
|
+
# expect(out.index('Wilderness Center Retreat')).to be > out.index('Science Museum Field Trip')
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# it 'should render 2-line event reports in same order as docx' do
|
205
|
+
# event_reports_starting_at = xml.index('#BEGIN_ROW:EVENT_REPORTS#')
|
206
|
+
# expect(event_reports_starting_at).to be >= 0
|
207
|
+
# expect(xml.index('$EACH:NAME$', event_reports_starting_at)).to be > event_reports_starting_at
|
208
|
+
# expect(xml.index('$EACH:NOTES$', event_reports_starting_at)).to be > event_reports_starting_at
|
209
|
+
# expect(xml.index('$EACH:NOTES$', event_reports_starting_at)).to be > xml.index('$EACH:NAME$', event_reports_starting_at)
|
210
|
+
#
|
211
|
+
# out = parser.render(xml)
|
212
|
+
# expect(out.index('PTA sponsored event. Spoke to Astronaut with HAM radio.')).to be > out.index('Science Museum Field Trip')
|
213
|
+
# end
|
214
|
+
#
|
215
|
+
# it 'should render sums of input data' do
|
216
|
+
# expect(xml).to include('#SUM')
|
217
|
+
# out = parser.render(xml)
|
218
|
+
# expect(out).not_to include('#SUM')
|
219
|
+
# expect(out).to include("#{data[:roster].count} Students")
|
220
|
+
# expect(out).to include("#{data[:event_reports].count} Events")
|
221
|
+
# end
|
222
|
+
# end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lm_docstache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Roey Chasman
|
8
|
+
- Will Cosgrove
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.6'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.6'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rubyzip
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '1.1'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1.1'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: 3.1.0
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 3.1.0
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: pry-byebug
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1'
|
70
|
+
description: Integrates data into MS Word docx template files. Processing supports
|
71
|
+
loops and replacement of strings of data both outside and within loops.
|
72
|
+
email:
|
73
|
+
- roey@lawmatics.com
|
74
|
+
- will@willcosgrove.com
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- CHANGELOG.md
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- lib/lm_docstache.rb
|
86
|
+
- lib/lm_docstache/block.rb
|
87
|
+
- lib/lm_docstache/data_scope.rb
|
88
|
+
- lib/lm_docstache/document.rb
|
89
|
+
- lib/lm_docstache/renderer.rb
|
90
|
+
- lib/lm_docstache/version.rb
|
91
|
+
- lm_docstache.gemspec
|
92
|
+
- spec/data_scope_spec.rb
|
93
|
+
- spec/empty_data_scope_spec.rb
|
94
|
+
- spec/example_input/ExampleTemplate.docx
|
95
|
+
- spec/example_input/word/document.xml
|
96
|
+
- spec/integration_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- spec/template_processor_spec.rb
|
99
|
+
homepage: https://www.lawmatics.com
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubygems_version: 3.0.3
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Merges Hash of Data into Word docx template files using mustache syntax
|
122
|
+
test_files:
|
123
|
+
- spec/data_scope_spec.rb
|
124
|
+
- spec/empty_data_scope_spec.rb
|
125
|
+
- spec/example_input/ExampleTemplate.docx
|
126
|
+
- spec/example_input/word/document.xml
|
127
|
+
- spec/integration_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
- spec/template_processor_spec.rb
|