omnizip 0.3.9 → 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/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/dictionary.rb +4 -3
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +1 -1
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +6 -5
- 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.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 +69 -53
- 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/reader.rb +1 -0
- data/lib/omnizip/formats/msi/string_pool.rb +2 -3
- data/lib/omnizip/formats/msi.rb +1 -4
- data/lib/omnizip/formats/ole/allocation_table.rb +0 -1
- data/lib/omnizip/formats/ole/dirent.rb +9 -4
- data/lib/omnizip/formats/ole/header.rb +0 -1
- data/lib/omnizip/formats/ole/ranges_io.rb +1 -1
- data/lib/omnizip/formats/ole/storage.rb +5 -6
- data/lib/omnizip/formats/ole.rb +0 -1
- 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 +1 -0
- data/lib/omnizip/formats/rar/header.rb +0 -1
- data/lib/omnizip/formats/rar/rar5/writer.rb +0 -3
- data/lib/omnizip/formats/rar/reader.rb +2 -3
- data/lib/omnizip/formats/rar/writer.rb +0 -1
- data/lib/omnizip/formats/rar.rb +0 -1
- data/lib/omnizip/formats/rpm/entry.rb +0 -1
- data/lib/omnizip/formats/rpm/header.rb +1 -6
- 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 +0 -1
- 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 +0 -1
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
- data/lib/omnizip/formats/seven_zip/parser.rb +1 -2
- data/lib/omnizip/formats/seven_zip/reader.rb +13 -2
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -1
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +4 -6
- data/lib/omnizip/formats/seven_zip.rb +2 -4
- 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 +1 -2
- 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 +8 -24
- 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 +3 -4
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +0 -1
- 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 -58
- metadata +24 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d688703887c5ac25ffa43a48582dbb0580c74a94eff91d663d8e42c86943c4bb
|
|
4
|
+
data.tar.gz: ebe8ffdfddb46f63a508b4bc95e3d17ad85eef0e64ffe8e512d5c45f094cfedd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 067dbae2e0301457c15fdeed52dacbb3d85376aa3282d6fa09e519b3853252db4d2cfffe53d95d71129aaecaef6f0f566d89088cd0fb242300f7f294ab9a3d18
|
|
7
|
+
data.tar.gz: 2d5777ce1a3bd115a5084fa4f3b2bae7b566087043978506c105cdbb905d8575912304eb6814b7d8d3ace483b1cd39833a76e03182e4d22930f09b3d6cbae63c
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# 00 — Refactor Plan Overview
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-20 (updated 2026-07-21)
|
|
4
|
+
Scope: `lib/` and `spec/` of the omnizip gem.
|
|
5
|
+
|
|
6
|
+
## Goals
|
|
7
|
+
|
|
8
|
+
Apply OCP, DRY, MECE, model-driven design, open/closed principle, and high
|
|
9
|
+
performance to the omnizip codebase. Eliminate the anti-patterns declared
|
|
10
|
+
as absolute rules:
|
|
11
|
+
|
|
12
|
+
- `require_relative` (and internal `require`) inside `lib/`
|
|
13
|
+
- `.send` against private methods (breaks encapsulation)
|
|
14
|
+
- `instance_variable_set` / `instance_variable_get` on other objects
|
|
15
|
+
- `respond_to?` as a substitute for proper typing
|
|
16
|
+
- `double()` in specs (use real instances or `Struct`)
|
|
17
|
+
- Hand-rolled `to_h` / `from_h` / `to_json` on model classes (use `lutaml-model`)
|
|
18
|
+
|
|
19
|
+
## Track status
|
|
20
|
+
|
|
21
|
+
| # | File | Priority | Status |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| 01 | `01-registry-base-class.md` | high | **DONE** |
|
|
24
|
+
| 02 | `02-error-hierarchy.md` | high | **DONE** |
|
|
25
|
+
| 03 | `03-autoload-entry-point.md` | high | **DONE** |
|
|
26
|
+
| 04 | `04-filter-base-consolidation.md` | medium | **DONE** |
|
|
27
|
+
| 05 | `05-send-private-methods.md` | high | **DONE** |
|
|
28
|
+
| 06 | `06-instance-variable-access.md` | high | **DONE** |
|
|
29
|
+
| 07 | `07-respond-to-replacement.md` | medium | **PARTIAL** — IO::Source/Sink adapter built; type-check sites converted; ~76 IO capability checks remain in xz_impl/* and ole/* (legitimate duck-typing for ungetbyte / set_encoding etc.) |
|
|
30
|
+
| 08 | `08-spec-doubles.md` | low | **DONE** |
|
|
31
|
+
| 09 | `09-cli-shared-module.md` | low | **DONE** |
|
|
32
|
+
| 10 | `10-format-detector-ocp.md` | medium | **DONE** |
|
|
33
|
+
| 11 | `11-convenience-decoupling.md` | medium | **DONE** (ArchiveHandler dispatcher + ZipHandler + TarHandler) |
|
|
34
|
+
| 12 | `12-thread-safety.md` | covered by 01 | n/a |
|
|
35
|
+
| 13 | `13-lutaml-model-migration.md` | medium | **PARTIAL** — CompressionOptions and AlgorithmMetadata migrated; ConversionOptions pending lutaml symbol-type support |
|
|
36
|
+
| 14 | `14-add-missing-specs.md` | high | **DONE** |
|
|
37
|
+
|
|
38
|
+
## Anti-pattern counts (lib/)
|
|
39
|
+
|
|
40
|
+
| Pattern | Before | After |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `require_relative` / `require "omnizip/..."` | ~290 | **0** |
|
|
43
|
+
| `.send` on private methods | 17 | **0** |
|
|
44
|
+
| `instance_variable_set`/`get` | 54 | **0** |
|
|
45
|
+
| `double()` in specs | 1 | **0** |
|
|
46
|
+
| `respond_to?` | 111 | 76 (mostly IO capability detection) |
|
|
47
|
+
| Hand-rolled model `to_h` | 26 | 24 (2 migrated to lutaml-model) |
|
|
48
|
+
|
|
49
|
+
## Architecture summary
|
|
50
|
+
|
|
51
|
+
- **`Omnizip::Registry`** — thread-safe base class with lazy-load triggers.
|
|
52
|
+
- **`Omnizip::IO::Source` / `Sink`** — polymorphic adapters for
|
|
53
|
+
String/Path/IO/StringIO/Tempfile.
|
|
54
|
+
- **`Omnizip::ArchiveHandler`** — dispatcher decoupling Convenience from
|
|
55
|
+
ZIP. New formats plug in by registering a handler.
|
|
56
|
+
- **`Omnizip::Platform`** — predicates for symlink/hardlink/NTFS support.
|
|
57
|
+
- **Error hierarchy** consolidated with backward-compat aliases.
|
|
58
|
+
- **`Filter` / `FilterBase`** unified.
|
|
59
|
+
|
|
60
|
+
## Test coverage
|
|
61
|
+
|
|
62
|
+
3643 examples, 0 failures. New specs:
|
|
63
|
+
|
|
64
|
+
- `spec/omnizip/registry_spec.rb`
|
|
65
|
+
- `spec/omnizip/error_spec.rb`
|
|
66
|
+
- `spec/omnizip/io/source_spec.rb`
|
|
67
|
+
- `spec/omnizip/convenience_tar_spec.rb`
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 01 — Registry base class
|
|
2
|
+
|
|
3
|
+
Priority: **high**.
|
|
4
|
+
Status: TODO.
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
Five registry classes duplicate an identical pattern:
|
|
9
|
+
|
|
10
|
+
- `Omnizip::AlgorithmRegistry` (`lib/omnizip/algorithm_registry.rb`)
|
|
11
|
+
- `Omnizip::FormatRegistry` (`lib/omnizip/format_registry.rb`)
|
|
12
|
+
- `Omnizip::FilterRegistry` (`lib/omnizip/filter_registry.rb`)
|
|
13
|
+
- `Omnizip::ChecksumRegistry` (`lib/omnizip/checksum_registry.rb`)
|
|
14
|
+
- `Omnizip::OptimizationRegistry` (`lib/omnizip/optimization_registry.rb`)
|
|
15
|
+
- `Omnizip::Password::EncryptionRegistry` (`lib/omnizip/password/encryption_registry.rb`)
|
|
16
|
+
- `Omnizip::Converter::ConversionRegistry` (`lib/omnizip/converter/conversion_registry.rb`)
|
|
17
|
+
|
|
18
|
+
Each implements a slightly different version of `register / get /
|
|
19
|
+
registered? / available / reset` with different naming, error classes, and
|
|
20
|
+
key-normalization rules.
|
|
21
|
+
|
|
22
|
+
Inconsistencies:
|
|
23
|
+
|
|
24
|
+
| Registry | Storage | Reset | Not-found error |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| AlgorithmRegistry | `@algorithms ||= {}` | `reset!` | `UnknownAlgorithmError` |
|
|
27
|
+
| FormatRegistry | `@registry ||= {}` | (none) | returns nil |
|
|
28
|
+
| FilterRegistry | `@filters = {}` | `reset!` | `UnknownFilterError` |
|
|
29
|
+
| ChecksumRegistry | `@checksums = {}` | `clear` | `UnknownAlgorithmError` |
|
|
30
|
+
| OptimizationRegistry | `@strategies ||= {}` | `clear!` | `OptimizationNotFound` |
|
|
31
|
+
| EncryptionRegistry | `@strategies = {}` | `reset` | `ArgumentError` |
|
|
32
|
+
| ConversionRegistry | array of classes | `reset` | returns nil |
|
|
33
|
+
|
|
34
|
+
None are thread-safe.
|
|
35
|
+
|
|
36
|
+
## Plan
|
|
37
|
+
|
|
38
|
+
1. Create `lib/omnizip/registry.rb` defining `Omnizip::Registry` — a
|
|
39
|
+
thread-safe generic registry with:
|
|
40
|
+
- `register(name, value, **opts)` (overrides per subclass)
|
|
41
|
+
- `get(name)` → raises `not_found_error` if missing
|
|
42
|
+
- `registered?(name)` → bool
|
|
43
|
+
- `available` / `all` → keys
|
|
44
|
+
- `reset!` → clears store (canonical name)
|
|
45
|
+
- `Mutex`-synchronized mutations
|
|
46
|
+
- Configurable `not_found_error` class and `normalize_key` hook.
|
|
47
|
+
|
|
48
|
+
2. Migrate each registry to inherit from `Omnizip::Registry`. Preserve the
|
|
49
|
+
existing public class names and method names so call sites still work.
|
|
50
|
+
For backward compatibility, alias `clear`/`reset` to `reset!`,
|
|
51
|
+
`supported_formats`/`strategies` to `available` where applicable.
|
|
52
|
+
|
|
53
|
+
3. FormatRegistry keeps `normalize_extension` as its `normalize_key` hook.
|
|
54
|
+
|
|
55
|
+
4. FilterRegistry keeps its `formats:` registry metadata and
|
|
56
|
+
`get_for_format` / `supports_format?` / `filters_for_format` extensions
|
|
57
|
+
on top of the base.
|
|
58
|
+
|
|
59
|
+
5. ConversionRegistry is array-based and is the only outlier; leave as-is
|
|
60
|
+
or convert to a key based on `[source, target]` tuple. Pragmatically:
|
|
61
|
+
leave as-is, document why.
|
|
62
|
+
|
|
63
|
+
## Acceptance
|
|
64
|
+
|
|
65
|
+
- All existing registry specs pass unchanged.
|
|
66
|
+
- `bundle exec rspec` failure count is ≤ baseline (2).
|
|
67
|
+
- No new rubocop offenses.
|
|
68
|
+
- `lib/omnizip/registry_spec.rb` covers register/get/registered?/reset! and
|
|
69
|
+
thread-safety smoke test.
|
|
70
|
+
|
|
71
|
+
## Files to touch
|
|
72
|
+
|
|
73
|
+
- create `lib/omnizip/registry.rb`
|
|
74
|
+
- create `spec/omnizip/registry_spec.rb`
|
|
75
|
+
- modify `lib/omnizip/algorithm_registry.rb`
|
|
76
|
+
- modify `lib/omnizip/format_registry.rb`
|
|
77
|
+
- modify `lib/omnizip/filter_registry.rb`
|
|
78
|
+
- modify `lib/omnizip/checksum_registry.rb`
|
|
79
|
+
- modify `lib/omnizip/optimization_registry.rb`
|
|
80
|
+
- modify `lib/omnizip/password/encryption_registry.rb`
|
|
81
|
+
- modify `lib/omnizip.rb` to autoload `:Registry`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# 02 — Error hierarchy consolidation
|
|
2
|
+
|
|
3
|
+
Priority: **high**.
|
|
4
|
+
Status: TODO.
|
|
5
|
+
|
|
6
|
+
## Problems
|
|
7
|
+
|
|
8
|
+
1. `AlgorithmNotFoundError` and `UnknownAlgorithmError` both exist in
|
|
9
|
+
`lib/omnizip/error.rb` and are used interchangeably. The two registries
|
|
10
|
+
that raise on miss (`AlgorithmRegistry`, `ChecksumRegistry`) both raise
|
|
11
|
+
`UnknownAlgorithmError`.
|
|
12
|
+
2. `OptimizationNotFound` lacks the `Error` suffix convention.
|
|
13
|
+
3. `Omnizip::IOError` shadows Ruby's built-in `IOError` — confusing for
|
|
14
|
+
users who `rescue IOError`.
|
|
15
|
+
4. `UnknownFilterError` is defined at the bottom of
|
|
16
|
+
`lib/omnizip/filter_registry.rb` instead of `lib/omnizip/error.rb`.
|
|
17
|
+
5. `EncryptionRegistry` raises vanilla `ArgumentError` instead of a
|
|
18
|
+
domain-specific error.
|
|
19
|
+
6. `ConversionRegistry` returns nil instead of raising.
|
|
20
|
+
|
|
21
|
+
## Plan
|
|
22
|
+
|
|
23
|
+
1. In `lib/omnizip/error.rb`:
|
|
24
|
+
- Keep `AlgorithmNotFoundError` as canonical; make
|
|
25
|
+
`UnknownAlgorithmError` an alias (subclass) for backward compatibility.
|
|
26
|
+
Document the alias.
|
|
27
|
+
- Rename `OptimizationNotFound` → `OptimizationNotFoundError`. Keep the
|
|
28
|
+
old name as an alias for backward compatibility.
|
|
29
|
+
- Rename `IOError` → `IOOperationError`. Keep `IOError` as an alias for
|
|
30
|
+
backward compatibility (or deprecate it).
|
|
31
|
+
- Add `UnknownFilterError` to `error.rb` (and remove from
|
|
32
|
+
`filter_registry.rb`).
|
|
33
|
+
- Add `UnknownEncryptionStrategyError < Error`.
|
|
34
|
+
- Add `ConversionNotSupportedError < Error`.
|
|
35
|
+
|
|
36
|
+
2. Update all internal `raise` sites to use the canonical names.
|
|
37
|
+
|
|
38
|
+
3. Update `FormatRegistry`, `ConversionRegistry` to raise their new errors
|
|
39
|
+
instead of returning nil where the caller cannot reasonably handle nil.
|
|
40
|
+
|
|
41
|
+
4. Update `lib/omnizip.rb` autoload entries to include the new error names.
|
|
42
|
+
|
|
43
|
+
## Acceptance
|
|
44
|
+
|
|
45
|
+
- All existing specs pass.
|
|
46
|
+
- No new rubocop offenses.
|
|
47
|
+
- `lib/omnizip/error_spec.rb` confirms hierarchy and aliases.
|
|
48
|
+
|
|
49
|
+
## Files
|
|
50
|
+
|
|
51
|
+
- modify `lib/omnizip/error.rb`
|
|
52
|
+
- modify `lib/omnizip/filter_registry.rb` (remove inline error)
|
|
53
|
+
- modify `lib/omnizip/optimization_registry.rb` (raise renamed error)
|
|
54
|
+
- modify `lib/omnizip/password/encryption_registry.rb` (raise new error)
|
|
55
|
+
- modify `lib/omnizip/converter/conversion_registry.rb` (raise on miss)
|
|
56
|
+
- modify `lib/omnizip.rb` autoload list
|
|
57
|
+
- create `spec/omnizip/error_spec.rb`
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 03 — Autoload entry point
|
|
2
|
+
|
|
3
|
+
Priority: **high**.
|
|
4
|
+
Status: TODO.
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
`lib/omnizip.rb` lines 99–139 contain 31 `require_relative` calls for
|
|
9
|
+
algorithms, filters, checksums, and formats. Per the global rule,
|
|
10
|
+
`require_relative` is forbidden inside `lib/`. The justification given is
|
|
11
|
+
"these classes self-register at load time" — but self-registration only
|
|
12
|
+
needs to happen *before first lookup*, not at gem boot.
|
|
13
|
+
|
|
14
|
+
Additional inconsistency: line 179 uses `require "omnizip/convenience"`
|
|
15
|
+
while similar modules are autoloaded.
|
|
16
|
+
|
|
17
|
+
## Plan
|
|
18
|
+
|
|
19
|
+
1. Convert each `require_relative` line in `lib/omnizip.rb` to an autoload
|
|
20
|
+
declaration in the appropriate namespace:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
module Omnizip
|
|
24
|
+
module Algorithms
|
|
25
|
+
autoload :LZMA, "omnizip/algorithms/lzma"
|
|
26
|
+
autoload :LZMA2, "omnizip/algorithms/lzma2"
|
|
27
|
+
# ...
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
2. For self-registering classes, keep the registration call at the bottom
|
|
33
|
+
of each implementation file (`AlgorithmRegistry.register(:lzma, LZMA)`).
|
|
34
|
+
Autoload will load the file on first reference, which triggers
|
|
35
|
+
registration. Tests that pre-populate the registry should reference the
|
|
36
|
+
constant first (e.g., `Omnizip::Algorithms::LZMA`).
|
|
37
|
+
|
|
38
|
+
3. For files that are currently eager-required because *nothing else*
|
|
39
|
+
references them (only the registration matters), add an explicit
|
|
40
|
+
autoload entry in the right namespace *and* arrange for the constant to
|
|
41
|
+
be referenced on first registry miss. Concretely: change registries to
|
|
42
|
+
trigger autoload by referencing the constant rather than calling
|
|
43
|
+
`register` from the file body. See `08-registry-lazy-trigger.md` if a
|
|
44
|
+
fallback is needed.
|
|
45
|
+
|
|
46
|
+
4. Replace the trailing `require "omnizip/convenience"` with an autoload
|
|
47
|
+
entry — `autoload :Convenience, "omnizip/convenience"` — and reference
|
|
48
|
+
it lazily where used (or eager-load at the very end of `lib/omnizip.rb`
|
|
49
|
+
via `require "omnizip/convenience"` ONLY if Convenience must extend
|
|
50
|
+
Omnizip at boot; document why).
|
|
51
|
+
|
|
52
|
+
5. Run specs. If any spec fails because a class never autoloaded, fix by
|
|
53
|
+
adding a `require` at the top of the spec file (specs are NOT subject
|
|
54
|
+
to the no-`require_relative` rule for library code; they ARE subject to
|
|
55
|
+
it for sibling spec files but not for library files).
|
|
56
|
+
|
|
57
|
+
## Acceptance
|
|
58
|
+
|
|
59
|
+
- `grep -rn "require_relative" lib/` returns zero matches.
|
|
60
|
+
- `grep -rn '^require "omnizip/' lib/` is limited to the convenience
|
|
61
|
+
fallback (documented) and any in-file registration triggers.
|
|
62
|
+
- All specs pass.
|
|
63
|
+
|
|
64
|
+
## Files
|
|
65
|
+
|
|
66
|
+
- modify `lib/omnizip.rb` (replace lines 99–139 and line 179)
|
|
67
|
+
- ensure each `lib/omnizip/algorithms/*.rb`, `lib/omnizip/filters/*.rb`,
|
|
68
|
+
`lib/omnizip/checksums/*.rb`, `lib/omnizip/formats/*.rb` ends with its
|
|
69
|
+
own registration call (most already do).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 04 — Filter base consolidation
|
|
2
|
+
|
|
3
|
+
Priority: **medium**.
|
|
4
|
+
Status: TODO.
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
Two parallel base classes exist:
|
|
9
|
+
|
|
10
|
+
- `Omnizip::Filter` (`lib/omnizip/filter.rb`) — has `architecture` and
|
|
11
|
+
`name` attrs plus `id_for_format(format)`. Used by `BCJ` and `Delta`.
|
|
12
|
+
- `Omnizip::Filters::FilterBase` (`lib/omnizip/filters/filter_base.rb`) —
|
|
13
|
+
minimal `encode`/`decode`/`.metadata` stub. Used by `BcjX86`, `BcjArm`,
|
|
14
|
+
`BcjArm64`, `BcjIa64`, `BcjPpc`, `BcjSparc`, `Bcj2`.
|
|
15
|
+
|
|
16
|
+
They share identical `encode(data, position=0)` / `decode(data,
|
|
17
|
+
position=0)` / `.metadata` stubs. `FilterBase` is a subset of `Filter`.
|
|
18
|
+
|
|
19
|
+
## Plan
|
|
20
|
+
|
|
21
|
+
1. Keep `Filter` as the canonical base, located at
|
|
22
|
+
`lib/omnizip/filters/base.rb`. Make `architecture` optional (defaults
|
|
23
|
+
to nil).
|
|
24
|
+
2. Make `Omnizip::Filters::FilterBase` an alias / subclass of the unified
|
|
25
|
+
base for backward compatibility.
|
|
26
|
+
3. Migrate subclasses of `FilterBase` to inherit from the unified base.
|
|
27
|
+
4. Delete the body of `lib/omnizip/filters/filter_base.rb`, leaving it as
|
|
28
|
+
a backward-compat alias:
|
|
29
|
+
```ruby
|
|
30
|
+
require "omnizip/filters/base"
|
|
31
|
+
module Omnizip::Filters
|
|
32
|
+
FilterBase = Filter # backward-compat alias
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
(or define `FilterBase < Filter` if a true subclass is preferred.)
|
|
36
|
+
|
|
37
|
+
## Acceptance
|
|
38
|
+
|
|
39
|
+
- All filter specs pass.
|
|
40
|
+
- No public API breaks.
|
|
41
|
+
- `lib/omnizip/filter.rb` removed or stubbed to alias.
|
|
42
|
+
|
|
43
|
+
## Files
|
|
44
|
+
|
|
45
|
+
- modify `lib/omnizip/filter.rb`
|
|
46
|
+
- create or modify `lib/omnizip/filters/base.rb`
|
|
47
|
+
- modify `lib/omnizip/filters/filter_base.rb` (alias)
|
|
48
|
+
- modify each subclass to inherit from the new base
|
|
49
|
+
- add autoload entry in `lib/omnizip/filters.rb`
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# 05 — Eliminate `.send` against private methods
|
|
2
|
+
|
|
3
|
+
Priority: **high**.
|
|
4
|
+
Status: TODO.
|
|
5
|
+
|
|
6
|
+
## Rule
|
|
7
|
+
|
|
8
|
+
> NEVER use `send` to call private methods. Private methods are private
|
|
9
|
+
> for a reason. If you need to call it from outside, the API boundary is
|
|
10
|
+
> wrong — redesign, don't bypass.
|
|
11
|
+
|
|
12
|
+
## Inventory (17 occurrences)
|
|
13
|
+
|
|
14
|
+
| File:Line | Call | Fix |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `lib/omnizip/parity.rb:178` | `verifier.send(:parse_par2_file)` | Promote to public |
|
|
17
|
+
| `lib/omnizip/parity.rb:180` | `verifier.send(:calculate_total_blocks)` | Promote to public |
|
|
18
|
+
| `lib/omnizip/parity/par2_repairer.rb:174` | `@verifier.send(:find_file_path, ...)` | Promote to public |
|
|
19
|
+
| `lib/omnizip/parallel/parallel_compressor.rb:306` | `writer.send(:create_entry, ...)` | Promote to public |
|
|
20
|
+
| `lib/omnizip/parallel/parallel_compressor.rb:324` | `writer.send(:write_with_precompressed_data, ...)` | Promote |
|
|
21
|
+
| `lib/omnizip/parallel/parallel_compressor.rb:136` | `opts.send(:"#{k}=", v)` | Replace with `CompressionOptions#apply(hash)` |
|
|
22
|
+
| `lib/omnizip/parallel/parallel_extractor.rb:110` | `reader.send(:decompress_data, ...)` | Promote to public |
|
|
23
|
+
| `lib/omnizip/parallel/parallel_extractor.rb:135` | `opts.send(:"#{k}=", v)` | Same as above |
|
|
24
|
+
| `lib/omnizip/formats/format_spec_loader.rb:203` | `format.send(key)` | Replace with explicit dispatch |
|
|
25
|
+
| `lib/omnizip/converter/seven_zip_to_zip_strategy.rb:62` | `reader.send(:extract_entry_data, ...)` | Promote to public |
|
|
26
|
+
| `lib/omnizip/formats/seven_zip/parser.rb:573` | `entry.send(:"#{attr}=", ...)` | Add public writer or ctor arg |
|
|
27
|
+
| `lib/omnizip/formats/rpm/header.rb:36` | `header.send(:parse!, io)` | Promote `parse!` to public |
|
|
28
|
+
| `lib/omnizip/algorithms/zstandard/frame/header.rb:72,76,80` | `header.send(:parse_window_descriptor, ...)` etc. | Promote parse helpers to public, or inline |
|
|
29
|
+
| `lib/omnizip/zip/file.rb:419` | `@reader.send(:decompress_data, ...)` | Promote to public |
|
|
30
|
+
| `lib/omnizip/crypto/aes256/cipher.rb:90` | `cipher.send(mode)` | Replace with explicit `if encrypt? ... else ...` |
|
|
31
|
+
|
|
32
|
+
## Plan
|
|
33
|
+
|
|
34
|
+
For each call site, choose ONE of:
|
|
35
|
+
|
|
36
|
+
1. **Promote the method to public.** If callers legitimately need it, the
|
|
37
|
+
`private` declaration was wrong. Add a doc comment and make it public.
|
|
38
|
+
2. **Add a public facade.** Keep the private method, expose a public
|
|
39
|
+
method with a clearer name that wraps it.
|
|
40
|
+
3. **Inline the call.** Sometimes the private method is one line and the
|
|
41
|
+
`.send` is just laziness — inline the body.
|
|
42
|
+
|
|
43
|
+
`opts.send(:"#{k}=", v)` (parallel_compressor.rb:136 and
|
|
44
|
+
parallel_extractor.rb:135) deserves a real fix: add
|
|
45
|
+
`CompressionOptions#apply(attributes)` and `ParallelOptions#apply` methods
|
|
46
|
+
that take a hash and set attributes via `public_send` only on whitelisted
|
|
47
|
+
keys.
|
|
48
|
+
|
|
49
|
+
`format.send(key)` (format_spec_loader.rb:203) is data-driven dispatch on
|
|
50
|
+
a spec format — replace with a hash of known keys → lambdas, or with a
|
|
51
|
+
case statement that calls public methods explicitly.
|
|
52
|
+
|
|
53
|
+
`cipher.send(mode)` (crypto/aes256/cipher.rb:90) — replace with an
|
|
54
|
+
explicit branch on whether to call `encrypt` or `decrypt`.
|
|
55
|
+
|
|
56
|
+
## Acceptance
|
|
57
|
+
|
|
58
|
+
- `grep -rn "\.send(" lib/` returns at most a handful of intentional
|
|
59
|
+
metaprogramming cases (e.g., `public_send` with documented reason).
|
|
60
|
+
- All specs pass.
|
|
61
|
+
|
|
62
|
+
## Files
|
|
63
|
+
|
|
64
|
+
See table above. 11 files to modify.
|
|
@@ -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.
|