summaryrefslogtreecommitdiffhomepage
path: root/lib/axlsx/util/zip_command.rb
diff options
context:
space:
mode:
authorRyan Winograd <[email protected]>2020-08-13 21:05:59 -0500
committerRyan Winograd <[email protected]>2020-08-19 14:59:17 -0500
commite7673f431813cde7aab2d032a4417d4ddeeb9342 (patch)
tree95d2758cd969c9c4b7c3d38a887ae75a65185f7e /lib/axlsx/util/zip_command.rb
parent33a53474a90f1825ce20c66dab481fdcfa1106bf (diff)
downloadcaxlsx-e7673f431813cde7aab2d032a4417d4ddeeb9342.tar.gz
caxlsx-e7673f431813cde7aab2d032a4417d4ddeeb9342.zip
Add option to `#serialize` with system zip command
Add a `:zip_command` option to `Axlsx::Package#serialize` that allows the user to specify an alternate command to use to perform the zip operation on the XLSX file contents. The default zip operation is provided by RubyZip. On large documents users may experience faster zip times using a zip binary. Resolves #55
Diffstat (limited to 'lib/axlsx/util/zip_command.rb')
-rw-r--r--lib/axlsx/util/zip_command.rb73
1 files changed, 73 insertions, 0 deletions
diff --git a/lib/axlsx/util/zip_command.rb b/lib/axlsx/util/zip_command.rb
new file mode 100644
index 00000000..fb336209
--- /dev/null
+++ b/lib/axlsx/util/zip_command.rb
@@ -0,0 +1,73 @@
+# encoding: UTF-8
+require 'open3'
+require 'shellwords'
+
+module Axlsx
+
+ # The ZipCommand class supports zipping the Excel file contents using
+ # a binary zip program instead of RubyZip's `Zip::OutputStream`.
+ #
+ # The methods provided here mimic `Zip::OutputStream` so that `ZipCommand` can
+ # be used as a drop-in replacement. Note that method signatures are not
+ # identical to `Zip::OutputStream`, they are only sufficiently close so that
+ # `ZipCommand` and `Zip::OutputStream` can be interchangeably used within
+ # `caxlsx`.
+ class ZipCommand
+ # Raised when the zip command exits with a non-zero status.
+ class ZipError < StandardError; end
+
+ def initialize(zip_command)
+ @current_file = nil
+ @files = []
+ @zip_command = zip_command
+ end
+
+ # Create a temporary directory for writing files to.
+ #
+ # The directory and its contents are removed at the end of the block.
+ def open(output, &block)
+ Dir.mktmpdir do |dir|
+ @dir = dir
+ block.call(self)
+ write_file
+ zip_parts(output)
+ end
+ end
+
+ # Closes the current entry and opens a new for writing.
+ def put_next_entry(entry)
+ write_file
+ @current_file = "#{@dir}/#{entry.name}"
+ @files << entry.name
+ FileUtils.mkdir_p(File.dirname(@current_file))
+ end
+
+ # Write to a buffer that will be written to the current entry
+ def write(content)
+ @buffer << content
+ end
+ alias << write
+
+ private
+
+ def write_file
+ if @current_file
+ @buffer.rewind
+ File.open(@current_file, "wb") { |f| f.write @buffer.read }
+ end
+ @current_file = nil
+ @buffer = StringIO.new
+ end
+
+ def zip_parts(output)
+ output = Shellwords.shellescape(File.absolute_path(output))
+ inputs = Shellwords.shelljoin(@files)
+ escaped_dir = Shellwords.shellescape(@dir)
+ command = "cd #{escaped_dir} && #{@zip_command} #{output} #{inputs}"
+ stdout_and_stderr, status = Open3.capture2e(command)
+ if !status.success?
+ raise(ZipError.new(stdout_and_stderr))
+ end
+ end
+ end
+end