clamsy 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.
Files changed (49) hide show
  1. data/HISTORY.txt +7 -0
  2. data/README.rdoc +56 -15
  3. data/VERSION +1 -1
  4. data/clamsy.gemspec +51 -16
  5. data/clamsy.png +0 -0
  6. data/lib/clamsy.rb +26 -123
  7. data/lib/clamsy.yml +8 -0
  8. data/lib/clamsy/base_printer.rb +64 -0
  9. data/lib/clamsy/configuration.rb +128 -0
  10. data/lib/clamsy/cups_pdf_printer.rb +26 -0
  11. data/lib/clamsy/file_system_support.rb +36 -0
  12. data/lib/clamsy/template_open_doc.rb +122 -0
  13. data/spec/clamsy/base_printer_spec.rb +19 -0
  14. data/spec/clamsy/configuration_spec.rb +290 -0
  15. data/spec/clamsy/cups_pdf_printer_spec.rb +40 -0
  16. data/spec/{data → clamsy/data}/clamsy.png +0 -0
  17. data/spec/{data → clamsy/data}/clamsy2.png +0 -0
  18. data/spec/clamsy/data/embedded_ruby_after.odt +0 -0
  19. data/spec/clamsy/data/embedded_ruby_before.odt +0 -0
  20. data/spec/clamsy/data/escaped_text_after.odt +0 -0
  21. data/spec/{data/escaped_text_example.odt → clamsy/data/escaped_text_before.odt} +0 -0
  22. data/spec/clamsy/data/invalid_content_example.odt +0 -0
  23. data/spec/clamsy/data/invalid_zip_example.odt +0 -0
  24. data/spec/{data/multiple_contexts_example.odt → clamsy/data/multiple_contexts_after.odt} +0 -0
  25. data/spec/{data/plain_text_example.odt → clamsy/data/multiple_contexts_before.odt} +0 -0
  26. data/spec/clamsy/data/picture_after.odt +0 -0
  27. data/spec/{data/picture_example.odt → clamsy/data/picture_before.odt} +0 -0
  28. data/spec/clamsy/data/plain_text_after.odt +0 -0
  29. data/spec/clamsy/data/plain_text_before.odt +0 -0
  30. data/spec/clamsy/file_system_support_spec.rb +93 -0
  31. data/spec/clamsy/invalid_printer_spec.rb +21 -0
  32. data/spec/clamsy/template_open_doc_spec.rb +95 -0
  33. data/spec/{clamsy_spec.rb → integration/cups_pdf_printer_spec.rb} +12 -6
  34. data/spec/{data → integration/data}/embedded_ruby_example.odt +0 -0
  35. data/spec/{data → integration/data}/embedded_ruby_example.pdf +0 -0
  36. data/spec/integration/data/escaped_text_example.odt +0 -0
  37. data/spec/{data → integration/data}/escaped_text_example.pdf +0 -0
  38. data/spec/integration/data/multiple_contexts_example.odt +0 -0
  39. data/spec/{data → integration/data}/multiple_contexts_example.pdf +0 -0
  40. data/spec/integration/data/norm_clamsy.png +0 -0
  41. data/spec/integration/data/picture_example.odt +0 -0
  42. data/spec/integration/data/picture_example.pdf +0 -0
  43. data/spec/integration/data/plain_text_example.odt +0 -0
  44. data/spec/{data → integration/data}/plain_text_example.pdf +0 -0
  45. data/spec/integration/data/sunny_clamsy.png +0 -0
  46. data/spec/integration/has_stardand_integration_support_shared_spec.rb +27 -0
  47. data/spec/spec_helper.rb +1 -25
  48. metadata +52 -17
  49. data/spec/data/picture_example.pdf +0 -0
