omnizip 0.3.8 → 0.3.10

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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +51 -50
  3. data/Gemfile +2 -0
  4. data/Rakefile +3 -0
  5. data/TODO.refactor/00-overview.md +67 -0
  6. data/TODO.refactor/01-registry-base-class.md +81 -0
  7. data/TODO.refactor/02-error-hierarchy.md +57 -0
  8. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  9. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  10. data/TODO.refactor/05-send-private-methods.md +64 -0
  11. data/TODO.refactor/06-instance-variable-access.md +69 -0
  12. data/TODO.refactor/07-respond-to-replacement.md +91 -0
  13. data/TODO.refactor/08-spec-doubles.md +27 -0
  14. data/TODO.refactor/09-cli-shared-module.md +35 -0
  15. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  16. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  17. data/TODO.refactor/12-thread-safety.md +8 -0
  18. data/TODO.refactor/13-lutaml-model-migration.md +82 -0
  19. data/TODO.refactor/14-add-missing-specs.md +25 -0
  20. data/lib/omnizip/algorithm.rb +0 -2
  21. data/lib/omnizip/algorithm_registry.rb +29 -57
  22. data/lib/omnizip/algorithms/lzma/bit_model.rb +1 -1
  23. data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
  24. data/lib/omnizip/algorithms/lzma/distance_coder.rb +5 -102
  25. data/lib/omnizip/algorithms/lzma/length_coder.rb +4 -92
  26. data/lib/omnizip/algorithms/lzma/literal_decoder.rb +4 -119
  27. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
  28. data/lib/omnizip/algorithms/lzma/match_finder.rb +59 -29
  29. data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +20 -12
  30. data/lib/omnizip/algorithms/lzma/range_decoder.rb +77 -269
  31. data/lib/omnizip/algorithms/lzma/range_encoder.rb +0 -9
  32. data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +0 -15
  33. data/lib/omnizip/algorithms/lzma/xz_state.rb +0 -8
  34. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +189 -968
  35. data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
  36. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
  37. data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
  38. data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
  39. data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
  40. data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
  41. data/lib/omnizip/algorithms/ppmd8.rb +0 -2
  42. data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
  43. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
  44. data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
  45. data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
  46. data/lib/omnizip/algorithms.rb +1 -0
  47. data/lib/omnizip/archive_handler.rb +75 -0
  48. data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
  49. data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
  50. data/lib/omnizip/archive_handlers.rb +11 -0
  51. data/lib/omnizip/checksum_registry.rb +23 -96
  52. data/lib/omnizip/cli/shared.rb +31 -0
  53. data/lib/omnizip/cli.rb +6 -43
  54. data/lib/omnizip/commands/archive_create_command.rb +0 -6
  55. data/lib/omnizip/commands/archive_extract_command.rb +0 -4
  56. data/lib/omnizip/commands/archive_list_command.rb +0 -4
  57. data/lib/omnizip/commands/archive_repair_command.rb +0 -2
  58. data/lib/omnizip/commands/archive_verify_command.rb +0 -2
  59. data/lib/omnizip/commands/compress_command.rb +0 -2
  60. data/lib/omnizip/commands/decompress_command.rb +0 -2
  61. data/lib/omnizip/commands/list_command.rb +0 -2
  62. data/lib/omnizip/commands/metadata_command.rb +0 -5
  63. data/lib/omnizip/commands/parity_create_command.rb +0 -2
  64. data/lib/omnizip/commands/parity_repair_command.rb +0 -2
  65. data/lib/omnizip/commands/parity_verify_command.rb +0 -2
  66. data/lib/omnizip/commands/profile_list_command.rb +0 -2
  67. data/lib/omnizip/commands/profile_show_command.rb +0 -2
  68. data/lib/omnizip/convenience.rb +111 -234
  69. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
  70. data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
  71. data/lib/omnizip/error.rb +22 -18
  72. data/lib/omnizip/filter.rb +5 -3
  73. data/lib/omnizip/filter_registry.rb +74 -98
  74. data/lib/omnizip/filters/bcj2/decoder.rb +196 -130
  75. data/lib/omnizip/filters/bcj2.rb +0 -2
  76. data/lib/omnizip/filters/bcj_x86.rb +0 -2
  77. data/lib/omnizip/filters/filter_base.rb +4 -70
  78. data/lib/omnizip/filters/registry.rb +0 -2
  79. data/lib/omnizip/format_detector.rb +21 -12
  80. data/lib/omnizip/format_registry.rb +79 -38
  81. data/lib/omnizip/formats/bzip2_file.rb +0 -2
  82. data/lib/omnizip/formats/cpio/entry.rb +0 -2
  83. data/lib/omnizip/formats/cpio/reader.rb +0 -1
  84. data/lib/omnizip/formats/cpio/writer.rb +0 -1
  85. data/lib/omnizip/formats/cpio.rb +0 -1
  86. data/lib/omnizip/formats/format_spec_loader.rb +1 -1
  87. data/lib/omnizip/formats/gzip.rb +0 -2
  88. data/lib/omnizip/formats/iso/directory_record.rb +6 -3
  89. data/lib/omnizip/formats/iso/reader.rb +2 -2
  90. data/lib/omnizip/formats/iso.rb +0 -1
  91. data/lib/omnizip/formats/lzip.rb +5 -25
  92. data/lib/omnizip/formats/lzma_alone.rb +7 -25
  93. data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
  94. data/lib/omnizip/formats/msi/constants.rb +157 -0
  95. data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
  96. data/lib/omnizip/formats/msi/entry.rb +128 -0
  97. data/lib/omnizip/formats/msi/reader.rb +301 -0
  98. data/lib/omnizip/formats/msi/string_pool.rb +246 -0
  99. data/lib/omnizip/formats/msi/table_parser.rb +244 -0
  100. data/lib/omnizip/formats/msi.rb +84 -0
  101. data/lib/omnizip/formats/ole/allocation_table.rb +10 -1
  102. data/lib/omnizip/formats/ole/dirent.rb +20 -3
  103. data/lib/omnizip/formats/ole/header.rb +0 -1
  104. data/lib/omnizip/formats/ole/ranges_io.rb +21 -3
  105. data/lib/omnizip/formats/ole/storage.rb +19 -4
  106. data/lib/omnizip/formats/ole.rb +0 -2
  107. data/lib/omnizip/formats/rar/block_parser.rb +0 -1
  108. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
  109. data/lib/omnizip/formats/rar/decompressor.rb +38 -1
  110. data/lib/omnizip/formats/rar/header.rb +0 -1
  111. data/lib/omnizip/formats/rar/rar5/writer.rb +0 -2
  112. data/lib/omnizip/formats/rar/reader.rb +2 -3
  113. data/lib/omnizip/formats/rar/writer.rb +0 -1
  114. data/lib/omnizip/formats/rar.rb +2 -2
  115. data/lib/omnizip/formats/rpm/entry.rb +0 -1
  116. data/lib/omnizip/formats/rpm/header.rb +3 -8
  117. data/lib/omnizip/formats/rpm/lead.rb +37 -26
  118. data/lib/omnizip/formats/rpm/tag.rb +0 -1
  119. data/lib/omnizip/formats/rpm/writer.rb +0 -1
  120. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +98 -61
  121. data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
  122. data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
  123. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
  124. data/lib/omnizip/formats/seven_zip/header.rb +0 -1
  125. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
  126. data/lib/omnizip/formats/seven_zip/header_writer.rb +22 -61
  127. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
  128. data/lib/omnizip/formats/seven_zip/parser.rb +14 -13
  129. data/lib/omnizip/formats/seven_zip/reader.rb +82 -19
  130. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -15
  131. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
  132. data/lib/omnizip/formats/seven_zip/writer.rb +22 -68
  133. data/lib/omnizip/formats/seven_zip.rb +63 -5
  134. data/lib/omnizip/formats/tar/entry.rb +0 -2
  135. data/lib/omnizip/formats/tar/header.rb +0 -2
  136. data/lib/omnizip/formats/tar/reader.rb +0 -2
  137. data/lib/omnizip/formats/tar/writer.rb +0 -2
  138. data/lib/omnizip/formats/tar.rb +0 -1
  139. data/lib/omnizip/formats/xar/entry.rb +0 -1
  140. data/lib/omnizip/formats/xar/header.rb +0 -1
  141. data/lib/omnizip/formats/xar/reader.rb +0 -1
  142. data/lib/omnizip/formats/xar/toc.rb +3 -3
  143. data/lib/omnizip/formats/xar/writer.rb +11 -3
  144. data/lib/omnizip/formats/xar.rb +0 -1
  145. data/lib/omnizip/formats/xz/reader.rb +7 -3
  146. data/lib/omnizip/formats/xz.rb +3 -40
  147. data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
  148. data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
  149. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
  150. data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
  151. data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
  152. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
  153. data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
  154. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
  155. data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
  156. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
  157. data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
  158. data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
  159. data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
  160. data/lib/omnizip/formats/zip/reader.rb +12 -2
  161. data/lib/omnizip/formats/zip/writer.rb +73 -1
  162. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
  163. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
  164. data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
  165. data/lib/omnizip/formats/zip.rb +0 -1
  166. data/lib/omnizip/formats.rb +9 -22
  167. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
  168. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
  169. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
  170. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +18 -185
  171. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +3 -3
  172. data/lib/omnizip/io/source.rb +136 -0
  173. data/lib/omnizip/io.rb +2 -0
  174. data/lib/omnizip/link_handler.rb +7 -3
  175. data/lib/omnizip/metadata/metadata_editor.rb +1 -1
  176. data/lib/omnizip/models/algorithm_metadata.rb +33 -60
  177. data/lib/omnizip/models/compression_options.rb +40 -53
  178. data/lib/omnizip/models/conversion_options.rb +9 -18
  179. data/lib/omnizip/models/parallel_options.rb +25 -0
  180. data/lib/omnizip/optimization_registry.rb +8 -44
  181. data/lib/omnizip/parallel/job_scheduler.rb +26 -8
  182. data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
  183. data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
  184. data/lib/omnizip/parity/par2_repairer.rb +5 -5
  185. data/lib/omnizip/parity/par2_verifier.rb +27 -22
  186. data/lib/omnizip/parity.rb +4 -4
  187. data/lib/omnizip/password/encryption_registry.rb +11 -50
  188. data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
  189. data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
  190. data/lib/omnizip/profile/archive_profile.rb +0 -2
  191. data/lib/omnizip/profile/binary_profile.rb +0 -2
  192. data/lib/omnizip/profile/compression_profile.rb +0 -1
  193. data/lib/omnizip/profile/profile_detector.rb +0 -2
  194. data/lib/omnizip/profile/text_profile.rb +0 -2
  195. data/lib/omnizip/profile.rb +0 -2
  196. data/lib/omnizip/registry.rb +112 -0
  197. data/lib/omnizip/version.rb +1 -1
  198. data/lib/omnizip/zip/file.rb +20 -9
  199. data/lib/omnizip.rb +26 -55
  200. data/lib/tasks/msi_test_files.rake +66 -0
  201. metadata +57 -6
