cbz_tools 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34014f96dd63882bd66f0de3f3a3548faa507e279d37f335fc12ecf8cef3ab41
4
- data.tar.gz: f54439b98ddad12995b30e74ca1d6e45f559acc5c7df0190af5610623e1911e1
3
+ metadata.gz: 96ee68c0b79bcb1256324ad692bf441f0926443ab71e87db3715c0ca5c89cac0
4
+ data.tar.gz: 15d6235b0d040d8836a752da8e304bb89d2fd0976f9eb371f6f514b6dd8c97c4
5
5
  SHA512:
6
- metadata.gz: 37bc01a7b250c5382507314c57d6c3c208f122c8fd39802ce5308aec16baa07860530e120f37cb5ce69bbf5dcdcc0a145e7a3ce0ad81fc2276ba4ae0e461c835
7
- data.tar.gz: 9b83a50b99751749896e4d395af34665d85004a49768b7d7b9dac9d578bf4d90fd58190e87c18415620ae3935def84893a61ed40c7dc93ccc682ca31cd819fd5
6
+ metadata.gz: dfaee61a3965d6901835d58ca0206ee5bd5f4a05eb1ffb00d2248ca16755aeb1d418b54f3550fe9ef6d0d125aff393955aec459990a279259bc05d874e7d1adc
7
+ data.tar.gz: 7e2e12a38206a9a6ea43d49eb4474a60770d094c48d47746a3cb6f249496abf382af564caacd41e5426684077a23135018ef108795232c87e592a835740ef435
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-08-03
4
+
5
+ - Add `CbzTools::CbrConverter` for converting CBR archives to CBZ. The gem ships the orchestration (extract → repack → error model) but no default extractor — callers pass `extractor:` (anything responding to `call(cbr_path, destination_dir)` and raising `ExtractionError` on failure).
6
+
3
7
  ## [0.3.0] - 2026-08-03
4
8
 
5
9
  - Add `CbzTools::ComicInfo` (parse/build ComicInfo.xml, `SCHEMA`); raises `ParseError` on bad XML.
data/README.md CHANGED
@@ -18,7 +18,57 @@ gem install cbz_tools
18
18
 
19
19
  ## Usage
20
20
 
21
- Usage documentation will be added as the features land.
21
+ ### Reading a CBZ archive
22
+
23
+ ```ruby
24
+ CbzTools::Reader.open("path/to/comic.cbz") do |reader|
25
+ reader.image_names # => ["page_001.jpg", "page_002.jpg", ...]
26
+ reader.first_image # => "<image bytes>"
27
+ reader.comic_info # => {title: "...", page_count: "32", ...}
28
+ end
29
+ ```
30
+
31
+ ### Editing ComicInfo.xml inside a CBZ
32
+
33
+ ```ruby
34
+ CbzTools::Reader.open("path/to/comic.cbz") do |reader|
35
+ reader.update_comic_info(title: "New Title", page_count: "32")
36
+ end
37
+ ```
38
+
39
+ ### Converting CBR to CBZ
40
+
41
+ `CbzTools::CbrConverter` orchestrates the extract → repack → CBZ step,
42
+ but it does **not** ship an extractor. Pass anything responding to
43
+ `call(cbr_path, destination_dir)` (and that raises `ExtractionError`
44
+ on failure):
45
+
46
+ ```ruby
47
+ extractor = MyApp::UnrarExtractor.new
48
+ CbzTools::CbrConverter.convert("path/to/comic.cbr",
49
+ to: "path/to/comic.cbz",
50
+ extractor: extractor)
51
+ ```
52
+
53
+ Reuse one extractor across many conversions with `work_in:` to pool
54
+ the tmpdir:
55
+
56
+ ```ruby
57
+ extractor = MyApp::UnrarExtractor.new
58
+ work_in = Dir.mktmpdir("batch")
59
+ converter = CbzTools::CbrConverter.new(extractor: extractor)
60
+
61
+ paths.each do |cbr|
62
+ converter.convert(cbr, to: cbr.sub(/\.cbr\z/i, ".cbz"), work_in: work_in)
63
+ end
64
+ ```
65
+
66
+ Errors inherit from `CbzTools::Error`:
67
+
68
+ - `CbzTools::CbrConverter::ExtractionError` — the extractor failed.
69
+ - `CbzTools::CbrConverter::ArchiveError` — no extractor supplied,
70
+ the destination couldn't be written, or `overwrite: false` and the
71
+ destination already exists.
22
72
 
