vectory 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcde8950a26370677a54de04ba44f5737eec9768a0c611fb3788c5169c729d9c
4
- data.tar.gz: dcc826fc4ad94fbf07829d52b65625069c2581aa202a2d3016769ad70ac83881
3
+ metadata.gz: 50efb043c71096f66bc72c7804b8707b6210ab4af071f231d7e81f52a0c66da0
4
+ data.tar.gz: a3ffda2f587f7796f2033fab1230db852d98e82da88b269d230646dad6acbd39
5
5
  SHA512:
6
- metadata.gz: 9b2d225eb97cf8b367609be20123e2fd7776fe291ae11005c47fc61fd09be9808b40926991d302092e9c9b64f8d9868d52bfa6f6e3ea4104a51775295a470f85
7
- data.tar.gz: 73016cfa8360d5914add0fbd07b7dd811097fe5f956a02ff9403a8b16a1e8c72082c722026db0af7d70ce762dc308df3ba8fc5f3cd80816a372c73ec209f5298
6
+ metadata.gz: aca9af8bee9a5f916fc0360b9473705307bfeda4b0086ae9fa53929381169a4791a483a126f98f1295574da06d70a2ea92bf3a706cd7359f91fa6b492c305205
7
+ data.tar.gz: '05286cb944c4235c5d60d5591b63be2d15c43f796fa0d63b00205ced005fe8c84f314c2d468fd5ec2a02336682d4b4c0357fd63bd6757056f95aa727ec8a3884'
data/README.adoc CHANGED
@@ -173,7 +173,7 @@ link references:
173
173
 
174
174
  [source,ruby]
175
175
  ----
176
- xml_string = Vectory::SvgMapping.from_path("doc.xml").call
176
+ xml_string = Vectory::SvgMapping.from_path("doc.xml").to_xml
177
177
  ----
178
178
 
179
179
  In order to do that an initial XML should support the `svgmap` tag with links
@@ -0,0 +1,115 @@
1
+ require "timeout"
2
+
3
+ module Vectory
4
+ module Capture
5
+ class << self
6
+ # rubocop:disable all
7
+ #
8
+ # Originally from https://gist.github.com/pasela/9392115
9
+ #
10
+ # Capture the standard output and the standard error of a command.
11
+ # Almost same as Open3.capture3 method except for timeout handling and return value.
12
+ # See Open3.capture3.
13
+ #
14
+ # result = capture3_with_timeout([env,] cmd... [, opts])
15
+ #
16
+ # The arguments env, cmd and opts are passed to Process.spawn except
17
+ # opts[:stdin_data], opts[:binmode], opts[:timeout], opts[:signal]
18
+ # and opts[:kill_after]. See Process.spawn.
19
+ #
20
+ # If opts[:stdin_data] is specified, it is sent to the command's standard input.
21
+ #
22
+ # If opts[:binmode] is true, internal pipes are set to binary mode.
23
+ #
24
+ # If opts[:timeout] is specified, SIGTERM is sent to the command after specified seconds.
25
+ #
26
+ # If opts[:signal] is specified, it is used instead of SIGTERM on timeout.
27
+ #
28
+ # If opts[:kill_after] is specified, also send a SIGKILL after specified seconds.
29
+ # it is only sent if the command is still running after the initial signal was sent.
30
+ #
31
+ # The return value is a Hash as shown below.
32
+ #
33
+ # {
34
+ # :pid => PID of the command,
35
+ # :status => Process::Status of the command,
36
+ # :stdout => the standard output of the command,
37
+ # :stderr => the standard error of the command,
38
+ # :timeout => whether the command was timed out,
39
+ # }
40
+ def with_timeout(*cmd)
41
+ spawn_opts = Hash === cmd.last ? cmd.pop.dup : {}
42
+ opts = {
43
+ :stdin_data => spawn_opts.delete(:stdin_data) || "",
44
+ :binmode => spawn_opts.delete(:binmode) || false,
45
+ :timeout => spawn_opts.delete(:timeout),
46
+ :signal => spawn_opts.delete(:signal) || :TERM,
47
+ :kill_after => spawn_opts.delete(:kill_after),
48
+ }
49
+
50
+ in_r, in_w = IO.pipe
51
+ out_r, out_w = IO.pipe
52
+ err_r, err_w = IO.pipe
53
+ in_w.sync = true
54
+
55
+ if opts[:binmode]
56
+ in_w.binmode
57
+ out_r.binmode
58
+ err_r.binmode
59
+ end
60
+
61
+ spawn_opts[:in] = in_r
62
+ spawn_opts[:out] = out_w
63
+ spawn_opts[:err] = err_w
64
+
65
+ result = {
66
+ :pid => nil,
67
+ :status => nil,
68
+ :stdout => nil,
69
+ :stderr => nil,
70
+ :timeout => false,
71
+ }
72
+
73
+ out_reader = nil
74
+ err_reader = nil
75
+ wait_thr = nil
76
+
77
+ begin
78
+ Timeout.timeout(opts[:timeout]) do
79
+ result[:pid] = spawn(*cmd, spawn_opts)
80
+ wait_thr = Process.detach(result[:pid])
81
+ in_r.close
82
+ out_w.close
83
+ err_w.close
84
+
85
+ out_reader = Thread.new { out_r.read }
86
+ err_reader = Thread.new { err_r.read }
87
+
88
+ in_w.write opts[:stdin_data]
89
+ in_w.close
90
+
91
+ result[:status] = wait_thr.value
92
+ end
93
+ rescue Timeout::Error
94
+ result[:timeout] = true
95
+ pid = spawn_opts[:pgroup] ? -result[:pid] : result[:pid]
96
+ Process.kill(opts[:signal], pid)
97
+ if opts[:kill_after]
98
+ unless wait_thr.join(opts[:kill_after])
99
+ Process.kill(:KILL, pid)
100
+ end
101
+ end
102
+ ensure
103
+ result[:status] = wait_thr.value if wait_thr
104
+ result[:stdout] = out_reader.value if out_reader
105
+ result[:stderr] = err_reader.value if err_reader
106
+ out_r.close unless out_r.closed?
107
+ err_r.close unless err_r.closed?
108
+ end
109
+
110
+ result
111
+ end
112
+ # rubocop:enable all
113
+ end
114
+ end
115
+ end
@@ -22,23 +22,30 @@ module Vectory
22
22
  "processing-instruction()|.//processing-instruction()".freeze
