diff options
| author | Ryan Winograd <[email protected]> | 2020-08-13 21:05:59 -0500 |
|---|---|---|
| committer | Ryan Winograd <[email protected]> | 2020-08-19 14:59:17 -0500 |
| commit | e7673f431813cde7aab2d032a4417d4ddeeb9342 (patch) | |
| tree | 95d2758cd969c9c4b7c3d38a887ae75a65185f7e /lib/axlsx/util/zip_command.rb | |
| parent | 33a53474a90f1825ce20c66dab481fdcfa1106bf (diff) | |
| download | caxlsx-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.rb | 73 |
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 |
