sablon 0.0.3 → 0.0.4
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/README.md +19 -4
- data/bin/sablon +19 -0
- data/lib/sablon/operations.rb +8 -4
- data/lib/sablon/parser/mail_merge.rb +1 -1
- data/lib/sablon/version.rb +1 -1
- data/test/executable_test.rb +23 -0
- data/test/expression_test.rb +15 -2
- data/test/fixtures/shopping_list_context.json +8 -0
- data/test/fixtures/shopping_list_sample.docx +0 -0
- data/test/fixtures/shopping_list_template.docx +0 -0
- data/test/mail_merge_parser_test.rb +54 -25
- metadata +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9bed4d04a0e610c47abed3aa044222d37c7f716
|
4
|
+
data.tar.gz: 0942a8f508bc8198b41d4bee926e0fbf7e4a4ec3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cd8914ff55c1178b946fcb9704759d579816d332e2da8b69b7a0da9d6792bfc9489c9cd1d05cd3c48a37ca5dc9f3d7e3dcbeeed04bdda6308dd4e0defed3d5d
|
7
|
+
data.tar.gz: 04ae3adf13a090fdc4adca34d1b038281cc5468444217f4fcccdaf3e651c170a7eaab25742ef5d003a4e4e1a0c7d13dda651d2e51555aec4ee2e6b6f6e822741
|
data/README.md
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
[](http://badge.fury.io/rb/sablon) [](https://travis-ci.org/senny/sablon)
|
4
4
|
|
5
5
|
Is a document template processor for Word `docx` files. It leverages Word's
|
6
|
-
built-in formatting and layouting capabilities to make
|
7
|
-
|
6
|
+
built-in formatting and layouting capabilities to make template creation easy
|
7
|
+
and efficient.
|
8
8
|
|
9
|
-
*Note: Sablon is still in early development.
|
9
|
+
*Note: Sablon is still in early development. Please report if you encounter any issues along the way.*
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
@@ -51,6 +51,10 @@ It's also possible to call a method on a context object using:
|
|
51
51
|
«=post.title»
|
52
52
|
```
|
53
53
|
|
54
|
+
NOTE: The dot operator can also be used to perform a hash lookup.
|
55
|
+
This means that it's not possible to call methods on a hash instance.
|
56
|
+
Sablon will always try to make a lookup instead.
|
57
|
+
|
54
58
|
|
55
59
|
#### Conditionals
|
56
60
|
|
@@ -97,6 +101,17 @@ to repeat. Have a look at the
|
|
97
101
|
It is possible to nest loops and conditionals.
|
98
102
|
|
99
103
|
|
104
|
+
### Executable
|
105
|
+
|
106
|
+
The `sablon` executable can be used to process templates on the command-line.
|
107
|
+
The usage is as follows:
|
108
|
+
|
109
|
+
```
|
110
|
+
cat <context path>.json | sablon <template path> <output path>
|
111
|
+
```
|
112
|
+
|
113
|
+
Have a look at [this test](test/executable_test.rb) for an example.
|
114
|
+
|
100
115
|
### Examples
|
101
116
|
|
102
117
|
There is a [sample template](test/fixtures/sablon_template.docx) in the
|
@@ -130,7 +145,7 @@ For more details, check out this [test case](test/sablon_test.rb).
|
|
130
145
|
|
131
146
|
## Inspiration
|
132
147
|
|
133
|
-
|
148
|
+
These projects address a similar goal and inspired the work on Sablon:
|
134
149
|
|
135
150
|
* [ruby-docx-templater](https://github.com/jawspeak/ruby-docx-templater)
|
136
151
|
* [docx_mailmerge](https://github.com/annaswims/docx_mailmerge)
|
data/bin/sablon
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sablon'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
if ARGV.size != 2
|
7
|
+
puts <<-HELP
|
8
|
+
cat <context path> | sablon <template path> <output path>
|
9
|
+
HELP
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
template_path = File.expand_path(ARGV[0])
|
14
|
+
output_path = File.expand_path(ARGV[1])
|
15
|
+
context_json = STDIN.readlines.join
|
16
|
+
context = JSON.parse(context_json)
|
17
|
+
|
18
|
+
template = Sablon.template(template_path)
|
19
|
+
template.render_to_file(output_path, context)
|
data/lib/sablon/operations.rb
CHANGED
@@ -52,20 +52,24 @@ module Sablon
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
class
|
55
|
+
class LookupOrMethodCall < Struct.new(:receiver_expr, :method)
|
56
56
|
def evaluate(context)
|
57
|
-
receiver.evaluate(context)
|
57
|
+
receiver = receiver_expr.evaluate(context)
|
58
|
+
case receiver
|
59
|
+
when Hash; receiver[method]
|
60
|
+
else; receiver.public_send method
|
61
|
+
end
|
58
62
|
end
|
59
63
|
|
60
64
|
def inspect
|
61
|
-
"«#{
|
65
|
+
"«#{receiver_expr.name}.#{method}»"
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
65
69
|
def self.parse(expression)
|
66
70
|
if expression.include?(".")
|
67
71
|
parts = expression.split(".")
|
68
|
-
|
72
|
+
LookupOrMethodCall.new(Variable.new(parts.first), parts.last)
|
69
73
|
else
|
70
74
|
Variable.new(expression)
|
71
75
|
end
|
data/lib/sablon/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "test_helper"
|
3
|
+
|
4
|
+
class ExecutableTest < Sablon::TestCase
|
5
|
+
include Sablon::Test::Assertions
|
6
|
+
|
7
|
+
def setup
|
8
|
+
super
|
9
|
+
@base_path = Pathname.new(File.expand_path("../", __FILE__))
|
10
|
+
@output_path = @base_path + "sandbox/shopping_list.docx"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_generate_document_from_template
|
14
|
+
template_path = @base_path + "fixtures/shopping_list_template.docx"
|
15
|
+
context_path = @base_path + "fixtures/shopping_list_context.json"
|
16
|
+
|
17
|
+
executable_path = @base_path + '../bin/sablon'
|
18
|
+
|
19
|
+
`cat #{context_path} | #{executable_path} #{template_path} #{@output_path}`
|
20
|
+
|
21
|
+
assert_docx_equal @base_path + "fixtures/shopping_list_sample.docx", @output_path
|
22
|
+
end
|
23
|
+
end
|
data/test/expression_test.rb
CHANGED
@@ -16,13 +16,26 @@ class VariableExpressionTest < Sablon::TestCase
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
class
|
20
|
-
def
|
19
|
+
class LookupOrMethodCallTest < Sablon::TestCase
|
20
|
+
def test_calls_method_on_object
|
21
21
|
user = OpenStruct.new(first_name: "Jack")
|
22
22
|
expr = Sablon::Expression.parse("user.first_name")
|
23
23
|
assert_equal "Jack", expr.evaluate({"user" => user})
|
24
24
|
end
|
25
25
|
|
26
|
+
def test_calls_perform_lookup_on_hash_with_string_keys
|
27
|
+
user = {"first_name" => "Jack"}
|
28
|
+
expr = Sablon::Expression.parse("user.first_name")
|
29
|
+
assert_equal "Jack", expr.evaluate({"user" => user})
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_calls_perform_lookup_on_hash_with_symbol_keys
|
33
|
+
skip
|
34
|
+
user = {first_name: "Jack"}
|
35
|
+
expr = Sablon::Expression.parse("user.first_name")
|
36
|
+
assert_equal "Jack", expr.evaluate({"user" => user})
|
37
|
+
end
|
38
|
+
|
26
39
|
def test_inspect
|
27
40
|
expr = Sablon::Expression.parse("user.first_name")
|
28
41
|
assert_equal "«user.first_name»", expr.inspect
|
Binary file
|
Binary file
|
@@ -36,57 +36,60 @@ module MailMergeParser
|
|
36
36
|
|
37
37
|
def test_replace
|
38
38
|
field.replace("Hello")
|
39
|
-
|
40
|
-
<w:r w:rsidR=\"004B49F0\">
|
39
|
+
xml = <<-xml
|
40
|
+
<w:r w:rsidR=\"004B49F0\">
|
41
41
|
<w:rPr><w:noProof/></w:rPr>
|
42
42
|
<w:t>Hello</w:t>
|
43
43
|
</w:r>
|
44
|
-
|
44
|
+
xml
|
45
|
+
assert_equal body_xml.strip, xml.strip
|
45
46
|
end
|
46
47
|
|
47
48
|
def test_replace_with_newlines
|
48
49
|
field.replace("First\nSecond\n\nThird")
|
49
|
-
|
50
|
-
assert_equal <<-body_xml.strip, body_xml
|
50
|
+
xml = <<-xml
|
51
51
|
<w:r w:rsidR=\"004B49F0\">
|
52
52
|
<w:rPr><w:noProof/></w:rPr>
|
53
53
|
<w:t>First</w:t><w:br/><w:t>Second</w:t><w:br/><w:br/><w:t>Third</w:t>
|
54
54
|
</w:r>
|
55
|
-
|
55
|
+
xml
|
56
|
+
assert_equal body_xml.strip, xml.strip
|
56
57
|
end
|
57
58
|
|
58
59
|
def test_replace_with_nil
|
59
60
|
field.replace(nil)
|
60
|
-
|
61
|
-
assert_equal <<-body_xml.strip, body_xml.gsub(/^\s+$/,'')
|
61
|
+
xml = <<-xml
|
62
62
|
<w:r w:rsidR=\"004B49F0\">
|
63
63
|
<w:rPr><w:noProof/></w:rPr>
|
64
64
|
|
65
65
|
</w:r>
|
66
|
-
|
66
|
+
xml
|
67
|
+
assert_equal body_xml.gsub(/^\s+$/,'').strip, xml.strip
|
67
68
|
end
|
68
69
|
|
69
70
|
def test_replace_with_numeric
|
70
71
|
field.replace(45)
|
71
|
-
|
72
|
-
assert_equal <<-body_xml.strip, body_xml.gsub(/^\s+$/,'')
|
72
|
+
xml = <<-xml
|
73
73
|
<w:r w:rsidR=\"004B49F0\">
|
74
74
|
<w:rPr><w:noProof/></w:rPr>
|
75
75
|
<w:t>45</w:t>
|
76
76
|
</w:r>
|
77
|
-
|
77
|
+
xml
|
78
|
+
assert_equal body_xml.gsub(/^\s+$/,'').strip, xml.strip
|
78
79
|
end
|
79
80
|
|
80
81
|
private
|
82
|
+
|
81
83
|
def xml
|
82
|
-
|
84
|
+
xml = <<-xml
|
83
85
|
<w:fldSimple w:instr=" MERGEFIELD =first_name \\* MERGEFORMAT ">
|
84
86
|
<w:r w:rsidR="004B49F0">
|
85
87
|
<w:rPr><w:noProof/></w:rPr>
|
86
88
|
<w:t>«=first_name»</w:t>
|
87
89
|
</w:r>
|
88
90
|
</w:fldSimple>
|
89
|
-
|
91
|
+
xml
|
92
|
+
wrap(xml)
|
90
93
|
end
|
91
94
|
end
|
92
95
|
|
@@ -99,7 +102,7 @@ body_xml
|
|
99
102
|
|
100
103
|
def test_replace
|
101
104
|
field.replace("Hello")
|
102
|
-
|
105
|
+
xml = <<-xml
|
103
106
|
<w:r w:rsidR="004B49F0">
|
104
107
|
<w:rPr>
|
105
108
|
<w:b/>
|
@@ -107,13 +110,13 @@ body_xml
|
|
107
110
|
</w:rPr>
|
108
111
|
<w:t>Hello</w:t>
|
109
112
|
</w:r>
|
110
|
-
|
113
|
+
xml
|
114
|
+
assert_equal body_xml.strip, xml.strip
|
111
115
|
end
|
112
116
|
|
113
117
|
def test_replace_with_newlines
|
114
118
|
field.replace("First\nSecond\n\nThird")
|
115
|
-
|
116
|
-
assert_equal <<-body_xml.strip, body_xml
|
119
|
+
xml = <<-xml
|
117
120
|
<w:r w:rsidR="004B49F0">
|
118
121
|
<w:rPr>
|
119
122
|
<w:b/>
|
@@ -121,13 +124,14 @@ body_xml
|
|
121
124
|
</w:rPr>
|
122
125
|
<w:t>First</w:t><w:br/><w:t>Second</w:t><w:br/><w:br/><w:t>Third</w:t>
|
123
126
|
</w:r>
|
124
|
-
|
127
|
+
xml
|
128
|
+
assert_equal body_xml.strip, xml.strip
|
125
129
|
end
|
126
130
|
|
127
131
|
def test_replace_with_nil
|
128
132
|
field.replace(nil)
|
129
133
|
|
130
|
-
|
134
|
+
xml = <<-xml
|
131
135
|
<w:r w:rsidR="004B49F0">
|
132
136
|
<w:rPr>
|
133
137
|
<w:b/>
|
@@ -135,12 +139,14 @@ body_xml
|
|
135
139
|
</w:rPr>
|
136
140
|
|
137
141
|
</w:r>
|
138
|
-
|
142
|
+
xml
|
143
|
+
assert_equal body_xml.gsub(/^\s+$/,'').strip, xml.strip
|
139
144
|
end
|
140
145
|
|
141
146
|
private
|
147
|
+
|
142
148
|
def xml
|
143
|
-
|
149
|
+
xml = <<-xml
|
144
150
|
<w:r w:rsidR="00BE47B1" w:rsidRPr="00BE47B1">
|
145
151
|
<w:rPr><w:b/></w:rPr>
|
146
152
|
<w:fldChar w:fldCharType="begin"/>
|
@@ -164,7 +170,8 @@ body_xml
|
|
164
170
|
<w:rPr><w:b/></w:rPr>
|
165
171
|
<w:fldChar w:fldCharType="end"/>
|
166
172
|
</w:r>
|
167
|
-
|
173
|
+
xml
|
174
|
+
wrap(xml)
|
168
175
|
end
|
169
176
|
end
|
170
177
|
|
@@ -176,8 +183,9 @@ body_xml
|
|
176
183
|
end
|
177
184
|
|
178
185
|
private
|
186
|
+
|
179
187
|
def xml
|
180
|
-
|
188
|
+
xml = <<-xml
|
181
189
|
<w:p w14:paraId="0CF428D7" w14:textId="77777777" w:rsidR="00043618" w:rsidRDefault="00043618" w:rsidP="00B960C2">
|
182
190
|
<w:pPr>
|
183
191
|
<w:pStyle w:val="Footer" />
|
@@ -218,7 +226,28 @@ body_xml
|
|
218
226
|
<w:fldChar w:fldCharType="end" />
|
219
227
|
</w:r>
|
220
228
|
</w:p>
|
221
|
-
|
229
|
+
xml
|
230
|
+
wrap(xml)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class FieldWithWhitespaceTest < Sablon::TestCase
|
235
|
+
include SharedBehavior
|
236
|
+
|
237
|
+
def test_recognizes_expression
|
238
|
+
assert_equal ["=title"], fields.map(&:expression)
|
239
|
+
end
|
240
|
+
|
241
|
+
def xml
|
242
|
+
xml = <<-xml
|
243
|
+
<w:fldSimple w:instr=" MERGEFIELD =title \\* MERGEFORMAT ">
|
244
|
+
<w:r w:rsidR="004B49F0">
|
245
|
+
<w:rPr><w:noProof/></w:rPr>
|
246
|
+
<w:t>«=title»</w:t>
|
247
|
+
</w:r>
|
248
|
+
</w:fldSimple>
|
249
|
+
xml
|
250
|
+
wrap(xml)
|
222
251
|
end
|
223
252
|
end
|
224
253
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sablon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yves Senn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -98,7 +98,8 @@ description: Sablon is a document template processor. At this time it works only
|
|
98
98
|
docx and MailMerge fields.
|
99
99
|
email:
|
100
100
|
- yves.senn@gmail.com
|
101
|
-
executables:
|
101
|
+
executables:
|
102
|
+
- sablon
|
102
103
|
extensions: []
|
103
104
|
extra_rdoc_files: []
|
104
105
|
files:
|
@@ -108,6 +109,7 @@ files:
|
|
108
109
|
- LICENSE.txt
|
109
110
|
- README.md
|
110
111
|
- Rakefile
|
112
|
+
- bin/sablon
|
111
113
|
- lib/sablon.rb
|
112
114
|
- lib/sablon/operations.rb
|
113
115
|
- lib/sablon/parser/mail_merge.rb
|
@@ -120,9 +122,13 @@ files:
|
|
120
122
|
- misc/output.png
|
121
123
|
- misc/template.png
|
122
124
|
- sablon.gemspec
|
125
|
+
- test/executable_test.rb
|
123
126
|
- test/expression_test.rb
|
124
127
|
- test/fixtures/sablon_sample.docx
|
125
128
|
- test/fixtures/sablon_template.docx
|
129
|
+
- test/fixtures/shopping_list_context.json
|
130
|
+
- test/fixtures/shopping_list_sample.docx
|
131
|
+
- test/fixtures/shopping_list_template.docx
|
126
132
|
- test/fixtures/xml/complex_field.xml
|
127
133
|
- test/fixtures/xml/conditional.xml
|
128
134
|
- test/fixtures/xml/conditional_with_predicate.xml
|
@@ -163,14 +169,18 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
169
|
version: '0'
|
164
170
|
requirements: []
|
165
171
|
rubyforge_project:
|
166
|
-
rubygems_version: 2.
|
172
|
+
rubygems_version: 2.4.5
|
167
173
|
signing_key:
|
168
174
|
specification_version: 4
|
169
175
|
summary: docx tempalte processor
|
170
176
|
test_files:
|
177
|
+
- test/executable_test.rb
|
171
178
|
- test/expression_test.rb
|
172
179
|
- test/fixtures/sablon_sample.docx
|
173
180
|
- test/fixtures/sablon_template.docx
|
181
|
+
- test/fixtures/shopping_list_context.json
|
182
|
+
- test/fixtures/shopping_list_sample.docx
|
183
|
+
- test/fixtures/shopping_list_template.docx
|
174
184
|
- test/fixtures/xml/complex_field.xml
|
175
185
|
- test/fixtures/xml/conditional.xml
|
176
186
|
- test/fixtures/xml/conditional_with_predicate.xml
|