23
23
 
24
24
  def self.from_path(path)
25
- new(File.read(path))
25
+ new(Nokogiri::XML(File.read(path)))
26
26
  end
27
27
 
28
- def initialize(xml, local_directory = "")
29
- @xml = xml
28
+ def self.from_xml(xml)
29
+ new(Nokogiri::XML(xml))
30
+ end
31
+
32
+ def initialize(doc, local_directory = "")
33
+ @doc = doc
30
34
  @local_directory = local_directory
31
35
  end
32
36
 
33
37
  def call
34
- xmldoc = Nokogiri::XML(@xml)
35
- @namespace = Namespace.new(xmldoc)
38
+ @namespace = Namespace.new(@doc)
36
39
 
37
- xmldoc.xpath(@namespace.ns("//svgmap")).each_with_index do |svgmap, index|
40
+ @doc.xpath(@namespace.ns("//svgmap")).each_with_index do |svgmap, index|
38
41
  process_svgmap(svgmap, index)
39
42
  end
40
43
 
41
- xmldoc.to_xml
44
+ @doc
45
+ end
46
+
47
+ def to_xml
48
+ call.to_xml
42
49
  end
43
50
 
44
51
  private
@@ -1,4 +1,5 @@
1
1
  require "open3"
2
+ require_relative "capture"
2
3
 
3
4
  module Vectory
4
5
  class SystemCall
@@ -30,9 +31,9 @@ module Vectory
30
31
  end
31
32
 
32
33
  def execute(cmd)
33
- result = Utils.capture3_with_timeout(cmd,
34
- timeout: @timeout,
35
- kill_after: @timeout)
34
+ result = Capture.with_timeout(cmd,
35
+ timeout: @timeout,
36
+ kill_after: @timeout)
36
37
  @stdout = result[:stdout]
37
38
  @stderr = result[:stderr]
38
39
  @status = result[:status]
data/lib/vectory/utils.rb CHANGED
@@ -1,117 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "timeout"
3
+ require "base64"
4
+ require "marcel"
5
+ require "tempfile"
4
6
 
