cabriolet 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +3 -0
  3. data/lib/cabriolet/binary/bitstream.rb +32 -21
  4. data/lib/cabriolet/binary/bitstream_writer.rb +21 -4
  5. data/lib/cabriolet/cab/compressor.rb +85 -53
  6. data/lib/cabriolet/cab/decompressor.rb +2 -1
  7. data/lib/cabriolet/cab/extractor.rb +2 -35
  8. data/lib/cabriolet/cab/file_compression_work.rb +52 -0
  9. data/lib/cabriolet/cab/file_compression_worker.rb +89 -0
  10. data/lib/cabriolet/checksum.rb +49 -0
  11. data/lib/cabriolet/collections/file_collection.rb +175 -0
  12. data/lib/cabriolet/compressors/quantum.rb +3 -51
  13. data/lib/cabriolet/decompressors/quantum.rb +81 -52
  14. data/lib/cabriolet/extraction/base_extractor.rb +88 -0
  15. data/lib/cabriolet/extraction/extractor.rb +171 -0
  16. data/lib/cabriolet/extraction/file_extraction_work.rb +60 -0
  17. data/lib/cabriolet/extraction/file_extraction_worker.rb +106 -0
  18. data/lib/cabriolet/format_base.rb +79 -0
  19. data/lib/cabriolet/hlp/quickhelp/compressor.rb +28 -503
  20. data/lib/cabriolet/hlp/quickhelp/file_writer.rb +125 -0
  21. data/lib/cabriolet/hlp/quickhelp/offset_calculator.rb +61 -0
  22. data/lib/cabriolet/hlp/quickhelp/structure_builder.rb +93 -0
  23. data/lib/cabriolet/hlp/quickhelp/topic_builder.rb +52 -0
  24. data/lib/cabriolet/hlp/quickhelp/topic_compressor.rb +83 -0
  25. data/lib/cabriolet/huffman/encoder.rb +15 -12
  26. data/lib/cabriolet/lit/compressor.rb +45 -689
  27. data/lib/cabriolet/lit/content_encoder.rb +76 -0
  28. data/lib/cabriolet/lit/content_type_detector.rb +50 -0
  29. data/lib/cabriolet/lit/directory_builder.rb +153 -0
  30. data/lib/cabriolet/lit/guid_generator.rb +16 -0
  31. data/lib/cabriolet/lit/header_writer.rb +124 -0
  32. data/lib/cabriolet/lit/piece_builder.rb +74 -0
  33. data/lib/cabriolet/lit/structure_builder.rb +252 -0
  34. data/lib/cabriolet/quantum_shared.rb +105 -0
  35. data/lib/cabriolet/version.rb +1 -1
  36. data/lib/cabriolet.rb +114 -3
  37. metadata +38 -4
  38. data/lib/cabriolet/auto.rb +0 -173
  39. data/lib/cabriolet/parallel.rb +0 -333
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fractor"
4
+
5
+ module Cabriolet
6
+ module Extraction
7
+ # Work item for file extraction using Fractor
8
+ class FileExtractionWork < Fractor::Work
9
+ # Initialize work item for extracting a single file
10
+ #
11
+ # @param file [Object] File object from archive (responds to :name, :data)
12
+ # @param output_dir [String] Output directory path
13
+ # @param preserve_paths [Boolean] Whether to preserve directory structure
14
+ # @param overwrite [Boolean] Whether to overwrite existing files
15
+ def initialize(file, output_dir:, preserve_paths: true, overwrite: false)
16
+ super({
17
+ file: file,
18
+ output_dir: output_dir,
19
+ preserve_paths: preserve_paths,
20
+ overwrite: overwrite,
21
+ })
22
+ end
23
+
24
+ # The file object to extract
25
+ #
26
+ # @return [Object] File from archive
27
+ def file
28
+ input[:file]
29
+ end
30
+
31
+ # Output directory for extraction
32
+ #
33
+ # @return [String] Directory path
34
+ def output_dir
35
+ input[:output_dir]
36
+ end
37
+
38
+ # Whether to preserve directory structure
39
+ #
40
+ # @return [Boolean]
41
+ def preserve_paths
42
+ input[:preserve_paths]
43
+ end
44
+
45
+ # Whether to overwrite existing files
46
+ #
47
+ # @return [Boolean]
48
+ def overwrite
49
+ input[:overwrite]
50
+ end
51
+
52
+ # Unique identifier for this work item (filename based)
53
+ #
54
+ # @return [String] Unique identifier
55
+ def id
56
+ file.name
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Cabriolet
6
+ module Extraction
7
+ # Worker for extracting files using Fractor
8
+ class FileExtractionWorker < Fractor::Worker
9
+ # Process a file extraction work item
10
+ #
11
+ # @param work [FileExtractionWork] Work item to process
12
+ # @return [Fractor::WorkResult] Result of extraction
13
+ def process(work)
14
+ output_path = build_output_path(work)
15
+
16
+ # Check if file exists and skip if not overwriting
17
+ if ::File.exist?(output_path) && !work.overwrite
18
+ return skipped_result(work, "File already exists")
19
+ end
20
+
21
+ # Create parent directory
22
+ dir = ::File.dirname(output_path)
23
+ FileUtils.mkdir_p(dir) unless ::File.directory?(dir)
24
+
25
+ # Get file data
26
+ data = work.file.data
27
+ unless data
28
+ return skipped_result(work, "No data available")
29
+ end
30
+
31
+ # Write file data
32
+ ::File.binwrite(output_path, data)
33
+
34
+ # Preserve file attributes if available
35
+ preserve_file_attributes(output_path, work.file)
36
+
37
+ # Return success result
38
+ Fractor::WorkResult.new(
39
+ result: {
40
+ path: output_path,
41
+ size: data.bytesize,
42
+ name: work.file.name,
43
+ },
44
+ work: work,
45
+ )
46
+ rescue StandardError => e
47
+ # Return error result
48
+ Fractor::WorkResult.new(
49
+ error: {
50
+ message: e.message,
51
+ class: e.class.name,
52
+ backtrace: e.backtrace.first(5),
53
+ },
54
+ work: work,
55
+ )
56
+ end
57
+
58
+ private
59
+
60
+ # Build the output path for a file
61
+ #
62
+ # @param work [FileExtractionWork] Work item containing file and options
63
+ # @return [String] Full output path
64
+ def build_output_path(work)
65
+ # Normalize path separators (Windows archives use backslashes)
66
+ clean_name = work.file.name.gsub("\\", "/")
67
+
68
+ if work.preserve_paths
69
+ ::File.join(work.output_dir, clean_name)
70
+ else
71
+ ::File.join(work.output_dir, ::File.basename(clean_name))
72
+ end
73
+ end
74
+
75
+ # Preserve file attributes (timestamps, etc.)
76
+ #
77
+ # @param path [String] Path to extracted file
78
+ # @param file [Object] File object from archive
79
+ def preserve_file_attributes(path, file)
80
+ # Try various timestamp attributes that different formats use
81
+ if file.respond_to?(:datetime) && file.datetime
82
+ ::File.utime(::File.atime(path), file.datetime, path)
83
+ elsif file.respond_to?(:mtime) && file.mtime
84
+ atime = file.respond_to?(:atime) ? file.atime : ::File.atime(path)
85
+ ::File.utime(atime, file.mtime, path)
86
+ end
87
+ end
88
+
89
+ # Create a skipped result
90
+ #
91
+ # @param work [FileExtractionWork] Work item that was skipped
92
+ # @param reason [String] Reason for skipping
93
+ # @return [Fractor::WorkResult] Skipped result
94
+ def skipped_result(work, reason)
95
+ Fractor::WorkResult.new(
96
+ result: {
97
+ status: :skipped,
98
+ name: work.file.name,
99
+ reason: reason,
100
+ },
101
+ work: work,
102
+ )
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cabriolet
4
+ # FormatBase provides common functionality for all format-specific compressors
5
+ # and decompressors, reducing code duplication and establishing consistent patterns.
6
+ class FormatBase
7
+ # Initialize a format handler with common dependencies
8
+ #
9
+ # @param io_system [System::IOSystem, nil] I/O system for file operations
10
+ # @param algorithm_factory [AlgorithmFactory, nil] Factory for compression algorithms
11
+ def initialize(io_system = nil, algorithm_factory = nil)
12
+ @io_system = io_system || System::IOSystem.new
13
+ @algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory
14
+ end
15
+
16
+ protected
17
+
18
+ # Execute a block with file handles, automatically closing them after completion
19
+ #
20
+ # @param input_path [String] Path to input file
21
+ # @param output_path [String, nil] Path to output file (optional)
22
+ # @yield [Array<System::FileHandle>] File handles for input and output
23
+ # @return [Object] Return value of the block
24
+ def with_file_handles(input_path, output_path = nil)
25
+ input_handle = @io_system.open(input_path, Constants::MODE_READ)
26
+ output_handle = if output_path
27
+ @io_system.open(output_path,
28
+ Constants::MODE_WRITE)
29
+ end
30
+
31
+ begin
32
+ yield [input_handle, output_handle].compact
33
+ ensure
34
+ @io_system.close(input_handle) if input_handle
35
+ @io_system.close(output_handle) if output_handle
36
+ end
37
+ end
38
+
39
+ # Create a compressor using the algorithm factory
40
+ #
41
+ # @param algorithm [Symbol] Compression algorithm type
42
+ # @param input [System::FileHandle, System::MemoryHandle] Input handle
43
+ # @param output [System::FileHandle, System::MemoryHandle] Output handle
44
+ # @param size [Integer] Data size
45
+ # @param options [Hash] Additional options for the compressor
46
+ # @return [Object] Compressor instance
47
+ def create_compressor(algorithm, input, output, size, **options)
48
+ @algorithm_factory.create(
49
+ algorithm,
50
+ :compressor,
51
+ @io_system,
52
+ input,
53
+ output,
54
+ size,
55
+ **options,
56
+ )
57
+ end
58
+
59
+ # Create a decompressor using the algorithm factory
60
+ #
61
+ # @param algorithm [Symbol] Compression algorithm type
62
+ # @param input [System::FileHandle, System::MemoryHandle] Input handle
63
+ # @param output [System::FileHandle, System::MemoryHandle] Output handle
64
+ # @param size [Integer] Data size
65
+ # @param options [Hash] Additional options for the decompressor
66
+ # @return [Object] Decompressor instance
67
+ def create_decompressor(algorithm, input, output, size, **options)
68
+ @algorithm_factory.create(
69
+ algorithm,
70
+ :decompressor,
71
+ @io_system,
72
+ input,
73
+ output,
74
+ size,
75
+ **options,
76
+ )
77
+ end
78
+ end
79
+ end