vectory 0.5.0 → 0.6.0

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.
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