23
73
  ## Development
24
74
 
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "fileutils"
5
+ require "zip"
6
+ require "pathname"
7
+
8
+ module CbzTools
9
+ # Converts a CBR (Comic Book RAR) archive into a CBZ (Comic Book ZIP)
10
+ # archive.
11
+ #
12
+ # The CBR extraction step is delegated to an injectable +extractor+
13
+ # — anything that responds to +call(cbr_path, destination_dir)+ and
14
+ # raises {CbzTools::CbrConverter::ExtractionError} on failure. Callers (not this gem) own the
15
+ # extractor; this class is deliberately agnostic about which binary,
16
+ # library, or pure-Ruby implementation is used to expand the source.
17
+ #
18
+ # @example Single-shot conversion with an explicit extractor
19
+ # extractor = MyApp::UnrarExtractor.new
20
+ # CbzTools::CbrConverter.convert("path/to/comic.cbr",
21
+ # to: "path/to/comic.cbz",
22
+ # extractor: extractor)
23
+ #
24
+ # @example Reusing one extractor across many conversions
25
+ # extractor = MyApp::UnrarExtractor.new
26
+ # converter = CbzTools::CbrConverter.new(extractor: extractor)
27
+ # paths.each { |p| converter.convert(p, to: p.sub(/\.cbr\z/i, ".cbz")) }
28
+ class CbrConverter
29
+ # Raised when the underlying extractor fails to expand the source
30
+ # CBR into a directory. Propagated untouched so callers can rescue
31
+ # it without unwrapping.
32
+ class ExtractionError < Error
33
+ end
34
+
35
+ # Raised when the resulting CBZ cannot be written, when the
36
+ # destination already exists and +overwrite:+ is false, or when no
37
+ # +extractor:+ was supplied. Propagated untouched from the
38
+ # relevant step.
39
+ class ArchiveError < Error
40
+ end
41
+
42
+ # @return [#call, nil] the configured extractor, or +nil+ if none
43
+ # was supplied. Read-only; there is no corresponding writer.
44
+ attr_reader :extractor
45
+
46
+ # @see #convert
47
+ #
48
+ # @param cbr_path [String] source CBR path
49
+ # @param to [String] destination CBZ path
50
+ # @param overwrite [Boolean] when false, raise {ArchiveError} if
51
+ # +to+ already exists; default true
52
+ # @param work_in [String, nil] work directory; when nil (default),
53
+ # a fresh +Dir.mktmpdir+ is created and cleaned up. When set, the
54
+ # converter uses that directory and leaves cleanup to the caller
55
+ # (useful when batching many conversions).
56
+ # @param extractor [#call] extractor backend; required.
57
+ # @return [String] the destination CBZ path
58
+ # @raise [ArchiveError] when +extractor:+ is missing or target cannot be written
59
+ # @raise [ExtractionError] when the extractor fails
60
+ def self.convert(cbr_path, to:, overwrite: true, work_in: nil, extractor: nil)
61
+ new(extractor: extractor).convert(cbr_path, to: to, overwrite: overwrite, work_in: work_in)
62
+ end
63
+
64
+ # @param extractor [#call, nil] anything responding to
65
+ # +call(cbr_path, destination_dir)+ that raises {ExtractionError}
66
+ # on failure. Required — {#convert} raises {ArchiveError} if left
67
+ # as +nil+.
68
+ def initialize(extractor: nil)
69
+ @extractor = extractor
70
+ end
71
+
72
+ # @see .convert
73
+ #
74
+ # @param cbr_path [String] source CBR path
75
+ # @param to [String] destination CBZ path
76
+ # @param overwrite [Boolean] when false, raise {ArchiveError} if
77
+ # +to+ already exists; default true
78
+ # @param work_in [String, nil] see {.convert}
79
+ # @return [String] the destination CBZ path
80
+ # @raise [ArchiveError] when no extractor was configured or target cannot be written
81
+ # @raise [ExtractionError] when the extractor fails
82
+ def convert(cbr_path, to:, overwrite: true, work_in: nil)
83
+ ensure_extractor!
84
+ ensure_target_slot!(to, overwrite: overwrite)
85
+ File.delete(to) if overwrite && File.exist?(to)
86
+
87
+ in_workdir(work_in) do |temp_dir|
88
+ @extractor.call(cbr_path, temp_dir)
89
+ repack(temp_dir, to)
90
+ end
91
+
92
+ to
93
+ end
94
+
95
+ private
96
+
97
+ def ensure_extractor!
98
+ raise ArchiveError,
99
+ "No extractor configured; pass `extractor:` to CbrConverter.convert or CbrConverter.new" unless @extractor
100
+ end
101
+
102
+ def ensure_target_slot!(to, overwrite:)
103
+ return if overwrite || !File.exist?(to)
104
+
105
+ raise ArchiveError, "Target #{to} already exists (pass overwrite: true to replace)"
106
+ end
107
+
108
+ def in_workdir(work_in, &block)
109
+ if work_in
110
+ FileUtils.mkdir_p(work_in)
111
+ yield work_in
112
+ else
113
+ Dir.mktmpdir(&block)
114
+ end
115
+ end
116
+
117
+ def repack(source_dir, target_path)
118
+ Zip::File.open(target_path, create: true) do |zipfile|
119
+ each_file_in(source_dir) { |file| add_to_zip(zipfile, source_dir, file) }
120
+ end
121
+ rescue Zip::Error, SystemCallError => e
122
+ raise ArchiveError, "Failed to write CBZ #{target_path}: #{e.class}: #{e.message}"
123
+ end
124
+
125
+ def each_file_in(source_dir)
126
+ Dir["#{source_dir}/**/*"].each do |path|
127
+ yield path unless File.directory?(path)
128
+ end
129
+ end
130
+
131
+ def add_to_zip(zipfile, source_dir, file_path)
132
+ relative = Pathname.new(file_path).relative_path_from(Pathname.new(source_dir)).to_s
133
+ zipfile.add(relative, file_path)
134
+ end
135
+ end
136
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CbzTools
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/cbz_tools.rb CHANGED
@@ -4,3 +4,4 @@ require_relative "cbz_tools/version"
4
4
  require_relative "cbz_tools/error"
