epub_tools 0.6.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 +17 -25
- data/Gemfile +5 -8
- data/Gemfile.lock +60 -54
- data/README.md +31 -4
- data/bin/epub-tools +1 -3
- data/epub_tools.gemspec +3 -4
- data/lib/epub_tools/add_chapters.rb +17 -18
- data/lib/epub_tools/append_book.rb +8 -15
- data/lib/epub_tools/book_builder.rb +28 -22
- data/lib/epub_tools/chapter_order.rb +33 -0
- data/lib/epub_tools/chapter_validator.rb +6 -3
- data/lib/epub_tools/cli.rb +121 -23
- data/lib/epub_tools/compile_book.rb +1 -1
- 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 +22 -20
- 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 +2 -1
- data/test/add_chapters_test.rb +64 -0
- data/test/append_book_test.rb +11 -11
- data/test/chapter_validator_test.rb +12 -0
- data/test/cli_test.rb +40 -3
- data/test/compile_book_test.rb +14 -0
- 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 +18 -12
- 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 +14 -35
- data/lib/epub_tools/chapter_marker_detector.rb +0 -46
- data/lib/epub_tools/cli/command_options_configurator.rb +0 -128
- 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 -114
- 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
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: epub_tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jaime Rodas
|
|
@@ -15,42 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
18
|
+
version: '1.19'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '1.
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: rake
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '13.2'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '13.2'
|
|
25
|
+
version: '1.19'
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
27
|
name: rubyzip
|
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
|
43
29
|
requirements:
|
|
44
30
|
- - "~>"
|
|
45
31
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '3.
|
|
32
|
+
version: '3.7'
|
|
47
33
|
type: :runtime
|
|
48
34
|
prerelease: false
|
|
49
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
36
|
requirements:
|
|
51
37
|
- - "~>"
|
|
52
38
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '3.
|
|
39
|
+
version: '3.7'
|
|
54
40
|
email:
|
|
55
41
|
- rodas@hey.com
|
|
56
42
|
executables:
|
|
@@ -76,42 +62,35 @@ files:
|
|
|
76
62
|
- lib/epub_tools/add_chapters.rb
|
|
77
63
|
- lib/epub_tools/append_book.rb
|
|
78
64
|
- lib/epub_tools/book_builder.rb
|
|
79
|
-
- lib/epub_tools/
|
|
65
|
+
- lib/epub_tools/chapter_order.rb
|
|
80
66
|
- lib/epub_tools/chapter_validator.rb
|
|
81
67
|
- lib/epub_tools/cli.rb
|
|
82
|
-
- lib/epub_tools/cli/command_options_configurator.rb
|
|
83
|
-
- lib/epub_tools/cli/command_registry.rb
|
|
84
|
-
- lib/epub_tools/cli/option_builder.rb
|
|
85
|
-
- lib/epub_tools/cli/runner.rb
|
|
86
68
|
- lib/epub_tools/compile_book.rb
|
|
87
|
-
- lib/epub_tools/compile_workspace.rb
|
|
88
|
-
- lib/epub_tools/epub_configuration.rb
|
|
89
|
-
- lib/epub_tools/epub_file_writer.rb
|
|
90
69
|
- lib/epub_tools/epub_initializer.rb
|
|
91
|
-
- lib/epub_tools/epub_metadata_builder.rb
|
|
92
70
|
- lib/epub_tools/loggable.rb
|
|
93
71
|
- lib/epub_tools/pack_ebook.rb
|
|
72
|
+
- lib/epub_tools/pdf_converter.rb
|
|
73
|
+
- lib/epub_tools/pdf_layout.rb
|
|
74
|
+
- lib/epub_tools/set_cover.rb
|
|
94
75
|
- lib/epub_tools/split_chapters.rb
|
|
95
76
|
- lib/epub_tools/style_finder.rb
|
|
96
77
|
- lib/epub_tools/unpack_ebook.rb
|
|
97
78
|
- lib/epub_tools/version.rb
|
|
98
79
|
- lib/epub_tools/xhtml_cleaner.rb
|
|
99
80
|
- lib/epub_tools/xhtml_extractor.rb
|
|
100
|
-
- lib/epub_tools/xhtml_generator.rb
|
|
101
81
|
- style.css
|
|
102
82
|
- test/add_chapters_test.rb
|
|
103
83
|
- test/append_book_test.rb
|
|
104
84
|
- test/chapter_validator_test.rb
|
|
105
|
-
- test/cli/command_registry_test.rb
|
|
106
|
-
- test/cli/option_builder_test.rb
|
|
107
|
-
- test/cli/runner_test.rb
|
|
108
85
|
- test/cli_commands_test.rb
|
|
109
86
|
- test/cli_test.rb
|
|
110
87
|
- test/cli_version_test.rb
|
|
111
88
|
- test/compile_book_test.rb
|
|
112
|
-
- test/compile_workspace_test.rb
|
|
113
89
|
- test/epub_initializer_test.rb
|
|
114
90
|
- test/pack_ebook_test.rb
|
|
91
|
+
- test/pdf_converter_test.rb
|
|
92
|
+
- test/pdf_layout_test.rb
|
|
93
|
+
- test/set_cover_test.rb
|
|
115
94
|
- test/split_chapters_test.rb
|
|
116
95
|
- test/style_finder_test.rb
|
|
117
96
|
- test/test_helper.rb
|
|
@@ -132,14 +111,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
132
111
|
requirements:
|
|
133
112
|
- - ">="
|
|
134
113
|
- !ruby/object:Gem::Version
|
|
135
|
-
version: '
|
|
114
|
+
version: '4.0'
|
|
136
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
116
|
requirements:
|
|
138
117
|
- - ">="
|
|
139
118
|
- !ruby/object:Gem::Version
|
|
140
119
|
version: '0'
|
|
141
120
|
requirements: []
|
|
142
|
-
rubygems_version:
|
|
121
|
+
rubygems_version: 4.0.23
|
|
143
122
|
specification_version: 4
|
|
144
123
|
summary: Tools to extract, split, and compile EPUB books
|
|
145
124
|
test_files: []
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module EpubTools
|
|
4
|
-
# Detects chapter boundary markers in XHTML nodes.
|
|
5
|
-
# Recognizes: "Chapter N", "Chapter N (continued)", and "Prologue".
|
|
6
|
-
class ChapterMarkerDetector
|
|
7
|
-
# Tags that can contain chapter markers
|
|
8
|
-
MARKER_TAGS = %w[p span h2 h3 h4].freeze
|
|
9
|
-
# Tags that can contain prologue markers
|
|
10
|
-
PROLOGUE_TAGS = %w[h3 h4].freeze
|
|
11
|
-
|
|
12
|
-
# Detect what type of chapter marker a node represents
|
|
13
|
-
# @param node [Nokogiri::XML::Node] The XHTML node to check
|
|
14
|
-
# @return [Symbol, nil] :chapter, :continued, :prologue, or nil
|
|
15
|
-
def detect(node)
|
|
16
|
-
if continued_marker?(node)
|
|
17
|
-
:continued
|
|
18
|
-
elsif chapter_marker?(node)
|
|
19
|
-
:chapter
|
|
20
|
-
elsif prologue_marker?(node)
|
|
21
|
-
:prologue
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Extract the chapter number from a node's text
|
|
26
|
-
# @param node [Nokogiri::XML::Node] A node containing "Chapter N" text
|
|
27
|
-
# @return [Integer] The chapter number
|
|
28
|
-
def extract_chapter_number(node)
|
|
29
|
-
node.text.match(/Chapter\s+(\d+)/i)[1].to_i
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def continued_marker?(node)
|
|
35
|
-
MARKER_TAGS.include?(node.name) && node.text.match?(/Chapter\s+\d+\s*\(continued\)/i)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def chapter_marker?(node)
|
|
39
|
-
MARKER_TAGS.include?(node.name) && node.text.match?(/Chapter\s+\d+/i) && !continued_marker?(node)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def prologue_marker?(node)
|
|
43
|
-
PROLOGUE_TAGS.include?(node.name) && node.text.strip.match?(/\APrologue\z/i)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module EpubTools
|
|
4
|
-
module CLI
|
|
5
|
-
# Handles command-specific option configuration for CLI commands
|
|
6
|
-
class CommandOptionsConfigurator
|
|
7
|
-
# Configure command-specific options using dynamic dispatch
|
|
8
|
-
# @param cmd [String] Command name
|
|
9
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
10
|
-
def configure(cmd, builder)
|
|
11
|
-
method_name = "configure_#{cmd.tr('-', '_')}_options"
|
|
12
|
-
raise ArgumentError, "Unknown command: #{cmd}" unless respond_to?(method_name, true)
|
|
13
|
-
|
|
14
|
-
send(method_name, builder)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
# Configure options for the 'add' command
|
|
20
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
21
|
-
def configure_add_options(builder)
|
|
22
|
-
builder.with_custom_options do |opts, options|
|
|
23
|
-
opts.on('-c DIR', '--chapters-dir DIR', 'Chapters directory (required)') { |v| options[:chapters_dir] = v }
|
|
24
|
-
opts.on('-e DIR', '--oebps-dir DIR', 'EPUB OEBPS directory (required)') { |v| options[:oebps_dir] = v }
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Configure options for the 'extract' command
|
|
29
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
30
|
-
def configure_extract_options(builder)
|
|
31
|
-
builder.with_custom_options do |opts, options|
|
|
32
|
-
opts.on('-s DIR', '--source-dir DIR', 'Directory with EPUBs to extract XHTMLs from (required)') do |v|
|
|
33
|
-
options[:source_dir] = v
|
|
34
|
-
end
|
|
35
|
-
opts.on('-t DIR', '--target-dir DIR',
|
|
36
|
-
'Directory where the XHTML files will be extracted to (required)') do |v|
|
|
37
|
-
options[:target_dir] = v
|
|
38
|
-
end
|
|
39
|
-
end.with_verbose_option
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Configure options for the 'split' command
|
|
43
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
44
|
-
def configure_split_options(builder)
|
|
45
|
-
builder.with_custom_options do |opts, options|
|
|
46
|
-
add_split_input_options(opts, options)
|
|
47
|
-
add_split_output_options(opts, options)
|
|
48
|
-
end.with_verbose_option
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def add_split_input_options(opts, options)
|
|
52
|
-
opts.on('-i FILE', '--input FILE', 'Source XHTML file (required)') { |v| options[:input_file] = v }
|
|
53
|
-
opts.on('-t TITLE', '--title TITLE', 'Book title for HTML <title> tags (required)') do |v|
|
|
54
|
-
options[:book_title] = v
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def add_split_output_options(opts, options)
|
|
59
|
-
opts.on('-o DIR', '--output-dir DIR',
|
|
60
|
-
"Output directory for chapter files (default: #{options[:output_dir]})") do |v|
|
|
61
|
-
options[:output_dir] = v
|
|
62
|
-
end
|
|
63
|
-
opts.on('-p PREFIX', '--prefix PREFIX', "Filename prefix for chapters (default: #{options[:prefix]})") do |v|
|
|
64
|
-
options[:prefix] = v
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Configure options for the 'init' command
|
|
69
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
70
|
-
def configure_init_options(builder)
|
|
71
|
-
builder.with_title_option
|
|
72
|
-
.with_author_option
|
|
73
|
-
.with_custom_options do |opts, options|
|
|
74
|
-
opts.on('-o DIR', '--output-dir DIR', 'Destination EPUB directory (required)') do |v|
|
|
75
|
-
options[:destination] = v
|
|
76
|
-
end
|
|
77
|
-
end.with_cover_option
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Configure options for the 'pack' command
|
|
81
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
82
|
-
def configure_pack_options(builder)
|
|
83
|
-
builder.with_input_dir('EPUB directory to package')
|
|
84
|
-
.with_output_file('Output EPUB file path')
|
|
85
|
-
.with_verbose_option
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Configure options for the 'unpack' command
|
|
89
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
90
|
-
def configure_unpack_options(builder)
|
|
91
|
-
builder.with_custom_options do |opts, options|
|
|
92
|
-
opts.on('-i FILE', '--input-file FILE', 'EPUB file to unpack (required)') { |v| options[:epub_file] = v }
|
|
93
|
-
opts.on('-o DIR', '--output-dir DIR', 'Output directory to extract into (default: basename of epub)') do |v|
|
|
94
|
-
options[:output_dir] = v
|
|
95
|
-
end
|
|
96
|
-
end.with_verbose_option
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Configure options for the 'append' command
|
|
100
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
101
|
-
def configure_append_options(builder)
|
|
102
|
-
builder.with_custom_options do |opts, options|
|
|
103
|
-
opts.on('-s DIR', '--source-dir DIR', 'Directory with EPUBs to append (required)') do |v|
|
|
104
|
-
options[:source_dir] = v
|
|
105
|
-
end
|
|
106
|
-
opts.on('-t FILE', '--target-epub FILE', 'Existing EPUB file to append to (required)') do |v|
|
|
107
|
-
options[:target_epub] = v
|
|
108
|
-
end
|
|
109
|
-
end.with_verbose_option
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Configure options for the 'compile' command
|
|
113
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
114
|
-
def configure_compile_options(builder)
|
|
115
|
-
builder.with_title_option
|
|
116
|
-
.with_author_option
|
|
117
|
-
.with_custom_options do |opts, options|
|
|
118
|
-
opts.on('-s DIR', '--source-dir DIR', 'Directory with EPUBs to extract XHTMLs from (required)') do |v|
|
|
119
|
-
options[:source_dir] = v
|
|
120
|
-
end
|
|
121
|
-
opts.on('-o FILE', '--output FILE', 'EPUB to create (default: book title in source dir)') do |v|
|
|
122
|
-
options[:output_file] = v
|
|
123
|
-
end
|
|
124
|
-
end.with_cover_option.with_verbose_option
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module EpubTools
|
|
4
|
-
module CLI
|
|
5
|
-
# Manages the registration and retrieval of commands
|
|
6
|
-
class CommandRegistry
|
|
7
|
-
attr_reader :commands
|
|
8
|
-
|
|
9
|
-
def initialize
|
|
10
|
-
@commands = {}
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
# Register a new command in the registry
|
|
14
|
-
# @param name [String] the command name
|
|
15
|
-
# @param command_class [Class] the class that implements the command
|
|
16
|
-
# @param required_keys [Array<Symbol>] keys that must be present in options
|
|
17
|
-
# @param default_options [Hash] default options for the command
|
|
18
|
-
# @return [self]
|
|
19
|
-
def register(name, command_class, required_keys = [], default_options = {})
|
|
20
|
-
@commands[name] = {
|
|
21
|
-
class: command_class,
|
|
22
|
-
required_keys: required_keys,
|
|
23
|
-
default_options: default_options
|
|
24
|
-
}
|
|
25
|
-
self
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Get a command by name
|
|
29
|
-
# @param name [String] the command name
|
|
30
|
-
# @return [Hash, nil] the command configuration or nil if not found
|
|
31
|
-
def get(name)
|
|
32
|
-
@commands[name]
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Get all available command names
|
|
36
|
-
# @return [Array<String>] list of registered command names
|
|
37
|
-
def available_commands
|
|
38
|
-
@commands.keys
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Check if a command is registered
|
|
42
|
-
# @param name [String] the command name
|
|
43
|
-
# @return [Boolean] true if command exists
|
|
44
|
-
def command_exists?(name)
|
|
45
|
-
@commands.key?(name)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'optparse'
|
|
4
|
-
|
|
5
|
-
module EpubTools
|
|
6
|
-
module CLI
|
|
7
|
-
# Builds and manages command line options
|
|
8
|
-
class OptionBuilder
|
|
9
|
-
attr_reader :options, :required_keys, :parser
|
|
10
|
-
|
|
11
|
-
# Initialize a new OptionBuilder
|
|
12
|
-
# @param default_options [Hash] Default options to start with
|
|
13
|
-
# @param required_keys [Array<Symbol>] Keys that must be present in the final options
|
|
14
|
-
def initialize(default_options = {}, required_keys = [])
|
|
15
|
-
@options = default_options.dup
|
|
16
|
-
@required_keys = required_keys
|
|
17
|
-
@parser = OptionParser.new
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Add banner to the option parser
|
|
21
|
-
# @param text [String] Banner text
|
|
22
|
-
# @return [self] for method chaining
|
|
23
|
-
def with_banner(text)
|
|
24
|
-
@parser.banner = text
|
|
25
|
-
self
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Add help option to the parser
|
|
29
|
-
# @return [self] for method chaining
|
|
30
|
-
def with_help_option
|
|
31
|
-
@parser.on('-h', '--help', 'Print this help') do
|
|
32
|
-
puts @parser
|
|
33
|
-
exit
|
|
34
|
-
end
|
|
35
|
-
self
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Add verbose option to the parser
|
|
39
|
-
# @return [self] for method chaining
|
|
40
|
-
def with_verbose_option
|
|
41
|
-
@options[:verbose] = true unless @options.key?(:verbose)
|
|
42
|
-
@parser.on('-q', '--quiet', 'Run quietly (default: verbose)') { |v| @options[:verbose] = !v }
|
|
43
|
-
self
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Add input file option to the parser
|
|
47
|
-
# @param description [String] Option description
|
|
48
|
-
# @param required [Boolean] Whether this option is required
|
|
49
|
-
# @return [self] for method chaining
|
|
50
|
-
def with_input_file(description = 'Input file', required: true)
|
|
51
|
-
desc = required ? "#{description} (required)" : description
|
|
52
|
-
@parser.on('-i FILE', '--input-file FILE', desc) { |v| @options[:input_file] = v }
|
|
53
|
-
self
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Add input directory option to the parser
|
|
57
|
-
# @param description [String] Option description
|
|
58
|
-
# @param required [Boolean] Whether this option is required
|
|
59
|
-
# @return [self] for method chaining
|
|
60
|
-
def with_input_dir(description = 'Input directory', required: true)
|
|
61
|
-
desc = required ? "#{description} (required)" : description
|
|
62
|
-
@parser.on('-i DIR', '--input-dir DIR', desc) { |v| @options[:input_dir] = v }
|
|
63
|
-
self
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Add output directory option to the parser
|
|
67
|
-
# @param description [String] Option description
|
|
68
|
-
# @param default [String, nil] Default value
|
|
69
|
-
# @return [self] for method chaining
|
|
70
|
-
def with_output_dir(description = 'Output directory', default = nil)
|
|
71
|
-
if default
|
|
72
|
-
desc = "#{description} (default: #{default})"
|
|
73
|
-
@options[:output_dir] = default unless @options.key?(:output_dir)
|
|
74
|
-
else
|
|
75
|
-
desc = "#{description} (required)"
|
|
76
|
-
end
|
|
77
|
-
@parser.on('-o DIR', '--output-dir DIR', desc) { |v| @options[:output_dir] = v }
|
|
78
|
-
self
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Add output file option to the parser
|
|
82
|
-
# @param description [String] Option description
|
|
83
|
-
# @param required [Boolean] Whether this option is required
|
|
84
|
-
# @return [self] for method chaining
|
|
85
|
-
def with_output_file(description = 'Output file', required: true)
|
|
86
|
-
desc = required ? "#{description} (required)" : description
|
|
87
|
-
@parser.on('-o FILE', '--output-file FILE', desc) { |v| @options[:output_file] = v }
|
|
88
|
-
self
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Add title option to the parser
|
|
92
|
-
# @return [self] for method chaining
|
|
93
|
-
def with_title_option
|
|
94
|
-
@parser.on('-t TITLE', '--title TITLE', 'Book title (required)') { |v| @options[:title] = v }
|
|
95
|
-
self
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Add author option to the parser
|
|
99
|
-
# @return [self] for method chaining
|
|
100
|
-
def with_author_option
|
|
101
|
-
@parser.on('-a AUTHOR', '--author AUTHOR', 'Author name (required)') { |v| @options[:author] = v }
|
|
102
|
-
self
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Add cover option to the parser
|
|
106
|
-
# @return [self] for method chaining
|
|
107
|
-
def with_cover_option
|
|
108
|
-
@parser.on('-c PATH', '--cover PATH', 'Cover image file path (optional)') { |v| @options[:cover_image] = v }
|
|
109
|
-
self
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Add a custom option to the parser
|
|
113
|
-
# @param short [String] Short option flag
|
|
114
|
-
# @param long [String] Long option flag
|
|
115
|
-
# @param description [String] Option description
|
|
116
|
-
# @param option_key [Symbol] Key in the options hash
|
|
117
|
-
# @param block [Proc] Optional block for custom processing
|
|
118
|
-
# @return [self] for method chaining
|
|
119
|
-
def with_option(short, long, description, option_key)
|
|
120
|
-
@parser.on(short, long, description) do |v|
|
|
121
|
-
@options[option_key] = block_given? ? yield(v) : v
|
|
122
|
-
end
|
|
123
|
-
self
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Add a custom block to configure options
|
|
127
|
-
# @yield [OptionParser, Hash] Yields the parser and options hash
|
|
128
|
-
# @return [self] for method chaining
|
|
129
|
-
def with_custom_options
|
|
130
|
-
yield @parser, @options if block_given?
|
|
131
|
-
self
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Parse the command line arguments
|
|
135
|
-
# @param args [Array<String>] Command line arguments
|
|
136
|
-
# @return [Hash] Parsed options
|
|
137
|
-
# @raise [SystemExit] If required options are missing
|
|
138
|
-
def parse(args = ARGV)
|
|
139
|
-
begin
|
|
140
|
-
@parser.parse!(args.dup)
|
|
141
|
-
validate_required_keys
|
|
142
|
-
rescue ArgumentError => e
|
|
143
|
-
abort "#{e.message}\n#{@parser}"
|
|
144
|
-
end
|
|
145
|
-
@options
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
private
|
|
149
|
-
|
|
150
|
-
# Validate that all required keys are present in the options
|
|
151
|
-
# @raise [SystemExit] If required options are missing
|
|
152
|
-
def validate_required_keys
|
|
153
|
-
return if @required_keys.empty?
|
|
154
|
-
|
|
155
|
-
missing = @required_keys.select { |k| @options[k].nil? }
|
|
156
|
-
return if missing.empty?
|
|
157
|
-
|
|
158
|
-
raise ArgumentError, "Missing required options: #{missing_keys(missing)}"
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def missing_keys(keys)
|
|
162
|
-
keys.map { |k| "--#{k.to_s.tr('_', '-')}" }.join(', ')
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'command_registry'
|
|
4
|
-
require_relative 'option_builder'
|
|
5
|
-
require_relative 'command_options_configurator'
|
|
6
|
-
|
|
7
|
-
module EpubTools
|
|
8
|
-
module CLI
|
|
9
|
-
# Main runner for the CLI application
|
|
10
|
-
class Runner
|
|
11
|
-
attr_reader :registry, :program_name
|
|
12
|
-
|
|
13
|
-
# Initialize a new CLI Runner
|
|
14
|
-
# @param program_name [String] Name of the program
|
|
15
|
-
def initialize(program_name = nil)
|
|
16
|
-
@registry = CommandRegistry.new
|
|
17
|
-
@program_name = program_name || File.basename($PROGRAM_NAME)
|
|
18
|
-
@options_configurator = CommandOptionsConfigurator.new
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Run the CLI application
|
|
22
|
-
# @param args [Array<String>] Command line arguments
|
|
23
|
-
# @return [Boolean] true if the command was run successfully
|
|
24
|
-
def run(args = ARGV)
|
|
25
|
-
handle_version_flag(args)
|
|
26
|
-
validate_command_args(args)
|
|
27
|
-
|
|
28
|
-
cmd = args.shift
|
|
29
|
-
handle_command(cmd, args)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Handle a specific command
|
|
33
|
-
# @param cmd [String] Command name
|
|
34
|
-
# @param args [Array<String>] Command line arguments
|
|
35
|
-
# @return [Boolean] true if the command was run successfully
|
|
36
|
-
def handle_command(cmd, args = ARGV)
|
|
37
|
-
command_config = registry.get(cmd)
|
|
38
|
-
return false unless command_config
|
|
39
|
-
|
|
40
|
-
builder = build_option_parser(cmd, command_config)
|
|
41
|
-
execute_command(command_config, builder, args)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
# Handle version flag and exit if present
|
|
47
|
-
# @param args [Array<String>] Command line arguments
|
|
48
|
-
def handle_version_flag(args)
|
|
49
|
-
return unless ['-v', '--version'].include?(args[0])
|
|
50
|
-
|
|
51
|
-
puts EpubTools::VERSION
|
|
52
|
-
exit 0
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Validate command arguments and exit if invalid
|
|
56
|
-
# @param args [Array<String>] Command line arguments
|
|
57
|
-
def validate_command_args(args)
|
|
58
|
-
commands = registry.available_commands
|
|
59
|
-
return unless args.empty? || !commands.include?(args[0])
|
|
60
|
-
|
|
61
|
-
print_usage(commands)
|
|
62
|
-
exit 1
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Build option parser for a command
|
|
66
|
-
# @param cmd [String] Command name
|
|
67
|
-
# @param command_config [Hash] Command configuration
|
|
68
|
-
# @return [OptionBuilder] Configured option builder
|
|
69
|
-
def build_option_parser(cmd, command_config)
|
|
70
|
-
options = command_config[:default_options].dup
|
|
71
|
-
required_keys = command_config[:required_keys]
|
|
72
|
-
|
|
73
|
-
builder = OptionBuilder.new(options, required_keys)
|
|
74
|
-
.with_banner("Usage: #{program_name} #{cmd} [options]")
|
|
75
|
-
.with_help_option
|
|
76
|
-
|
|
77
|
-
@options_configurator.configure(cmd, builder)
|
|
78
|
-
builder
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Execute a command with parsed options
|
|
82
|
-
# @param command_config [Hash] Command configuration
|
|
83
|
-
# @param builder [OptionBuilder] Option builder instance
|
|
84
|
-
# @param args [Array<String>] Command line arguments
|
|
85
|
-
# @return [Object] Command instance
|
|
86
|
-
def execute_command(command_config, builder, args)
|
|
87
|
-
options = builder.parse(args)
|
|
88
|
-
command_class = command_config[:class]
|
|
89
|
-
command_instance = command_class.new(options)
|
|
90
|
-
command_instance.run
|
|
91
|
-
command_instance
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Print usage information
|
|
95
|
-
# @param commands [Array<String>] Available commands
|
|
96
|
-
def print_usage(_commands)
|
|
97
|
-
puts "Usage: #{program_name} COMMAND [options]"
|
|
98
|
-
puts 'Commands:'
|
|
99
|
-
print_command_list
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def print_command_list
|
|
103
|
-
puts ' init Initialize a bare-bones EPUB'
|
|
104
|
-
puts ' extract Extract XHTML files from EPUBs'
|
|
105
|
-
puts ' split Split XHTML into separate XHTMLs per chapter'
|
|
106
|
-
puts ' add Add chapter XHTML files into an EPUB'
|
|
107
|
-
puts ' pack Package an EPUB directory into a .epub file'
|
|
108
|
-
puts ' unpack Unpack an EPUB file into a directory'
|
|
109
|
-
puts ' compile Takes EPUBs in a dir and splits, cleans, and compiles into a single EPUB.'
|
|
110
|
-
puts ' append Extracts and splits EPUBs from a dir and appends them to an existing EPUB.'
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'fileutils'
|
|
4
|
-
|
|
5
|
-
module EpubTools
|
|
6
|
-
# Manages the build workspace for book compilation
|
|
7
|
-
class CompileWorkspace
|
|
8
|
-
attr_reader :build_dir
|
|
9
|
-
|
|
10
|
-
def initialize(build_dir)
|
|
11
|
-
@build_dir = build_dir
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Cleans the build directory
|
|
15
|
-
def clean
|
|
16
|
-
FileUtils.rm_rf(@build_dir)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Prepares all necessary directories
|
|
20
|
-
def prepare_directories
|
|
21
|
-
FileUtils.mkdir_p(xhtml_dir)
|
|
22
|
-
FileUtils.mkdir_p(chapters_dir)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Gets the XHTML extraction directory
|
|
26
|
-
def xhtml_dir
|
|
27
|
-
@xhtml_dir ||= File.join(@build_dir, 'xhtml')
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Gets the chapters directory
|
|
31
|
-
def chapters_dir
|
|
32
|
-
@chapters_dir ||= File.join(@build_dir, 'chapters')
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Gets the EPUB build directory
|
|
36
|
-
def epub_dir
|
|
37
|
-
@epub_dir ||= File.join(@build_dir, 'epub')
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|