cbz_tools 0.3.0 → 0.5.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: 30f58af31425d78057d97105cfe21164b6ec05263f0b86d2b2a0dca150792d5d
4
+ data.tar.gz: edd397b9588670cd49a3bcfe517f684ae9f684f4469a9725d1ac440f00da0fbe
5
5
  SHA512:
6
- metadata.gz: 37bc01a7b250c5382507314c57d6c3c208f122c8fd39802ce5308aec16baa07860530e120f37cb5ce69bbf5dcdcc0a145e7a3ce0ad81fc2276ba4ae0e461c835
7
- data.tar.gz: 9b83a50b99751749896e4d395af34665d85004a49768b7d7b9dac9d578bf4d90fd58190e87c18415620ae3935def84893a61ed40c7dc93ccc682ca31cd819fd5
6
+ metadata.gz: 57e09be8a89fec63e2cb22b140c6500afa29a0363a1f0e7b1b6d5ef75b9a137e4f6da4b1012031d18b2875db856e73145ded2fabf8532a6080d9f80d9ff98894
7
+ data.tar.gz: 73deffc4ac234b2288b2b8c5898cd12dc005561b79a7b5941cfb052ace8766fef6227bf935b6835226381f679d0bfb5fd2f9553fc68fd8cb2bc5ce309e7bfd45
@@ -1,3 +1,3 @@
1
1
  {
2
- "cSpell.words": ["comicinfo", "gtin", "penciller"]
2
+ "cSpell.words": ["comicinfo", "gtin", "penciller", "unrar"]
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2026-08-04
4
+
5
+ - `CbzTools::CbrConverter` now uses `unrar` directly — no extractor param required.
6
+ - **Breaking**: Removed `extractor:` param, `ExtractionError`, and `extractor` attribute.
7
+ - Runtime check raises `ArchiveError` if `unrar` is not in PATH.
8
+
9
+ ## [0.4.0] - 2026-08-03
10
+
11
+ - 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).
12
+
3
13
  ## [0.3.0] - 2026-08-03
4
14
 
5
15
  - Add `CbzTools::ComicInfo` (parse/build ComicInfo.xml, `SCHEMA`); raises `ParseError` on bad XML.
