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
- READ_ARCHIVE_FORMAT_EXTENSIONS =
Extensions whose format has a READ-ONLY handler: extraction and listing route to it, while creation keeps raising.
{ ".rar" => :rar, }.freeze
- DECOMPRESSORS =
Extension -> single-file decompressor (stream interface). The Gzip/Bzip2File/Xz classes take path or stream; use the ones exposing decompress_stream for symmetry. Xz exposes a path-based decompress rather than a stream one, so it is marked specially here.
{ ".gz" => Formats::Gzip, ".bz2" => Formats::Bzip2File, ".xz" => :xz, ".lzma" => Formats::LzmaAlone, ".lz" => Formats::Lzip, ".zst" => :zstandard, }.freeze
- SINGLE_FILE_COMPRESSORS =
{ ".gz" => lambda do |input, output, | Omnizip::Formats::Gzip.compress(input, output, ) end, ".bz2" => lambda do |input, output, | Omnizip::Formats::Bzip2File.compress(input, output, ) end, ".xz" => lambda do |input, output, | Omnizip::Formats::Xz.create(::File.binread(input), output, ) end, ".lzma" => lambda do |input, output, | ::File.open(input, "rb") do |input_io| ::File.open(output, "wb") do |output_io| Omnizip::Formats::LzmaAlone.compress_stream(input_io, output_io, ) end end end, ".lz" => lambda do |input, output, | ::File.open(input, "rb") do |input_io| ::File.open(output, "wb") do |output_io| Omnizip::Formats::Lzip.compress_stream(input_io, output_io, ) end end end, ".zst" => lambda do |input, output, | ::File.open(input, "rb") do |input_io| ::File.open(output, "wb") do |output_io| Omnizip::Algorithms::Zstandard.new.compress(input_io, output_io, ) end end end, }.freeze
Instance Method Summary collapse
-
#add_to_archive(archive_path, entry_name, source_path, format: DEFAULT_FORMAT) ⇒ String
Add a file to an existing archive.
-
#compress_directory(input_dir, output_path, format: DEFAULT_FORMAT, recursive: true, **options) ⇒ String
Compress a directory into an archive.
-
#compress_file(input_path, output_path, format: DEFAULT_FORMAT, **options) ⇒ String
Compress a single file into an archive.
-
#create_rar(archive_path, **options, &block) ⇒ Object
Create a RAR archive (requires RAR license — see NotLicensedError).
-
#decompress_file(compressed_path, output_path) ⇒ String
Decompress a single-file compressed stream (the counterpart of #compress_file).
-
#extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT, overwrite: false, **options) ⇒ Array<String>
Extract an archive to a directory.
-
#list_archive(archive_path, format: DEFAULT_FORMAT, details: false, **options) ⇒ Array<String>, Array<Hash>
List contents of an archive.
-
#read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) ⇒ String
Read a single entry from an archive.
-
#remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) ⇒ String
Remove an entry from an existing archive.
-
#resolve_archive_format(path, format, writing: true) ⇒ Object
Archive format for a path: an explicit
formatwins unless it is the default; otherwise the extension routes. -
#single_file_handler(output_path) ⇒ Object
The single-file compressor matching the output extension, or nil.
Instance Method Details
#add_to_archive(archive_path, entry_name, source_path, format: DEFAULT_FORMAT) ⇒ String
Add a file to an existing archive.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/omnizip/convenience.rb', line 175 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 resolved = resolve_archive_format(archive_path, format) handler = Omnizip::ArchiveHandler.for(resolved) # 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 #{resolved.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.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/omnizip/convenience.rb', line 95 def compress_directory(input_dir, output_path, format: DEFAULT_FORMAT, recursive: true, **) 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 # A directory cannot be written through a single-file stream # format; routing it to the default would silently produce a # ZIP under a foreign extension. if format == DEFAULT_FORMAT && single_file_handler(output_path) raise Omnizip::UnsupportedFormatError, "#{::File.extname(output_path).downcase} is a single-file " \ "stream format and cannot contain a directory; use an " \ "archive format (.zip/.tar/.7z) or compress entries " \ "individually" end if [:profile] first_file = find_first_file(input_dir) apply_profile(first_file, ) 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.
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, **) 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 = apply_profile(input_path, ) if [:profile] if [:chunked] return Omnizip::Chunked.compress_file(input_path, output_path, **) 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, ) 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
217 218 219 220 |
# File 'lib/omnizip/convenience.rb', line 217 def create_rar(archive_path, **, &block) [:version] ||= 5 Omnizip::Formats::Rar.create(archive_path, , &block) end |
#decompress_file(compressed_path, output_path) ⇒ String
Decompress a single-file compressed stream (the counterpart of #compress_file). The format is resolved from the input extension: .gz, .bz2, .xz, .lzma, .lz.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/omnizip/convenience.rb', line 57 def decompress_file(compressed_path, output_path) require_archive!(compressed_path) format_class = DECOMPRESSORS[::File.extname(compressed_path).downcase] unless format_class raise Omnizip::UnsupportedFormatError, "decompress_file supports #{DECOMPRESSORS.keys.join(', ')}; " \ "use extract_archive for archive formats" end case format_class when :xz ::File.binwrite(output_path, Formats::Xz.decompress(compressed_path)) when :zstandard zstd = Algorithms::Zstandard.new ::File.open(compressed_path, "rb") do |input_io| ::File.open(output_path, "wb") do |output_io| zstd.decompress(input_io, output_io) end end else ::File.open(compressed_path, "rb") do |input_io| ::File.open(output_path, "wb") do |output_io| format_class.decompress_stream(input_io, output_io) end end end output_path end |
#extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT, overwrite: false, **options) ⇒ Array<String>
Extract an archive to a directory.
135 136 137 138 139 140 141 |
# File 'lib/omnizip/convenience.rb', line 135 def extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT, overwrite: false, **) require_archive!(archive_path) Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format, writing: false)) .extract_to(archive_path, output_dir, overwrite: overwrite, **) end |
#list_archive(archive_path, format: DEFAULT_FORMAT, details: false, **options) ⇒ Array<String>, Array<Hash>
List contents of an archive.
150 151 152 153 154 155 |
# File 'lib/omnizip/convenience.rb', line 150 def list_archive(archive_path, format: DEFAULT_FORMAT, details: false, **) require_archive!(archive_path) Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format, writing: false)) .list(archive_path, details: details, **) end |
#read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) ⇒ String
Read a single entry from an archive.
163 164 165 166 |
# File 'lib/omnizip/convenience.rb', line 163 def read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) require_archive!(archive_path) Omnizip::ArchiveHandler.for(resolve_archive_format(archive_path, format, writing: false)).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.
201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/omnizip/convenience.rb', line 201 def remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT) require_archive!(archive_path) resolved = resolve_archive_format(archive_path, format) handler = Omnizip::ArchiveHandler.for(resolved) # allowed: handler is a registered duck; missing remove_entry raises below unless handler.respond_to?(:remove_entry) raise Omnizip::UnsupportedFormatError, "Format #{resolved.inspect} does not support removing entries" end handler.remove_entry(archive_path, entry_name) archive_path end |
#resolve_archive_format(path, format, writing: true) ⇒ 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. writing: false (read operations) additionally routes
extensions with read-only handlers.
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/omnizip/convenience.rb', line 306 def resolve_archive_format(path, format, writing: true) return format unless format == DEFAULT_FORMAT ext = ::File.extname(path).downcase routed = ARCHIVE_FORMAT_EXTENSIONS[ext] routed ||= READ_ARCHIVE_FORMAT_EXTENSIONS[ext] unless writing 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.
296 297 298 299 |
# File 'lib/omnizip/convenience.rb', line 296 def single_file_handler(output_path) ext = ::File.extname(output_path).downcase SINGLE_FILE_COMPRESSORS[ext] end |