clamsy 0.0.2 → 0.0.3

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.
@@ -1,3 +1,7 @@
1
+ === 0.0.3 (Apr 25, 2010)
2
+
3
+ * support to replace pictures (useful for inserting digital signatures) [#ngty]
4
+
1
5
  === 0.0.2 (Apr 22, 2010)
2
6
 
3
7
  = 2nd official (yet still as embarrassing) release
@@ -4,56 +4,204 @@ Ruby wrapper for generating a single pdf for multiple contexts from an odt templ
4
4
 
5
5
  clamsy = clumsy + shellish
6
6
  | |
7
- | |-- under the hood, we are making system calls like ooffice,
8
- | ps2pdf & pdf2ps, etc.
7
+ | |-- under the hood, we are making system calls to ooffice &
8
+ | cups server needs to be running
9
9
  |
10
- .-- setup isn't straight forward, need to install a couple of packages
10
+ |-- setup isn't straight forward, need to install a couple of packages
11
11
  (how bad it is depends on ur platform) & even manual setting up of
12
- cups printer is needed
12
+ cups-pdf printer is needed
13
13
 
14
- == Using It (sorry, still WIP for more decent examples)
14
+ == 1. Basics
15
15
 
16
- #1. Generating single context pdf:
16
+ A template odt is just an ordinary odt (http://en.wikipedia.org/wiki/OpenDocument).
17
+ In order for it to function as a template, we just need to decorate it (just use
18
+ openoffice, pls don't buy ms office for that) with placeholders & embedded ruby code.
19
+
20
+ Under the hood, clamsy is using a slightly hacked version of the awesome tenjin
21
+ template engine (http://www.kuwata-lab.com/tenjin) to do the rendering. The following
22
+ is all u need to know to write odt templates:
23
+
24
+ * '{? ... ?}' represents embedded Ruby statement (this differs from vanilla tenjin)
25
+ * '#{ ... }' represents embedded Ruby expression
26
+ * '${ ... }' represents embedded Ruby expression which is to be escaped
27
+ (eg. '& < > "' are escaped to '&amp; &lt; &gt; &quot;')
28
+
29
+ == 2. Examples
30
+
31
+ === 2.1. Text Replacement Example
32
+
33
+ Template Odt:
34
+
35
+ --------------------------------------------
36
+ #{@company_full_name}
37
+ #{@company_address_line_1}
38
+ #{@company_address_line_2}
39
+
40
+ S/N Item Description Amount (SGD)
41
+ {? @items.each_with_index do |item, i| ?}
42
+ #{i+1} #{item.description} #{amount}
43
+ {? end ?}
44
+ Total #{total}
45
+ ----------------------------------- Page 1 -
46
+
47
+ Code:
48
+
49
+ class Item
50
+ attr_reader :description, :amount
51
+ def initialize(description, amount)
52
+ @description, @amount = description, amount
53
+ end
54
+ end
55
+
56
+ items = [
57
+ Item.new('Shells (x2 Bags)', 10),
58
+ Item.new('Clams (x2 Bags)', 20)
59
+ ]
60
+
61
+ context = {
62
+ :company_full_name => 'Monster Inc',
63
+ :company_address_line_1 => 'Planet Mars, Street xyz,',
64
+ :company_address_line_2 => 'Postal Code 009900',
65
+ :items => items,
66
+ :total => items.inject(0) {|sum, item| sum + item.amount }
67
+ }
68
+
69
+ Clamsy.process(
70
+ context,
71
+ path_to_template_odt, # eg. '/tmp/invoice.odt'
72
+ path_to_final_pdf, # eg. '/tmp/invoice.pdf'
73
+ )
74
+
75
+ Generated Pdf:
76
+
77
+ --------------------------------------------
78
+ Monster Inc
79
+ Planet Mars, Street xyz,
80
+ Postal Code 009900
81
+
82
+ S/N Item Description Amount (SGD)
83
+
84
+ 1 Shells (x2 Bags) 10
85
+ 2 Clams (x2 Bags) 20
86
+
87
+ Total 30
88
+ ----------------------------------- Page 1 -
89
+
90
+ === 2.2: Multiple Contexts Example
91
+
92
+ Taking the above example one step further, if we have multiple contexts:
93
+
94
+ items << Item.new('Shrimps (x2 Bags)', 15)
95
+ items << Item.new('Prawns (x2 Bags)', 25)
96
+
97
+ contexts = [{
98
+ :company_full_name => 'Monster Inc',
99
+ :company_address_line_1 => 'Planet Mars, Street xyz,',
100
+ :company_address_line_2 => 'Postal Code 009900',
101
+ :items => items[0..1],
102
+ :total => items[0..1].inject(0) {|sum, item| sum + item.amount }
103
+ }, {
104
+ :company_full_name => 'Fisherman Inc',
105
+ :company_address_line_1 => 'Planet Jupiter, Street xyz,',
106
+ :company_address_line_2 => 'Postal Code 008800',
107
+ :items => items[2..3],
108
+ :total => items[2..3].inject(0) {|sum, item| sum + item.amount }
109
+ }]
17
110
 
18
111
  Clamsy.process(
19
- {:someone => 'Peter', :mood => 'Happy'},
20
- path_to_template_odt,
21
- path_to_final_pdf
112
+ contexts,
113
+ path_to_template_odt, # eg. '/tmp/invoice.odt'
114
+ path_to_final_pdf # eg. '/tmp/invoice.pdf'
22
115
  )
23
116
 
24
- #2. Generating multi-contexts pdf:
117
+ Generated Pdf:
118
+
119
+ --------------------------------------------
120
+ Monster Inc
121
+ Planet Mars, Street xyz,
122
+ Postal Code 009900
123
+
124
+ S/N Item Description Amount (SGD)
125
+
126
+ 1 Shells (x2 Bags) 10
127
+ 2 Clams (x2 Bags) 20
128
+
129
+ Total 30
130
+ ----------------------------------- Page 1 -
131
+ --------------------------------------------
132
+ Fisherman Inc
133
+ Planet Jupiter, Street xyz,
134
+ Postal Code 008800
135
+
136
+ S/N Item Description Amount (SGD)
137
+
138
+ 1 Shrimps (x2 Bags) 15
139
+ 2 Prawns (x2 Bags) 25
140
+
141
+ Total 40
142
+ ----------------------------------- Page 1 -
143
+
144
+ As you can see, (whether you like it or not) the pages are merely concatenated
145
+ together to form a single pdf !!
146
+
147
+ === 2.3: Image Replacement Example
148
+
149
+ It is possible to replace images in an odt file, a useful feature if u need to insert
150
+ digital signature into the final pdf. To acheive this, u have to:
151
+ * insert a placeholder image into the odt file,
152
+ * resize & move it to your heart content
153
+ * right click on the picture, under [Picture ...]/[Options], assign a unique value
154
+ for [Name]
155
+
156
+ Template Odt:
157
+
158
+ --------------------------------------------
159
+ ~~~~~~~~~~~~~~~~~~
160
+ Hey | nice_guy_image |
161
+ ~~~~~~~~~~~~~~~~~~
162
+
163
+ This is a gentle reminder that i still
164
+ owe you some money ...
165
+
166
+ ~~~~~~~~~~~~~~~~~~~~~~
167
+ | my_signature_image |
168
+ ~~~~~~~~~~~~~~~~~~~~~~
169
+ ----------------------------------- Page 1 -
170
+
171
+ Assuming 'nice_guy_image' & 'my_signature_image' as names of the images, the following
172
+ code does the appropriate replacing of images:
25
173
 
26
174
  Clamsy.process(
27
- [
28
- {:someone => 'Peter', :mood => 'Happy'},
29
- {:someone => 'Jane', :mood => 'Sad'}
30
- ],
31
- path_to_template_odt,
32
- path_to_final_pdf
175
+ context = {:_pictures => {
176
+ :nice_guy_image => path_to_avatar_image, # eg. '/tmp/handsome.png'
177
+ :my_signature_image => path_to_signature_image # eg. '/tmp/signature.png'
178
+ }},
179
+ path_to_template_odt # eg. '/tmp/confession.odt'
180
+ path_to_final_pdf # eg. '/tmp/confession.pdf'
33
181
  )
34
182
 
35
- == Pre-requisites
183
+ == 3. Pre-requisites
36
184
 
37
- #1. Archlinux
185
+ === 3.1. Archlinux
38
186
 
39
- * Installing packages:
40
- $ sudo pacman -S ghostscript cups cups-pdf go-openoffice
187
+ * Installing packages:
188
+ $ sudo pacman -S ghostscript cups cups-pdf go-openoffice
41
189
 
42
- * Setting up the cups-pdf virtual printer by following instructions @
43
- http://wiki.archlinux.org/index.php/CUPS#Configuring_CUPS-PDF_virtual_printer
190
+ * Setting up the cups-pdf virtual printer by following instructions @
191
+ http://wiki.archlinux.org/index.php/CUPS#Configuring_CUPS-PDF_virtual_printer
44
192
 
45
- * Making sure cups is running:
46
- $ sudo /etc/rc.d/cups start
193
+ * Making sure cups is running:
194
+ $ sudo /etc/rc.d/cups start
47
195
 
48
- #2. Ubuntu (to-be-updated)
196
+ === 3.2. Ubuntu (to-be-updated)
49
197
 
50
- #3. Mac (to-be-updated)
198
+ === 3.3. Mac (to-be-updated)
51
199
 
52
200
  == TODO
53
201
 
54
202
  * add support for configuration, eg. Clamsy.configure {|config| ... }
55
203
 
56
- * automate cups-pdf printer setup
204
+ * automate cups-pdf printer setup or have ooffice directly export odt to pdf
57
205
 
58
206
  * avoid making system call to 'ooffice'
59
207
 
data/Rakefile CHANGED
@@ -12,6 +12,7 @@ begin
12
12
  gem.authors = ["NgTzeYang"]
13
13
  gem.add_dependency "rubyzip", "= 0.9.4"
14
14
  gem.add_dependency "rghost", "= 0.8.7.2"
15
+ gem.add_dependency "nokogiri", "= 1.4.1"
15
16
  gem.add_development_dependency "bacon", ">= 1.1.0"
16
17
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
18
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{clamsy}
8
- s.version = "0.0.2"
8
+ s.version = "0.0.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["NgTzeYang"]
12
- s.date = %q{2010-04-22}
12
+ s.date = %q{2010-04-25}
13
13
  s.description = %q{}
14
14
  s.email = %q{ngty77@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -28,12 +28,16 @@ Gem::Specification.new do |s|
28
28
  "lib/clamsy.rb",
29
29
  "lib/clamsy/tenjin.rb",
30
30
  "spec/clamsy_spec.rb",
31
+ "spec/data/clamsy.png",
32
+ "spec/data/clamsy2.png",
31
33
  "spec/data/embedded_ruby_example.odt",
32
34
  "spec/data/embedded_ruby_example.pdf",
33
35
  "spec/data/escaped_text_example.odt",
34
36
  "spec/data/escaped_text_example.pdf",
35
37
  "spec/data/multiple_contexts_example.odt",
36
38
  "spec/data/multiple_contexts_example.pdf",
39
+ "spec/data/picture_example.odt",
40
+ "spec/data/picture_example.pdf",
37
41
  "spec/data/plain_text_example.odt",
38
42
  "spec/data/plain_text_example.pdf",
39
43
  "spec/spec_helper.rb"
@@ -55,15 +59,18 @@ Gem::Specification.new do |s|
55
59
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
60
  s.add_runtime_dependency(%q<rubyzip>, ["= 0.9.4"])
57
61
  s.add_runtime_dependency(%q<rghost>, ["= 0.8.7.2"])
62
+ s.add_runtime_dependency(%q<nokogiri>, ["= 1.4.1"])
58
63
  s.add_development_dependency(%q<bacon>, [">= 1.1.0"])
59
64
  else
60
65
  s.add_dependency(%q<rubyzip>, ["= 0.9.4"])
61
66
  s.add_dependency(%q<rghost>, ["= 0.8.7.2"])
67
+ s.add_dependency(%q<nokogiri>, ["= 1.4.1"])
62
68
  s.add_dependency(%q<bacon>, [">= 1.1.0"])
63
69
  end
64
70
  else
65
71
  s.add_dependency(%q<rubyzip>, ["= 0.9.4"])
66
72
  s.add_dependency(%q<rghost>, ["= 0.8.7.2"])
73
+ s.add_dependency(%q<nokogiri>, ["= 1.4.1"])
67
74
  s.add_dependency(%q<bacon>, [">= 1.1.0"])
68
75
  end
69
76
  end
@@ -1,6 +1,7 @@
1
1
  require 'ftools'
2
2
  require 'digest/md5'
3
3
  require 'tempfile'
4
+ require 'nokogiri'
4
5
  require 'zip/zip'
5
6
  require 'clamsy/tenjin'
6
7
  require 'rghost'
@@ -47,9 +48,10 @@ module Clamsy
47
48
 
48
49
  def render(context)
49
50
  @context_id = Digest::MD5.hexdigest(context.to_s)
50
- Zip::ZipFile.open(working_odt.path) do |zip|
51
- zip.select {|entry| entry.file? && entry.to_s =~ /\.xml$/ }.each do |entry|
52
- zip.get_output_stream(entry.to_s) {|io| io.write(workers[entry].render(context)) }
51
+ Zip::ZipFile.open(working_odt.path) do |@zip|
52
+ @zip.select {|entry| entry.file? && entry.to_s =~ /\.xml$/ }.each do |entry|
53
+ @zip.get_output_stream(entry.to_s) {|io| io.write(workers[entry].render(context)) }
54
+ replace_pictures(entry, context[:_pictures] || {})
53
55
  end
54
56
  end
55
57
  working_odt
@@ -57,6 +59,14 @@ module Clamsy
57
59
 
58
60
  private
59
61
 
62
+ def replace_pictures(entry, pictures)
63
+ xpaths = lambda {|name| %\//drawframe[@drawname="#{name}"]/drawimage/@xlinkhref\ }
64
+ doc = Nokogiri::XML(entry.get_input_stream.read.gsub(':','')) # Hack to avoid namespace error
65
+ pictures.each do |name, path|
66
+ (node = doc.xpath(xpaths[name])[0]) && @zip.replace(node.value, path)
67
+ end
68
+ end
69
+
60
70
  def working_odt
61
71
  (@working_odts ||= {})[@context_id] ||=
62
72
  begin
@@ -1,15 +1,17 @@
1
- require 'spec_helper'
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
3
  describe "Clamsy" do
4
4
 
5
5
  behaves_like 'has standard files support'
6
6
 
7
7
  before do
8
- @check_processing_yields_text = lambda do |contexts, example|
8
+ @check_processing_yields_content = lambda do |contexts, example|
9
9
  generated_pdf = tmp_file('clamsy_pdf').path
10
- expected_content = comparable_content(expected_pdf(example))
11
10
  Clamsy.process(contexts, template_odt(example), generated_pdf)
12
- comparable_content(generated_pdf).should.equal expected_content
11
+ expected_content = comparable_content(expected_pdf(example))
12
+ generated_content = comparable_content(generated_pdf)
13
+ generated_content.size.should.equal expected_content.size
14
+ (generated_content - expected_content).should.equal []
13
15
  end
14
16
  end
15
17
 
@@ -17,15 +19,22 @@ describe "Clamsy" do
17
19
  trash_tmp_files
18
20
  end
19
21
 
22
+ it 'should do picture replacement for matching <draw:frame draw:name="..." />' do
23
+ @check_processing_yields_content[
24
+ context = {:_pictures => {:to_be_replaced_pic => data_file('clamsy.png')}},
25
+ example = :picture
26
+ ]
27
+ end
28
+
20
29
  it 'should do #{...} plain text replacement' do
21
- @check_processing_yields_text[
30
+ @check_processing_yields_content[
22
31
  context = {:someone => 'Peter', :mood => 'Happy'},
23
32
  example = :plain_text
24
33
  ]
25
34
  end
26
35
 
27
36
  it 'should do ${...} escaped (santized) replacement' do
28
- @check_processing_yields_text[
37
+ @check_processing_yields_content[
29
38
  context = {:someone => '<Peter>', :mood => '<Happy>'},
30
39
  example = :escaped_text
31
40
  ]
@@ -38,14 +47,14 @@ describe "Clamsy" do
38
47
  @name, @mood = name, mood
39
48
  end
40
49
  end
41
- @check_processing_yields_text[
50
+ @check_processing_yields_content[
42
51
  context = {:everyone => [@someone.new('Peter','Happy'), @someone.new('Jane','Sad')]},
43
52
  example = :embedded_ruby
44
53
  ]
45
54
  end
46
55
 
47
56
  it 'should concat multiple contexts processing to a single pdf' do
48
- @check_processing_yields_text[
57
+ @check_processing_yields_content[
49
58
  contexts = [{:someone => 'Peter', :mood => 'Happy'}, {:someone => 'Jane', :mood => 'Sad'}],
50
59
  example = :multiple_contexts
51
60
  ]
Binary file
Binary file
@@ -10,25 +10,37 @@ require 'clamsy'
10
10
 
11
11
  shared 'has standard files support' do
12
12
  class << self
13
+
14
+ INSIGNIFICANT_AND_UNMATCHABLE_PATTERNS = [
15
+ /^q\[\-?\d+\.?\d*( \-?\d+\.?\d*){5}\]concat\n$/,
16
+ /^\d+\.?\d*( \d+\.?\d*){3} re\n$/
17
+ ]
18
+
13
19
  def data_file(file)
14
20
  File.join(File.dirname(__FILE__), 'data', file)
15
21
  end
22
+
16
23
  def template_odt(name)
17
24
  data_file("#{name}_example.odt")
18
25
  end
26
+
19
27
  def expected_pdf(name)
20
28
  data_file("#{name}_example.pdf")
21
29
  end
30
+
22
31
  def comparable_content(file)
23
32
  RGhost::Convert.new(file).to(:ps).read.grep(/^[^%][^%]?/).
24
- reject {|line| line =~ /^q\[\-?\d+( \-?\d+){5}\]concat\n$/ }
33
+ reject {|line| INSIGNIFICANT_AND_UNMATCHABLE_PATTERNS.any? {|regexp| regexp =~ line } }
25
34
  end
35
+
26
36
  def trash_tmp_files
27
37
  (@trashable_tmp_files || []).select {|f| f.path }.map(&:unlink)
28
38
  end
39
+
29
40
  def tmp_file(file_name)
30
41
  ((@trashable_tmp_files ||= []) << Tempfile.new(file_name))[-1]
31
42
  end
43
+
32
44
  end
33
45
  end
34
46
 
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 2
9
- version: 0.0.2
8
+ - 3
9
+ version: 0.0.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - NgTzeYang
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-22 00:00:00 +08:00
17
+ date: 2010-04-25 00:00:00 +08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -47,9 +47,23 @@ dependencies:
47
47
  type: :runtime
48
48
  version_requirements: *id002
49
49
  - !ruby/object:Gem::Dependency
50
- name: bacon
50
+ name: nokogiri
51
51
  prerelease: false
52
52
  requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 1
58
+ - 4
59
+ - 1
60
+ version: 1.4.1
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: bacon
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
53
67
  requirements:
54
68
  - - ">="
55
69
  - !ruby/object:Gem::Version
@@ -59,7 +73,7 @@ dependencies:
59
73
  - 0
60
74
  version: 1.1.0
61
75
  type: :development
62
- version_requirements: *id003
76
+ version_requirements: *id004
63
77
  description: ""
64
78
  email: ngty77@gmail.com
65
79
  executables: []
@@ -81,12 +95,16 @@ files:
81
95
  - lib/clamsy.rb
82
96
  - lib/clamsy/tenjin.rb
83
97
  - spec/clamsy_spec.rb
98
+ - spec/data/clamsy.png
99
+ - spec/data/clamsy2.png
84
100
  - spec/data/embedded_ruby_example.odt
85
101
  - spec/data/embedded_ruby_example.pdf
86
102
  - spec/data/escaped_text_example.odt
87
103
  - spec/data/escaped_text_example.pdf
88
104
  - spec/data/multiple_contexts_example.odt
89
105
  - spec/data/multiple_contexts_example.pdf
106
+ - spec/data/picture_example.odt
107
+ - spec/data/picture_example.pdf
90
108
  - spec/data/plain_text_example.odt
91
109
  - spec/data/plain_text_example.pdf
92
110
  - spec/spec_helper.rb