data/README.md CHANGED
@@ -18,7 +18,37 @@ 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` converts CBR archives to CBZ using the `unrar`
42
+ command-line tool (which must be installed on your system).
43
+
44
+ ```ruby
45
+ CbzTools::CbrConverter.convert("path/to/comic.cbr", to: "path/to/comic.cbz")
46
+ ```
47
+
48
+ Errors inherit from `CbzTools::Error`:
49
+
50
+ - `CbzTools::CbrConverter::ArchiveError` — the destination couldn't be written,
51
+ `overwrite: false` and the destination already exists, or `unrar` is not installed.
22
52
 
23
53
  ## Development
24
54
 
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "fileutils"
5
+ require "zip"
6
+ require "pathname"
7
+ require "open3"
8
+
9
+ require_relative "system_dependencies"
10
+
11
+ module CbzTools
12
+ # Converts a CBR (Comic Book RAR) archive into a CBZ (Comic Book ZIP)
13
+ # archive.
14
+ #
15
+ # Extraction is handled internally using the +unrar+ command-line tool,
16
+ # which must be installed on the system. Consumers do not need to
17
+ # install or configure any extraction backend.
18
+ #
19
+ # @example Single-shot conversion
20
+ # CbzTools::CbrConverter.convert("path/to/comic.cbr", to: "path/to/comic.cbz")
21
+ #
22
+ # @example Reusing one converter across many conversions
23
+ # converter = CbzTools::CbrConverter.new
24
+ # paths.each { |p| converter.convert(p, to: p.sub(/\.cbr\z/i, ".cbz")) }
25
+ class CbrConverter
26
+ class ArchiveError < Error
27
+ end
28
+
29
+ # @see #convert
30
+ #
31
+ # @param cbr_path [String] source CBR path
32
+ # @param to [String] destination CBZ path
33
+ # @param overwrite [Boolean] when false, raise {ArchiveError} if
34
+ # +to+ already exists; default true
35
+ # @param work_in [String, nil] work directory; when nil (default),
36
+ # a fresh +Dir.mktmpdir+ is created and cleaned up. When set, the
37
+ # converter uses that directory and leaves cleanup to the caller
38
+ # (useful when batching many conversions).
39
+ # @return [String] the destination CBZ path
40
+ # @raise [ArchiveError] when target cannot be written or unrar is unavailable
41
+ def self.convert(cbr_path, to:, overwrite: true, work_in: nil)
42
+ new.convert(cbr_path, to: to, overwrite: overwrite, work_in: work_in)
43
+ end
44
+
45
+ def convert(cbr_path, to:, overwrite: true, work_in: nil)
46
+ ensure_target_slot!(to, overwrite: overwrite)
47
+ File.delete(to) if overwrite && File.exist?(to)
48
+
49
+ in_workdir(work_in) do |temp_dir|
50
+ extract(cbr_path, temp_dir)
51
+ repack(temp_dir, to)
52
+ end
53
+
54
+ to
55
+ end
56
+
57
+ private
58
+
59
+ def extract(cbr_path, destination)
60
+ unrar = SystemDependencies.unrar!
61
+ _, stderr, status = Open3.capture3(unrar, "x", "-o+", cbr_path, "#{destination}/")
62
+
63
+ unless status.success?
64
+ raise ArchiveError, "unrar failed: #{stderr}"
65
+ end
66
+ rescue SystemDependencies::MissingExecutableError => e
67
+ raise ArchiveError, e.message
68
+ rescue => e
69
+ raise ArchiveError, "Failed to extract CBR: #{e.message}"
70
+ end
71
+
72
+ def ensure_target_slot!(to, overwrite:)
73
+ return if overwrite || !File.exist?(to)
74
+
75
+ raise ArchiveError, "Target #{to} already exists (pass overwrite: true to replace)"
76
+ end
77
+
78
+ def in_workdir(work_in, &block)
79
+ if work_in
80
+ FileUtils.mkdir_p(work_in)
81
+ yield work_in
82
+ else
83
+ Dir.mktmpdir(&block)
84
+ end
85
+ end
86
+
87
+ def repack(source_dir, target_path)
88
+ Zip::File.open(target_path, create: true) do |zipfile|
89
+ each_file_in(source_dir) { |file| add_to_zip(zipfile, source_dir, file) }
90
+ end
91
+ rescue Zip::Error, SystemCallError => e
92
+ raise ArchiveError, "Failed to write CBZ #{target_path}: #{e.class}: #{e.message}"
93
+ end
94
+
95
+ def each_file_in(source_dir)
96
+ Dir["#{source_dir}/**/*"].each do |path|
97
+ yield path unless File.directory?(path)
98
+ end
99
+ end
100
+
101
+ def add_to_zip(zipfile, source_dir, file_path)
102
+ relative = Pathname.new(file_path).relative_path_from(Pathname.new(source_dir)).to_s
103
+ zipfile.add(relative, file_path)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CbzTools
4
+ module SystemDependencies
5
+ class MissingExecutableError < StandardError; end
6
+
7
+ def self.find_executable(name)
8
+ extensions = Gem.win_platform? ? ENV.fetch("PATHEXT", "").split(";") : [""]
9
+
10
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |directory|
11
+ extensions.each do |extension|
12
+ path = File.join(directory, "#{name}#{extension}")
13
+ return path if File.file?(path) && File.executable?(path)
14
+ end
15
+ end
16
+
17
+ nil
18
+ end
19
+
20
+ def self.unrar!
21
+ find_executable("unrar") ||
22
+ raise(
23
+ MissingExecutableError,
24
+ "`unrar` is required for CBR conversion but was not found in PATH. " \
25
+ "Install unrar using your system's package manager."
26
+ )
27
+ end
28
+ end
29
+ 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.5.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.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Figueroa
@@ -51,9 +51,11 @@ 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
58
+ - lib/cbz_tools/system_dependencies.rb
57
59
  - lib/cbz_tools/version.rb
58
60
  - sig/cbz_tools.rbs
59
61
  homepage: https://github.com/dmfigueroa/cbz_tools
@@ -77,7 +79,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
79
  - - ">="
78
80
  - !ruby/object:Gem::Version
79
81
  version: '0'
80
- requirements: []
82
+ requirements:
83
+ - unrar executable available in PATH (for CBR conversion)
81
84
  rubygems_version: 4.0.17
82
85
  specification_version: 4
83
86
  summary: Read, extract, and edit metadata of CBZ comic archives, and convert CBR to