imaginator 0.1.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/Manifest.txt +5 -0
- data/README.markdown +40 -0
- data/Rakefile +10 -0
- data/lib/imaginator.rb +239 -0
- 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
|
+
|
data/Manifest.txt
ADDED
data/README.markdown
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
+
|
data/lib/imaginator.rb
ADDED
@@ -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
|
+
|