Module: Omnizip::Convenience

Included in:
Omnizip
Defined in:
lib/omnizip/convenience.rb

Overview

Convenience methods for common archive operations.

All public methods accept an optional format: keyword (default :zip) that selects the underlying archive handler via Omnizip::ArchiveHandler. New formats gain access to the convenience API by registering a handler; no edits to this file are needed.

Constant Summary collapse

DEFAULT_FORMAT =
:zip
ARCHIVE_FORMAT_EXTENSIONS =

Extension -> single-file compressor map. Each lambda takes (input_path, output_path, options). These formats are streams, not archives, so they bypass ArchiveHandler. Extensions naming archive formats with a registered handler; routed when the caller did not pass an explicit format:.

{
  ".zip" => :zip,
  ".tar" => :tar,
  ".7z" => :seven_zip,
}.freeze
READ_ONLY_FORMAT_EXTENSIONS =

Extensions naming real formats this gem can READ but not write through the convenience API. Writing them a ZIP under a foreign name was silent corruption; failing truthfully is the only honest behavior.

[".rar", ".iso", ".cpio"].freeze
SINGLE_FILE_COMPRESSORS =
{
  ".gz" => lambda do |input, output, options|
    Omnizip::Formats::Gzip.compress(input, output, options)
  end,
  ".bz2" => lambda do |input, output, options|
    Omnizip::Formats::Bzip2File.compress(input, output, options)
  end,
  ".xz" => lambda do |input, output, options|
    Omnizip::Formats::Xz.create(::File.binread(input), output, options)
  end,
  ".lzma" => lambda do |input, output, options|
    ::File.open(input, "rb") do |input_io|
      ::File.open(output, "wb") do |output_io|
        Omnizip::Formats::LzmaAlone.compress_stream(input_io, output_io,
                                                    options)
      end
    end
  end,
  ".lz" => lambda do |input, output, options|
    ::File.open(input, "rb") do |input_io|
      ::File.open(output, "wb") do |output_io|
        Omnizip::Formats::Lzip.compress_stream(input_io, output_io,
                                               options)
      end
    end
  end,
}.freeze

Instance Method Summary collapse

Instance Method Details

#add_to_archive(archive_path, entry_name, source_path, format: DEFAULT_FORMAT) ⇒ String

Add a file to an existing archive.

Parameters:

  • archive_path (String)

    Path to archive

  • entry_name (String)

    Entry name in archive

  • source_path (String)

    Path to source file

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

Returns:

  • (String)

    archive_path



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/omnizip/convenience.rb', line 127

def add_to_archive(archive_path, entry_name, source_path,
                   format: DEFAULT_FORMAT)
  require_archive!(archive_path)
  unless ::File.exist?(source_path)
    raise Errno::ENOENT, "Source file not found: #{source_path}"
  end

  handler = Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
  # allowed: handler is a registered duck; missing add_entry raises below
  if handler.respond_to?(:add_entry)
    handler.add_entry(archive_path, entry_name, source_path)
  else
    raise Omnizip::UnsupportedFormatError,
          "Format #{format.inspect} does not support adding entries"
  end

  archive_path
end

#compress_directory(input_dir, output_path, format: DEFAULT_FORMAT, recursive: true, **options) ⇒ String

Compress a directory into an archive.

Parameters:

  • input_dir (String)

    Path to input directory

  • output_path (String)

    Path to output archive

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

  • recursive (Boolean) (defaults to: true)

    Include subdirectories (default true)

  • options (Hash)

    Compression / format-specific options

Returns:

  • (String)

    output_path



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/omnizip/convenience.rb', line 58

def compress_directory(input_dir, output_path, format: DEFAULT_FORMAT,
                       recursive: true, **options)
  unless ::File.exist?(input_dir)
    raise Errno::ENOENT, "Input directory not found: #{input_dir}"
  end
  unless ::File.directory?(input_dir)
    raise ArgumentError, "Input is not a directory: #{input_dir}"
  end

  if options[:profile]
    first_file = find_first_file(input_dir)
    apply_profile(first_file, options)
  end

  Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path) do |archive|
    add_directory_contents(archive, input_dir, "", recursive: recursive)
  end

  output_path
end

#compress_file(input_path, output_path, format: DEFAULT_FORMAT, **options) ⇒ String

Compress a single file into an archive.

Parameters:

  • input_path (String)

    Path to input file

  • output_path (String)

    Path to output archive

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

  • options (Hash)

    Compression / format-specific options

Returns:

  • (String)

    output_path



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/omnizip/convenience.rb', line 20

