imaginator 0.1.5

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 (6) hide show
  1. data/LICENSE +22 -0
  2. data/Manifest.txt +5 -0
  3. data/README.markdown +40 -0
  4. data/Rakefile +10 -0
  5. data/lib/imaginator.rb +239 -0
  6. metadata +68 -0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 Daniel Mendler
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,5 @@
1
+ Manifest.txt
2
+ lib/imaginator.rb
3
+ README.markdown
4
+ LICENSE
5
+ Rakefile
@@ -0,0 +1,40 @@
1
+ README
2
+ ======
3
+
4
+ Imaginator is a ruby library to generate images from LaTeX/Graphviz code in a background job. This library is derived from my latex-renderer library.
5
+
6
+ Usage
7
+ -----
8
+
9
+ Imaginator.uri = 'drbunix://tmp/imaginator.sock'
10
+ Imaginator.dir = '/tmp/imaginator'
11
+
12
+ Imaginator.run do |server|
13
+ server.add_renderer(:math, Imaginator::LaTeX.new)
14
+ server.add_renderer(:dot, Imaginator::Graphviz.new(:cmd => 'dot'))
15
+ server.add_renderer(:neato, Imaginator::Graphviz.new(:cmd => 'neato'))
16
+ server.add_renderer(:twopi, Imaginator::Graphviz.new(:cmd => 'twopi'))
17
+ server.add_renderer(:circo, Imaginator::Graphviz.new(:cmd => 'circo'))
18
+ server.add_renderer(:fdp, Imaginator::Graphviz.new(:cmd => 'fdp'))
19
+ end
20
+
21
+ ...
22
+
23
+ name = Imaginator.get.enqueue(:math, '1+1')
24
+ ...
25
+ filename = Imaginator.get.result(name)
26
+
27
+ TODO
28
+ ----
29
+
30
+ * Write unit tests or specs
31
+
32
+ Authors
33
+ -------
34
+
35
+ Daniel Mendler
36
+
37
+ License
38
+ -------
39
+
40
+ This library is released under the MIT license.
@@ -0,0 +1,10 @@
1
+ require 'hoe'
2
+
3
+ $:.unshift 'lib'
4
+ require 'imaginator'
5
+
6
+ Hoe.new 'imaginator', Imaginator::VERSION do |imaginator|
7
+ imaginator.developer 'Daniel Mendler', 'mail@daniel-mendler.de'
8
+ imaginator.summary = 'Image generator for LaTeX/Graphviz source'
9
+ end
10
+
@@ -0,0 +1,239 @@
1
+ require 'digest'
2
+ require 'fileutils'
3
+ require 'thread'
4
+ require 'monitor'
5
+ require 'drb'
6
+ require 'timeout'
7
+
8
+ class Imaginator
9
+ VERSION = '0.1.5'
10
+
11
+ class LaTeX
12
+ def initialize(opts = {})
13
+ @opts = {
14
+ :convert_opts => '-trim -density 120',
15
+ :format => 'png',
16
+ :debug => false
17
+ }
18
+ @opts[:blacklist] = %w{
19
+ include def command loop repeat open toks output input
20
+ catcode name \\every \\errhelp \\errorstopmode \\scrollmode
21
+ \\nonstopmode \\batchmode \\read \\write csname \\newhelp
22
+ \\uppercase \\lowercase \\relax \\aftergroup \\afterassignment
23
+ \\expandafter \\noexpand \\special $$
24
+ }
25
+ @opts.update(opts)
26
+ end
27
+
28
+ def format
29
+ @opts[:format]
30
+ end
31
+
32
+ def render(code, file)
33
+ begin
34
+ temp_dir = create_temp_dir(file)
35
+ latex2dvi(temp_dir, code)
36
+ dvi2ps(temp_dir)
37
+ ps2image(temp_dir, file)
38
+ rescue
39
+ FileUtils.rm_rf(temp_dir) if !@opts[:debug]
40
+ else
41
+ FileUtils.rm_rf(temp_dir)
42
+ end
43
+ end
44
+
45
+ def process(code)
46
+ errors = @opts[:blacklist].select { |cmd| code.include?(cmd) }
47
+ errors.empty? || raise(ArgumentError.new("Invalid LaTeX commands #{errors.join(', ')}"))
48
+ code.strip
49
+ end
50
+
51
+ private
52
+
53
+ def template(code)
54
+ <<END # {{{
55
+ \\documentclass{minimal}
56
+ \\newcommand\\use[2][]{\\IfFileExists{#2.sty}{\\usepackage[#1]{#2}}{}}
57
+ \\use[utf8]{inputenc}
58
+ \\use{amsmath}
59
+ \\use{amsfonts}
60
+ \\use{amssymb}
61
+ \\use{mathrsfs}
62
+ \\use{esdiff}
63
+ \\use{cancel}
64
+ \\use[dvips,usenames]{color}
65
+ \\use{nicefrac}
66
+ \\use[fraction=nice]{siunitx}
67
+ \\use{mathpazo}
68
+ \\begin{document}
69
+ $$
70
+ #{code}
71
+ $$
72
+ \\end{document}
73
+ END
74
+ # }}}
75
+ end
76
+
77
+ def create_temp_dir(file)
78
+ temp_dir = file + '-tmp'
79
+ FileUtils.mkdir_p(temp_dir)
80
+ temp_dir
81
+ end
82
+
83
+ def sh(cmd, args)
84
+ `#{cmd} #{args} 2>&1 > /dev/null`
85
+ raise RuntimeError.new("Execution of #{cmd} failed with status #{$?}") if $? != 0
86
+ end
87
+
88
+ def latex2dvi(dir, code)
89
+ tex_file = File.join(dir, 'code.tex')
90
+ File.open(tex_file, 'w') {|f| f.write(template(code)) }
91
+ sh('latex', "--interaction=nonstopmode --output-directory=#{dir} #{tex_file}")
92
+ end
93
+
94
+ def dvi2ps(dir)
95
+ file = File.join(dir, 'code')
96
+ sh('dvips', "-E #{file}.dvi -o #{file}.ps")
97
+ end
98
+
99
+ def ps2image(dir, file)
100
+ ps_file = File.join(dir, 'code.ps')
101
+ sh('convert', "#{@opts[:convert_opts]} #{ps_file} #{file}")
102
+ end
103
+ end
104
+
105
+ class Graphviz
106
+ def initialize(opts = {})
107
+ @opts = {
108
+ :format => 'png',
109
+ :cmd => 'dot'
110
+ }
111
+ @opts.update(opts)
112
+ end
113
+
114
+ def format
115
+ @opts[:format]
116
+ end
117
+
118
+ def render(code, file)
119
+ IO.popen(@opts[:cmd] + " -T #{@opts[:format]} -o #{file}", 'w') {|io|
120
+ io.write(code)
121
+ io.close
122
+ }
123
+ end
124
+
125
+ def process(code)
126
+ code.strip
127
+ end
128
+ end
129
+
130
+ class Server
131
+ def initialize(renderer)
132
+ @renderer = renderer
133
+ @queue = []
134
+ @queue.extend(MonitorMixin)
135
+ @empty = @queue.new_cond
136
+ end
137
+
138
+ def enqueue(name, type, code)
139
+ @queue.synchronize do
140
+ @queue << [name, type, code]
141
+ @empty.signal
142
+ end
143
+ end
144
+
145
+ def enqueued?(name)
146
+ @queue.synchronize do
147
+ @queue.any? do |x|
148
+ x[0] == name
149
+ end
150
+ end
151
+ end
152
+
153
+ def run
154
+ loop do
155
+ name, type, code = @queue.synchronize do
156
+ return if !wait_while_empty
157
+ @queue.first
158
+ end
159
+ @renderer[type].render(code, name) rescue nil
160
+ @queue.synchronize do
161
+ @queue.shift
162
+ end
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def wait_while_empty
169
+ Timeout.timeout(5) do
170
+ while @queue.empty?
171
+ @empty.wait
172
+ end
173
+ return true
174
+ end
175
+ rescue Timeout::Error => ex
176
+ false
177
+ end
178
+ end
179
+
180
+ def initialize(uri, dir)
181
+ @uri = uri
182
+ @dir = dir
183
+ @renderer = {}
184
+ FileUtils.mkdir_p(@dir, :mode => 0755)
185
+ yield(self) if block_given?
186
+ end
187
+
188
+ def add_renderer(type, renderer)
189
+ @renderer[type.to_sym] = renderer
190
+ end
191
+
192
+ def enqueue(type, code)
193
+ type = type.to_sym
194
+ renderer = @renderer[type]
195
+ raise(ArgumentError, "Renderer not found") if !renderer
196
+ format = renderer.format
197
+ code = renderer.process(code)
198
+ name = Digest::MD5.hexdigest(code) + '.' + format
199
+ file = File.join(@dir, name)
200
+ if !File.exists?(file)
201
+ server.enqueue(file, type, code)
202
+ end
203
+ name
204
+ end
205
+
206
+ def result(name)
207
+ file = File.join(@dir, name)
208
+ if !File.exists?(file)
209
+ 20.times do
210
+ break if !server.enqueued?(file)
211
+ sleep 0.5
212
+ end
213
+ end
214
+ raise(RuntimeError, 'Image could not be generated') if !File.exists?(file)
215
+ file
216
+ end
217
+
218
+ def server
219
+ begin
220
+ @server ||= DRb::DRbObject.new(nil, @uri)
221
+ @server.respond_to? :enqueue
222
+ @server
223
+ rescue
224
+ if @uri =~ %r{^drbunix://(.+)$}
225
+ File.unlink($1) rescue nil
226
+ end
227
+ server = Server.new(@renderer)
228
+ drb_server = DRb.start_service(@uri, server)
229
+ Thread.new do
230
+ server.run
231
+ drb_server.stop_service
232
+ DRb.primary_server = nil if DRb.primary_server == drb_server
233
+ @server = nil
234
+ end
235
+ @server = server
236
+ end
237
+ end
238
+ end
239
+
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: imaginator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Mendler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-17 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ version:
25
+ description:
26
+ email:
27
+ - mail@daniel-mendler.de
28
+ executables: []
29
+
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - Manifest.txt
34
+ files:
35
+ - Manifest.txt
36
+ - lib/imaginator.rb
37
+ - README.markdown
38
+ - LICENSE
39
+ - Rakefile
40
+ has_rdoc: true
41
+ homepage: http://github.com/minad/imaginator
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --main
45
+ - README.txt
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project: imaginator
63
+ rubygems_version: 1.3.1
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: Image generator for LaTeX/Graphviz source
67
+ test_files: []
68
+