vectory 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/README.adoc +150 -0
- data/lib/vectory/image.rb +4 -0
- data/lib/vectory/inkscape_converter.rb +35 -8
- data/lib/vectory/svg.rb +84 -1
- data/lib/vectory/svg_mapping.rb +118 -0
- data/lib/vectory/system_call.rb +11 -2
- data/lib/vectory/utils.rb +111 -0
- data/lib/vectory/vector.rb +3 -2
- data/lib/vectory/version.rb +1 -1
- data/lib/vectory.rb +1 -0
- data/spec/examples/svg/action_schemaexpg1.svg +124 -0
- data/spec/examples/svg/action_schemaexpg2.svg +124 -0
- data/spec/examples/svg/doc-ref.xml +109 -0
- data/spec/examples/svg/doc.xml +51 -0
- data/spec/examples/svg/doc2-ref.xml +59 -0
- data/spec/examples/svg/doc2.xml +28 -0
- data/spec/support/vectory_helper.rb +14 -0
- data/spec/vectory/cli_spec.rb +12 -12
- data/spec/vectory/emf_spec.rb +3 -3
- data/spec/vectory/eps_spec.rb +3 -3
- data/spec/vectory/file_magic_spec.rb +3 -3
- data/spec/vectory/inkscape_converter_spec.rb +11 -6
- data/spec/vectory/ps_spec.rb +3 -3
- data/spec/vectory/svg_mapping_spec.rb +42 -0
- data/spec/vectory/svg_spec.rb +11 -3
- data/vectory.gemspec +0 -4
- metadata +22 -70
- /data/spec/examples/emf2eps/{img.eps → ref.eps} +0 -0
- /data/spec/examples/emf2ps/{img.ps → ref.ps} +0 -0
- /data/spec/examples/emf2svg/{img.svg → ref.svg} +0 -0
- /data/spec/examples/eps2emf/{img.emf → ref.emf} +0 -0
- /data/spec/examples/eps2ps/{img.ps → ref.ps} +0 -0
- /data/spec/examples/eps2svg/{img.svg → ref.svg} +0 -0
- /data/spec/examples/ps2emf/{img.emf → ref.emf} +0 -0
- /data/spec/examples/ps2eps/{img.eps → ref.eps} +0 -0
- /data/spec/examples/ps2svg/{img.svg → ref.svg} +0 -0
- /data/spec/examples/svg2emf/{img.emf → ref.emf} +0 -0
- /data/spec/examples/svg2eps/{img.eps → ref.eps} +0 -0
- /data/spec/examples/svg2ps/{img.ps → ref.ps} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 865823bf5ac58c0780cdffc76abf7d396c2b481bcf292c807e419c0983f44646
|
4
|
+
data.tar.gz: cd29b9a6f1c3a851e37fa207427474a60656ad8a1ba6f27d954d08d2ded47f5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01ee6626bca7c7a61f14dffa639355171e052637f9b1c58d4dfb39721a7b7a815e6b11cd5c1deb76b4faecba37abf873e45661c16ebd2eaf7434cb9031a6deff
|
7
|
+
data.tar.gz: 6706861380815c36d273445cc970db3b01693024208596c8072e9661ab301908db5c6c81e7d707826b501263aac8dbcc4b65f553fc62857a773cb45404d53de4
|
data/Gemfile
CHANGED
data/README.adoc
CHANGED
@@ -27,6 +27,9 @@ Vectory relies on the following software to be installed:
|
|
27
27
|
* https://inkscape.org[Inkscape]
|
28
28
|
* https://www.ghostscript.com/[Ghostscript]
|
29
29
|
|
30
|
+
NOTE: Inkscape 1.3.1 does not work properly with EPS/PS on Windows. To avoid
|
31
|
+
this issue, the 1.3.0 version of Inkscape can be used.
|
32
|
+
|
30
33
|
|
31
34
|
=== Gem install
|
32
35
|
|
@@ -157,6 +160,131 @@ Vectory::Eps.from_path("img.eps").to_uri.content
|
|
157
160
|
----
|
158
161
|
|
159
162
|
|
163
|
+
==== SVG mapping (for the metanorma project)
|
164
|
+
|
165
|
+
Vectory can integrate SVG files into XML or HTML, respecting internal id and
|
166
|
+
link references:
|
167
|
+
|
168
|
+
[source,ruby]
|
169
|
+
----
|
170
|
+
xml_string = Vectory::SvgMapping.from_path("doc.xml").call
|
171
|
+
----
|
172
|
+
|
173
|
+
In order to do that an initial XML should support the `svgmap` tag with links
|
174
|
+
mapping. For example, it can convert XML like this:
|
175
|
+
|
176
|
+
[source,xml]
|
177
|
+
----
|
178
|
+
<svgmap id="_4072bdcb-5895-4821-b636-5795b96787cb">
|
179
|
+
<figure><image src="action_schemaexpg1.svg"/></figure>
|
180
|
+
<target href="mn://action_schema">
|
181
|
+
<xref target="ref1">Computer</xref>
|
182
|
+
</target>
|
183
|
+
<target href="http://www.example.com">
|
184
|
+
<link target="http://www.example.com">Phone</link><
|
185
|
+
/target>
|
186
|
+
</svgmap>
|
187
|
+
----
|
188
|
+
|
189
|
+
.action_schemaexpg1.svg
|
190
|
+
[source,xml]
|
191
|
+
----
|
192
|
+
<?xml version="1.0" encoding="utf-8"?>
|
193
|
+
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
194
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
195
|
+
viewBox="0 0 595.28 841.89" style="enable-background:new 0 0 595.28 841.89;" xml:space="preserve">
|
196
|
+
<style type="text/css">
|
197
|
+
#Layer_1 { fill:none }
|
198
|
+
svg[id = 'Layer_1'] { fill:none }
|
199
|
+
.st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
|
200
|
+
</style>
|
201
|
+
<image style="overflow:visible;" width="368" height="315" xlink:href="data:image/gif;base64,R0lG..ommited to save space" transform="matrix(1 0 0 1 114 263.8898)">
|
202
|
+
</image>
|
203
|
+
<a xlink:href="mn://action_schema" xlink:dummy="Layer_1">
|
204
|
+
<rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
|
205
|
+
</a>
|
206
|
+
<a xlink:href="mn://basic_attribute_schema" >
|
207
|
+
<rect x="324.69" y="450.52" class="st0" width="132.62" height="40.75"/>
|
208
|
+
</a>
|
209
|
+
<a xlink:href="mn://support_resource_schema" >
|
210
|
+
<rect x="324.69" y="528.36" class="st0" width="148.16" height="40.75"/>
|
211
|
+
</a>
|
212
|
+
</svg>
|
213
|
+
----
|
214
|
+
|
215
|
+
into XML containing inline SVG tags. Notice changes in the `id` attributes and
|
216
|
+
the `a` tags:
|
217
|
+
|
218
|
+
[source,xml]
|
219
|
+
----
|
220
|
+
<figure>
|
221
|
+
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1_000000001' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
|
222
|
+
<style> ..ommited to save space </style>
|
223
|
+
<image> ..ommited </image>
|
224
|
+
<a xlink:href='#ref1' xlink:dummy='Layer_1_000000001'>
|
225
|
+
<rect x='123.28' y='273.93' class='st0' width='88.05' height='41.84'/>
|
226
|
+
</a>
|
227
|
+
<a xlink:href='mn://basic_attribute_schema'>
|
228
|
+
<rect x='324.69' y='450.52' class='st0' width='132.62' height='40.75'/>
|
229
|
+
</a>
|
230
|
+
<a xlink:href='mn://support_resource_schema'>
|
231
|
+
<rect x='324.69' y='528.36' class='st0' width='148.16' height='40.75'/>
|
232
|
+
</a>
|
233
|
+
</svg>
|
234
|
+
</figure>
|
235
|
+
----
|
236
|
+
|
237
|
+
It also supports SVG in a form of an inline tag:
|
238
|
+
|
239
|
+
[source,xml]
|
240
|
+
----
|
241
|
+
<svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
|
242
|
+
<figure>
|
243
|
+
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
|
244
|
+
<a href="mn://action_schema" >
|
245
|
+
<rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
|
246
|
+
</a>
|
247
|
+
<a href="mn://basic_attribute_schema" >
|
248
|
+
<rect x="324.69" y="450.52" class="st0" width="132.62" height="40.75"/>
|
249
|
+
</a>
|
250
|
+
<a xlink:href="mn://support_resource_schema" >
|
251
|
+
<rect x="324.69" y="528.36" class="st0" width="148.16" height="40.75"/>
|
252
|
+
</a>
|
253
|
+
</svg>
|
254
|
+
</figure>
|
255
|
+
<target href="mn://action_schema">
|
256
|
+
<xref target="ref1">Computer</xref>
|
257
|
+
</target>
|
258
|
+
<target href="http://www.example.com">
|
259
|
+
<link target="http://www.example.com">Phone</link>
|
260
|
+
</target>
|
261
|
+
</svgmap>
|
262
|
+
----
|
263
|
+
|
264
|
+
and datauri:
|
265
|
+
|
266
|
+
[source,xml]
|
267
|
+
----
|
268
|
+
<svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
|
269
|
+
<figure>
|
270
|
+
<image src='data:image/svg+xml;base64,PD94..ommited to save space' id='__ISO_17301-1_2016' mimetype='image/svg+xml' height='auto' width='auto' alt='Workmap1'/>
|
271
|
+
</figure>
|
272
|
+
<target href="href1.htm">
|
273
|
+
<xref target="ref1">Computer</xref>
|
274
|
+
</target>
|
275
|
+
<target href="mn://basic_attribute_schema">
|
276
|
+
<link target="http://www.example.com">Phone</link>
|
277
|
+
</target>
|
278
|
+
<target href="mn://support_resource_schema">
|
279
|
+
<eref type="express" bibitemid="express_action_schema" citeas="">
|
280
|
+
<localityStack><locality type="anchor"><referenceFrom>action_schema.basic</referenceFrom></locality></localityStack>
|
281
|
+
Coffee
|
282
|
+
</eref>
|
283
|
+
</target>
|
284
|
+
</svgmap>
|
285
|
+
----
|
286
|
+
|
287
|
+
|
160
288
|
==== File system operations
|
161
289
|
|
162
290
|
An image object contains information where it is written. It can be obtained
|
@@ -202,6 +330,28 @@ vector.initial_path # => "storage/images/img.eps"
|
|
202
330
|
----
|
203
331
|
|
204
332
|
|
333
|
+
== Development
|
334
|
+
|
335
|
+
=== Releasing
|
336
|
+
|
337
|
+
Releasing is done automatically with GitHub Actions. Just bump and tag with
|
338
|
+
`gem-release`.
|
339
|
+
|
340
|
+
For a patch release (0.0.x) use:
|
341
|
+
|
342
|
+
[source,sh]
|
343
|
+
----
|
344
|
+
gem bump --version patch --tag --push
|
345
|
+
----
|
346
|
+
|
347
|
+
For a minor release (0.x.0) use:
|
348
|
+
|
349
|
+
[source,sh]
|
350
|
+
----
|
351
|
+
gem bump --version minor --tag --push
|
352
|
+
----
|
353
|
+
|
354
|
+
|
205
355
|
== Contributing
|
206
356
|
|
207
357
|
Bug reports and pull requests are welcome on GitHub at:
|
data/lib/vectory/image.rb
CHANGED
@@ -19,8 +19,8 @@ module Vectory
|
|
19
19
|
|
20
20
|
call = SystemCall.new(cmd).call
|
21
21
|
|
22
|
-
output_path =
|
23
|
-
raise_conversion_error(call) unless
|
22
|
+
output_path = find_output(uri, output_extension)
|
23
|
+
raise_conversion_error(call) unless output_path
|
24
24
|
|
25
25
|
# and return Vectory::Utils::datauri(file)
|
26
26
|
|
@@ -36,19 +36,46 @@ module Vectory
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def inkscape_path
|
39
|
-
|
40
|
-
|
39
|
+
@inkscape_path ||= find_inkscape
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_inkscape
|
43
|
+
cmds.each do |cmd|
|
44
|
+
extensions.each do |ext|
|
45
|
+
paths.each do |path|
|
46
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
exe = File.join(path, "#{cmd}#{ext}")
|
45
|
-
return exe if File.executable?(exe) && !File.directory?(exe)
|
48
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
49
|
+
end
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
49
53
|
nil
|
50
54
|
end
|
51
55
|
|
56
|
+
def cmds
|
57
|
+
["inkscapecom", "inkscape"]
|
58
|
+
end
|
59
|
+
|
60
|
+
def extensions
|
61
|
+
ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
|
62
|
+
end
|
63
|
+
|
64
|
+
def paths
|
65
|
+
ENV["PATH"].split(File::PATH_SEPARATOR)
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_output(source_path, output_extension)
|
69
|
+
basenames = [File.basename(source_path, ".*"),
|
70
|
+
File.basename(source_path)]
|
71
|
+
|
72
|
+
paths = basenames.map do |basename|
|
73
|
+
"#{File.join(File.dirname(source_path), basename)}.#{output_extension}"
|
74
|
+
end
|
75
|
+
|
76
|
+
paths.find { |p| File.exist?(p) }
|
77
|
+
end
|
78
|
+
|
52
79
|
def raise_conversion_error(call)
|
53
80
|
raise Vectory::ConversionError,
|
54
81
|
"Could not convert with Inkscape. " \
|
data/lib/vectory/svg.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "nokogiri"
|
4
|
+
|
3
5
|
module Vectory
|
4
6
|
class Svg < Vector
|
5
|
-
|
7
|
+
SVG_NS = "http://www.w3.org/2000/svg"
|
6
8
|
|
7
9
|
def self.default_extension
|
8
10
|
"svg"
|
@@ -12,6 +14,10 @@ module Vectory
|
|
12
14
|
"image/svg+xml"
|
13
15
|
end
|
14
16
|
|
17
|
+
def content
|
18
|
+
@document&.to_xml || @content
|
19
|
+
end
|
20
|
+
|
15
21
|
def to_emf
|
16
22
|
convert_with_inkscape("--export-type=emf", Emf)
|
17
23
|
end
|
@@ -23,5 +29,82 @@ module Vectory
|
|
23
29
|
def to_ps
|
24
30
|
convert_with_inkscape("--export-type=ps", Ps)
|
25
31
|
end
|
32
|
+
|
33
|
+
def namespace(suffix, links, xpath_to_remove)
|
34
|
+
remap_links(links)
|
35
|
+
suffix_ids(suffix)
|
36
|
+
remove_xpath(xpath_to_remove)
|
37
|
+
end
|
38
|
+
|
39
|
+
def remap_links(map)
|
40
|
+
document.xpath(".//m:a", "m" => SVG_NS).each do |a|
|
41
|
+
href_attrs = ["xlink:href", "href"]
|
42
|
+
href_attrs.each do |p|
|
43
|
+
a[p] and x = map[File.expand_path(a[p])] and a[p] = x
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def suffix_ids(suffix)
|
51
|
+
ids = collect_ids
|
52
|
+
return if ids.empty?
|
53
|
+
|
54
|
+
update_ids_attrs(ids, suffix)
|
55
|
+
update_ids_css(ids, suffix)
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def remove_xpath(xpath)
|
61
|
+
document.xpath(xpath).remove
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def content=(content)
|
69
|
+
if @document
|
70
|
+
@document = Nokogiri::XML(content)
|
71
|
+
else
|
72
|
+
@content = content
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def document
|
77
|
+
@document ||= begin
|
78
|
+
doc = Nokogiri::XML(@content)
|
79
|
+
@content = nil
|
80
|
+
doc
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def collect_ids
|
85
|
+
document.xpath("./@id | .//@id").map(&:value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def update_ids_attrs(ids, suffix)
|
89
|
+
document.xpath(". | .//*[@*]").each do |a|
|
90
|
+
a.attribute_nodes.each do |x|
|
91
|
+
ids.include?(x.value) and x.value += sprintf("_%09d", suffix)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def update_ids_css(ids, suffix)
|
97
|
+
document.xpath("//m:style", "m" => SVG_NS).each do |s|
|
98
|
+
c = s.children.to_xml
|
99
|
+
ids.each do |i|
|
100
|
+
c = c.gsub(%r[##{i}\b],
|
101
|
+
sprintf("#%<id>s_%<suffix>09d", id: i, suffix: suffix))
|
102
|
+
.gsub(%r(\[id\s*=\s*['"]?#{i}['"]?\]),
|
103
|
+
sprintf("[id='%<id>s_%<suffix>09d']", id: i, suffix: suffix))
|
104
|
+
end
|
105
|
+
|
106
|
+
s.children = c
|
107
|
+
end
|
108
|
+
end
|
26
109
|
end
|
27
110
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require_relative "svg"
|
2
|
+
|
3
|
+
module Vectory
|
4
|
+
class SvgMapping
|
5
|
+
class Namespace
|
6
|
+
def initialize(xmldoc)
|
7
|
+
@namespace = xmldoc.root.namespace
|
8
|
+
end
|
9
|
+
|
10
|
+
def ns(path)
|
11
|
+
return path if @namespace.nil?
|
12
|
+
|
13
|
+
path.gsub(%r{/([a-zA-z])}, "/xmlns:\\1")
|
14
|
+
.gsub(%r{::([a-zA-z])}, "::xmlns:\\1")
|
15
|
+
.gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]* ?=)}, "[xmlns:\\1")
|
16
|
+
.gsub(%r{\[([a-zA-z][a-z0-9A-Z@/]*\])}, "[xmlns:\\1")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
SVG_NS = "http://www.w3.org/2000/svg".freeze
|
21
|
+
PROCESSING_XPATH =
|
22
|
+
"processing-instruction()|.//processing-instruction()".freeze
|
23
|
+
|
24
|
+
def self.from_path(path)
|
25
|
+
new(File.read(path))
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(xml, local_directory = "")
|
29
|
+
@xml = xml
|
30
|
+
@local_directory = local_directory
|
31
|
+
end
|
32
|
+
|
33
|
+
def call
|
34
|
+
xmldoc = Nokogiri::XML(@xml)
|
35
|
+
@namespace = Namespace.new(xmldoc)
|
36
|
+
|
37
|
+
xmldoc.xpath(@namespace.ns("//svgmap")).each_with_index do |svgmap, index|
|
38
|
+
process_svgmap(svgmap, index)
|
39
|
+
end
|
40
|
+
|
41
|
+
xmldoc.to_xml
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def process_svgmap(svgmap, suffix)
|
47
|
+
image = extract_image_tag(svgmap)
|
48
|
+
return unless image
|
49
|
+
|
50
|
+
content = generate_content(image, svgmap, suffix)
|
51
|
+
return unless content
|
52
|
+
|
53
|
+
image.replace(content)
|
54
|
+
|
55
|
+
simplify_svgmap(svgmap)
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract_image_tag(svgmap)
|
59
|
+
image = svgmap.at(@namespace.ns(".//image"))
|
60
|
+
return image if image && image["src"] && !image["src"].empty?
|
61
|
+
|
62
|
+
svgmap.at(".//m:svg", "m" => SVG_NS)
|
63
|
+
end
|
64
|
+
|
65
|
+
def generate_content(image, svgmap, suffix)
|
66
|
+
vector = build_vector(image)
|
67
|
+
return unless vector
|
68
|
+
|
69
|
+
links_map = from_targets_to_links_map(svgmap)
|
70
|
+
vector.namespace(suffix, links_map, PROCESSING_XPATH)
|
71
|
+
|
72
|
+
vector.content
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_vector(image)
|
76
|
+
return Vectory::Svg.from_content(image.to_xml) if image.name == "svg"
|
77
|
+
|
78
|
+
return unless image.name == "image"
|
79
|
+
|
80
|
+
src = image["src"]
|
81
|
+
return Vectory::Datauri.new(src).to_vector if /^data:/.match?(src)
|
82
|
+
|
83
|
+
path = @local_directory.empty? ? src : File.join(@local_directory, src)
|
84
|
+
return unless File.exist?(path)
|
85
|
+
|
86
|
+
Vectory::Svg.from_path(path)
|
87
|
+
end
|
88
|
+
|
89
|
+
def from_targets_to_links_map(svgmap)
|
90
|
+
targets = svgmap.xpath(@namespace.ns("./target"))
|
91
|
+
targets.each_with_object({}) do |target_tag, m|
|
92
|
+
target = link_target(target_tag)
|
93
|
+
next unless target
|
94
|
+
|
95
|
+
href = File.expand_path(target_tag["href"])
|
96
|
+
m[href] = target
|
97
|
+
|
98
|
+
target_tag.remove
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def link_target(target_tag)
|
103
|
+
xref = target_tag.at(@namespace.ns("./xref"))
|
104
|
+
return "##{xref['target']}" if xref
|
105
|
+
|
106
|
+
link = target_tag.at(@namespace.ns("./link"))
|
107
|
+
return unless link
|
108
|
+
|
109
|
+
link["target"]
|
110
|
+
end
|
111
|
+
|
112
|
+
def simplify_svgmap(svgmap)
|
113
|
+
return if svgmap.at(@namespace.ns("./target/eref"))
|
114
|
+
|
115
|
+
svgmap.replace(svgmap.at(@namespace.ns("./figure")))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/vectory/system_call.rb
CHANGED
@@ -2,10 +2,13 @@ require "open3"
|
|
2
2
|
|
3
3
|
module Vectory
|
4
4
|
class SystemCall
|
5
|
+
TIMEOUT = 60
|
6
|
+
|
5
7
|
attr_reader :status, :stdout, :stderr, :cmd
|
6
8
|
|
7
|
-
def initialize(cmd)
|
9
|
+
def initialize(cmd, timeout = TIMEOUT)
|
8
10
|
@cmd = cmd
|
11
|
+
@timeout = timeout
|
9
12
|
end
|
10
13
|
|
11
14
|
def call
|
@@ -27,7 +30,13 @@ module Vectory
|
|
27
30
|
end
|
28
31
|
|
29
32
|
def execute(cmd)
|
30
|
-
|
33
|
+
result = Utils.capture3_with_timeout(cmd,
|
34
|
+
timeout: @timeout,
|
35
|
+
kill_after: @timeout)
|
36
|
+
Vectory.ui.error(result.inspect)
|
37
|
+
@stdout = result[:stdout]
|
38
|
+
@stderr = result[:stderr]
|
39
|
+
@status = result[:status]
|
31
40
|
rescue Errno::ENOENT => e
|
32
41
|
raise SystemCallError, e.inspect
|
33
42
|
end
|
data/lib/vectory/utils.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "marcel"
|
4
|
+
require "timeout"
|
5
|
+
|
3
6
|
module Vectory
|
4
7
|
class Utils
|
5
8
|
# Extracted from https://github.com/metanorma/metanorma-utils/blob/v1.5.2/lib/utils/image.rb
|
@@ -49,6 +52,114 @@ module Vectory
|
|
49
52
|
def absolute_path?(uri)
|
50
53
|
%r{^/}.match?(uri) || %r{^[A-Z]:/}.match?(uri)
|
51
54
|
end
|
55
|
+
|
56
|
+
# rubocop:disable all
|
57
|
+
#
|
58
|
+
# Originally from https://gist.github.com/pasela/9392115
|
59
|
+
#
|
60
|
+
# Capture the standard output and the standard error of a command.
|
61
|
+
# Almost same as Open3.capture3 method except for timeout handling and return value.
|
62
|
+
# See Open3.capture3.
|
63
|
+
#
|
64
|
+
# result = capture3_with_timeout([env,] cmd... [, opts])
|
65
|
+
#
|
66
|
+
# The arguments env, cmd and opts are passed to Process.spawn except
|
67
|
+
# opts[:stdin_data], opts[:binmode], opts[:timeout], opts[:signal]
|
68
|
+
# and opts[:kill_after]. See Process.spawn.
|
69
|
+
#
|
70
|
+
# If opts[:stdin_data] is specified, it is sent to the command's standard input.
|
71
|
+
#
|
72
|
+
# If opts[:binmode] is true, internal pipes are set to binary mode.
|
73
|
+
#
|
74
|
+
# If opts[:timeout] is specified, SIGTERM is sent to the command after specified seconds.
|
75
|
+
#
|
76
|
+
# If opts[:signal] is specified, it is used instead of SIGTERM on timeout.
|
77
|
+
#
|
78
|
+
# If opts[:kill_after] is specified, also send a SIGKILL after specified seconds.
|
79
|
+
# it is only sent if the command is still running after the initial signal was sent.
|
80
|
+
#
|
81
|
+
# The return value is a Hash as shown below.
|
82
|
+
#
|
83
|
+
# {
|
84
|
+
# :pid => PID of the command,
|
85
|
+
# :status => Process::Status of the command,
|
86
|
+
# :stdout => the standard output of the command,
|
87
|
+
# :stderr => the standard error of the command,
|
88
|
+
# :timeout => whether the command was timed out,
|
89
|
+
# }
|
90
|
+
def capture3_with_timeout(*cmd)
|
91
|
+
spawn_opts = Hash === cmd.last ? cmd.pop.dup : {}
|
92
|
+
opts = {
|
93
|
+
:stdin_data => spawn_opts.delete(:stdin_data) || "",
|
94
|
+
:binmode => spawn_opts.delete(:binmode) || false,
|
95
|
+
:timeout => spawn_opts.delete(:timeout),
|
96
|
+
:signal => spawn_opts.delete(:signal) || :TERM,
|
97
|
+
:kill_after => spawn_opts.delete(:kill_after),
|
98
|
+
}
|
99
|
+
|
100
|
+
in_r, in_w = IO.pipe
|
101
|
+
out_r, out_w = IO.pipe
|
102
|
+
err_r, err_w = IO.pipe
|
103
|
+
in_w.sync = true
|
104
|
+
|
105
|
+
if opts[:binmode]
|
106
|
+
in_w.binmode
|
107
|
+
out_r.binmode
|
108
|
+
err_r.binmode
|
109
|
+
end
|
110
|
+
|
111
|
+
spawn_opts[:in] = in_r
|
112
|
+
spawn_opts[:out] = out_w
|
113
|
+
spawn_opts[:err] = err_w
|
114
|
+
|
115
|
+
result = {
|
116
|
+
:pid => nil,
|
117
|
+
:status => nil,
|
118
|
+
:stdout => nil,
|
119
|
+
:stderr => nil,
|
120
|
+
:timeout => false,
|
121
|
+
}
|
122
|
+
|
123
|
+
out_reader = nil
|
124
|
+
err_reader = nil
|
125
|
+
wait_thr = nil
|
126
|
+
|
127
|
+
begin
|
128
|
+
Timeout.timeout(opts[:timeout]) do
|
129
|
+
result[:pid] = spawn(*cmd, spawn_opts)
|
130
|
+
wait_thr = Process.detach(result[:pid])
|
131
|
+
in_r.close
|
132
|
+
out_w.close
|
133
|
+
err_w.close
|
134
|
+
|
135
|
+
out_reader = Thread.new { out_r.read }
|
136
|
+
err_reader = Thread.new { err_r.read }
|
137
|
+
|
138
|
+
in_w.write opts[:stdin_data]
|
139
|
+
in_w.close
|
140
|
+
|
141
|
+
result[:status] = wait_thr.value
|
142
|
+
end
|
143
|
+
rescue Timeout::Error
|
144
|
+
result[:timeout] = true
|
145
|
+
pid = spawn_opts[:pgroup] ? -result[:pid] : result[:pid]
|
146
|
+
Process.kill(opts[:signal], pid)
|
147
|
+
if opts[:kill_after]
|
148
|
+
unless wait_thr.join(opts[:kill_after])
|
149
|
+
Process.kill(:KILL, pid)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
ensure
|
153
|
+
result[:status] = wait_thr.value if wait_thr
|
154
|
+
result[:stdout] = out_reader.value if out_reader
|
155
|
+
result[:stderr] = err_reader.value if err_reader
|
156
|
+
out_r.close unless out_r.closed?
|
157
|
+
err_r.close unless err_r.closed?
|
158
|
+
end
|
159
|
+
|
160
|
+
result
|
161
|
+
end
|
162
|
+
# rubocop:enable all
|
52
163
|
end
|
53
164
|
end
|
54
165
|
end
|
data/lib/vectory/vector.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "tempfile"
|
3
4
|
require_relative "inkscape_converter"
|
4
5
|
|
5
6
|
module Vectory
|
@@ -44,7 +45,7 @@ module Vectory
|
|
44
45
|
|
45
46
|
def write(path = nil)
|
46
47
|
target_path = path || @path || tmp_path
|
47
|
-
File.binwrite(target_path,
|
48
|
+
File.binwrite(target_path, content)
|
48
49
|
@path = File.expand_path(target_path)
|
49
50
|
|
50
51
|
self
|
@@ -59,7 +60,7 @@ module Vectory
|
|
59
60
|
def with_file(input_extension)
|
60
61
|
Dir.mktmpdir do |dir|
|
61
62
|
input_path = File.join(dir, "image.#{input_extension}")
|
62
|
-
File.binwrite(input_path,
|
63
|
+
File.binwrite(input_path, content)
|
63
64
|
|
64
65
|
yield input_path
|
65
66
|
end
|
data/lib/vectory/version.rb
CHANGED