epub_tools 0.5.0 → 0.9.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 -8
- data/.gitignore +0 -1
- data/.tool-versions +1 -1
- data/CLAUDE.md +20 -24
- data/Gemfile +5 -8
- data/Gemfile.lock +60 -54
- data/README.md +65 -25
- data/bin/epub-tools +1 -3
- data/epub_tools.gemspec +3 -4
- data/lib/epub_tools/add_chapters.rb +26 -14
- data/lib/epub_tools/append_book.rb +74 -0
- data/lib/epub_tools/book_builder.rb +114 -0
- data/lib/epub_tools/chapter_order.rb +33 -0
- data/lib/epub_tools/chapter_validator.rb +20 -7
- data/lib/epub_tools/cli.rb +121 -22
- data/lib/epub_tools/compile_book.rb +12 -126
- data/lib/epub_tools/epub_initializer.rb +93 -99
- data/lib/epub_tools/pack_ebook.rb +2 -14
- data/lib/epub_tools/pdf_converter.rb +113 -0
- data/lib/epub_tools/pdf_layout.rb +116 -0
- data/lib/epub_tools/set_cover.rb +116 -0
- data/lib/epub_tools/split_chapters.rb +35 -70
- data/lib/epub_tools/style_finder.rb +11 -40
- data/lib/epub_tools/unpack_ebook.rb +2 -20
- data/lib/epub_tools/version.rb +1 -1
- data/lib/epub_tools/xhtml_cleaner.rb +4 -7
- data/lib/epub_tools/xhtml_extractor.rb +1 -7
- data/lib/epub_tools.rb +4 -0
- data/test/add_chapters_test.rb +134 -0
- data/test/append_book_test.rb +127 -0
- data/test/chapter_validator_test.rb +39 -0
- data/test/cli_commands_test.rb +9 -0
- data/test/cli_test.rb +40 -3
- data/test/compile_book_test.rb +15 -2
- data/test/epub_initializer_test.rb +18 -0
- data/test/pdf_converter_test.rb +69 -0
- data/test/pdf_layout_test.rb +57 -0
- data/test/set_cover_test.rb +68 -0
- data/test/split_chapters_test.rb +84 -9
- data/test/style_finder_test.rb +3 -9
- data/test/test_helper.rb +1 -1
- data/test/unpack_ebook_test.rb +1 -1
- data/test/xhtml_cleaner_test.rb +1 -4
- metadata +17 -34
- data/lib/epub_tools/cli/command_options_configurator.rb +0 -115
- data/lib/epub_tools/cli/command_registry.rb +0 -49
- data/lib/epub_tools/cli/option_builder.rb +0 -166
- data/lib/epub_tools/cli/runner.rb +0 -113
- data/lib/epub_tools/compile_workspace.rb +0 -40
- data/lib/epub_tools/epub_configuration.rb +0 -33
- data/lib/epub_tools/epub_file_writer.rb +0 -57
- data/lib/epub_tools/epub_metadata_builder.rb +0 -92
- data/lib/epub_tools/xhtml_generator.rb +0 -71
- data/test/cli/command_registry_test.rb +0 -68
- data/test/cli/option_builder_test.rb +0 -183
- data/test/cli/runner_test.rb +0 -91
- data/test/compile_workspace_test.rb +0 -55
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'fileutils'
|
|
4
|
-
|
|
5
|
-
module EpubTools
|
|
6
|
-
# Handles writing files for EPUB structure
|
|
7
|
-
class EpubFileWriter
|
|
8
|
-
def initialize(destination)
|
|
9
|
-
@destination = destination
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Creates the basic EPUB directory structure
|
|
13
|
-
def create_structure
|
|
14
|
-
FileUtils.mkdir_p("#{@destination}/META-INF")
|
|
15
|
-
FileUtils.mkdir_p("#{@destination}/OEBPS")
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Writes the mimetype file
|
|
19
|
-
def write_mimetype
|
|
20
|
-
File.write("#{@destination}/mimetype", 'application/epub+zip')
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Writes the container.xml file
|
|
24
|
-
def write_container
|
|
25
|
-
content = <<~XML
|
|
26
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
27
|
-
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
|
28
|
-
<rootfiles>
|
|
29
|
-
<rootfile full-path="OEBPS/package.opf" media-type="application/oebps-package+xml"/>
|
|
30
|
-
</rootfiles>
|
|
31
|
-
</container>
|
|
32
|
-
XML
|
|
33
|
-
File.write("#{@destination}/META-INF/container.xml", content)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Writes XHTML content to a file
|
|
37
|
-
def write_xhtml(filename, content)
|
|
38
|
-
File.write(File.join(@destination, 'OEBPS', filename), content)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Writes the package.opf file
|
|
42
|
-
def write_package_opf(content)
|
|
43
|
-
File.write(File.join(@destination, 'OEBPS', 'package.opf'), content)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Copies the project style.css to EPUB structure
|
|
47
|
-
def write_style
|
|
48
|
-
src = File.join(Dir.pwd, 'style.css')
|
|
49
|
-
dest = File.join(@destination, 'OEBPS', 'style.css')
|
|
50
|
-
unless File.exist?(src)
|
|
51
|
-
warn "Warning: style.css not found in project root (#{src}), skipping copy."
|
|
52
|
-
return
|
|
53
|
-
end
|
|
54
|
-
FileUtils.cp(src, dest)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module EpubTools
|
|
4
|
-
# Builds metadata content for EPUB package.opf files
|
|
5
|
-
class EpubMetadataBuilder
|
|
6
|
-
def initialize(config)
|
|
7
|
-
@config = config
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
# Builds complete metadata array
|
|
11
|
-
def build_metadata
|
|
12
|
-
metadata = []
|
|
13
|
-
add_dublin_core_metadata(metadata)
|
|
14
|
-
add_schema_metadata(metadata)
|
|
15
|
-
add_cover_metadata(metadata) if @config.cover_image_fname
|
|
16
|
-
metadata
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Builds manifest and spine items
|
|
20
|
-
def build_manifest_and_spine
|
|
21
|
-
manifest_items = []
|
|
22
|
-
spine_items = []
|
|
23
|
-
|
|
24
|
-
add_base_manifest_items(manifest_items)
|
|
25
|
-
add_cover_items(manifest_items, spine_items) if @config.cover_image_fname
|
|
26
|
-
add_title_items(manifest_items, spine_items)
|
|
27
|
-
|
|
28
|
-
[manifest_items, spine_items]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Builds complete OPF XML content
|
|
32
|
-
def build_opf_xml(metadata, manifest_items, spine_items)
|
|
33
|
-
<<~XML
|
|
34
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
35
|
-
<package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="pub-id" xml:lang="en">
|
|
36
|
-
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
37
|
-
#{metadata.map { |line| " #{line}" }.join("\n")}
|
|
38
|
-
</metadata>
|
|
39
|
-
<manifest>
|
|
40
|
-
#{manifest_items.map { |line| " #{line}" }.join("\n")}
|
|
41
|
-
</manifest>
|
|
42
|
-
<spine>
|
|
43
|
-
#{spine_items.map { |line| " #{line}" }.join("\n")}
|
|
44
|
-
</spine>
|
|
45
|
-
</package>
|
|
46
|
-
XML
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def add_dublin_core_metadata(metadata)
|
|
52
|
-
metadata << %(<dc:identifier id="pub-id">#{@config.uuid}</dc:identifier>)
|
|
53
|
-
metadata << %(<dc:title>#{@config.title}</dc:title>)
|
|
54
|
-
metadata << %(<dc:creator>#{@config.author}</dc:creator>)
|
|
55
|
-
metadata << '<dc:language>en</dc:language>'
|
|
56
|
-
metadata << %(<meta property="dcterms:modified">#{@config.modified}</meta>)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def add_schema_metadata(metadata)
|
|
60
|
-
metadata << %(<meta property="schema:accessMode">textual</meta>)
|
|
61
|
-
metadata << %(<meta property="schema:accessibilityFeature">unknown</meta>)
|
|
62
|
-
metadata << %(<meta property="schema:accessibilityHazard">none</meta>)
|
|
63
|
-
metadata << %(<meta property="schema:accessModeSufficient">textual</meta>)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def add_cover_metadata(metadata)
|
|
67
|
-
metadata << %(<meta name="cover" content="cover-image"/>)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def add_base_manifest_items(manifest_items)
|
|
71
|
-
manifest_items << mitem('style', 'style.css', 'text/css')
|
|
72
|
-
manifest_items << mitem('nav', 'nav.xhtml', 'application/xhtml+xml', 'nav')
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def add_cover_items(manifest_items, spine_items)
|
|
76
|
-
manifest_items << mitem('cover-image', @config.cover_image_fname, @config.cover_image_media_type, 'cover-image')
|
|
77
|
-
manifest_items << mitem('cover-page', 'cover.xhtml', 'application/xhtml+xml')
|
|
78
|
-
spine_items << '<itemref idref="cover-page"/>'
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def add_title_items(manifest_items, spine_items)
|
|
82
|
-
manifest_items << mitem('title', 'title.xhtml', 'application/xhtml+xml')
|
|
83
|
-
spine_items << '<itemref idref="title"/>'
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def mitem(id, href, type, properties = nil)
|
|
87
|
-
xml = "<item id=\"#{id}\" href=\"#{href}\" media-type=\"#{type}\""
|
|
88
|
-
xml += " properties=\"#{properties}\"" if properties
|
|
89
|
-
"#{xml}/>"
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
@@ -1,71 +0,0 @@
|
|
|
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
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../test_helper'
|
|
4
|
-
require_relative '../../lib/epub_tools'
|
|
5
|
-
|
|
6
|
-
class CommandRegistryTest < Minitest::Test
|
|
7
|
-
class DummyCommand
|
|
8
|
-
attr_reader :options
|
|
9
|
-
|
|
10
|
-
def initialize(options)
|
|
11
|
-
@options = options
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def run
|
|
15
|
-
true
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def setup
|
|
20
|
-
@registry = EpubTools::CLI::CommandRegistry.new
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def test_registry_initializes_empty
|
|
24
|
-
assert_empty @registry.commands
|
|
25
|
-
assert_empty @registry.available_commands
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def test_register_command
|
|
29
|
-
@registry.register('test', DummyCommand, [:required_option], { default: 'value' })
|
|
30
|
-
|
|
31
|
-
assert_equal 1, @registry.commands.size
|
|
32
|
-
assert_includes @registry.available_commands, 'test'
|
|
33
|
-
|
|
34
|
-
command = @registry.get('test')
|
|
35
|
-
|
|
36
|
-
assert_equal DummyCommand, command[:class]
|
|
37
|
-
assert_equal [:required_option], command[:required_keys]
|
|
38
|
-
assert_equal({ default: 'value' }, command[:default_options])
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def test_register_multiple_commands
|
|
42
|
-
@registry.register('test1', DummyCommand)
|
|
43
|
-
@registry.register('test2', DummyCommand)
|
|
44
|
-
|
|
45
|
-
assert_equal 2, @registry.commands.size
|
|
46
|
-
assert_includes @registry.available_commands, 'test1'
|
|
47
|
-
assert_includes @registry.available_commands, 'test2'
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def test_get_nonexistent_command
|
|
51
|
-
assert_nil @registry.get('nonexistent')
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def test_command_exists
|
|
55
|
-
@registry.register('test', DummyCommand)
|
|
56
|
-
|
|
57
|
-
assert @registry.command_exists?('test')
|
|
58
|
-
refute @registry.command_exists?('nonexistent')
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def test_chained_registration
|
|
62
|
-
result = @registry.register('test1', DummyCommand)
|
|
63
|
-
.register('test2', DummyCommand)
|
|
64
|
-
|
|
65
|
-
assert_equal @registry, result
|
|
66
|
-
assert_equal 2, @registry.commands.size
|
|
67
|
-
end
|
|
68
|
-
end
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../test_helper'
|
|
4
|
-
require_relative '../../lib/epub_tools'
|
|
5
|
-
require 'stringio'
|
|
6
|
-
|
|
7
|
-
class OptionBuilderTest < Minitest::Test
|
|
8
|
-
def setup
|
|
9
|
-
@builder = EpubTools::CLI::OptionBuilder.new
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def test_initialize_with_defaults
|
|
13
|
-
builder = EpubTools::CLI::OptionBuilder.new({ default: 'value' }, [:required])
|
|
14
|
-
|
|
15
|
-
assert_equal({ default: 'value' }, builder.options)
|
|
16
|
-
assert_equal [:required], builder.required_keys
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def test_with_banner
|
|
20
|
-
@builder.with_banner('Test Banner')
|
|
21
|
-
|
|
22
|
-
assert_equal 'Test Banner', @builder.parser.banner
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def test_with_help_option
|
|
26
|
-
# This test is tricky as --help would exit, so we'll test indirectly
|
|
27
|
-
@builder.with_help_option
|
|
28
|
-
|
|
29
|
-
assert_includes @builder.parser.to_s, '-h, --help'
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test_with_verbose_option
|
|
33
|
-
@builder.with_verbose_option
|
|
34
|
-
|
|
35
|
-
assert @builder.options[:verbose]
|
|
36
|
-
|
|
37
|
-
# Parse args with --quiet to change verbose to false
|
|
38
|
-
@builder.parse(['--quiet'])
|
|
39
|
-
|
|
40
|
-
refute @builder.options[:verbose]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def test_with_input_file
|
|
44
|
-
@builder.with_input_file('Test input')
|
|
45
|
-
|
|
46
|
-
verify_parser_option_format('-i, --input-file FILE', 'Test input (required)')
|
|
47
|
-
|
|
48
|
-
builder2 = EpubTools::CLI::OptionBuilder.new
|
|
49
|
-
builder2.with_input_file('Test input', required: false)
|
|
50
|
-
|
|
51
|
-
verify_optional_parser_format(builder2, 'Test input')
|
|
52
|
-
|
|
53
|
-
@builder.parse(['-i', 'file.txt'])
|
|
54
|
-
|
|
55
|
-
assert_equal 'file.txt', @builder.options[:input_file]
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def test_with_input_dir
|
|
59
|
-
@builder.with_input_dir('Test input dir')
|
|
60
|
-
|
|
61
|
-
assert_includes @builder.parser.to_s, '-i, --input-dir DIR'
|
|
62
|
-
assert_includes @builder.parser.to_s, 'Test input dir (required)'
|
|
63
|
-
|
|
64
|
-
@builder.parse(['-i', 'dir/path'])
|
|
65
|
-
|
|
66
|
-
assert_equal 'dir/path', @builder.options[:input_dir]
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def test_with_output_dir
|
|
70
|
-
@builder.with_output_dir('Test output dir', 'default/path')
|
|
71
|
-
|
|
72
|
-
verify_parser_option_format('-o, --output-dir DIR', 'Test output dir (default: default/path)')
|
|
73
|
-
|
|
74
|
-
assert_equal 'default/path', @builder.options[:output_dir]
|
|
75
|
-
|
|
76
|
-
builder2 = EpubTools::CLI::OptionBuilder.new
|
|
77
|
-
builder2.with_output_dir('Test output dir')
|
|
78
|
-
verify_required_parser_format(builder2, 'Test output dir (required)')
|
|
79
|
-
|
|
80
|
-
@builder.parse(['-o', 'new/path'])
|
|
81
|
-
|
|
82
|
-
assert_equal 'new/path', @builder.options[:output_dir]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def test_with_output_file
|
|
86
|
-
@builder.with_output_file('Test output file')
|
|
87
|
-
|
|
88
|
-
assert_includes @builder.parser.to_s, '-o, --output-file FILE'
|
|
89
|
-
assert_includes @builder.parser.to_s, 'Test output file (required)'
|
|
90
|
-
|
|
91
|
-
@builder.parse(['-o', 'output.txt'])
|
|
92
|
-
|
|
93
|
-
assert_equal 'output.txt', @builder.options[:output_file]
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def test_with_title_option
|
|
97
|
-
@builder.with_title_option
|
|
98
|
-
|
|
99
|
-
assert_includes @builder.parser.to_s, '-t, --title TITLE'
|
|
100
|
-
assert_includes @builder.parser.to_s, 'Book title (required)'
|
|
101
|
-
|
|
102
|
-
@builder.parse(['-t', 'Book Title'])
|
|
103
|
-
|
|
104
|
-
assert_equal 'Book Title', @builder.options[:title]
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def test_with_author_option
|
|
108
|
-
@builder.with_author_option
|
|
109
|
-
|
|
110
|
-
assert_includes @builder.parser.to_s, '-a, --author AUTHOR'
|
|
111
|
-
assert_includes @builder.parser.to_s, 'Author name (required)'
|
|
112
|
-
|
|
113
|
-
@builder.parse(['-a', 'Author Name'])
|
|
114
|
-
|
|
115
|
-
assert_equal 'Author Name', @builder.options[:author]
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def test_with_cover_option
|
|
119
|
-
@builder.with_cover_option
|
|
120
|
-
|
|
121
|
-
assert_includes @builder.parser.to_s, '-c, --cover PATH'
|
|
122
|
-
assert_includes @builder.parser.to_s, 'Cover image file path (optional)'
|
|
123
|
-
|
|
124
|
-
@builder.parse(['-c', 'cover.jpg'])
|
|
125
|
-
|
|
126
|
-
assert_equal 'cover.jpg', @builder.options[:cover_image]
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def test_with_option
|
|
130
|
-
@builder.with_option('-x', '--extra VALUE', 'Extra option', :extra_option)
|
|
131
|
-
|
|
132
|
-
assert_includes @builder.parser.to_s, '-x, --extra VALUE'
|
|
133
|
-
assert_includes @builder.parser.to_s, 'Extra option'
|
|
134
|
-
|
|
135
|
-
@builder.parse(['-x', 'extra-value'])
|
|
136
|
-
|
|
137
|
-
assert_equal 'extra-value', @builder.options[:extra_option]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def test_with_custom_options
|
|
141
|
-
@builder.with_custom_options do |opts, options|
|
|
142
|
-
opts.on('-c', '--custom VALUE', 'Custom option') { |v| options[:custom] = v }
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
assert_includes @builder.parser.to_s, '-c, --custom VALUE'
|
|
146
|
-
assert_includes @builder.parser.to_s, 'Custom option'
|
|
147
|
-
|
|
148
|
-
@builder.parse(['-c', 'custom-value'])
|
|
149
|
-
|
|
150
|
-
assert_equal 'custom-value', @builder.options[:custom]
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def test_parse_validates_required_keys
|
|
154
|
-
builder = EpubTools::CLI::OptionBuilder.new({}, [:required_option])
|
|
155
|
-
assert_output('', /Missing required options/) do
|
|
156
|
-
assert_raises(SystemExit) { builder.parse([]) }
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def test_method_chaining
|
|
161
|
-
result = @builder.with_banner('Test')
|
|
162
|
-
.with_verbose_option
|
|
163
|
-
.with_title_option
|
|
164
|
-
|
|
165
|
-
assert_equal @builder, result
|
|
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
|
|
183
|
-
end
|
data/test/cli/runner_test.rb
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../test_helper'
|
|
4
|
-
require_relative '../../lib/epub_tools'
|
|
5
|
-
require 'stringio'
|
|
6
|
-
|
|
7
|
-
class RunnerTest < Minitest::Test
|
|
8
|
-
class TestCommand
|
|
9
|
-
attr_reader :options, :run_called
|
|
10
|
-
|
|
11
|
-
def initialize(options)
|
|
12
|
-
@options = options
|
|
13
|
-
@run_called = false
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def run
|
|
17
|
-
@run_called = true
|
|
18
|
-
puts 'Called!'
|
|
19
|
-
true
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def setup
|
|
24
|
-
@runner = EpubTools::CLI::Runner.new('test-program')
|
|
25
|
-
|
|
26
|
-
# Register a test command
|
|
27
|
-
@runner.registry.register('test-cmd', TestCommand,
|
|
28
|
-
[:required_option],
|
|
29
|
-
{ default_option: 'default' })
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test_initialize
|
|
33
|
-
assert_equal 'test-program', @runner.program_name
|
|
34
|
-
assert_kind_of EpubTools::CLI::CommandRegistry, @runner.registry
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def test_handle_command
|
|
38
|
-
@runner.registry.register('add', TestCommand)
|
|
39
|
-
|
|
40
|
-
assert_output(/Usage: test-program add/) do
|
|
41
|
-
assert_raises(SystemExit) { @runner.handle_command('add', ['-h']) }
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def test_handle_command_with_required_args
|
|
46
|
-
runner = EpubTools::CLI::Runner.new('test-program')
|
|
47
|
-
runner.registry.register('add', TestCommand)
|
|
48
|
-
|
|
49
|
-
assert_output("Called!\n") { assert runner.handle_command('add') }
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def test_handle_nonexistent_command
|
|
53
|
-
result = @runner.handle_command('nonexistent')
|
|
54
|
-
|
|
55
|
-
refute result # Should return false for nonexistent command
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def test_run_with_version_flag
|
|
59
|
-
assert_output(/^\d+\.\d+\.\d+$/) do
|
|
60
|
-
assert_raises(SystemExit) { @runner.run(['-v']) }
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def test_run_with_no_args_shows_usage
|
|
65
|
-
assert_output(/Usage: test-program COMMAND \[options\]/) do
|
|
66
|
-
assert_raises(SystemExit) { @runner.run([]) }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def test_program_name_defaults_to_current_program
|
|
71
|
-
default_runner = EpubTools::CLI::Runner.new
|
|
72
|
-
|
|
73
|
-
assert_equal File.basename($PROGRAM_NAME), default_runner.program_name
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def test_configure_command_options
|
|
77
|
-
# Test command configuration through public interface
|
|
78
|
-
@runner.registry.register('add', TestCommand)
|
|
79
|
-
|
|
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
|
-
end
|
|
85
|
-
|
|
86
|
-
def test_run_with_unknown_command
|
|
87
|
-
assert_output(/Usage: test-program COMMAND \[options\]/) do
|
|
88
|
-
assert_raises(SystemExit) { @runner.run(['unknown-command']) }
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|