epub_tools 0.4.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 +4 -4
- data/.github/workflows/ci.yml +3 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +10 -17
- data/.tool-versions +1 -0
- data/CLAUDE.md +124 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +44 -37
- data/README.md +10 -6
- data/Rakefile +2 -0
- data/bin/epub-tools +2 -0
- data/epub_tools.gemspec +3 -1
- data/lib/epub_tools/add_chapters.rb +48 -30
- 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 +50 -26
- 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 +3 -1
- 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 +15 -5
- data/.ruby-version +0 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
2
4
|
require 'nokogiri'
|
|
3
5
|
require 'yaml'
|
|
4
6
|
require_relative 'loggable'
|
|
@@ -9,6 +11,7 @@ module EpubTools
|
|
|
9
11
|
# {SplitChapters}[rdoc-ref:EpubTools::SplitChapters].
|
|
10
12
|
class StyleFinder
|
|
11
13
|
include Loggable
|
|
14
|
+
|
|
12
15
|
# Initializes the class
|
|
13
16
|
# @param options [Hash] Configuration options
|
|
14
17
|
# @option options [String] :file_path XHTML file to be analyzed (required)
|
|
@@ -24,18 +27,26 @@ module EpubTools
|
|
|
24
27
|
# Runs the finder
|
|
25
28
|
# @return [Hash] Data containing the extracted style classes (italics and bolds)
|
|
26
29
|
def run
|
|
30
|
+
style_blocks = extract_style_blocks
|
|
31
|
+
italics, bolds = extract_style_classes(style_blocks)
|
|
32
|
+
generate_output(italics, bolds)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extract_style_blocks
|
|
27
36
|
doc = Nokogiri::HTML(File.read(@file_path))
|
|
28
|
-
|
|
37
|
+
doc.xpath('//style').map(&:text).join("\n")
|
|
38
|
+
end
|
|
29
39
|
|
|
40
|
+
def extract_style_classes(style_blocks)
|
|
30
41
|
italics = extract_classes(style_blocks, /font-style\s*:\s*italic/)
|
|
31
|
-
bolds
|
|
42
|
+
bolds = extract_classes(style_blocks, /font-weight\s*:\s*700/)
|
|
43
|
+
[italics, bolds]
|
|
44
|
+
end
|
|
32
45
|
|
|
46
|
+
def generate_output(italics, bolds)
|
|
33
47
|
print_summary(italics, bolds) if @verbose
|
|
34
48
|
|
|
35
|
-
data = {
|
|
36
|
-
'italics' => italics,
|
|
37
|
-
'bolds' => bolds
|
|
38
|
-
}
|
|
49
|
+
data = { 'italics' => italics, 'bolds' => bolds }
|
|
39
50
|
File.write(@output_path, data.to_yaml)
|
|
40
51
|
data
|
|
41
52
|
end
|
|
@@ -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
|
# Unpacks an EPUB (.epub file) into a directory
|
|
7
9
|
class UnpackEbook
|
|
8
10
|
include Loggable
|
|
11
|
+
|
|
9
12
|
# Initializes the class
|
|
10
13
|
# @param options [Hash] Configuration options
|
|
11
14
|
# @option options [String] :epub_file Path to the .epub file to unpack (required)
|
|
@@ -23,22 +26,29 @@ module EpubTools
|
|
|
23
26
|
def run
|
|
24
27
|
validate!
|
|
25
28
|
FileUtils.mkdir_p(@output_dir)
|
|
29
|
+
extract_entries
|
|
30
|
+
log "Unpacked #{File.basename(@epub_file)} to #{@output_dir}"
|
|
31
|
+
@output_dir
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def extract_entries
|
|
26
37
|
Zip::File.open(@epub_file) do |zip|
|
|
27
38
|
zip.each do |entry|
|
|
28
|
-
|
|
29
|
-
if entry.directory?
|
|
30
|
-
FileUtils.mkdir_p(dest_path)
|
|
31
|
-
else
|
|
32
|
-
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
33
|
-
entry.extract(dest_path) { true }
|
|
34
|
-
end
|
|
39
|
+
extract_entry(entry)
|
|
35
40
|
end
|
|
36
41
|
end
|
|
37
|
-
log "Unpacked #{File.basename(@epub_file)} to #{@output_dir}"
|
|
38
|
-
@output_dir
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
def extract_entry(entry)
|
|
45
|
+
if entry.directory?
|
|
46
|
+
FileUtils.mkdir_p(File.join(@output_dir, entry.name))
|
|
47
|
+
else
|
|
48
|
+
FileUtils.mkdir_p(File.join(@output_dir, File.dirname(entry.name)))
|
|
49
|
+
entry.extract(destination_directory: @output_dir) { true }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
42
52
|
|
|
43
53
|
def default_dir
|
|
44
54
|
[File.dirname(@epub_file), File.basename(@epub_file, '.epub')].join('/')
|
data/lib/epub_tools/version.rb
CHANGED
|
@@ -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
|
-
|
|
57
|
-
|
|
58
|
+
result = EpubTools::AddChapters.new(chapters_dir: @chapters_dir, oebps_dir: @epub_dir).run
|
|
59
|
+
|
|
60
|
+
verify_return_value(result)
|
|
61
|
+
verify_files_moved
|
|
62
|
+
verify_opf_structure
|
|
63
|
+
verify_nav_structure
|
|
64
|
+
end
|
|
58
65
|
|
|
59
|
-
|
|
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, '--epub-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'
|
|
@@ -79,7 +81,7 @@ class CLICommandsTest < Minitest::Test
|
|
|
79
81
|
|
|
80
82
|
assert_match(/Usage: epub-tools add \[options\]/, output)
|
|
81
83
|
assert_includes output, '--chapters-dir DIR'
|
|
82
|
-
assert_includes output, '--
|
|
84
|
+
assert_includes output, '--oebps-dir DIR'
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
def test_pack_command
|
data/test/cli_test.rb
CHANGED