@@ -0,0 +1,69 @@
1
+ # 06 — Remove `instance_variable_set` / `instance_variable_get`
2
+
3
+ Priority: **high**.
4
+ Status: TODO.
5
+
6
+ ## Rule
7
+
8
+ > NEVER use `instance_variable_set` or `instance_variable_get`. Accessing
9
+ > another object's instance variables breaks encapsulation. If you need
10
+ > the data, add a public accessor or rethink the ownership.
11
+
12
+ ## Inventory (54 occurrences)
13
+
14
+ ### `instance_variable_set` (writers)
15
+
16
+ | File:Line | What | Fix |
17
+ |---|---|---|
18
+ | `lib/omnizip/formats/rpm/lead.rb:53,66-73` | Sets 9 attrs from a parsed hash in a factory | Replace with ctor args |
19
+ | `lib/omnizip/password/zip_crypto_strategy.rb:118` | `crc32.instance_variable_set(:@crc, crc)` | Add `Crc32#reset(crc)` public method |
20
+ | `lib/omnizip/formats/iso/reader.rb:161` | Sets `@full_path` on a record | Add public writer or store externally |
21
+ | `lib/omnizip/formats/xz_impl/block_decoder.rb:573-574,629-630` | Sets decoder state | Add public setters on LZMA2 decoder |
22
+ | `lib/omnizip/algorithms/lzma/xz_utils_decoder.rb:434-435,448,450` | Sets decoder state | Promote to ctor / public setters |
23
+ | `lib/omnizip/zip/file.rb:378,387,450` | Sets entry state | Add public setters |
24
+ | `lib/omnizip/formats/seven_zip/writer.rb:65,178,196` | Sets writer state | Add public setters |
25
+
26
+ ### `instance_variable_get` (readers)
27
+
28
+ | File:Line | What | Fix |
29
+ |---|---|---|
30
+ | `lib/omnizip/parity.rb:181,187` | Reads `@recovery_blocks`, `@file_list` from verifier | Add public readers |
31
+ | `lib/omnizip/parallel/parallel_compressor.rb:317` | Reads `@entries` from writer | Add public reader |
32
+ | `lib/omnizip/formats/lzma_alone.rb:152-156` | Reads decoder state (`@lc`, `@lp`, `@pb`, `@dict_size`, `@uncompressed_size`) | Add `Decoder#properties` Struct or public attr_readers |
33
+ | `lib/omnizip/formats/lzip.rb:149-151` | Reads decoder state | Same pattern |
34
+ | `lib/omnizip/formats/iso/reader.rb:180` | Reads `@full_path` | Use the public reader from above |
35
+ | `lib/omnizip/formats/msi/string_pool.rb:89` | Reads `@stream_name_map` | Add public reader |
36
+ | `lib/omnizip/formats/rar/compression/ppmd/encoder.rb:55` | Reads `@mem_size` from model | Add public reader |
37
+ | `lib/omnizip/formats/ole/dirent.rb:157` | Reads `@name_lookup` from parent | Add public reader |
38
+ | `lib/omnizip/formats/xz_impl/block_decoder.rb` | (covered above) | |
39
+
40
+ ## Plan
41
+
42
+ For each hotspot:
43
+
44
+ 1. Add a `attr_reader` (or `attr_accessor` if mutation is legitimate) on
45
+ the target class for the ivar being accessed.
46
+ 2. Replace `obj.instance_variable_get(:@foo)` with `obj.foo`.
47
+ 3. For setters: either (a) add a public writer if mutation is part of the
48
+ API contract, or (b) refactor to pass the value through the
49
+ constructor, or (c) add a domain-named method (e.g.,
50
+ `crc.reset(value)` instead of writing `@crc`).
51
+
52
+ For `lib/omnizip/formats/rpm/lead.rb` (worst offender, 9 ivars set in a
53
+ factory): replace the factory with `Lead.new(lead_type:, magic:, ...)`.
54
+ Use a Struct or a lutaml-model class.
55
+
56
+ For decoder state introspection (lzma_alone.rb, lzip.rb,
57
+ block_decoder.rb): define a `Decoder::Properties` Struct (or
58
+ `Omnizip::Models::DecoderProperties`) and expose it via a single
59
+ `decoder.properties` accessor.
60
+
61
+ ## Acceptance
62
+
63
+ - `grep -rn "instance_variable_set\|instance_variable_get" lib/` returns
64
+ zero matches (or only well-documented exceptions).
65
+ - All specs pass.
66
+
67
+ ## Files
68
+
69
+ ~21 files; see table above.
@@ -0,0 +1,91 @@
1
+ # 07 — Replace `respond_to?` with proper typing
2
+
3
+ Priority: **medium**.
4
+ Status: **partial — see Remaining Work**.
5
+
6
+ ## Rule
7
+
8
+ > NEVER use `respond_to?` for type checking. Use `is_a?` for type checks,
9
+ > or better yet, design the type hierarchy so the check isn't needed.
10
+
11
+ ## Done in this pass
12
+
13
+ - Removed `opts.respond_to?(:"#{k}=")` from `parallel_compressor.rb` and
14
+ `parallel_extractor.rb` by adding `ParallelOptions#apply(hash)` with a
15
+ fixed setter whitelist.
16
+ - Removed `format.respond_to?(:metadata)` from
17
+ `optimization_registry.rb` by relying on `Strategy.metadata` being
18
+ defined on the base class.
19
+
20
+ ## Remaining work
21
+
22
+ 109 `respond_to?` occurrences remain in `lib/`. They fall into these
23
+ categories (sampled):
24
+
25
+ ### A. Duck-typed IO/String inputs (largest category)
26
+
27
+ `input.respond_to?(:read)` / `output.respond_to?(:write)` in
28
+ `formats/xz.rb`, `formats/lzma_alone.rb`, `formats/lzip.rb`,
29
+ `formats/xz_impl/*.rb`, `pipe/*.rb`.
30
+
31
+ **Fix:** introduce `Omnizip::IO::Source.for(input)` and
32
+ `Omnizip::IO::Sink.for(output)` adapter methods in `lib/omnizip/io.rb`
33
+ that wrap String / StringIO / File / Pathname behind a single API.
34
+ Replace each `respond_to?`-guarded branch with a polymorphic call on the
35
+ adapter.
36
+
37
+ ### B. Job-size duck typing
38
+
39
+ `job.respond_to?(:size)` in `parallel/job_scheduler.rb` (6 occurrences).
40
+
41
+ **Fix:** define `Parallel::Job` base with `#size` returning a default of
42
+ 1. Jobs that know their size override; others inherit the default. The
43
+ scheduler stops needing to ask.
44
+
45
+ ### C. Entry-type duck typing
46
+
47
+ `entry.respond_to?(:name)`, `entry.respond_to?(:path)`,
48
+ `entry.respond_to?(:filename)`, `entry.respond_to?(:read)`,
49
+ `entry.respond_to?(:get_input_stream)` in
50
+ `extraction/selective_extractor.rb`, `parallel/parallel_extractor.rb`,
51
+ `commands/archive_list_command.rb`, `pipe/stream_decompressor.rb`,
52
+ `formats/rar/reader.rb`.
53
+
54
+ **Fix:** define `Omnizip::Models::Entry` as a common base for
55
+ zip/seven_zip/rar/tar entry classes. Each format's entry inherits and
56
+ implements the contract (`#name`, `#size`, `#unix_perms`, `#mtime`,
57
+ etc., returning nil when not applicable).
58
+
59
+ ### D. Platform feature detection
60
+
61
+ `File.respond_to?(:symlink)`, `File.respond_to?(:link)`,
62
+ `io.respond_to?(:pos)`, `io.respond_to?(:seek)` in `link_handler.rb`,
63
+ `file_type.rb`.
64
+
65
+ **Fix:** add `Platform.symlinks_supported?`,
66
+ `Platform.seekable?(io)` predicates in `lib/omnizip/platform.rb`.
67
+
68
+ ### E. Hash-to-object metaprogramming
69
+
70
+ `opts.respond_to?(:"#{k}=")` — same as ParallelOptions pattern. Fix
71
+ case-by-case with whitelisted `apply(hash)` methods.
72
+
73
+ ### F. Reader-specific methods
74
+
75
+ `reader.respond_to?(:split_reader)`,
76
+ `elem.respond_to?(:text)`, `entry.respond_to?(:method)` — these are
77
+ narrow cases. Convert to `is_a?` checks against the concrete class, or
78
+ introduce a small type hierarchy.
79
+
80
+ ## Recommended execution order
81
+
82
+ 1. Tracks A and D — small, localized, no API change for callers.
83
+ 2. Track B — single file, base class addition.
84
+ 3. Track C — larger; needs the Entry base class first.
85
+ 4. Track E — case-by-case.
86
+ 5. Track F — case-by-case.
87
+
88
+ ## Acceptance
89
+
90
+ `grep -rn "respond_to?" lib/` returns zero matches (or each remaining
91
+ occurrence is annotated with `# allowed: <reason>`).
@@ -0,0 +1,27 @@
1
+ # 08 — Remove `double()` from specs
2
+
3
+ Priority: **low** (1 occurrence).
4
+ Status: TODO.
5
+
6
+ ## Rule
7
+
8
+ > NEVER use `double()` in specs. Use real model instances or lightweight
9
+ Structs.
10
+
11
+ ## Inventory
12
+
13
+ | File:Line | Code |
14
+ |---|---|
15
+ | `spec/omnizip/formats/rar/rar5/solid/solid_stream_spec.rb:41` | `stat = double("stat")` |
16
+
17
+ ## Plan
18
+
19
+ 1. Read the spec context to see which `File::Stat` interface the spec
20
+ exercises.
21
+ 2. Replace with `Struct.new(:mtime, :size, :mode, ...).new(...)` or
22
+ `File::Stat.new(real_path)` if a real file is available.
23
+
24
+ ## Acceptance
25
+
26
+ - `grep -rn "double(" spec/` returns zero matches.
27
+ - Spec passes.
@@ -0,0 +1,35 @@
1
+ # 09 — CLI shared module
2
+
3
+ Priority: **low**.
4
+ Status: TODO.
5
+
6
+ ## Problem
7
+
8
+ `handle_error` is duplicated verbatim in three Thor classes inside
9
+ `lib/omnizip/cli.rb` (lines 81–84, 312–315, 557–560). `format_bytes`
10
+ (line 562) is a utility that doesn't belong inside the CLI class.
11
+
12
+ ## Plan
13
+
14
+ 1. Create `lib/omnizip/cli/shared.rb` defining:
15
+ ```ruby
16
+ module Omnizip
17
+ class Cli
18
+ module Shared
19
+ def handle_error(err)
20
+ # ...
21
+ end
22
+ module_function :format_bytes
23
+ end
24
+ end
25
+ end
26
+ ```
27
+ 2. Each Thor class `include`s `Shared`.
28
+ 3. Move `format_bytes` out of the CLI class proper; place it in a
29
+ `Omnizip::Cli::Formatters` module (or under `Omnizip::Utils::Bytes` if
30
+ broader use exists).
31
+
32
+ ## Acceptance
33
+
34
+ - Single definition of `handle_error` and `format_bytes`.
35
+ - CLI specs pass.
@@ -0,0 +1,34 @@
1
+ # 10 — FormatDetector OCP
2
+
3
+ Priority: **medium**.
4
+ Status: TODO.
5
+
6
+ ## Problem
7
+
8
+ `lib/omnizip/format_detector.rb:112-123` maps format symbols to reader
9
+ classes via a `case` statement. Adding a new format requires modifying
10
+ this method — a clear OCP violation. The `FormatRegistry` already
11
+ registers each format handler; this case statement duplicates that
12
+ knowledge.
13
+
14
+ ## Plan
15
+
16
+ 1. Replace the case statement with a lookup against `FormatRegistry`:
17
+ ```ruby
18
+ def reader_for(format)
19
+ FormatRegistry.get(format) ||
20
+ raise(UnsupportedFormatError, "No reader for #{format}")
21
+ end
22
+ ```
23
+ 2. Ensure every format that previously appeared in the case statement is
24
+ registered in `FormatRegistry` with its reader class.
25
+ 3. If a format is detected but its handler hasn't been required yet, the
26
+ registry lookup must trigger autoload — verify the autoload wiring in
27
+ `lib/omnizip.rb` makes this work.
28
+
29
+ ## Acceptance
30
+
31
+ - `lib/omnizip/format_detector.rb` no longer has a format-specific case.
32
+ - All format_detector specs pass.
33
+ - A new format can be added by registering it, with zero edits to
34
+ FormatDetector.
@@ -0,0 +1,38 @@
1
+ # 11 — Convenience module decoupling
2
+
3
+ Priority: **medium**.
4
+ Status: TODO.
5
+
6
+ ## Problem
7
+
8
+ `lib/omnizip/convenience.rb` (359 lines) hardcodes `Omnizip::Zip::File`
9
+ in 7 method bodies (lines 45, 87, 112, 149, 175, 207, 227) —
10
+ `compress_file`, `compress_directory`, `extract_archive`,
11
+ `list_archive`, `read_from_archive`, `add_to_archive`,
12
+ `remove_from_archive`. The codebase supports 7z, XZ, tar, gzip, bzip2,
13
+ RAR, but convenience methods are ZIP-only.
14
+
15
+ ## Plan
16
+
17
+ 1. Add a `format:` keyword argument to each convenience method.
18
+ Default to `:zip` for backward compatibility.
19
+ 2. Resolve the format handler via `FormatRegistry.get(format)` and
20
+ delegate. If the handler doesn't expose the needed operation, raise a
21
+ clear `UnsupportedFormatError` describing what's missing.
22
+ 3. Each format handler (zip, seven_zip, tar, gzip, xz, bzip2) should
23
+ implement a shared `Handler` interface with `#add_file`,
24
+ `#add_directory`, `#extract`, `#list`, `#read`, `#remove`. This
25
+ becomes the contract for being usable from `convenience.rb`.
26
+
27
+ ## Acceptance
28
+
29
+ - Convenience methods accept `format:` and dispatch via registry.
30
+ - ZIP path remains the default with no behavior change.
31
+ - At least one non-ZIP format (e.g., tar) is wired up end-to-end.
32
+
33
+ ## Scope note
34
+
35
+ Full format handler interface extraction is large. This pass:
36
+ 1. Adds the `format:` keyword with ZIP default.
37
+ 2. Wires tar as a second supported format (simplest after zip).
38
+ 3. Leaves the remaining formats as documented future work.
@@ -0,0 +1,8 @@
1
+ # 12 — Thread safety
2
+
3
+ Priority: covered by track 01.
4
+ Status: n/a.
5
+
6
+ The `Registry` base class introduced in track 01 includes `Mutex`-based
7
+ synchronization around mutations (`register`, `reset!`). All migrated
8
+ registries inherit this for free. No standalone work.
@@ -0,0 +1,82 @@
1
+ # 13 — lutaml-model migration
2
+
3
+ Priority: **medium**.
4
+ Status: TODO (large; this pass establishes the pattern, full migration
5
+ is follow-up).
6
+
7
+ ## Rule
8
+
9
+ > ALL (de)serialization goes through the framework. Declare attributes
10
+ > with `attribute :name, :type`. Declare wire names with a `mapping do
11
+ > ... end` block. Never write `def to_h` / `def from_h` / `def to_json`
12
+ > / `def from_json` on a model class.
13
+
14
+ ## Inventory
15
+
16
+ 26 `def to_h` occurrences across 22 files. 2 `def to_json`. None use
17
+ lutaml-model today even though the gemspec declares the dependency.
18
+
19
+ ### Models (lib/omnizip/models/)
20
+
21
+ - `compression_options.rb` (`to_h`, `to_json`)
22
+ - `algorithm_metadata.rb` (`to_h`, `to_json`)
23
+ - `conversion_result.rb`
24
+ - `eta_result.rb`
25
+ - `filter_config.rb`
26
+ - `match_result.rb`
27
+ - `optimization_suggestion.rb`
28
+ - `parallel_options.rb`
29
+ - `performance_result.rb`
30
+ - `profile_report.rb`
31
+ - `progress_options.rb`
32
+ - `conversion_options.rb`
33
+
34
+ ### Format entry/metadata classes
35
+
36
+ - `formats/xar/entry.rb`
37
+ - `formats/zip/unix_extra_field.rb`
38
+ - `formats/rpm/header.rb`
39
+ - `metadata/entry_metadata.rb`
40
+ - `metadata/archive_metadata.rb`
41
+
42
+ ### Other
43
+
44
+ - `progress/operation_progress.rb`
45
+ - `profile/profile_registry.rb`, `custom_profile.rb`, `compression_profile.rb`
46
+ - `link_handler/symbolic_link.rb`, `hard_link.rb`
47
+
48
+ ## Plan (this pass)
49
+
50
+ 1. Establish the pattern by migrating **two representative models**:
51
+ `Omnizip::Models::CompressionOptions` and
52
+ `Omnizip::Models::AlgorithmMetadata`. Convert to lutaml-model:
53
+ ```ruby
54
+ class CompressionOptions < Lutaml::Model::Serializable
55
+ attribute :algorithm, :string
56
+ attribute :level, :integer, default: 6
57
+ # ...
58
+ mapping do
59
+ map :algorithm, to: :algorithm
60
+ map :level, to: :level
61
+ end
62
+ end
63
+ ```
64
+ The framework provides `to_hash`, `to_json`, `from_hash`,
65
+ `from_json` automatically — remove the hand-rolled versions.
66
+
67
+ 2. Delete the manual `to_h` / `to_json` from those two files.
68
+
69
+ 3. Document the migration recipe in this TODO file for the remaining
70
+ models.
71
+
72
+ ## Acceptance
73
+
74
+ - Two models migrated; their specs still pass.
75
+ - `grep -rn "def to_h\|def to_json" lib/omnizip/models/` count drops by
76
+ at least 2.
77
+
78
+ ## Remaining work (future pass)
79
+
80
+ Migrate the other ~24 files following the established recipe. The
81
+ metadata/entry classes likely share structure and may collapse into a
82
+ smaller set of richer models — review for MECE opportunities.
@@ -0,0 +1,25 @@
1
+ # 14 — Add missing specs
2
+
3
+ Priority: **high**.
4
+ Status: TODO.
5
+
6
+ ## Plan
7
+
8
+ Add or strengthen specs for:
9
+
10
+ 1. `Omnizip::Registry` (new base class from track 01) — register / get /
11
+ registered? / available / reset! / thread-safety smoke test / custom
12
+ not-found error.
13
+ 2. `Omnizip::Error` hierarchy (track 02) — confirm inheritance and
14
+ aliases.
15
+ 3. `Omnizip::Cli::Shared` (track 09) — `handle_error` and
16
+ `format_bytes`.
17
+ 4. `Omnizip::IO::Source` / `Omnizip::IO::Sink` (track 07) — adapter
18
+ behavior for String / StringIO / File / Pathname.
19
+ 5. Promoted public methods (track 05) — wherever a method moves from
20
+ private to public, ensure a spec covers its new visibility.
21
+
22
+ ## Acceptance
23
+
24
+ - All new specs pass.
25
+ - No `double()` introduced.
@@ -16,8 +16,6 @@
16
16
  # See the COPYING file for the complete text of the license.