def compress_file(input_path, output_path, format: DEFAULT_FORMAT, **options)
  unless ::File.exist?(input_path)
    raise Errno::ENOENT, "Input file not found: #{input_path}"
  end
  if ::File.directory?(input_path)
    raise ArgumentError, "Input is a directory: #{input_path}"
  end

  options = apply_profile(input_path, options) if options[:profile]

  if options[:chunked]
    return Omnizip::Chunked.compress_file(input_path, output_path, **options)
  end

  # Single-file compression formats route by output extension
  # (unless an archive format was requested explicitly): writing
  # output.lzma through the archive path silently produced a ZIP
  # under a foreign extension.
  if format == DEFAULT_FORMAT && (handler = single_file_handler(output_path))
    handler.call(input_path, output_path, options)
    return output_path
  end

  Omnizip::ArchiveHandler.for(resolve_archive_format(output_path, format)).create(output_path) do |archive|
    archive.add(::File.basename(input_path), input_path)
  end

  output_path
end

#create_rar(archive_path, **options, &block) ⇒ Object

Create a RAR archive (requires RAR license — see NotLicensedError). rubocop:disable-next Naming/BlockForwarding, Style/ArgumentsForwarding -- Ruby 3.0 compatibility



167
168
169
170
# File 'lib/omnizip/convenience.rb', line 167

def create_rar(archive_path, **options, &block)
  options[:version] ||= 5
  Omnizip::Formats::Rar.create(archive_path, options, &block)
end

#extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT, overwrite: false, **options) ⇒ Array<String>

Extract an archive to a directory.

Parameters:

  • archive_path (String)

    Path to archive

  • output_dir (String)

    Output directory

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

  • overwrite (Boolean) (defaults to: false)

    Overwrite existing files (default false)

  • options (Hash)

    Format-specific extraction options

Returns:

  • (Array<String>)

    Extracted file paths



87
88
89
90
91
92
93
# File 'lib/omnizip/convenience.rb', line 87

def extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT,
                    overwrite: false, **options)
  require_archive!(archive_path)
  Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
    .extract_to(archive_path, output_dir,
                overwrite: overwrite, **options)
end

#list_archive(archive_path, format: DEFAULT_FORMAT, details: false, **options) ⇒ Array<String>, Array<Hash>

List contents of an archive.

Parameters:

  • archive_path (String)

    Path to archive

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

  • details (Boolean) (defaults to: false)

    Include detailed info (default false)

  • options (Hash)

    Format-specific listing options

Returns:

  • (Array<String>, Array<Hash>)


102
103
104
105
106
107
# File 'lib/omnizip/convenience.rb', line 102

def list_archive(archive_path, format: DEFAULT_FORMAT, details: false,
                 **options)
  require_archive!(archive_path)
  Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
    .list(archive_path, details: details, **options)
end

#read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) ⇒ String

Read a single entry from an archive.

Parameters:

  • archive_path (String)

    Path to archive

  • entry_name (String)

    Entry to read

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

Returns:

  • (String)

    Entry contents



115
116
117
118
# File 'lib/omnizip/convenience.rb', line 115

def read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
  require_archive!(archive_path)
  Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format)).read_entry(archive_path, entry_name)
end

#remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) ⇒ String

Remove an entry from an existing archive.

Parameters:

  • archive_path (String)

    Path to archive

  • entry_name (String)

    Entry to remove

  • format (Symbol) (defaults to: DEFAULT_FORMAT)

    Archive format (default :zip)

Returns:

  • (String)

    archive_path



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/omnizip/convenience.rb', line 152

def remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
  require_archive!(archive_path)
  handler = Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format))
  # allowed: handler is a registered duck; missing remove_entry raises below
  unless handler.respond_to?(:remove_entry)
    raise Omnizip::UnsupportedFormatError,
          "Format #{format.inspect} does not support removing entries"
  end

  handler.remove_entry(archive_path, entry_name)
  archive_path
end

#resolve_archive_format(path, format) ⇒ Object

Archive format for a path: an explicit format wins unless it is the default; otherwise the extension routes. Known-but- unwritable extensions raise instead of receiving a mislabeled ZIP.



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/omnizip/convenience.rb', line 227

def resolve_archive_format(path, format)
  return format unless format == DEFAULT_FORMAT

  ext = ::File.extname(path).downcase
  routed = ARCHIVE_FORMAT_EXTENSIONS[ext]
  return routed if routed

  if READ_ONLY_FORMAT_EXTENSIONS.include?(ext)
    raise Omnizip::UnsupportedFormatError,
          "#{ext} archives cannot be written by the convenience " \
          "API (read-only support); use the format-specific reader"
  end

  DEFAULT_FORMAT
end

#single_file_handler(output_path) ⇒ Object

The single-file compressor matching the output extension, or nil.



218
219
220
221
# File 'lib/omnizip/convenience.rb', line 218

def single_file_handler(output_path)
  ext = ::File.extname(output_path).downcase
  SINGLE_FILE_COMPRESSORS[ext]
end