epub_tools 0.4.1 → 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 +4 -4
- data/.github/workflows/ci.yml +3 -0
- data/.rubocop.yml +10 -17
- data/CLAUDE.md +124 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +39 -34
- data/Rakefile +2 -0
- data/bin/epub-tools +2 -0
- data/epub_tools.gemspec +3 -1
- data/lib/epub_tools/add_chapters.rb +47 -29
- data/lib/epub_tools/chapter_validator.rb +40 -0
- data/lib/epub_tools/cli/command_options_configurator.rb +115 -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 +59 -110
- data/lib/epub_tools/cli.rb +16 -29
- data/lib/epub_tools/compile_book.rb +48 -65
- 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 +42 -17
- 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 +2 -0
- data/test/add_chapters_test.rb +49 -25
- data/test/chapter_validator_test.rb +47 -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 +2 -0
- data/test/cli_test.rb +2 -0
- data/test/cli_version_test.rb +2 -0
- data/test/compile_book_test.rb +17 -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 +27 -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 +13 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'zip'
|
|
2
4
|
require 'fileutils'
|
|
3
5
|
require_relative 'loggable'
|
|
@@ -6,6 +8,7 @@ module EpubTools
|
|
|
6
8
|
# Extracts text .xhtml files from EPUB archives, excluding nav.xhtml
|
|
7
9
|
class XHTMLExtractor
|
|
8
10
|
include Loggable
|
|
11
|
+
|
|
9
12
|
# Initializes the class
|
|
10
13
|
# @param options [Hash] Configuration options
|
|
11
14
|
# @option options [String] :source_dir Directory containing source .epub files (required)
|
|
@@ -39,21 +42,28 @@ module EpubTools
|
|
|
39
42
|
epub_name = File.basename(epub_path, '.epub')
|
|
40
43
|
log "Extracting from #{epub_name}.epub"
|
|
41
44
|
extracted_files = []
|
|
45
|
+
|
|
42
46
|
Zip::File.open(epub_path) do |zip_file|
|
|
43
|
-
zip_file.each
|
|
44
|
-
next unless entry.name.downcase.end_with?('.xhtml')
|
|
45
|
-
next if File.basename(entry.name).downcase == 'nav.xhtml'
|
|
46
|
-
|
|
47
|
-
output_path = File.join(@target_dir, "#{epub_name}_#{File.basename(entry.name)}")
|
|
48
|
-
FileUtils.mkdir_p(File.dirname(output_path))
|
|
49
|
-
entry.extract(output_path) { true }
|
|
50
|
-
log output_path
|
|
51
|
-
extracted_files << output_path
|
|
52
|
-
end
|
|
47
|
+
zip_file.each { |entry| extract_entry_if_xhtml(entry, epub_name, extracted_files) }
|
|
53
48
|
end
|
|
54
49
|
extracted_files
|
|
55
50
|
rescue Zip::Error => e
|
|
56
51
|
warn "⚠️ Failed to process #{epub_path}: #{e.message}"
|
|
57
52
|
end
|
|
53
|
+
|
|
54
|
+
def extract_entry_if_xhtml(entry, epub_name, extracted_files)
|
|
55
|
+
return unless xhtml_entry?(entry)
|
|
56
|
+
|
|
57
|
+
renamed = "#{epub_name}_#{File.basename(entry.name)}"
|
|
58
|
+
output_path = File.join(@target_dir, renamed)
|
|
59
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
60
|
+
entry.extract(renamed, destination_directory: @target_dir) { true }
|
|
61
|
+
log output_path
|
|
62
|
+
extracted_files << output_path
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def xhtml_entry?(entry)
|
|
66
|
+
entry.name.downcase.end_with?('.xhtml') && File.basename(entry.name).downcase != 'nav.xhtml'
|
|
67
|
+
end
|
|
58
68
|
end
|
|
59
69
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EpubTools
|
|
4
|
+
# Generates XHTML content for EPUB files
|
|
5
|
+
class XhtmlGenerator
|
|
6
|
+
attr_accessor :cover_image_fname
|
|
7
|
+
|
|
8
|
+
def initialize(title:, author:)
|
|
9
|
+
@title = title
|
|
10
|
+
@author = author
|
|
11
|
+
@cover_image_fname = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Generates title page XHTML content
|
|
15
|
+
def build_title_page
|
|
16
|
+
<<~XHTML
|
|
17
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
18
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="UTF-8" />
|
|
21
|
+
<title>#{@title}</title>
|
|
22
|
+
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<h1 class="title">#{@title}</h1>
|
|
26
|
+
<p class="author">by #{@author}</p>
|
|
27
|
+
</body>
|
|
28
|
+
</html>
|
|
29
|
+
XHTML
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Generates cover page XHTML content
|
|
33
|
+
def build_cover_page
|
|
34
|
+
<<~XHTML
|
|
35
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
36
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
|
37
|
+
<head>
|
|
38
|
+
<meta charset="UTF-8" />
|
|
39
|
+
<title>Cover</title>
|
|
40
|
+
<link rel="stylesheet" type="text/css" href="style.css"/>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div class="cover-image">
|
|
44
|
+
<img src="#{@cover_image_fname}" alt="Cover"/>
|
|
45
|
+
</div>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
48
|
+
XHTML
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Generates navigation XHTML content
|
|
52
|
+
def build_nav_page
|
|
53
|
+
<<~XHTML
|
|
54
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
55
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" lang="en">
|
|
56
|
+
<head>
|
|
57
|
+
<title>Table of Contents</title>
|
|
58
|
+
</head>
|
|
59
|
+
<body>
|
|
60
|
+
<nav epub:type="toc" id="toc">
|
|
61
|
+
<h1>Table of Contents</h1>
|
|
62
|
+
<ol>
|
|
63
|
+
<li><a href="title.xhtml">Title Page</a></li>
|
|
64
|
+
</ol>
|
|
65
|
+
</nav>
|
|
66
|
+
</body>
|
|
67
|
+
</html>
|
|
68
|
+
XHTML
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/epub_tools.rb
CHANGED
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,69 @@ 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
|
|
99
123
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
private
|
|
41
|
+
|
|
42
|
+
def create_chapter_files(numbers)
|
|
43
|
+
numbers.each do |num|
|
|
44
|
+
File.write(File.join(@tmp, "chapter_#{num}.xhtml"), '<html></html>')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
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
data/test/cli_test.rb
CHANGED
data/test/cli_version_test.rb
CHANGED
data/test/compile_book_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/compile_book'
|
|
3
5
|
|
|
@@ -11,7 +13,7 @@ class CompileBookTest < Minitest::Test
|
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def teardown
|
|
14
|
-
FileUtils.
|
|
16
|
+
FileUtils.rm_rf(@tmp)
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def test_default_output_file
|
|
@@ -67,39 +69,6 @@ class CompileBookTest < Minitest::Test
|
|
|
67
69
|
refute cb.verbose
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
def test_clean_build_dir_removes_directory
|
|
71
|
-
build = File.join(@tmp, 'build')
|
|
72
|
-
FileUtils.mkdir_p(build)
|
|
73
|
-
File.write(File.join(build, 'foo'), 'bar')
|
|
74
|
-
cb = EpubTools::CompileBook.new(
|
|
75
|
-
title: @title,
|
|
76
|
-
author: @author,
|
|
77
|
-
source_dir: @source,
|
|
78
|
-
build_dir: build
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
assert Dir.exist?(build)
|
|
82
|
-
cb.send(:clean_build_dir)
|
|
83
|
-
|
|
84
|
-
refute Dir.exist?(build)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def test_prepare_dirs_creates_xhtml_and_chapters_directories
|
|
88
|
-
build = File.join(@tmp, 'build')
|
|
89
|
-
cb = EpubTools::CompileBook.new(
|
|
90
|
-
title: @title,
|
|
91
|
-
author: @author,
|
|
92
|
-
source_dir: @source,
|
|
93
|
-
build_dir: build
|
|
94
|
-
)
|
|
95
|
-
cb.send(:prepare_dirs)
|
|
96
|
-
xhtml_dir = cb.send(:xhtml_dir)
|
|
97
|
-
chapters_dir = cb.send(:chapters_dir)
|
|
98
|
-
|
|
99
|
-
assert Dir.exist?(xhtml_dir)
|
|
100
|
-
assert Dir.exist?(chapters_dir)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
72
|
def test_log_outputs_when_verbose
|
|
104
73
|
cb = EpubTools::CompileBook.new(
|
|
105
74
|
title: @title,
|
|
@@ -122,81 +91,27 @@ class CompileBookTest < Minitest::Test
|
|
|
122
91
|
assert_silent { cb.send(:log, 'hello') }
|
|
123
92
|
end
|
|
124
93
|
|
|
125
|
-
def
|
|
126
|
-
|
|
127
|
-
title: @title,
|
|
128
|
-
author: @author,
|
|
129
|
-
source_dir: @source,
|
|
130
|
-
build_dir: @tmp
|
|
131
|
-
)
|
|
132
|
-
FileUtils.mkdir_p(cb.send(:chapters_dir))
|
|
133
|
-
err = assert_raises(RuntimeError) { cb.send(:validate_sequence) }
|
|
134
|
-
assert_match(/No chapter files found/, err.message)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def test_validate_sequence_raises_on_missing_chapters
|
|
138
|
-
build = File.join(@tmp, 'build')
|
|
94
|
+
def test_run_completes_workflow
|
|
95
|
+
# Test that run method executes the complete workflow
|
|
139
96
|
cb = EpubTools::CompileBook.new(
|
|
140
97
|
title: @title,
|
|
141
98
|
author: @author,
|
|
142
99
|
source_dir: @source,
|
|
143
|
-
build_dir:
|
|
100
|
+
build_dir: @tmp,
|
|
101
|
+
output_file: 'test.epub'
|
|
144
102
|
)
|
|
145
|
-
chapters = cb.send(:chapters_dir)
|
|
146
|
-
FileUtils.mkdir_p(chapters)
|
|
147
|
-
File.write(File.join(chapters, 'chap_1.xhtml'), '')
|
|
148
|
-
File.write(File.join(chapters, 'chap_3.xhtml'), '')
|
|
149
|
-
err = assert_raises(RuntimeError) { cb.send(:validate_sequence) }
|
|
150
|
-
assert_match(/Missing chapter numbers: 2/, err.message)
|
|
151
|
-
end
|
|
152
103
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
cb
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
161
|
-
chapters = cb.send(:chapters_dir)
|
|
162
|
-
FileUtils.mkdir_p(chapters)
|
|
163
|
-
File.write(File.join(chapters, 'chap_1.xhtml'), '')
|
|
164
|
-
File.write(File.join(chapters, 'chap_2.xhtml'), '')
|
|
165
|
-
File.write(File.join(chapters, 'chap_3.xhtml'), '')
|
|
104
|
+
# Mock the workflow methods to avoid complex file setup
|
|
105
|
+
def cb.extract_xhtmls; end
|
|
106
|
+
def cb.split_xhtmls; end
|
|
107
|
+
def cb.validate_chapters; end
|
|
108
|
+
def cb.initialize_epub; end
|
|
109
|
+
def cb.add_chapters; end
|
|
110
|
+
def cb.pack_epub; end
|
|
166
111
|
|
|
167
|
-
|
|
168
|
-
|
|
112
|
+
# Should complete without error
|
|
113
|
+
result = cb.run
|
|
169
114
|
|
|
170
|
-
|
|
171
|
-
cb = EpubTools::CompileBook.new(
|
|
172
|
-
title: @title,
|
|
173
|
-
author: @author,
|
|
174
|
-
source_dir: @source,
|
|
175
|
-
build_dir: @tmp,
|
|
176
|
-
output_file: 'o.epub'
|
|
177
|
-
)
|
|
178
|
-
seq = []
|
|
179
|
-
cb.define_singleton_method(:clean_build_dir) { seq << :clean }
|
|
180
|
-
cb.define_singleton_method(:prepare_dirs) { seq << :prepare }
|
|
181
|
-
cb.define_singleton_method(:extract_xhtmls) { seq << :extract }
|
|
182
|
-
cb.define_singleton_method(:split_xhtmls) { seq << :split }
|
|
183
|
-
cb.define_singleton_method(:validate_sequence) { seq << :validate }
|
|
184
|
-
cb.define_singleton_method(:initialize_epub) { seq << :init }
|
|
185
|
-
cb.define_singleton_method(:add_chapters) { seq << :add }
|
|
186
|
-
cb.define_singleton_method(:pack_epub) { seq << :pack }
|
|
187
|
-
cb.define_singleton_method(:log) { |msg| seq << [:log, msg] }
|
|
188
|
-
cb.run
|
|
189
|
-
expected = [
|
|
190
|
-
:clean, :prepare, :extract, :split,
|
|
191
|
-
:validate, :init, :add, :pack,
|
|
192
|
-
[:log, /Done\. Output EPUB: .*o\.epub/],
|
|
193
|
-
:clean
|
|
194
|
-
]
|
|
195
|
-
|
|
196
|
-
assert_equal expected[0..7], seq[0..7]
|
|
197
|
-
assert_kind_of Array, seq[8]
|
|
198
|
-
assert_equal :log, seq[8][0]
|
|
199
|
-
assert_match expected[8][1], seq[8][1]
|
|
200
|
-
assert_equal expected[9], seq[9]
|
|
115
|
+
assert_equal 'test.epub', result
|
|
201
116
|
end
|
|
202
117
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'test_helper'
|
|
4
|
+
require_relative '../lib/epub_tools/compile_workspace'
|
|
5
|
+
|
|
6
|
+
class CompileWorkspaceTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@tmp = Dir.mktmpdir
|
|
9
|
+
@workspace = EpubTools::CompileWorkspace.new(@tmp)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def teardown
|
|
13
|
+
FileUtils.rm_rf(@tmp)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_initialize_sets_build_dir
|
|
17
|
+
assert_equal @tmp, @workspace.build_dir
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_prepare_directories_creates_required_dirs
|
|
21
|
+
@workspace.prepare_directories
|
|
22
|
+
|
|
23
|
+
assert Dir.exist?(@workspace.xhtml_dir)
|
|
24
|
+
assert Dir.exist?(@workspace.chapters_dir)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_directory_paths_are_correct
|
|
28
|
+
assert_equal File.join(@tmp, 'xhtml'), @workspace.xhtml_dir
|
|
29
|
+
assert_equal File.join(@tmp, 'chapters'), @workspace.chapters_dir
|
|
30
|
+
assert_equal File.join(@tmp, 'epub'), @workspace.epub_dir
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_directory_paths_are_memoized
|
|
34
|
+
# First call creates the path
|
|
35
|
+
path1 = @workspace.xhtml_dir
|
|
36
|
+
# Second call should return the same object
|
|
37
|
+
path2 = @workspace.xhtml_dir
|
|
38
|
+
|
|
39
|
+
assert_equal path1, path2
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_clean_removes_build_directory
|
|
43
|
+
# Create the directory and some content
|
|
44
|
+
FileUtils.mkdir_p(@tmp)
|
|
45
|
+
test_file = File.join(@tmp, 'test.txt')
|
|
46
|
+
File.write(test_file, 'test content')
|
|
47
|
+
|
|
48
|
+
assert Dir.exist?(@tmp)
|
|
49
|
+
assert_path_exists test_file
|
|
50
|
+
|
|
51
|
+
@workspace.clean
|
|
52
|
+
|
|
53
|
+
refute Dir.exist?(@tmp)
|
|
54
|
+
end
|
|
55
|
+
end
|