lib/vectory/utils.rb in vectory-0.5.0 vs lib/vectory/utils.rb in vectory-0.6.0

- old
+ new

@@ -1,117 +1,99 @@ # frozen_string_literal: true -require "timeout" +require "base64" +require "marcel" +require "tempfile" module Vectory class Utils class << self - # rubocop:disable all + # Extracted from https://github.com/metanorma/metanorma-utils/blob/v1.5.2/lib/utils/image.rb # - # Originally from https://gist.github.com/pasela/9392115 - # - # Capture the standard output and the standard error of a command. - # Almost same as Open3.capture3 method except for timeout handling and return value. - # See Open3.capture3. - # - # result = capture3_with_timeout([env,] cmd... [, opts]) - # - # The arguments env, cmd and opts are passed to Process.spawn except - # opts[:stdin_data], opts[:binmode], opts[:timeout], opts[:signal] - # and opts[:kill_after]. See Process.spawn. - # - # If opts[:stdin_data] is specified, it is sent to the command's standard input. - # - # If opts[:binmode] is true, internal pipes are set to binary mode. - # - # If opts[:timeout] is specified, SIGTERM is sent to the command after specified seconds. - # - # If opts[:signal] is specified, it is used instead of SIGTERM on timeout. - # - # If opts[:kill_after] is specified, also send a SIGKILL after specified seconds. - # it is only sent if the command is still running after the initial signal was sent. - # - # The return value is a Hash as shown below. - # - # { - # :pid => PID of the command, - # :status => Process::Status of the command, - # :stdout => the standard output of the command, - # :stderr => the standard error of the command, - # :timeout => whether the command was timed out, - # } - def capture3_with_timeout(*cmd) - spawn_opts = Hash === cmd.last ? cmd.pop.dup : {} - opts = { - :stdin_data => spawn_opts.delete(:stdin_data) || "", - :binmode => spawn_opts.delete(:binmode) || false, - :timeout => spawn_opts.delete(:timeout), - :signal => spawn_opts.delete(:signal) || :TERM, - :kill_after => spawn_opts.delete(:kill_after), - } + # sources/plantuml/plantuml20200524-90467-1iqek5i.png + # already includes localdir + # Check whether just the local path or the other specified relative path + # works. + def datauri(uri, local_dir = ".") + (datauri?(uri) || url?(uri)) and return uri - in_r, in_w = IO.pipe - out_r, out_w = IO.pipe - err_r, err_w = IO.pipe - in_w.sync = true + path = path_which_exist(uri, local_dir) + path and return encode_datauri(path) - if opts[:binmode] - in_w.binmode - out_r.binmode - err_r.binmode + warn "Image specified at `#{uri}` does not exist." + uri # Return original provided location + end + + def path_which_exist(uri, local_dir) + options = absolute_path?(uri) ? [uri] : [uri, File.join(local_dir, uri)] + options.detect do |p| + File.file?(p) end + end - spawn_opts[:in] = in_r - spawn_opts[:out] = out_w - spawn_opts[:err] = err_w + def encode_datauri(path) + return nil unless File.exist?(path) - result = { - :pid => nil, - :status => nil, - :stdout => nil, - :stderr => nil, - :timeout => false, - } + type = Marcel::MimeType.for(Pathname.new(path)) || + 'text/plain; charset="utf-8"' - out_reader = nil - err_reader = nil - wait_thr = nil + bin = File.binread(path) + data = Base64.strict_encode64(bin) + "data:#{type};base64,#{data}" + # rescue StandardError + # warn "Data-URI encoding of `#{path}` failed." + # nil + end - begin - Timeout.timeout(opts[:timeout]) do - result[:pid] = spawn(*cmd, spawn_opts) - wait_thr = Process.detach(result[:pid]) - in_r.close - out_w.close - err_w.close + def datauri?(uri) + /^data:/.match?(uri) + end - out_reader = Thread.new { out_r.read } - err_reader = Thread.new { err_r.read } + def url?(url) + %r{^[A-Z]{2,}://}i.match?(url) + end - in_w.write opts[:stdin_data] - in_w.close + def absolute_path?(uri) + %r{^/}.match?(uri) || %r{^[A-Z]:/}.match?(uri) + end - result[:status] = wait_thr.value - end - rescue Timeout::Error - result[:timeout] = true - pid = spawn_opts[:pgroup] ? -result[:pid] : result[:pid] - Process.kill(opts[:signal], pid) - if opts[:kill_after] - unless wait_thr.join(opts[:kill_after]) - Process.kill(:KILL, pid) - end - end - ensure - result[:status] = wait_thr.value if wait_thr - result[:stdout] = out_reader.value if out_reader - result[:stderr] = err_reader.value if err_reader - out_r.close unless out_r.closed? - err_r.close unless err_r.closed? + def svgmap_rewrite0_path(src, localdirectory) + if /^data:/.match?(src) + save_dataimage(src) + else + File.file?(src) ? src : localdirectory + src end + end - result + def save_dataimage(uri) + %r{^data:(?:image|application)/(?<imgtype>[^;]+);(?:charset=[^;]+;)?base64,(?<imgdata>.+)$} =~ uri # rubocop:disable Layout/LineLength + imgtype.sub!(/\+[a-z0-9]+$/, "") # svg+xml + imgtype = "png" unless /^[a-z0-9]+$/.match? imgtype + Tempfile.open(["image", ".#{imgtype}"]) do |f| + f.binmode + f.write(Base64.strict_decode64(imgdata)) + f.path + end end - # rubocop:enable all + + # FIXME: This method should ONLY return 1 type, remove Array wrapper + def datauri2mime(uri) + output = decode_datauri(uri) + return nil unless output && output[:type_detected] + + [output[:type_detected]] + end + + def decode_datauri(uri) + %r{^data:(?<mimetype>[^;]+);base64,(?<mimedata>.+)$} =~ uri + return nil unless mimetype && mimedata + + data = Base64.strict_decode64(mimedata) + { + type_declared: mimetype, + type_detected: Marcel::MimeType.for(data, declared_type: mimetype), + data: data, + } + end end end end