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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +51 -50
- data/Gemfile +2 -0
- data/Rakefile +3 -0
- data/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +91 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +82 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/lib/omnizip/algorithm.rb +0 -2
- data/lib/omnizip/algorithm_registry.rb +29 -57
- data/lib/omnizip/algorithms/lzma/bit_model.rb +1 -1
- data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
- data/lib/omnizip/algorithms/lzma/distance_coder.rb +5 -102
- data/lib/omnizip/algorithms/lzma/length_coder.rb +4 -92
- data/lib/omnizip/algorithms/lzma/literal_decoder.rb +4 -119
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
- data/lib/omnizip/algorithms/lzma/match_finder.rb +59 -29
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +20 -12
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +77 -269
- data/lib/omnizip/algorithms/lzma/range_encoder.rb +0 -9
- data/lib/omnizip/algorithms/lzma/xz_range_encoder_exact.rb +0 -15
- data/lib/omnizip/algorithms/lzma/xz_state.rb +0 -8
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +189 -968
- data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
- data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
- data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8.rb +0 -2
- data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
- data/lib/omnizip/algorithms.rb +1 -0
- data/lib/omnizip/archive_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
- data/lib/omnizip/archive_handlers.rb +11 -0
- data/lib/omnizip/checksum_registry.rb +23 -96
- data/lib/omnizip/cli/shared.rb +31 -0
- data/lib/omnizip/cli.rb +6 -43
- data/lib/omnizip/commands/archive_create_command.rb +0 -6
- data/lib/omnizip/commands/archive_extract_command.rb +0 -4
- data/lib/omnizip/commands/archive_list_command.rb +0 -4
- data/lib/omnizip/commands/archive_repair_command.rb +0 -2
- data/lib/omnizip/commands/archive_verify_command.rb +0 -2
- data/lib/omnizip/commands/compress_command.rb +0 -2
- data/lib/omnizip/commands/decompress_command.rb +0 -2
- data/lib/omnizip/commands/list_command.rb +0 -2
- data/lib/omnizip/commands/metadata_command.rb +0 -5
- data/lib/omnizip/commands/parity_create_command.rb +0 -2
- data/lib/omnizip/commands/parity_repair_command.rb +0 -2
- data/lib/omnizip/commands/parity_verify_command.rb +0 -2
- data/lib/omnizip/commands/profile_list_command.rb +0 -2
- data/lib/omnizip/commands/profile_show_command.rb +0 -2
- data/lib/omnizip/convenience.rb +111 -234
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
- data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
- data/lib/omnizip/error.rb +22 -18
- data/lib/omnizip/filter.rb +5 -3
- data/lib/omnizip/filter_registry.rb +74 -98
- data/lib/omnizip/filters/bcj2/decoder.rb +196 -130
- data/lib/omnizip/filters/bcj2.rb +0 -2
- data/lib/omnizip/filters/bcj_x86.rb +0 -2
- data/lib/omnizip/filters/filter_base.rb +4 -70
- data/lib/omnizip/filters/registry.rb +0 -2
- data/lib/omnizip/format_detector.rb +21 -12
- data/lib/omnizip/format_registry.rb +79 -38
- data/lib/omnizip/formats/bzip2_file.rb +0 -2
- data/lib/omnizip/formats/cpio/entry.rb +0 -2
- data/lib/omnizip/formats/cpio/reader.rb +0 -1
- data/lib/omnizip/formats/cpio/writer.rb +0 -1
- data/lib/omnizip/formats/cpio.rb +0 -1
- data/lib/omnizip/formats/format_spec_loader.rb +1 -1
- data/lib/omnizip/formats/gzip.rb +0 -2
- data/lib/omnizip/formats/iso/directory_record.rb +6 -3
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso.rb +0 -1
- data/lib/omnizip/formats/lzip.rb +5 -25
- data/lib/omnizip/formats/lzma_alone.rb +7 -25
- data/lib/omnizip/formats/msi/cab_extractor.rb +181 -0
- data/lib/omnizip/formats/msi/constants.rb +157 -0
- data/lib/omnizip/formats/msi/directory_resolver.rb +188 -0
- data/lib/omnizip/formats/msi/entry.rb +128 -0
- data/lib/omnizip/formats/msi/reader.rb +301 -0
- data/lib/omnizip/formats/msi/string_pool.rb +246 -0
- data/lib/omnizip/formats/msi/table_parser.rb +244 -0
- data/lib/omnizip/formats/msi.rb +84 -0
- data/lib/omnizip/formats/ole/allocation_table.rb +10 -1
- data/lib/omnizip/formats/ole/dirent.rb +20 -3
- data/lib/omnizip/formats/ole/header.rb +0 -1
- data/lib/omnizip/formats/ole/ranges_io.rb +21 -3
- data/lib/omnizip/formats/ole/storage.rb +19 -4
- data/lib/omnizip/formats/ole.rb +0 -2
- data/lib/omnizip/formats/rar/block_parser.rb +0 -1
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
- data/lib/omnizip/formats/rar/decompressor.rb +38 -1
- data/lib/omnizip/formats/rar/header.rb +0 -1
- data/lib/omnizip/formats/rar/rar5/writer.rb +0 -2
- data/lib/omnizip/formats/rar/reader.rb +2 -3
- data/lib/omnizip/formats/rar/writer.rb +0 -1
- data/lib/omnizip/formats/rar.rb +2 -2
- data/lib/omnizip/formats/rpm/entry.rb +0 -1
- data/lib/omnizip/formats/rpm/header.rb +3 -8
- data/lib/omnizip/formats/rpm/lead.rb +37 -26
- data/lib/omnizip/formats/rpm/tag.rb +0 -1
- data/lib/omnizip/formats/rpm/writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +98 -61
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_writer.rb +22 -61
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
- data/lib/omnizip/formats/seven_zip/parser.rb +14 -13
- data/lib/omnizip/formats/seven_zip/reader.rb +82 -19
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -15
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +22 -68
- data/lib/omnizip/formats/seven_zip.rb +63 -5
- data/lib/omnizip/formats/tar/entry.rb +0 -2
- data/lib/omnizip/formats/tar/header.rb +0 -2
- data/lib/omnizip/formats/tar/reader.rb +0 -2
- data/lib/omnizip/formats/tar/writer.rb +0 -2
- data/lib/omnizip/formats/tar.rb +0 -1
- data/lib/omnizip/formats/xar/entry.rb +0 -1
- data/lib/omnizip/formats/xar/header.rb +0 -1
- data/lib/omnizip/formats/xar/reader.rb +0 -1
- data/lib/omnizip/formats/xar/toc.rb +3 -3
- data/lib/omnizip/formats/xar/writer.rb +11 -3
- data/lib/omnizip/formats/xar.rb +0 -1
- data/lib/omnizip/formats/xz/reader.rb +7 -3
- data/lib/omnizip/formats/xz.rb +3 -40
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
- data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
- data/lib/omnizip/formats/zip/reader.rb +12 -2
- data/lib/omnizip/formats/zip/writer.rb +73 -1
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
- data/lib/omnizip/formats/zip.rb +0 -1
- data/lib/omnizip/formats.rb +9 -22
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +18 -185
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +3 -3
- data/lib/omnizip/io/source.rb +136 -0
- data/lib/omnizip/io.rb +2 -0
- data/lib/omnizip/link_handler.rb +7 -3
- data/lib/omnizip/metadata/metadata_editor.rb +1 -1
- data/lib/omnizip/models/algorithm_metadata.rb +33 -60
- data/lib/omnizip/models/compression_options.rb +40 -53
- data/lib/omnizip/models/conversion_options.rb +9 -18
- data/lib/omnizip/models/parallel_options.rb +25 -0
- data/lib/omnizip/optimization_registry.rb +8 -44
- data/lib/omnizip/parallel/job_scheduler.rb +26 -8
- data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
- data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
- data/lib/omnizip/parity/par2_repairer.rb +5 -5
- data/lib/omnizip/parity/par2_verifier.rb +27 -22
- data/lib/omnizip/parity.rb +4 -4
- data/lib/omnizip/password/encryption_registry.rb +11 -50
- data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
- data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
- data/lib/omnizip/profile/archive_profile.rb +0 -2
- data/lib/omnizip/profile/binary_profile.rb +0 -2
- data/lib/omnizip/profile/compression_profile.rb +0 -1
- data/lib/omnizip/profile/profile_detector.rb +0 -2
- data/lib/omnizip/profile/text_profile.rb +0 -2
- data/lib/omnizip/profile.rb +0 -2
- data/lib/omnizip/registry.rb +112 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/file.rb +20 -9
- data/lib/omnizip.rb +26 -55
- data/lib/tasks/msi_test_files.rake +66 -0
- 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.
|
data/lib/omnizip/algorithm.rb
CHANGED
|
@@ -17,70 +17,42 @@
|
|
|
17
17
|
#
|
|
18
18
|
|
|
19
19
|
module Omnizip
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
# @return [void]
|
|
72
|
-
def reset!
|
|
73
|
-
algorithms.clear
|
|
37
|
+
def label
|
|
38
|
+
"Algorithm"
|
|
74
39
|
end
|
|
75
40
|
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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.
|
|
63
|
-
dict.
|
|
63
|
+
dict.buffer = @buffer.dup
|
|
64
|
+
dict.position = @position
|
|
64
65
|
dict
|
|
65
66
|
end
|
|
66
67
|
end
|