17
17
  #
18
18
 
19
- require "omnizip/algorithm_registry"
20
-
21
19
  module Omnizip
22
20
  # Abstract base class for compression algorithms.
23
21
  #
@@ -17,70 +17,42 @@
17
17
  #
18
18
 
19
19
  module Omnizip
20
- # Registry for managing compression algorithm classes.
21
- #
22
- # This class provides a centralized registry for compression algorithms,
23
- # allowing algorithms to self-register and be retrieved by name.
24
- # It implements a plugin-style architecture for extensibility.
25
- class AlgorithmRegistry
26
- class << self
27
- # Register an algorithm class with the registry.
28
- #
29
- # @param name [Symbol, String] The name identifier for the algorithm
30
- # @param klass [Class] The algorithm class to register
31
- # @raise [ArgumentError] If name or klass is nil
32
- # @return [void]
33
- def register(name, klass)
34
- raise ArgumentError, "Algorithm name cannot be nil" if name.nil?
35
- raise ArgumentError, "Algorithm class cannot be nil" if klass.nil?
36
-
37
- algorithms[name.to_sym] = klass
38
- end
39
-
40
- # Retrieve an algorithm class by name.
41
- #
42
- # @param name [Symbol, String] The name identifier for the algorithm
43
- # @raise [UnknownAlgorithmError] If algorithm is not registered
44
- # @return [Class] The registered algorithm class
45
- def get(name)
46
- algorithm = algorithms[name.to_sym]
47
- return algorithm if algorithm
48
-
49
- raise UnknownAlgorithmError,
50
- "Unknown algorithm: #{name}. " \
51
- "Available: #{available.join(', ')}"
52
- end
20
+ class AlgorithmRegistry < Omnizip::Registry
21
+ BUILTIN_ALGORITHMS = {
22
+ lzma: "Omnizip::Algorithms::LZMA",
23
+ lzma2: "Omnizip::Algorithms::LZMA2",
24
+ ppmd7: "Omnizip::Algorithms::PPMd7",
25
+ ppmd8: "Omnizip::Algorithms::PPMd8",
26
+ bzip2: "Omnizip::Algorithms::BZip2",
27
+ deflate: "Omnizip::Algorithms::Deflate",
28
+ deflate64: "Omnizip::Algorithms::Deflate64",
29
+ zstandard: "Omnizip::Algorithms::Zstandard",
30
+ }.freeze
53
31
 
