epub_tools 0.3.1 → 0.4.1
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/.gitignore +2 -0
- data/.rubocop.yml +41 -0
- data/.tool-versions +1 -0
- data/Gemfile +13 -9
- data/Gemfile.lock +63 -12
- data/README.md +26 -6
- data/bin/epub-tools +3 -109
- data/epub_tools.gemspec +6 -8
- data/lib/epub_tools/add_chapters.rb +41 -19
- data/lib/epub_tools/cli/command_registry.rb +47 -0
- data/lib/epub_tools/cli/option_builder.rb +164 -0
- data/lib/epub_tools/cli/runner.rb +164 -0
- data/lib/epub_tools/cli.rb +45 -0
- data/lib/epub_tools/compile_book.rb +60 -28
- data/lib/epub_tools/epub_initializer.rb +38 -28
- data/lib/epub_tools/loggable.rb +11 -0
- data/lib/epub_tools/pack_ebook.rb +18 -12
- data/lib/epub_tools/split_chapters.rb +31 -21
- data/lib/epub_tools/{text_style_class_finder.rb → style_finder.rb} +21 -17
- data/lib/epub_tools/unpack_ebook.rb +17 -12
- data/lib/epub_tools/version.rb +1 -1
- data/lib/epub_tools/xhtml_cleaner.rb +17 -13
- data/lib/epub_tools/xhtml_extractor.rb +20 -11
- data/lib/epub_tools.rb +2 -1
- data/test/add_chapters_test.rb +12 -5
- data/test/cli/command_registry_test.rb +66 -0
- data/test/cli/option_builder_test.rb +173 -0
- data/test/cli/runner_test.rb +91 -0
- data/test/cli_commands_test.rb +100 -0
- data/test/cli_test.rb +4 -0
- data/test/cli_version_test.rb +5 -3
- data/test/compile_book_test.rb +11 -2
- data/test/epub_initializer_test.rb +51 -31
- data/test/pack_ebook_test.rb +14 -8
- data/test/split_chapters_test.rb +22 -1
- data/test/{text_style_class_finder_test.rb → style_finder_test.rb} +7 -6
- data/test/test_helper.rb +4 -5
- data/test/unpack_ebook_test.rb +21 -5
- data/test/xhtml_cleaner_test.rb +13 -7
- data/test/xhtml_extractor_test.rb +17 -1
- metadata +21 -38
- data/.ruby-version +0 -1
- data/lib/epub_tools/cli_helper.rb +0 -31
@@ -1,21 +1,27 @@
|
|
1
1
|
require 'zip'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'pathname'
|
4
|
+
require_relative 'loggable'
|
4
5
|
|
5
6
|
module EpubTools
|
6
7
|
# Packages an EPUB directory into a .epub file
|
7
8
|
class PackEbook
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
9
|
+
include Loggable
|
10
|
+
# Initializes the class
|
11
|
+
# @param options [Hash] Configuration options
|
12
|
+
# @option options [String] :input_dir Path to the EPUB directory (containing mimetype, META-INF, OEBPS) (required)
|
13
|
+
# @option options [String] :output_file Path to resulting .epub file (default: <input_dir>.epub)
|
14
|
+
# @option options [Boolean] :verbose Whether to print progress to STDOUT (default: false)
|
15
|
+
def initialize(options = {})
|
16
|
+
@input_dir = File.expand_path(options.fetch(:input_dir))
|
12
17
|
default_name = "#{File.basename(@input_dir)}.epub"
|
18
|
+
output_file = options[:output_file]
|
13
19
|
@output_file = if output_file.nil? || output_file.empty?
|
14
20
|
default_name
|
15
21
|
else
|
16
22
|
output_file
|
17
23
|
end
|
18
|
-
@verbose = verbose
|
24
|
+
@verbose = options[:verbose] || false
|
19
25
|
end
|
20
26
|
|
21
27
|
# Runs the packaging process and returns the resulting file path
|
@@ -33,24 +39,24 @@ module EpubTools
|
|
33
39
|
Dir.glob('**/*', File::FNM_DOTMATCH).sort.each do |entry|
|
34
40
|
next if ['.', '..', 'mimetype'].include?(entry)
|
35
41
|
next if File.directory?(entry)
|
42
|
+
|
36
43
|
zip.add(entry, entry)
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
40
|
-
|
47
|
+
log "EPUB created: #{@output_file}"
|
41
48
|
@output_file
|
42
49
|
end
|
43
50
|
|
44
51
|
private
|
45
52
|
|
46
53
|
def validate_input!
|
47
|
-
unless Dir.exist?(@input_dir)
|
48
|
-
|
49
|
-
end
|
54
|
+
raise ArgumentError, "Directory '#{@input_dir}' does not exist." unless Dir.exist?(@input_dir)
|
55
|
+
|
50
56
|
mimetype = File.join(@input_dir, 'mimetype')
|
51
|
-
|
52
|
-
|
53
|
-
|
57
|
+
return if File.file?(mimetype)
|
58
|
+
|
59
|
+
raise ArgumentError, "Error: 'mimetype' file missing in #{@input_dir}"
|
54
60
|
end
|
55
61
|
|
56
62
|
def add_mimetype(zip)
|
@@ -1,42 +1,48 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'nokogiri'
|
3
3
|
require 'yaml'
|
4
|
-
|
4
|
+
require 'fileutils'
|
5
|
+
require_relative 'loggable'
|
6
|
+
require_relative 'style_finder'
|
5
7
|
require_relative 'xhtml_cleaner'
|
6
8
|
|
7
9
|
module EpubTools
|
8
10
|
# Takes a Google Docs generated, already extracted from their EPUB, XHTML files with multiple
|
9
11
|
# chapters and it:
|
10
|
-
# - Extracts classes using {
|
12
|
+
# - Extracts classes using {StyleFinder}[rdoc-ref:EpubTools::StyleFinder]
|
11
13
|
# - Looks for tags that say something like Chapter XX or Prologue and splits the text there
|
12
14
|
# - Creates new chapter_XX.xhtml files that are cleaned using
|
13
15
|
# {XHTMLCleaner}[rdoc-ref:EpubTools::XHTMLCleaner]
|
14
16
|
# - Saves those files to +output_dir+
|
15
17
|
class SplitChapters
|
16
|
-
|
17
|
-
#
|
18
|
-
# [
|
19
|
-
# [
|
20
|
-
# [
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@
|
26
|
-
@
|
18
|
+
include Loggable
|
19
|
+
# Initializes the class
|
20
|
+
# @param options [Hash] Configuration options
|
21
|
+
# @option options [String] :input_file Path to the source XHTML (required)
|
22
|
+
# @option options [String] :book_title Title to use in HTML <title> tags (required)
|
23
|
+
# @option options [String] :output_dir Where to write chapter files (default: './chapters')
|
24
|
+
# @option options [String] :output_prefix Filename prefix for chapter files (default: 'chapter')
|
25
|
+
# @option options [Boolean] :verbose Whether to print progress to STDOUT (default: false)
|
26
|
+
def initialize(options = {})
|
27
|
+
@input_file = options.fetch(:input_file)
|
28
|
+
@book_title = options.fetch(:book_title)
|
29
|
+
@output_dir = options[:output_dir] || './chapters'
|
30
|
+
@output_prefix = options[:output_prefix] || 'chapter'
|
31
|
+
@verbose = options[:verbose] || false
|
27
32
|
end
|
28
33
|
|
29
34
|
# Runs the splitter
|
35
|
+
# @return [Array<String>] List of generated chapter file paths
|
30
36
|
def run
|
31
37
|
# Prepare output dir
|
32
|
-
|
38
|
+
FileUtils.mkdir_p(@output_dir)
|
33
39
|
|
34
40
|
# Read the doc
|
35
41
|
raw_content = read_and_strip_problematic_tags
|
36
42
|
doc = Nokogiri::HTML(raw_content)
|
37
43
|
|
38
44
|
# Find Style Classes
|
39
|
-
|
45
|
+
StyleFinder.new({ file_path: @input_file, verbose: @verbose }).run
|
40
46
|
|
41
47
|
chapters = extract_chapters(doc)
|
42
48
|
write_chapter_files(chapters)
|
@@ -45,7 +51,7 @@ module EpubTools
|
|
45
51
|
private
|
46
52
|
|
47
53
|
def read_and_strip_problematic_tags
|
48
|
-
File.read(@input_file).gsub(
|
54
|
+
File.read(@input_file).gsub(%r{<hr\b[^>]*/?>}i, '').gsub(%r{<br\b[^>]*/?>}i, '')
|
49
55
|
end
|
50
56
|
|
51
57
|
def extract_chapters(doc)
|
@@ -74,9 +80,12 @@ module EpubTools
|
|
74
80
|
end
|
75
81
|
|
76
82
|
def write_chapter_files(chapters)
|
83
|
+
chapter_files = []
|
77
84
|
chapters.each do |number, content|
|
78
|
-
write_chapter_file(number, content)
|
85
|
+
filename = write_chapter_file(number, content)
|
86
|
+
chapter_files << filename
|
79
87
|
end
|
88
|
+
chapter_files
|
80
89
|
end
|
81
90
|
|
82
91
|
def write_chapter_file(label, content)
|
@@ -95,20 +104,21 @@ module EpubTools
|
|
95
104
|
</body>
|
96
105
|
</html>
|
97
106
|
HTML
|
98
|
-
XHTMLCleaner.new(filename).
|
99
|
-
|
107
|
+
XHTMLCleaner.new({ filename: filename }).run
|
108
|
+
log("Extracted: #{filename}")
|
109
|
+
filename
|
100
110
|
end
|
101
111
|
|
102
112
|
def display_label(label)
|
103
|
-
label
|
113
|
+
label.positive? ? "Chapter #{label}" : 'Prologue'
|
104
114
|
end
|
105
115
|
|
106
116
|
# Detect a bolded Prologue marker
|
107
117
|
def prologue_marker?(node)
|
108
118
|
return false unless %w[h3 h4].include?(node.name)
|
109
119
|
return false unless node.text.strip =~ /\APrologue\z/i
|
120
|
+
|
110
121
|
true
|
111
122
|
end
|
112
|
-
|
113
123
|
end
|
114
124
|
end
|
@@ -1,24 +1,29 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'nokogiri'
|
3
3
|
require 'yaml'
|
4
|
+
require_relative 'loggable'
|
4
5
|
|
5
6
|
module EpubTools
|
6
7
|
# Finds css classes for bold and italic texts in Google Docs-generated EPUBs. Used by
|
7
8
|
# {XHTMLCleaner}[rdoc-ref:EpubTools::XHTMLCleaner] and
|
8
9
|
# {SplitChapters}[rdoc-ref:EpubTools::SplitChapters].
|
9
|
-
class
|
10
|
-
|
11
|
-
#
|
12
|
-
# [
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
class StyleFinder
|
11
|
+
include Loggable
|
12
|
+
# Initializes the class
|
13
|
+
# @param options [Hash] Configuration options
|
14
|
+
# @option options [String] :file_path XHTML file to be analyzed (required)
|
15
|
+
# @option options [String] :output_path Path to write the YAML file (default: 'text_style_classes.yaml')
|
16
|
+
# @option options [Boolean] :verbose Whether to print progress to STDOUT (default: false)
|
17
|
+
def initialize(options = {})
|
18
|
+
@file_path = options.fetch(:file_path)
|
19
|
+
@output_path = options[:output_path] || 'text_style_classes.yaml'
|
20
|
+
@verbose = options[:verbose] || false
|
17
21
|
raise ArgumentError, "File does not exist: #{@file_path}" unless File.exist?(@file_path)
|
18
22
|
end
|
19
23
|
|
20
24
|
# Runs the finder
|
21
|
-
|
25
|
+
# @return [Hash] Data containing the extracted style classes (italics and bolds)
|
26
|
+
def run
|
22
27
|
doc = Nokogiri::HTML(File.read(@file_path))
|
23
28
|
style_blocks = doc.xpath('//style').map(&:text).join("\n")
|
24
29
|
|
@@ -28,10 +33,11 @@ module EpubTools
|
|
28
33
|
print_summary(italics, bolds) if @verbose
|
29
34
|
|
30
35
|
data = {
|
31
|
-
|
32
|
-
|
36
|
+
'italics' => italics,
|
37
|
+
'bolds' => bolds
|
33
38
|
}
|
34
39
|
File.write(@output_path, data.to_yaml)
|
40
|
+
data
|
35
41
|
end
|
36
42
|
|
37
43
|
private
|
@@ -42,13 +48,11 @@ module EpubTools
|
|
42
48
|
end
|
43
49
|
|
44
50
|
def print_summary(italics, bolds)
|
45
|
-
unless italics.empty?
|
46
|
-
puts "Classes with font-style: italic: #{italics.join(", ")}"
|
47
|
-
end
|
51
|
+
log "Classes with font-style: italic: #{italics.join(', ')}" unless italics.empty?
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
53
|
+
return if bolds.empty?
|
54
|
+
|
55
|
+
log "Classes with font-weight: 700: #{bolds.join(', ')}"
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
@@ -1,16 +1,21 @@
|
|
1
1
|
require 'zip'
|
2
2
|
require 'fileutils'
|
3
|
+
require_relative 'loggable'
|
3
4
|
|
4
5
|
module EpubTools
|
5
6
|
# Unpacks an EPUB (.epub file) into a directory
|
6
7
|
class UnpackEbook
|
7
|
-
|
8
|
-
#
|
9
|
-
# [
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
include Loggable
|
9
|
+
# Initializes the class
|
10
|
+
# @param options [Hash] Configuration options
|
11
|
+
# @option options [String] :epub_file Path to the .epub file to unpack (required)
|
12
|
+
# @option options [String] :output_dir Directory to extract into (default: basename of epub_file without .epub)
|
13
|
+
# @option options [Boolean] :verbose Whether to print progress to STDOUT (default: false)
|
14
|
+
def initialize(options = {})
|
15
|
+
@epub_file = File.expand_path(options.fetch(:epub_file))
|
16
|
+
output_dir = options[:output_dir]
|
17
|
+
@output_dir = output_dir.nil? || output_dir.empty? ? default_dir : output_dir
|
18
|
+
@verbose = options[:verbose] || false
|
14
19
|
end
|
15
20
|
|
16
21
|
# Extracts all entries from the EPUB into the output directory. Returns the output
|
@@ -29,20 +34,20 @@ module EpubTools
|
|
29
34
|
end
|
30
35
|
end
|
31
36
|
end
|
32
|
-
|
37
|
+
log "Unpacked #{File.basename(@epub_file)} to #{@output_dir}"
|
33
38
|
@output_dir
|
34
39
|
end
|
35
40
|
|
36
41
|
private
|
37
42
|
|
38
43
|
def default_dir
|
39
|
-
[File.dirname(@epub_file), File.basename(@epub_file, '.epub')].join(
|
44
|
+
[File.dirname(@epub_file), File.basename(@epub_file, '.epub')].join('/')
|
40
45
|
end
|
41
46
|
|
42
47
|
def validate!
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
return if File.file?(@epub_file)
|
49
|
+
|
50
|
+
raise ArgumentError, "EPUB file '#{@epub_file}' does not exist"
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
data/lib/epub_tools/version.rb
CHANGED
@@ -16,17 +16,20 @@ module EpubTools
|
|
16
16
|
# - Unwraps any <tt><span></tt> tags that have no classes assigned.
|
17
17
|
# - Outputs everything to a cleanly formatted +.xhtml+
|
18
18
|
class XHTMLCleaner
|
19
|
-
#
|
20
|
-
# [
|
21
|
-
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
19
|
+
# Initializes the class
|
20
|
+
# @param options [Hash] Configuration options
|
21
|
+
# @option options [String] :filename The path to the xhtml to clean (required)
|
22
|
+
# @option options [String] :class_config Path to a YAML file containing the bold and italic classes to check
|
23
|
+
# (default: 'text_style_classes.yaml')
|
24
|
+
def initialize(options = {})
|
25
|
+
@filename = options.fetch(:filename)
|
26
|
+
class_config = options[:class_config] || 'text_style_classes.yaml'
|
25
27
|
@classes = YAML.load_file(class_config).transform_keys(&:to_sym)
|
26
28
|
end
|
27
29
|
|
28
|
-
#
|
29
|
-
|
30
|
+
# Runs the cleaner
|
31
|
+
# @return [String] Path to the cleaned file
|
32
|
+
def run
|
30
33
|
raw_content = read_and_strip_problematic_hr
|
31
34
|
doc = parse_xml(raw_content)
|
32
35
|
remove_empty_paragraphs(doc)
|
@@ -34,24 +37,25 @@ module EpubTools
|
|
34
37
|
replace_italic_spans(doc)
|
35
38
|
unwrap_remaining_spans(doc)
|
36
39
|
write_pretty_output(doc)
|
40
|
+
@filename
|
37
41
|
end
|
38
42
|
|
39
43
|
private
|
40
44
|
|
41
45
|
def read_and_strip_problematic_hr
|
42
|
-
File.read(@filename).gsub(
|
46
|
+
File.read(@filename).gsub(%r{<hr\b[^>]*/?>}i, '').gsub(%r{<br\b[^>]*/?>}i, '')
|
43
47
|
end
|
44
48
|
|
45
49
|
def parse_xml(content)
|
46
50
|
Nokogiri::XML(content) { |config| config.default_xml.noblanks }
|
47
|
-
rescue => e
|
51
|
+
rescue StandardError => e
|
48
52
|
abort "Error parsing XML: #{e.message}"
|
49
53
|
end
|
50
54
|
|
51
55
|
def remove_empty_paragraphs(doc)
|
52
56
|
doc.css('p').each do |p|
|
53
57
|
content = p.inner_html.strip
|
54
|
-
if content.empty? || content =~
|
58
|
+
if content.empty? || content =~ %r{\A(<span[^>]*>\s*</span>\s*)+\z}
|
55
59
|
p.remove
|
56
60
|
else
|
57
61
|
p.remove_attribute('class')
|
@@ -70,14 +74,14 @@ module EpubTools
|
|
70
74
|
def replace_italic_spans(doc)
|
71
75
|
@classes[:italics].each do |class_name|
|
72
76
|
doc.css("span.#{class_name}").each do |node|
|
73
|
-
node.name =
|
77
|
+
node.name = 'i'
|
74
78
|
node.remove_attribute('class')
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
79
83
|
def unwrap_remaining_spans(doc)
|
80
|
-
doc.css(
|
84
|
+
doc.css('span').each do |span|
|
81
85
|
span.add_next_sibling(span.dup.content)
|
82
86
|
span.remove
|
83
87
|
end
|
@@ -1,24 +1,32 @@
|
|
1
1
|
require 'zip'
|
2
2
|
require 'fileutils'
|
3
|
+
require_relative 'loggable'
|
3
4
|
|
4
5
|
module EpubTools
|
5
6
|
# Extracts text .xhtml files from EPUB archives, excluding nav.xhtml
|
6
7
|
class XHTMLExtractor
|
7
|
-
|
8
|
-
#
|
9
|
-
# [
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
include Loggable
|
9
|
+
# Initializes the class
|
10
|
+
# @param options [Hash] Configuration options
|
11
|
+
# @option options [String] :source_dir Directory containing source .epub files (required)
|
12
|
+
# @option options [String] :target_dir Directory where .xhtml files will be extracted (required)
|
13
|
+
# @option options [Boolean] :verbose Whether to print progress to STDOUT (default: false)
|
14
|
+
def initialize(options = {})
|
15
|
+
@source_dir = File.expand_path(options.fetch(:source_dir))
|
16
|
+
@target_dir = File.expand_path(options.fetch(:target_dir))
|
17
|
+
@verbose = options[:verbose] || false
|
14
18
|
FileUtils.mkdir_p(@target_dir)
|
15
19
|
end
|
16
20
|
|
17
21
|
# Runs the extraction process
|
18
|
-
|
22
|
+
# @return [Array<String>] Paths to all extracted XHTML files
|
23
|
+
def run
|
24
|
+
all_extracted_files = []
|
19
25
|
epub_files.each do |epub_path|
|
20
|
-
extract_xhtmls_from(epub_path)
|
26
|
+
extracted = extract_xhtmls_from(epub_path)
|
27
|
+
all_extracted_files.concat(extracted) if extracted
|
21
28
|
end
|
29
|
+
all_extracted_files
|
22
30
|
end
|
23
31
|
|
24
32
|
private
|
@@ -29,16 +37,17 @@ module EpubTools
|
|
29
37
|
|
30
38
|
def extract_xhtmls_from(epub_path)
|
31
39
|
epub_name = File.basename(epub_path, '.epub')
|
32
|
-
|
40
|
+
log "Extracting from #{epub_name}.epub"
|
33
41
|
extracted_files = []
|
34
42
|
Zip::File.open(epub_path) do |zip_file|
|
35
43
|
zip_file.each do |entry|
|
36
44
|
next unless entry.name.downcase.end_with?('.xhtml')
|
37
45
|
next if File.basename(entry.name).downcase == 'nav.xhtml'
|
46
|
+
|
38
47
|
output_path = File.join(@target_dir, "#{epub_name}_#{File.basename(entry.name)}")
|
39
48
|
FileUtils.mkdir_p(File.dirname(output_path))
|
40
49
|
entry.extract(output_path) { true }
|
41
|
-
|
50
|
+
log output_path
|
42
51
|
extracted_files << output_path
|
43
52
|
end
|
44
53
|
end
|
data/lib/epub_tools.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require_relative 'epub_tools/version'
|
2
|
+
require_relative 'epub_tools/loggable'
|
2
3
|
require_relative 'epub_tools/add_chapters'
|
3
|
-
require_relative 'epub_tools/cli_helper'
|
4
4
|
require_relative 'epub_tools/epub_initializer'
|
5
5
|
require_relative 'epub_tools/split_chapters'
|
6
6
|
require_relative 'epub_tools/xhtml_cleaner'
|
@@ -8,6 +8,7 @@ require_relative 'epub_tools/xhtml_extractor'
|
|
8
8
|
require_relative 'epub_tools/pack_ebook'
|
9
9
|
require_relative 'epub_tools/unpack_ebook'
|
10
10
|
require_relative 'epub_tools/compile_book'
|
11
|
+
require_relative 'epub_tools/cli'
|
11
12
|
|
12
13
|
# Wrapper for all the other classes
|
13
14
|
module EpubTools
|
data/test/add_chapters_test.rb
CHANGED
@@ -54,12 +54,18 @@ class AddChaptersTest < Minitest::Test
|
|
54
54
|
|
55
55
|
def test_run_moves_files_and_updates_opf_and_nav
|
56
56
|
# Run the add chapters task
|
57
|
-
EpubTools::AddChapters.new(@chapters_dir, @epub_dir).run
|
57
|
+
result = EpubTools::AddChapters.new(chapters_dir: @chapters_dir, oebps_dir: @epub_dir).run
|
58
|
+
|
59
|
+
# Check return value is an array of moved file basenames
|
60
|
+
assert_instance_of Array, result
|
61
|
+
assert_equal 2, result.size
|
62
|
+
assert_includes result, 'chapter_0.xhtml'
|
63
|
+
assert_includes result, 'chapter_1.xhtml'
|
58
64
|
|
59
65
|
# Original chapter files should be moved
|
60
66
|
assert_empty Dir.glob(File.join(@chapters_dir, '*.xhtml'))
|
61
|
-
|
62
|
-
|
67
|
+
assert_path_exists File.join(@epub_dir, 'chapter_0.xhtml')
|
68
|
+
assert_path_exists File.join(@epub_dir, 'chapter_1.xhtml')
|
63
69
|
|
64
70
|
# package.opf should include manifest items and spine refs
|
65
71
|
doc = Nokogiri::XML(File.read(@opf_file)) { |cfg| cfg.default_xml.noblanks }
|
@@ -81,12 +87,13 @@ class AddChaptersTest < Minitest::Test
|
|
81
87
|
# strip namespaces for easy querying
|
82
88
|
nav_doc.remove_namespaces!
|
83
89
|
links = nav_doc.xpath('//nav/ol/li/a')
|
90
|
+
|
84
91
|
assert_equal 2, links.size
|
85
92
|
# First is Prologue (chapter_0)
|
86
93
|
assert_equal 'chapter_0.xhtml', links[0]['href']
|
87
|
-
assert_equal 'Prologue',
|
94
|
+
assert_equal 'Prologue', links[0].text
|
88
95
|
# Second is Chapter 1
|
89
96
|
assert_equal 'chapter_1.xhtml', links[1]['href']
|
90
|
-
assert_equal 'Chapter 1',
|
97
|
+
assert_equal 'Chapter 1', links[1].text
|
91
98
|
end
|
92
99
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require_relative '../../lib/epub_tools'
|
3
|
+
|
4
|
+
class CommandRegistryTest < Minitest::Test
|
5
|
+
class DummyCommand
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup
|
18
|
+
@registry = EpubTools::CLI::CommandRegistry.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_registry_initializes_empty
|
22
|
+
assert_empty @registry.commands
|
23
|
+
assert_empty @registry.available_commands
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_register_command
|
27
|
+
@registry.register('test', DummyCommand, [:required_option], { default: 'value' })
|
28
|
+
|
29
|
+
assert_equal 1, @registry.commands.size
|
30
|
+
assert_includes @registry.available_commands, 'test'
|
31
|
+
|
32
|
+
command = @registry.get('test')
|
33
|
+
|
34
|
+
assert_equal DummyCommand, command[:class]
|
35
|
+
assert_equal [:required_option], command[:required_keys]
|
36
|
+
assert_equal({ default: 'value' }, command[:default_options])
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_register_multiple_commands
|
40
|
+
@registry.register('test1', DummyCommand)
|
41
|
+
@registry.register('test2', DummyCommand)
|
42
|
+
|
43
|
+
assert_equal 2, @registry.commands.size
|
44
|
+
assert_includes @registry.available_commands, 'test1'
|
45
|
+
assert_includes @registry.available_commands, 'test2'
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_get_nonexistent_command
|
49
|
+
assert_nil @registry.get('nonexistent')
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_command_exists
|
53
|
+
@registry.register('test', DummyCommand)
|
54
|
+
|
55
|
+
assert @registry.command_exists?('test')
|
56
|
+
refute @registry.command_exists?('nonexistent')
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_chained_registration
|
60
|
+
result = @registry.register('test1', DummyCommand)
|
61
|
+
.register('test2', DummyCommand)
|
62
|
+
|
63
|
+
assert_equal @registry, result
|
64
|
+
assert_equal 2, @registry.commands.size
|
65
|
+
end
|
66
|
+
end
|