5
7
  module Vectory
6
8
  class Utils
7
9
  class << self
8
- # rubocop:disable all
10
+ # Extracted from https://github.com/metanorma/metanorma-utils/blob/v1.5.2/lib/utils/image.rb
9
11
  #
10
- # Originally from https://gist.github.com/pasela/9392115
11
- #
12
- # Capture the standard output and the standard error of a command.
13
- # Almost same as Open3.capture3 method except for timeout handling and return value.
14
- # See Open3.capture3.
15
- #
16
- # result = capture3_with_timeout([env,] cmd... [, opts])
17
- #
18
- # The arguments env, cmd and opts are passed to Process.spawn except
19
- # opts[:stdin_data], opts[:binmode], opts[:timeout], opts[:signal]
20
- # and opts[:kill_after]. See Process.spawn.
21
- #
22
- # If opts[:stdin_data] is specified, it is sent to the command's standard input.
23
- #
24
- # If opts[:binmode] is true, internal pipes are set to binary mode.
25
- #
26
- # If opts[:timeout] is specified, SIGTERM is sent to the command after specified seconds.
27
- #
28
- # If opts[:signal] is specified, it is used instead of SIGTERM on timeout.
29
- #
30
- # If opts[:kill_after] is specified, also send a SIGKILL after specified seconds.
31
- # it is only sent if the command is still running after the initial signal was sent.
32
- #
33
- # The return value is a Hash as shown below.
34
- #
35
- # {
36
- # :pid => PID of the command,
37
- # :status => Process::Status of the command,
38
- # :stdout => the standard output of the command,
39
- # :stderr => the standard error of the command,
40
- # :timeout => whether the command was timed out,
41
- # }
42
- def capture3_with_timeout(*cmd)
43
- spawn_opts = Hash === cmd.last ? cmd.pop.dup : {}
44
- opts = {
45
- :stdin_data => spawn_opts.delete(:stdin_data) || "",
46
- :binmode => spawn_opts.delete(:binmode) || false,
47
- :timeout => spawn_opts.delete(:timeout),
48
- :signal => spawn_opts.delete(:signal) || :TERM,
49
- :kill_after => spawn_opts.delete(:kill_after),
50
- }
12
+ # sources/plantuml/plantuml20200524-90467-1iqek5i.png
13
+ # already includes localdir
14
+ # Check whether just the local path or the other specified relative path
15
+ # works.
16
+ def datauri(uri, local_dir = ".")
17
+ (datauri?(uri) || url?(uri)) and return uri
18
+
19
+ path = path_which_exist(uri, local_dir)
20
+ path and return encode_datauri(path)
51
21
 
52
- in_r, in_w = IO.pipe
53
- out_r, out_w = IO.pipe
54
- err_r, err_w = IO.pipe
55
- in_w.sync = true
22
+ warn "Image specified at `#{uri}` does not exist."
23
+ uri # Return original provided location
24
+ end
56
25
 
57
- if opts[:binmode]
58
- in_w.binmode
59
- out_r.binmode
60
- err_r.binmode
26
+ def path_which_exist(uri, local_dir)
27
+ options = absolute_path?(uri) ? [uri] : [uri, File.join(local_dir, uri)]
28
+ options.detect do |p|
29
+ File.file?(p)
61
30
  end
31
+ end
62
32
 
63
- spawn_opts[:in] = in_r
64
- spawn_opts[:out] = out_w
65
- spawn_opts[:err] = err_w
33
+ def encode_datauri(path)
34
+ return nil unless File.exist?(path)
66
35
 