54
- # Check if an algorithm is registered.
55
- #
56
- # @param name [Symbol, String] The name identifier for the algorithm
57
- # @return [Boolean] True if algorithm is registered, false otherwise
58
- def registered?(name)
59
- algorithms.key?(name.to_sym)
60
- end
61
-
62
- # Get list of all registered algorithm names.
63
- #
64
- # @return [Array<Symbol>] Array of registered algorithm names
65
- def available
66
- algorithms.keys
32
+ class << self
33
+ def not_found_error_class
34
+ Omnizip::UnknownAlgorithmError
67
35
  end
68
36
 
69
- # Reset the registry (primarily for testing).
70
- #
71
- # @return [void]
72
- def reset!
73
- algorithms.clear
37
+ def label
38
+ "Algorithm"
74
39
  end
75
40
 
76
- private
41
+ def register(name, klass)
42
+ raise ArgumentError, "Algorithm name cannot be nil" if name.nil?
43
+ raise ArgumentError, "Algorithm class cannot be nil" if klass.nil?
77
44
 
78
- # Get or initialize the algorithms hash.
79
- #
80
- # @return [Hash] The algorithms registry
81
- def algorithms
82
- @algorithms ||= {}
45
+ super
83
46
  end
84
47
  end
85
48
  end
86
49
  end
