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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac6584d3c9681b51c131bb10ae1f76fc0b1b5538
4
- data.tar.gz: 80475970e6f321ef36650bf593f4e36e08a29101
3
+ metadata.gz: b9bed4d04a0e610c47abed3aa044222d37c7f716
4
+ data.tar.gz: 0942a8f508bc8198b41d4bee926e0fbf7e4a4ec3
5
5
  SHA512:
6
- metadata.gz: 16e48b0a357ae6449daad1dbf7d26329e4616f103fa53c568ecb01bfd7b36a5830a70a773e83c4c2504c906bcfaddf9689926dbbe0b3d5f830edc8634d4c0a31
7
- data.tar.gz: d56a4d4fcbeec941f44e0dd1f3dc96731a383c808cf3fc571a6cad71a6b13a98f057eba4a190215debe8c02d2e16bf93c48384fe8a815a2f0c5f614c3cf817d7
6
+ metadata.gz: 5cd8914ff55c1178b946fcb9704759d579816d332e2da8b69b7a0da9d6792bfc9489c9cd1d05cd3c48a37ca5dc9f3d7e3dcbeeed04bdda6308dd4e0defed3d5d
7
+ data.tar.gz: 04ae3adf13a090fdc4adca34d1b038281cc5468444217f4fcccdaf3e651c170a7eaab25742ef5d003a4e4e1a0c7d13dda651d2e51555aec4ee2e6b6f6e822741
data/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/sablon.svg)](http://badge.fury.io/rb/sablon) [![Build Status](https://travis-ci.org/senny/sablon.svg?branch=master)](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 it easy to create your
7
- templates.
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. If you encounter any issues along the way please report.*
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
- The following projects address a similar goal and inspired the work on Sablon:
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)
@@ -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)
@@ -52,20 +52,24 @@ module Sablon
52
52
  end
53
53
  end
54
54
 
55
- class SimpleMethodCall < Struct.new(:receiver, :method)
55
+ class LookupOrMethodCall < Struct.new(:receiver_expr, :method)
56
56
  def evaluate(context)
57
- receiver.evaluate(context).public_send method
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
- "«#{receiver.name}.#{method}»"
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
- SimpleMethodCall.new(Variable.new(parts.first), parts.last)
72
+ LookupOrMethodCall.new(Variable.new(parts.first), parts.last)
69
73
  else
70
74
  Variable.new(expression)
71
75
  end
@@ -2,7 +2,7 @@ module Sablon
2
2
  module Parser
3
3
  class MailMerge
4
4
  class MergeField
5
- KEY_PATTERN = /^\s*MERGEFIELD ([^ ]+)\s+\\\* MERGEFORMAT\s*$/
5
+ KEY_PATTERN = /^\s*MERGEFIELD\s+([^ ]+)\s+\\\*\s+MERGEFORMAT\s*$/
6
6
  def expression
7
7
  $1 if @raw_expression =~ KEY_PATTERN
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module Sablon
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -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
@@ -16,13 +16,26 @@ class VariableExpressionTest < Sablon::TestCase
16
16
  end
17
17
  end
18
18
 
19
- class SimpleMethodCallTest < Sablon::TestCase
20
- def test_calls_method_on_context_variable
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "title": "Shopping List",
3
+ "items": [
4
+ {"name": "Milk", "amount": "1L"},
5
+ {"name": "Sugar", "amount": "1kg"},
6
+ {"name": "Tomatoes", "amount": "500g"}
7
+ ]
8
+ }
@@ -36,57 +36,60 @@ module MailMergeParser
36
36
 
37
37
  def test_replace
38
38
  field.replace("Hello")
39
- assert_equal <<-body_xml.strip, body_xml
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
- body_xml
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
- body_xml
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
- body_xml
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
- body_xml
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
- wrap(<<-xml)
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
- xml
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
- assert_equal <<-body_xml.strip, body_xml
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
- body_xml
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
- body_xml
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
- assert_equal <<-body_xml.strip, body_xml.gsub(/^\s+$/,'')
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
- body_xml
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
- wrap(<<-xml)
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
- xml
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
- wrap(<<-xml)
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
- xml
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.3
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: 2014-11-17 00:00:00.000000000 Z
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.2.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