5
5
  require_relative "cbz_tools/reader"
6
6
  require_relative "cbz_tools/comic_info"
7
+ require_relative "cbz_tools/cbr_converter"
data/sig/cbz_tools.rbs CHANGED
@@ -4,6 +4,43 @@ module CbzTools
4
4
  class Error < StandardError
5
5
  end
6
6
 
7
+ class CbrConverter
8
+ class ExtractionError < CbzTools::Error
9
+ end
10
+
11
+ class ArchiveError < CbzTools::Error
12
+ end
13
+
14
+ attr_reader extractor: untyped
15
+
16
+ def self.convert: (
17
+ String cbr_path,
18
+ to: String,
19
+ ?overwrite: bool,
20
+ ?work_in: String?,
21
+ ?extractor: untyped
22
+ ) -> String
23
+
24
+ def initialize: (?extractor: untyped) -> void
25
+
26
+ def convert: (
27
+ String cbr_path,
28
+ to: String,
29
+ ?overwrite: bool,
30
+ ?work_in: String?
31
+ ) -> String
32
+
33
+ private
34
+
35
+ def ensure_extractor!: -> void
36
+
37
+ def ensure_target_slot!: (String to, overwrite: bool) -> void
38
+
39
+ def in_workdir: (String? work_in) { (String) -> void } -> void
40
+
41
+ def repack: (String source_dir, String target_path) -> void
42
+ end
43
+
7
44
  class Reader
8
45
  IMAGE_EXTENSIONS: Array[String]
9
46
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cbz_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Figueroa
@@ -51,6 +51,7 @@ files:
51
51
  - LICENSE.txt
52
52
  - README.md
53
53
  - lib/cbz_tools.rb
54
+ - lib/cbz_tools/cbr_converter.rb
54
55
  - lib/cbz_tools/comic_info.rb
55
56
  - lib/cbz_tools/error.rb
56
57
  - lib/cbz_tools/reader.rb