67
- result = {
68
- :pid => nil,
69
- :status => nil,
70
- :stdout => nil,
71
- :stderr => nil,
72
- :timeout => false,
73
- }
36
+ type = Marcel::MimeType.for(Pathname.new(path)) ||
37
+ 'text/plain; charset="utf-8"'
38
+
39
+ bin = File.binread(path)
40
+ data = Base64.strict_encode64(bin)
41
+ "data:#{type};base64,#{data}"
42
+ # rescue StandardError
43
+ # warn "Data-URI encoding of `#{path}` failed."
44
+ # nil
45
+ end
46
+
47
+ def datauri?(uri)
48
+ /^data:/.match?(uri)
49
+ end
50
+
51
+ def url?(url)
52
+ %r{^[A-Z]{2,}://}i.match?(url)
53
+ end
54
+
55
+ def absolute_path?(uri)
56
+ %r{^/}.match?(uri) || %r{^[A-Z]:/}.match?(uri)
57
+ end
58
+
59
+ def svgmap_rewrite0_path(src, localdirectory)
60
+ if /^data:/.match?(src)
61
+ save_dataimage(src)
62
+ else
63
+ File.file?(src) ? src : localdirectory + src
64
+ end
65
+ end
74
66
 
75
- out_reader = nil
76
- err_reader = nil
77
- wait_thr = nil
78
-
79
- begin
80
- Timeout.timeout(opts[:timeout]) do
81
- result[:pid] = spawn(*cmd, spawn_opts)
82
- wait_thr = Process.detach(result[:pid])
83
- in_r.close
84
- out_w.close
85
- err_w.close
86
-
87
- out_reader = Thread.new { out_r.read }
88
- err_reader = Thread.new { err_r.read }
89
-
90
- in_w.write opts[:stdin_data]
91
- in_w.close
92
-
93
- result[:status] = wait_thr.value
94
- end
95
- rescue Timeout::Error
96
- result[:timeout] = true
97
- pid = spawn_opts[:pgroup] ? -result[:pid] : result[:pid]
98
- Process.kill(opts[:signal], pid)
99
- if opts[:kill_after]
100
- unless wait_thr.join(opts[:kill_after])
101
- Process.kill(:KILL, pid)
102
- end
103
- end
104
- ensure
105
- result[:status] = wait_thr.value if wait_thr
106
- result[:stdout] = out_reader.value if out_reader
107
- result[:stderr] = err_reader.value if err_reader
108
- out_r.close unless out_r.closed?
109
- err_r.close unless err_r.closed?
67
+ def save_dataimage(uri)
68
+ %r{^data:(?:image|application)/(?<imgtype>[^;]+);(?:charset=[^;]+;)?base64,(?<imgdata>.+)$} =~ uri # rubocop:disable Layout/LineLength
69
+ imgtype.sub!(/\+[a-z0-9]+$/, "") # svg+xml
70
+ imgtype = "png" unless /^[a-z0-9]+$/.match? imgtype
71
+ Tempfile.open(["image", ".#{imgtype}"]) do |f|
72
+ f.binmode
73
+ f.write(Base64.strict_decode64(imgdata))
74
+ f.path
110
75
  end
76
+ end
77
+
78
+ # FIXME: This method should ONLY return 1 type, remove Array wrapper
79
+ def datauri2mime(uri)
80
+ output = decode_datauri(uri)
81
+ return nil unless output && output[:type_detected]
111
82
 
112
- result
83
+ [output[:type_detected]]
84
+ end
85
+
86
+ def decode_datauri(uri)
87
+ %r{^data:(?<mimetype>[^;]+);base64,(?<mimedata>.+)$} =~ uri
88
+ return nil unless mimetype && mimedata
89
+
90
+ data = Base64.strict_decode64(mimedata)
91
+ {
92
+ type_declared: mimetype,
93
+ type_detected: Marcel::MimeType.for(data, declared_type: mimetype),
94
+ data: data,
95
+ }
113
96
  end
114
- # rubocop:enable all
115
97
  end
116
98
  end
117
99
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vectory
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/vectory.gemspec CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.required_ruby_version = ">= 2.5.0"
28
28
 
29
29
  spec.add_runtime_dependency "emf2svg"
30
+ spec.add_runtime_dependency "marcel", "~> 1.0.0"
30
31
  spec.add_runtime_dependency "nokogiri", "~> 1.14"
31
32
  spec.add_runtime_dependency "thor", "~> 1.2.1"
32
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vectory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-12-25 00:00:00.000000000 Z
11
+ date: 2024-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: emf2svg
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: marcel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: nokogiri
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +87,7 @@ files:
73
87
  - Rakefile
74
88
  - exe/vectory
75
89
  - lib/vectory.rb
90
+ - lib/vectory/capture.rb
76
91
  - lib/vectory/cli.rb
77
92
  - lib/vectory/datauri.rb
78
93
  - lib/vectory/emf.rb