50
+
51
+ # Lazy triggers — explicitly re-register on each miss so the entry
52
+ # survives reset!. Object.const_get alone doesn't re-run the file body
53
+ # once the constant is loaded.
54
+ Omnizip::AlgorithmRegistry::BUILTIN_ALGORITHMS.each do |name, const_path|
55
+ Omnizip::AlgorithmRegistry.register_lazy(name) do
56
+ Omnizip::AlgorithmRegistry.register(name, Object.const_get(const_path))
57
+ end
58
+ end
@@ -37,7 +37,7 @@ module Omnizip
37
37
  MAX_PROB = 1 << 11 # 4096
38
38
  BIT_MODEL_TOTAL = 0x800 # XZ Utils RC_BIT_MODEL_TOTAL = 2048
39
39
 
40
- attr_reader :probability
40
+ attr_accessor :probability
41
41
 
42
42
  # Initialize a new bit probability model
43
43
  #
@@ -6,7 +6,8 @@ module Omnizip
6
6
  # Circular buffer dictionary for LZMA sliding window
7
7
  # Ported from XZ Utils lzma_decoder.c
8
8
  class Dictionary
9
- attr_reader :size, :position, :buffer
9
+ attr_accessor :position, :buffer
10
+ attr_reader :size
10
11
 
11
12
  def initialize(size)
12
13
  @size = size
@@ -59,8 +60,8 @@ module Omnizip
59
60
  # Clone dictionary
60
61
  def clone
61
62
  dict = Dictionary.new(@size)
62
- dict.instance_variable_set(:@buffer, @buffer.dup)
63
- dict.instance_variable_set(:@position, @position)
63
+ dict.buffer = @buffer.dup
64
+ dict.position = @position
64
65
  dict
65
66
  end
66
67
  end