clamsy 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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