epub_tools 0.4.1 → 0.6.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 +4 -4
- data/.github/workflows/ci.yml +3 -0
- data/.rubocop.yml +10 -17
- data/CLAUDE.md +128 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +39 -34
- data/README.md +37 -24
- data/Rakefile +2 -0
- data/bin/epub-tools +2 -0
- data/epub_tools.gemspec +3 -1
- data/lib/epub_tools/add_chapters.rb +64 -33
- data/lib/epub_tools/append_book.rb +81 -0
- data/lib/epub_tools/book_builder.rb +108 -0
- data/lib/epub_tools/chapter_marker_detector.rb +46 -0
- data/lib/epub_tools/chapter_validator.rb +50 -0
- data/lib/epub_tools/cli/command_options_configurator.rb +128 -0
- data/lib/epub_tools/cli/command_registry.rb +2 -0
- data/lib/epub_tools/cli/option_builder.rb +5 -3
- data/lib/epub_tools/cli/runner.rb +60 -110
- data/lib/epub_tools/cli.rb +17 -29
- data/lib/epub_tools/compile_book.rb +15 -146
- data/lib/epub_tools/compile_workspace.rb +40 -0
- data/lib/epub_tools/epub_configuration.rb +33 -0
- data/lib/epub_tools/epub_file_writer.rb +57 -0
- data/lib/epub_tools/epub_initializer.rb +83 -162
- data/lib/epub_tools/epub_metadata_builder.rb +92 -0
- data/lib/epub_tools/loggable.rb +2 -0
- data/lib/epub_tools/pack_ebook.rb +28 -14
- data/lib/epub_tools/split_chapters.rb +44 -56
- data/lib/epub_tools/style_finder.rb +17 -6
- data/lib/epub_tools/unpack_ebook.rb +20 -10
- data/lib/epub_tools/version.rb +3 -1
- data/lib/epub_tools/xhtml_cleaner.rb +1 -0
- data/lib/epub_tools/xhtml_extractor.rb +20 -10
- data/lib/epub_tools/xhtml_generator.rb +71 -0
- data/lib/epub_tools.rb +5 -0
- data/test/add_chapters_test.rb +119 -25
- data/test/append_book_test.rb +127 -0
- data/test/chapter_validator_test.rb +74 -0
- data/test/cli/command_registry_test.rb +2 -0
- data/test/cli/option_builder_test.rb +24 -14
- data/test/cli/runner_test.rb +15 -15
- data/test/cli_commands_test.rb +11 -0
- data/test/cli_test.rb +2 -0
- data/test/cli_version_test.rb +2 -0
- data/test/compile_book_test.rb +16 -102
- data/test/compile_workspace_test.rb +55 -0
- data/test/epub_initializer_test.rb +55 -27
- data/test/pack_ebook_test.rb +33 -9
- data/test/split_chapters_test.rb +96 -7
- data/test/style_finder_test.rb +2 -0
- data/test/test_helper.rb +2 -0
- data/test/unpack_ebook_test.rb +45 -20
- data/test/xhtml_cleaner_test.rb +2 -0
- data/test/xhtml_extractor_test.rb +3 -1
- metadata +17 -3
data/test/add_chapters_test.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'test_helper'
|
|
2
4
|
require_relative '../lib/epub_tools/add_chapters'
|
|
3
5
|
require 'nokogiri'
|
|
@@ -53,47 +55,139 @@ class AddChaptersTest < Minitest::Test
|
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def test_run_moves_files_and_updates_opf_and_nav
|
|
56
|
-
# Run the add chapters task
|
|
57
58
|
result = EpubTools::AddChapters.new(chapters_dir: @chapters_dir, oebps_dir: @epub_dir).run
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
verify_return_value(result)
|
|
61
|
+
verify_files_moved
|
|
62
|
+
verify_opf_structure
|
|
63
|
+
verify_nav_structure
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def verify_return_value(result)
|
|
60
69
|
assert_instance_of Array, result
|
|
61
70
|
assert_equal 2, result.size
|
|
62
71
|
assert_includes result, 'chapter_0.xhtml'
|
|
63
72
|
assert_includes result, 'chapter_1.xhtml'
|
|
73
|
+
end
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
def verify_files_moved
|
|
66
76
|
assert_empty Dir.glob(File.join(@chapters_dir, '*.xhtml'))
|
|
67
77
|
assert_path_exists File.join(@epub_dir, 'chapter_0.xhtml')
|
|
68
78
|
assert_path_exists File.join(@epub_dir, 'chapter_1.xhtml')
|
|
79
|
+
end
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
doc =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
hrefs
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
assert_includes
|
|
79
|
-
assert_includes
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
# nav.xhtml should have list entries for each chapter
|
|
86
|
-
nav_doc = Nokogiri::XML(File.read(@nav_file))
|
|
87
|
-
# strip namespaces for easy querying
|
|
88
|
-
nav_doc.remove_namespaces!
|
|
89
|
-
links = nav_doc.xpath('//nav/ol/li/a')
|
|
81
|
+
def verify_opf_structure
|
|
82
|
+
doc = parse_opf_document
|
|
83
|
+
opf_data = extract_opf_data(doc)
|
|
84
|
+
|
|
85
|
+
assert_includes opf_data[:hrefs], 'chapter_0.xhtml'
|
|
86
|
+
assert_includes opf_data[:hrefs], 'chapter_1.xhtml'
|
|
87
|
+
assert_includes opf_data[:ids], 'chap0'
|
|
88
|
+
assert_includes opf_data[:ids], 'chap1'
|
|
89
|
+
assert_includes opf_data[:refs], 'chap0'
|
|
90
|
+
assert_includes opf_data[:refs], 'chap1'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def verify_nav_structure
|
|
94
|
+
links = extract_nav_links
|
|
90
95
|
|
|
91
96
|
assert_equal 2, links.size
|
|
92
|
-
# First is Prologue (chapter_0)
|
|
93
97
|
assert_equal 'chapter_0.xhtml', links[0]['href']
|
|
94
98
|
assert_equal 'Prologue', links[0].text
|
|
95
|
-
# Second is Chapter 1
|
|
96
99
|
assert_equal 'chapter_1.xhtml', links[1]['href']
|
|
97
100
|
assert_equal 'Chapter 1', links[1].text
|
|
98
101
|
end
|
|
102
|
+
|
|
103
|
+
def parse_opf_document
|
|
104
|
+
Nokogiri::XML(File.read(@opf_file)) { |cfg| cfg.default_xml.noblanks }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extract_opf_data(doc)
|
|
108
|
+
items = doc.xpath('//xmlns:manifest/xmlns:item')
|
|
109
|
+
idrefs = doc.xpath('//xmlns:spine/xmlns:itemref')
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
hrefs: items.map { |i| i['href'] },
|
|
113
|
+
ids: items.map { |i| i['id'] },
|
|
114
|
+
refs: idrefs.map { |ir| ir['idref'] }
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def extract_nav_links
|
|
119
|
+
nav_doc = Nokogiri::XML(File.read(@nav_file))
|
|
120
|
+
nav_doc.remove_namespaces!
|
|
121
|
+
nav_doc.xpath('//nav/ol/li/a')
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class AddChaptersHalfChapterTest < Minitest::Test
|
|
126
|
+
def setup
|
|
127
|
+
@tmp = Dir.mktmpdir
|
|
128
|
+
@chapters_dir = File.join(@tmp, 'chapters')
|
|
129
|
+
@epub_dir = File.join(@tmp, 'OEBPS')
|
|
130
|
+
Dir.mkdir(@chapters_dir)
|
|
131
|
+
Dir.mkdir(@epub_dir)
|
|
132
|
+
create_chapter_files
|
|
133
|
+
create_opf_and_nav
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def teardown
|
|
137
|
+
FileUtils.remove_entry(@tmp)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_half_chapter_sorting_and_labels
|
|
141
|
+
result = EpubTools::AddChapters.new(chapters_dir: @chapters_dir, oebps_dir: @epub_dir).run
|
|
142
|
+
|
|
143
|
+
assert_equal %w[chapter_1.xhtml chapter_1_5.xhtml chapter_2.xhtml], result
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def test_half_chapter_nav_label
|
|
147
|
+
EpubTools::AddChapters.new(chapters_dir: @chapters_dir, oebps_dir: @epub_dir).run
|
|
148
|
+
|
|
149
|
+
nav_doc = Nokogiri::XML(File.read(File.join(@epub_dir, 'nav.xhtml')))
|
|
150
|
+
nav_doc.remove_namespaces!
|
|
151
|
+
links = nav_doc.xpath('//nav/ol/li/a')
|
|
152
|
+
|
|
153
|
+
assert_equal 'Chapter 1', links[0].text
|
|
154
|
+
assert_equal 'Chapter 1.5', links[1].text
|
|
155
|
+
assert_equal 'Chapter 2', links[2].text
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test_half_chapter_opf_id
|
|
159
|
+
EpubTools::AddChapters.new(chapters_dir: @chapters_dir, oebps_dir: @epub_dir).run
|
|
160
|
+
|
|
161
|
+
doc = Nokogiri::XML(File.read(File.join(@epub_dir, 'package.opf')))
|
|
162
|
+
ids = doc.xpath('//xmlns:manifest/xmlns:item').map { |i| i['id'] }
|
|
163
|
+
|
|
164
|
+
assert_includes ids, 'chap1_5'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def create_chapter_files
|
|
170
|
+
File.write(File.join(@chapters_dir, 'chapter_1.xhtml'), '<html><body>Ch1</body></html>')
|
|
171
|
+
File.write(File.join(@chapters_dir, 'chapter_1_5.xhtml'), '<html><body>Ch1.5</body></html>')
|
|
172
|
+
File.write(File.join(@chapters_dir, 'chapter_2.xhtml'), '<html><body>Ch2</body></html>')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def create_opf_and_nav
|
|
176
|
+
File.write(File.join(@epub_dir, 'package.opf'), <<~XML)
|
|
177
|
+
<?xml version="1.0"?>
|
|
178
|
+
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="pub-id" xml:lang="en">
|
|
179
|
+
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"></metadata>
|
|
180
|
+
<manifest></manifest>
|
|
181
|
+
<spine></spine>
|
|
182
|
+
</package>
|
|
183
|
+
XML
|
|
184
|
+
File.write(File.join(@epub_dir, 'nav.xhtml'), <<~XHTML)
|
|
185
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
186
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="en">
|
|
187
|
+
<body>
|
|
188
|
+
<nav epub:type="toc" id="toc"><ol></ol></nav>
|
|
189
|
+
</body>
|
|
190
|
+
</html>
|
|
191
|
+
XHTML
|
|
192
|
+
end
|
|
99
193
|
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_helper'
|
|
4
|
+
require_relative '../lib/epub_tools/append_book'
|
|
5
|
+
|
|
6
|
+
class AppendBookTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@tmp = Dir.mktmpdir
|
|
9
|
+
@source = File.join(@tmp, 'src')
|
|
10
|
+
@target = File.join(@tmp, 'target.epub')
|
|
11
|
+
FileUtils.mkdir_p(@source)
|
|
12
|
+
FileUtils.touch(@target)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
FileUtils.rm_rf(@tmp)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_initialize_assigns_attributes
|
|
20
|
+
ab = build_append_book(verbose: true)
|
|
21
|
+
|
|
22
|
+
assert_equal @source, ab.source_dir
|
|
23
|
+
assert_equal File.expand_path(@target), ab.target_epub
|
|
24
|
+
assert_equal @tmp, ab.build_dir
|
|
25
|
+
assert ab.verbose
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_default_build_dir
|
|
29
|
+
ab = EpubTools::AppendBook.new(source_dir: @source, target_epub: @target)
|
|
30
|
+
|
|
31
|
+
assert ab.build_dir.end_with?('.epub_tools_build')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_log_outputs_when_verbose
|
|
35
|
+
ab = build_append_book(verbose: true)
|
|
36
|
+
|
|
37
|
+
assert_output("hello\n") { ab.send(:log, 'hello') }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_log_silent_when_not_verbose
|
|
41
|
+
ab = build_append_book(verbose: false)
|
|
42
|
+
|
|
43
|
+
assert_silent { ab.send(:log, 'hello') }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_detect_conflicts_raises_on_overlap
|
|
47
|
+
ab = build_append_book
|
|
48
|
+
setup_conflict_dirs(ab, new_chapters: [1, 2], existing_chapters: [1])
|
|
49
|
+
|
|
50
|
+
error = assert_raises(ArgumentError) { ab.send(:detect_conflicts) }
|
|
51
|
+
assert_match(/chapters 1 already exist/, error.message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_detect_conflicts_passes_with_no_overlap
|
|
55
|
+
ab = build_append_book
|
|
56
|
+
setup_conflict_dirs(ab, new_chapters: [5], existing_chapters: [1, 2])
|
|
57
|
+
|
|
58
|
+
ab.send(:detect_conflicts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_detect_conflicts_with_half_chapters
|
|
62
|
+
ab = build_append_book
|
|
63
|
+
chapters_dir = File.join(@tmp, 'chapters')
|
|
64
|
+
oebps_dir = File.join(@tmp, 'epub', 'OEBPS')
|
|
65
|
+
FileUtils.mkdir_p([chapters_dir, oebps_dir])
|
|
66
|
+
|
|
67
|
+
FileUtils.touch(File.join(chapters_dir, 'chapter_3_5.xhtml'))
|
|
68
|
+
FileUtils.touch(File.join(oebps_dir, 'chapter_3_5.xhtml'))
|
|
69
|
+
|
|
70
|
+
workspace = ab.instance_variable_get(:@workspace)
|
|
71
|
+
workspace.instance_variable_set(:@chapters_dir, chapters_dir)
|
|
72
|
+
workspace.instance_variable_set(:@epub_dir, File.join(@tmp, 'epub'))
|
|
73
|
+
|
|
74
|
+
error = assert_raises(ArgumentError) { ab.send(:detect_conflicts) }
|
|
75
|
+
assert_match(/3\.5 already exist/, error.message)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_run_completes_workflow
|
|
79
|
+
ab = build_append_book
|
|
80
|
+
|
|
81
|
+
def ab.prepare_epub; end
|
|
82
|
+
def ab.extract_xhtmls; end
|
|
83
|
+
def ab.split_xhtmls; end
|
|
84
|
+
def ab.validate_chapters; end
|
|
85
|
+
def ab.before_add_chapters; end
|
|
86
|
+
def ab.add_chapters; end
|
|
87
|
+
def ab.pack_epub; end
|
|
88
|
+
|
|
89
|
+
result = ab.run
|
|
90
|
+
|
|
91
|
+
assert_equal File.expand_path(@target), result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_backup_creates_bak_file
|
|
95
|
+
File.write(@target, 'epub content')
|
|
96
|
+
ab = build_append_book
|
|
97
|
+
|
|
98
|
+
ab.send(:backup_target)
|
|
99
|
+
|
|
100
|
+
backup_path = "#{File.expand_path(@target)}.bak"
|
|
101
|
+
|
|
102
|
+
assert_path_exists backup_path
|
|
103
|
+
assert_equal 'epub content', File.read(backup_path)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def build_append_book(verbose: false)
|
|
109
|
+
EpubTools::AppendBook.new(
|
|
110
|
+
source_dir: @source, target_epub: @target,
|
|
111
|
+
build_dir: @tmp, verbose: verbose
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def setup_conflict_dirs(append_book, new_chapters:, existing_chapters:)
|
|
116
|
+
chapters_dir = File.join(@tmp, 'chapters')
|
|
117
|
+
oebps_dir = File.join(@tmp, 'epub', 'OEBPS')
|
|
118
|
+
FileUtils.mkdir_p([chapters_dir, oebps_dir])
|
|
119
|
+
|
|
120
|
+
new_chapters.each { |n| FileUtils.touch(File.join(chapters_dir, "chapter_#{n}.xhtml")) }
|
|
121
|
+
existing_chapters.each { |n| FileUtils.touch(File.join(oebps_dir, "chapter_#{n}.xhtml")) }
|
|
122
|
+
|
|
123
|
+
workspace = append_book.instance_variable_get(:@workspace)
|
|
124
|
+
workspace.instance_variable_set(:@chapters_dir, chapters_dir)
|
|
125
|
+
workspace.instance_variable_set(:@epub_dir, File.join(@tmp, 'epub'))
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_helper'
|
|
4
|
+
require_relative '../lib/epub_tools/chapter_validator'
|
|
5
|
+
|
|
6
|
+
class ChapterValidatorTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@tmp = Dir.mktmpdir
|
|
9
|
+
@validator = EpubTools::ChapterValidator.new(chapters_dir: @tmp)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
FileUtils.rm_rf(@tmp)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_validates_complete_sequence
|
|
17
|
+
create_chapter_files([1, 2, 3, 4, 5])
|
|
18
|
+
|
|
19
|
+
assert_silent { @validator.validate }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_raises_on_missing_chapters
|
|
23
|
+
create_chapter_files([1, 2, 4, 5]) # Missing 3
|
|
24
|
+
|
|
25
|
+
error = assert_raises(RuntimeError) { @validator.validate }
|
|
26
|
+
assert_match(/Missing chapter numbers: 3/, error.message)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_raises_on_no_chapters
|
|
30
|
+
error = assert_raises(RuntimeError) { @validator.validate }
|
|
31
|
+
assert_match(/No chapter files found/, error.message)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_handles_non_sequential_start
|
|
35
|
+
create_chapter_files([5, 6, 7, 8])
|
|
36
|
+
|
|
37
|
+
assert_silent { @validator.validate }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_validates_sequence_with_half_chapters
|
|
41
|
+
create_chapter_files([1, 2, 3])
|
|
42
|
+
create_half_chapter_files([2])
|
|
43
|
+
|
|
44
|
+
assert_silent { @validator.validate }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_validates_sequence_without_half_chapters_present
|
|
48
|
+
create_chapter_files([1, 2, 3])
|
|
49
|
+
|
|
50
|
+
assert_silent { @validator.validate }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_raises_on_missing_integer_despite_half_chapter
|
|
54
|
+
create_chapter_files([1, 3]) # Missing 2
|
|
55
|
+
create_half_chapter_files([1])
|
|
56
|
+
|
|
57
|
+
error = assert_raises(RuntimeError) { @validator.validate }
|
|
58
|
+
assert_match(/Missing chapter numbers: 2/, error.message)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def create_chapter_files(numbers)
|
|
64
|
+
numbers.each do |num|
|
|
65
|
+
File.write(File.join(@tmp, "chapter_#{num}.xhtml"), '<html></html>')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def create_half_chapter_files(numbers)
|
|
70
|
+
numbers.each do |num|
|
|
71
|
+
File.write(File.join(@tmp, "chapter_#{num}_5.xhtml"), '<html></html>')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../test_helper'
|
|
2
4
|
require_relative '../../lib/epub_tools'
|
|
3
5
|
require 'stringio'
|
|
@@ -41,17 +43,13 @@ class OptionBuilderTest < Minitest::Test
|
|
|
41
43
|
def test_with_input_file
|
|
42
44
|
@builder.with_input_file('Test input')
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
assert_includes @builder.parser.to_s, 'Test input (required)'
|
|
46
|
+
verify_parser_option_format('-i, --input-file FILE', 'Test input (required)')
|
|
46
47
|
|
|
47
|
-
# Test with required=false
|
|
48
48
|
builder2 = EpubTools::CLI::OptionBuilder.new
|
|
49
|
-
builder2.with_input_file('Test input', false)
|
|
49
|
+
builder2.with_input_file('Test input', required: false)
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
refute_includes builder2.parser.to_s, 'Test input (required)'
|
|
51
|
+
verify_optional_parser_format(builder2, 'Test input')
|
|
53
52
|
|
|
54
|
-
# Test with actual parsing
|
|
55
53
|
@builder.parse(['-i', 'file.txt'])
|
|
56
54
|
|
|
57
55
|
assert_equal 'file.txt', @builder.options[:input_file]
|
|
@@ -69,20 +67,16 @@ class OptionBuilderTest < Minitest::Test
|
|
|
69
67
|
end
|
|
70
68
|
|
|
71
69
|
def test_with_output_dir
|
|
72
|
-
# Test with default value
|
|
73
70
|
@builder.with_output_dir('Test output dir', 'default/path')
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
verify_parser_option_format('-o, --output-dir DIR', 'Test output dir (default: default/path)')
|
|
73
|
+
|
|
77
74
|
assert_equal 'default/path', @builder.options[:output_dir]
|
|
78
75
|
|
|
79
|
-
# Test without default
|
|
80
76
|
builder2 = EpubTools::CLI::OptionBuilder.new
|
|
81
77
|
builder2.with_output_dir('Test output dir')
|
|
78
|
+
verify_required_parser_format(builder2, 'Test output dir (required)')
|
|
82
79
|
|
|
83
|
-
assert_includes builder2.parser.to_s, 'Test output dir (required)'
|
|
84
|
-
|
|
85
|
-
# Test actual parsing
|
|
86
80
|
@builder.parse(['-o', 'new/path'])
|
|
87
81
|
|
|
88
82
|
assert_equal 'new/path', @builder.options[:output_dir]
|
|
@@ -170,4 +164,20 @@ class OptionBuilderTest < Minitest::Test
|
|
|
170
164
|
|
|
171
165
|
assert_equal @builder, result
|
|
172
166
|
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def verify_parser_option_format(option_format, description)
|
|
171
|
+
assert_includes @builder.parser.to_s, option_format
|
|
172
|
+
assert_includes @builder.parser.to_s, description
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def verify_optional_parser_format(builder, description)
|
|
176
|
+
assert_includes builder.parser.to_s, description
|
|
177
|
+
refute_includes builder.parser.to_s, "#{description} (required)"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def verify_required_parser_format(builder, description)
|
|
181
|
+
assert_includes builder.parser.to_s, description
|
|
182
|
+
end
|
|
173
183
|
end
|
data/test/cli/runner_test.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../test_helper'
|
|
2
4
|
require_relative '../../lib/epub_tools'
|
|
3
5
|
require 'stringio'
|
|
@@ -33,15 +35,18 @@ class RunnerTest < Minitest::Test
|
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def test_handle_command
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
@runner.registry.register('add', TestCommand)
|
|
39
|
+
|
|
40
|
+
assert_output(/Usage: test-program add/) do
|
|
41
|
+
assert_raises(SystemExit) { @runner.handle_command('add', ['-h']) }
|
|
38
42
|
end
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def test_handle_command_with_required_args
|
|
42
46
|
runner = EpubTools::CLI::Runner.new('test-program')
|
|
43
|
-
runner.registry.register('
|
|
44
|
-
|
|
47
|
+
runner.registry.register('add', TestCommand)
|
|
48
|
+
|
|
49
|
+
assert_output("Called!\n") { assert runner.handle_command('add') }
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
def test_handle_nonexistent_command
|
|
@@ -69,18 +74,13 @@ class RunnerTest < Minitest::Test
|
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
def test_configure_command_options
|
|
72
|
-
#
|
|
73
|
-
|
|
77
|
+
# Test command configuration through public interface
|
|
78
|
+
@runner.registry.register('add', TestCommand)
|
|
74
79
|
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@runner.send(:configure_command_options, 'add', builder)
|
|
80
|
-
|
|
81
|
-
# Check add command options were added
|
|
82
|
-
assert_includes builder.parser.to_s, '--chapters-dir DIR'
|
|
83
|
-
assert_includes builder.parser.to_s, '--oebps-dir DIR'
|
|
80
|
+
# Should show help without errors
|
|
81
|
+
assert_output(/Usage: test-program add/) do
|
|
82
|
+
assert_raises(SystemExit) { @runner.handle_command('add', ['-h']) }
|
|
83
|
+
end
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def test_run_with_unknown_command
|
data/test/cli_commands_test.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'test_helper'
|
|
2
4
|
require 'fileutils'
|
|
3
5
|
require 'tempfile'
|
|
@@ -20,6 +22,7 @@ class CLICommandsTest < Minitest::Test
|
|
|
20
22
|
assert_includes output, 'pack'
|
|
21
23
|
assert_includes output, 'unpack'
|
|
22
24
|
assert_includes output, 'compile'
|
|
25
|
+
assert_includes output, 'append'
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def test_show_version
|
|
@@ -90,6 +93,14 @@ class CLICommandsTest < Minitest::Test
|
|
|
90
93
|
assert_includes output, '--output-file FILE'
|
|
91
94
|
end
|
|
92
95
|
|
|
96
|
+
def test_append_command
|
|
97
|
+
output = `#{@bin_path} append --help`
|
|
98
|
+
|
|
99
|
+
assert_match(/Usage: epub-tools append \[options\]/, output)
|
|
100
|
+
assert_includes output, '--source-dir DIR'
|
|
101
|
+
assert_includes output, '--target-epub FILE'
|
|
102
|
+
end
|
|
103
|
+
|
|
93
104
|
def test_unpack_command
|
|
94
105
|
output = `#{@bin_path} unpack --help`
|
|
95
106
|
|
data/test/cli_test.rb
CHANGED