@@ -1,3 +1,10 @@
1
+ === 0.0.4 (May 04, 2010)
2
+
3
+ * fixed bug in multiple pictures replacement [#ngty]
4
+ * support for configuring clamsy (see README section 3) [#jasonong & #ngty]
5
+ * clamsy now has a logo (<PROJECT_ROOT>/clamsy.png) [#zhenyi]
6
+ * well-prepared for alternative printers support [#ngty]
7
+
1
8
  === 0.0.3 (Apr 25, 2010)
2
9
 
3
10
  * support to replace pictures (useful for inserting digital signatures) [#ngty]
@@ -22,9 +22,9 @@ template engine (http://www.kuwata-lab.com/tenjin) to do the rendering. The foll
22
22
  is all u need to know to write odt templates:
23
23
 
24
24
  * '{? ... ?}' represents embedded Ruby statement (this differs from vanilla tenjin)
25
- * '#{ ... }' represents embedded Ruby expression
26
25
  * '${ ... }' represents embedded Ruby expression which is to be escaped
27
26
  (eg. '& < > "' are escaped to '&amp; &lt; &gt; &quot;')
27
+ * '#{ ... }' represents embedded Ruby expression (u should really avoid using this)
28
28
 
29
29
  == 2. Examples
30
30
 
@@ -33,15 +33,15 @@ is all u need to know to write odt templates:
33
33
  Template Odt:
34
34
 
35
35
  --------------------------------------------
36
- #{@company_full_name}
37
- #{@company_address_line_1}
38
- #{@company_address_line_2}
36
+ ${@company_full_name}
37
+ ${@company_address_line_1}
38
+ ${@company_address_line_2}
39
39
 
40
40
  S/N Item Description Amount (SGD)
41
41
  {? @items.each_with_index do |item, i| ?}
42
- #{i+1} #{item.description} #{amount}
42
+ ${i+1} ${item.description} ${amount}
43
43
  {? end ?}
44
- Total #{total}
44
+ Total ${total}
45
45
  ----------------------------------- Page 1 -
46
46
 
47
47
  Code:
@@ -180,9 +180,50 @@ code does the appropriate replacing of images:
180
180
  path_to_final_pdf # eg. '/tmp/confession.pdf'
181
181
  )
182
182
 
183
- == 3. Pre-requisites
183
+ == 3. Configuration
184
+
185
+ === 3.1. User-defined ~/.clamsy.yml
186
+
187
+ printer: cups_pdf
188
+ cups_pdf:
189
+ cups_output_dir: /var/spool/cups-pdf/${USER}
190
+ cups_output_file:
191
+ ooffice_cmd: ooffice -norestore -nofirststartwizard -nologo -headless -pt Cups-PDF
192
+
193
+ NOTES:
194
+ * ${...} gets replaced by the corresponding ENV variable, eg. ${USER} gets replaced
195
+ by ENV['USER']
196
+ * printer ... describes which printer to use (currently, we ONLY have 'cups_pdf')
197
+ * cups_output_dir ... the directory where cups-pdf places the intermediate working pdf,
198
+ in linux box, it is specifed by the 'Out' variable in /etc/cups/cups-pdf.conf
199
+ * cups_output_file ... specifies the exact intermediate working pdf, when this is
200
+ specified, cups_output_dir is ignored
201
+ * ooffice_cmd ... the openoffice command & arguments to use
202
+
203
+ === 3.2. Clamsy\.configure {|config| ... }
204
+
205
+ Clamsy.configure do |config|
206
+ config.printer = 'another_printer'
207
+ config.config_file = '/path/to/alternative/clamsy.yml'
208
+ config.cups_output_dir = '/another/output/dir'
209
+ config.cups_output_file = lambda { Dir.glob('/yet/another/output/dir/*.pdf').sort.last }
210
+ end
211
+
212
+ NOTES:
213
+ * config_file ... alternative user-defined config file, when specified, ~/.clamsy.yml will
214
+ be ignored (whatever is supported in this config file is exactly the same as 3.1)
215
+ * cups_output_file ... unlike what we have in the yaml config file, this can be either a
216
+ proc or a string
184
217
 
185
- === 3.1. Archlinux
218
+ === 3.3. Clamsy\.process(...) {|config| ... }
219
+
220
+ Clamsy.process(contexts, template_odt, final_pdf) do |config|
221
+ # the rest is the same as 3.2
222
+ end
223
+
224
+ == 4. Pre-requisites
225
+
226
+ === 4.1. Archlinux
186
227
 
187
228
  * Installing packages:
188
229
  $ sudo pacman -S ghostscript cups cups-pdf go-openoffice
@@ -193,17 +234,15 @@ code does the appropriate replacing of images:
193
234
  * Making sure cups is running:
194
235
  $ sudo /etc/rc.d/cups start
195
236
 
196
- === 3.2. Ubuntu (to-be-updated)
237
+ === 4.2. Ubuntu (to-be-updated)
197
238
 
198
- === 3.3. Mac (to-be-updated)
239
+ === 4.3. Mac (to-be-updated)
199
240
 
200
241
  == TODO
201
242
 
202
- * add support for configuration, eg. Clamsy.configure {|config| ... }
203
-
204
- * automate cups-pdf printer setup or have ooffice directly export odt to pdf
243
+ * automate cups-pdf printer setup
205
244
 
206
- * avoid making system call to 'ooffice'
245
+ * more printers support (currently, we ONLY have Clamsy::CupsPdfPrinter)
207
246
 
208
247
  == Note on Patches/Pull Requests
209
248
 
@@ -219,7 +258,9 @@ code does the appropriate replacing of images:
219
258
 
220
259
  Written 2010 by:
221
260
 
222
- #1. NgTzeYang, contact ngty77[at]gmail.com or http://github.com/ngty
261
+ 1. NgTzeYang, contact http://github.com/ngty
262
+ 2. JasonOng, contact http://github.com/jasonong
263
+ 3. ZhenyiTan, contact http://github.com/zhenyi
223
264
 
224
265
  Released under the MIT license
225
266
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{clamsy}
8
- s.version = "0.0.3"
8
+ s.version = "0.0.4"
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-25}
12
+ s.date = %q{2010-05-04}
13
13
  s.description = %q{}
14
14
  s.email = %q{ngty77@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -25,21 +25,49 @@ Gem::Specification.new do |s|
25
25
  "Rakefile",
26
26
  "VERSION",
27
27
  "clamsy.gemspec",
28
+ "clamsy.png",
28
29
  "lib/clamsy.rb",
30
+ "lib/clamsy.yml",
31
+ "lib/clamsy/base_printer.rb",
32
+ "lib/clamsy/configuration.rb",
33
+ "lib/clamsy/cups_pdf_printer.rb",
34
+ "lib/clamsy/file_system_support.rb",
35
+ "lib/clamsy/template_open_doc.rb",
29
36
  "lib/clamsy/tenjin.rb",
30
- "spec/clamsy_spec.rb",
31
- "spec/data/clamsy.png",
32
- "spec/data/clamsy2.png",
33
- "spec/data/embedded_ruby_example.odt",
34
- "spec/data/embedded_ruby_example.pdf",
35
- "spec/data/escaped_text_example.odt",
36
- "spec/data/escaped_text_example.pdf",
37
- "spec/data/multiple_contexts_example.odt",
38
- "spec/data/multiple_contexts_example.pdf",
39
- "spec/data/picture_example.odt",
40
- "spec/data/picture_example.pdf",
41
- "spec/data/plain_text_example.odt",
42
- "spec/data/plain_text_example.pdf",
37
+ "spec/clamsy/base_printer_spec.rb",
38
+ "spec/clamsy/configuration_spec.rb",
39
+ "spec/clamsy/cups_pdf_printer_spec.rb",
40
+ "spec/clamsy/data/clamsy.png",
41
+ "spec/clamsy/data/clamsy2.png",
42
+ "spec/clamsy/data/embedded_ruby_after.odt",
43
+ "spec/clamsy/data/embedded_ruby_before.odt",
44
+ "spec/clamsy/data/escaped_text_after.odt",
45
+ "spec/clamsy/data/escaped_text_before.odt",
46
+ "spec/clamsy/data/invalid_content_example.odt",
47
+ "spec/clamsy/data/invalid_zip_example.odt",
48
+ "spec/clamsy/data/multiple_contexts_after.odt",
49
+ "spec/clamsy/data/multiple_contexts_before.odt",
50
+ "spec/clamsy/data/picture_after.odt",
51
+ "spec/clamsy/data/picture_before.odt",
52
+ "spec/clamsy/data/plain_text_after.odt",
53
+ "spec/clamsy/data/plain_text_before.odt",
54
+ "spec/clamsy/file_system_support_spec.rb",
55
+ "spec/clamsy/invalid_printer_spec.rb",
56
+ "spec/clamsy/template_open_doc_spec.rb",
57
+ "spec/integration/cups_pdf_printer_spec.rb",
58
+ "spec/integration/data/embedded_ruby_example.odt",
59
+ "spec/integration/data/embedded_ruby_example.pdf",
60
+ "spec/integration/data/escaped_text_example.odt",
61
+ "spec/integration/data/escaped_text_example.pdf",
62
+ "spec/integration/data/multiple_contexts_example.odt",
63
+ "spec/integration/data/multiple_contexts_example.pdf",
64
+ "spec/integration/data/norm_clamsy.png",
65
+ "spec/integration/data/picture_example.odt",
66
+ "spec/integration/data/picture_example.pdf",
67
+ "spec/integration/data/plain_text_example.odt",
68
+ "spec/integration/data/plain_text_example.pdf",
69
+ "spec/integration/data/sunny_clamsy.png",
70
+ "spec/integration/has_stardand_integration_support_shared_spec.rb",
43
71
  "spec/spec_helper.rb"
44
72
  ]
45
73
  s.homepage = %q{http://github.com/ngty/clamsy}
@@ -48,7 +76,14 @@ Gem::Specification.new do |s|
48
76
  s.rubygems_version = %q{1.3.6}
49
77
  s.summary = %q{A clumsily shellish way to generate a single pdf for multiple contexts from an odt template}
50
78
  s.test_files = [
51
- "spec/clamsy_spec.rb",
79
+ "spec/integration/cups_pdf_printer_spec.rb",
80
+ "spec/integration/has_stardand_integration_support_shared_spec.rb",
81
+ "spec/clamsy/file_system_support_spec.rb",
82
+ "spec/clamsy/base_printer_spec.rb",
83
+ "spec/clamsy/cups_pdf_printer_spec.rb",
84
+ "spec/clamsy/invalid_printer_spec.rb",
85
+ "spec/clamsy/configuration_spec.rb",
86
+ "spec/clamsy/template_open_doc_spec.rb",
52
87
  "spec/spec_helper.rb"
53
88
  ]
54
89
 
Binary file
@@ -1,142 +1,45 @@
1
- require 'ftools'
2
- require 'digest/md5'
3
- require 'tempfile'
4
- require 'nokogiri'
5
- require 'zip/zip'
6
1
  require 'clamsy/tenjin'
7
- require 'rghost'
2
+ require 'clamsy/file_system_support'
3
+ require 'clamsy/base_printer'
4
+ require 'clamsy/template_open_doc'
5
+ require 'clamsy/configuration'
8
6
 
9
7
  module Clamsy
10
8
 
11
- class MissingFileError < Exception ; end
12
-
13
9
  class << self
14
10
 
15
- def process(contexts, template_odt, final_pdf)
16
- begin
17
- @template_odt = TemplateOdt.new(template_odt)
18
- odts = [contexts].flatten.map {|ctx| @template_odt.render(ctx) }
19
- Shell.print_odts_to_pdf(odts, final_pdf)
20
- ensure
21
- @template_odt.trash_tmp_files
22
- end
23
- end
24
-
25
- end
26
-
27
- private
28
-
29
- module HasTrashableTempFiles
30
-
31
- def trash_tmp_files
32
- (@trashable_tmp_files || []).select {|f| f.path }.map(&:unlink)
33
- end
34
-
35
- def tmp_file(file_name)
36
- ((@trashable_tmp_files ||= []) << Tempfile.new(file_name))[-1]
37
- end
11
+ include FileSystemSupport
38
12
 
13
+ def configure(&blk)
14
+ yield(config)
39
15
  end
40
16
 
41
- class TemplateOdt
42
-
43
- include HasTrashableTempFiles
44
-
45
- def initialize(template_odt)
46
- @template_odt = template_odt
47
- end
48
-
49
- def render(context)
50
- @context_id = Digest::MD5.hexdigest(context.to_s)
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] || {})
55
- end
56
- end
57
- working_odt
58
- end
59
-
60
- private
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
-
70
- def working_odt
71
- (@working_odts ||= {})[@context_id] ||=
72
- begin
73
- dest_odt = tmp_file(@context_id)
74
- File.copy(@template_odt, dest_odt.path) ; dest_odt
75
- end
76
- end
77
-
78
- def workers
79
- lambda do |entry|
80
- (@workers ||= {})[entry.to_s] ||=
81
- begin
82
- tmp_file = tmp_file("#{@context_id}.#{File.basename(entry.to_s)}")
83
- tmp_file.write(entry.get_input_stream.read)
84
- tmp_file.close
85
- Tenjin::Template.new(tmp_file.path)
86
- end
87
- end
88
- end
89
-
17
+ def process(contexts, template_doc, final_pdf, &blk)
18
+ block_given? && configure(&blk)
19
+ generate_pdf(contexts, template_doc, final_pdf)
90
20
  end
91
21
 
92
- class Shell
93
-
94
- # The folder where cups-pdf generated pdfs are stored:
95
- # * in archlinux, this is specified in /etc/cups/cups-pdf.conf
96
- PDF_OUTPUT_DIR = "/tmp/cups-pdf/#{`whoami`.strip}"
97
-
98
- # The openoffice command to print odt to pdf, requires package cups-pdf & 'Cups-PDF' printer
99
- # to be set up in cups.
100
- ODT_TO_PDF_CMD = "ooffice -norestore -nofirststartwizard -nologo -headless -pt Cups-PDF"
22
+ private
101
23
 
102
- class << self
103
-
104
- include HasTrashableTempFiles
105
-
106
- def print_odts_to_pdf(from_odts, to_pdf)
107
- begin
108
- tmp_ps = tmp_file(File.basename(to_pdf, '.pdf'))
109
- from_odts.map {|odt| tmp_ps << odt_to_ps_stream(odt) }
110
- tmp_ps.close
111
- gs_convert(:pdf, tmp_ps.path, to_pdf)
112
- ensure
113
- trash_tmp_files
114
- end
24
+ def generate_pdf(contexts, template_doc, final_pdf)
25
+ begin
26
+ @template_doc = TemplateOpenDoc.new(template_doc)
27
+ docs = [contexts].flatten.map {|ctx| @template_doc.render(ctx) }
28
+ printer.docs_to_pdf(docs, final_pdf)
29
+ ensure
30
+ @template_doc.trash_tmp_files
115
31
  end
32
+ end
116
33
 
117
- private
118
-
119
- def odt_to_ps_stream(odt)
120
- ensure_file_exists!(odt_path = odt.path)
121
- pdf_path = File.join(PDF_OUTPUT_DIR, File.basename(odt_path, '.odt')) + '.pdf'
122
- system("#{ODT_TO_PDF_CMD} #{odt_path}")
123
- gs_convert(:ps, pdf_path)
124
- end
125
-
126
- def gs_convert(format, src_path, dest_path=nil)
127
- ensure_file_exists!(src_path)
128
- method, opts = dest_path ? [:path, {:filename => dest_path}] : [:read, {}]
129
- RGhost::Convert.new(src_path).to(format, opts).send(method)
130
- end
131
-
132
- def ensure_file_exists!(file_path)
133
- exist_flg = false
134
- 0.upto(10) {|i| File.exists?(file_path) ? (exist_flg = true; break) : sleep(1) }
135
- exist_flg or raise MissingFileError.new(file_path)
136
- end
34
+ def config
35
+ @config ||=
36
+ Configuration.new(File.join(File.dirname(__FILE__), 'clamsy.yml'), true)
37
+ end
137
38
 
39
+ def printer
40
+ Clamsy::BasePrinter.get(config.printer, config)
138
41
  end
139
42
 
140
- end
43
+ end
141
44
 
142
45
  end
@@ -0,0 +1,8 @@
1
+ printer: cups_pdf
2
+ config_file: ~/.clamsy.yml
3
+
4
+ cups_pdf:
5
+ cups_output_dir: /var/spool/cups-pdf/${USER}
6
+ cups_output_file:
7
+ ooffice_cmd: ooffice -norestore -nofirststartwizard -nologo -headless -pt Cups-PDF
8
+
@@ -0,0 +1,64 @@
1
+ require 'rghost'
2
+
3
+ module Clamsy
4
+
5
+ class ImplementationNotFoundError < Exception ; end
6
+ class PrinterNotFoundError < Exception ; end
7
+
8
+ class BasePrinter
9
+ class << self
10
+
11
+ include Clamsy::FileSystemSupport
12
+ attr_reader :subclasses, :config
13
+
14
+ def docs_to_pdf(from_docs, to_pdf)
15
+ begin
16
+ tmp_ps = tmp_file(File.basename(to_pdf, '.pdf'))
17
+ from_docs.map {|doc| tmp_ps << doc_to_ps_stream(doc.path) }
18
+ tmp_ps.close
19
+ gs_convert(:pdf, tmp_ps.path, to_pdf)
20
+ ensure
21
+ trash_tmp_files
22
+ end
23
+ end
24
+
25
+ def get(name, config)
26
+ begin
27
+ require File.join(File.dirname(__FILE__), "#{name}_printer.rb")
28
+ printer = self.subclasses[name.gsub(/[^a-zA-Z0-9]/,'').downcase]
29
+ printer.configure(config) ; printer
30
+ rescue Exception
31
+ raise PrinterNotFoundError.new("Printer '#{name}' cannot be found.")
32
+ end
33
+ end
34
+
35
+ protected
36
+
37
+ def doc_to_ps_stream(doc_path)
38
+ file_must_exist!(doc_path)
39
+ gs_convert(:ps, doc_to_pdf(doc_path))
40
+ end
41
+
42
+ def configure(config)
43
+ @config = config
44
+ end
45
+
46
+ def doc_to_pdf(doc_path)
47
+ raise Clamsy::ImplementationNotFoundError.new("#{self.to_s}.doc_to_pdf not implemented.")
48
+ end
49
+
50
+ def gs_convert(format, src_path, dest_path=nil)
51
+ file_must_exist!(src_path)
52
+ method, opts = dest_path ? [:path, {:filename => dest_path}] : [:read, {}]
53
+ RGhost::Convert.new(src_path).to(format, opts).send(method)
54
+ end
55
+
56
+ def inherited(subclass)
57
+ key = "#{subclass}".sub(/Clamsy::(\w+)Printer/,'\1').downcase
58
+ (@subclasses ||= {})[key] = subclass
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end