epub_tools 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +3 -0
- data/.rubocop.yml +10 -17
- data/CLAUDE.md +124 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +39 -34
- data/Rakefile +2 -0
- data/bin/epub-tools +2 -0
- data/epub_tools.gemspec +3 -1
- data/lib/epub_tools/add_chapters.rb +47 -29
- data/lib/epub_tools/chapter_validator.rb +40 -0
- data/lib/epub_tools/cli/command_options_configurator.rb +115 -0
- data/lib/epub_tools/cli/command_registry.rb +2 -0
- data/lib/epub_tools/cli/option_builder.rb +5 -3
- data/lib/epub_tools/cli/runner.rb +59 -110
- data/lib/epub_tools/cli.rb +16 -29
- data/lib/epub_tools/compile_book.rb +48 -65
- data/lib/epub_tools/compile_workspace.rb +40 -0
- data/lib/epub_tools/epub_configuration.rb +33 -0
- data/lib/epub_tools/epub_file_writer.rb +57 -0
- data/lib/epub_tools/epub_initializer.rb +83 -162
- data/lib/epub_tools/epub_metadata_builder.rb +92 -0
- data/lib/epub_tools/loggable.rb +2 -0
- data/lib/epub_tools/pack_ebook.rb +28 -14
- data/lib/epub_tools/split_chapters.rb +42 -17
- data/lib/epub_tools/style_finder.rb +17 -6
- data/lib/epub_tools/unpack_ebook.rb +20 -10
- data/lib/epub_tools/version.rb +3 -1
- data/lib/epub_tools/xhtml_cleaner.rb +1 -0
- data/lib/epub_tools/xhtml_extractor.rb +20 -10
- data/lib/epub_tools/xhtml_generator.rb +71 -0
- data/lib/epub_tools.rb +2 -0
- data/test/add_chapters_test.rb +49 -25
- data/test/chapter_validator_test.rb +47 -0
- data/test/cli/command_registry_test.rb +2 -0
- data/test/cli/option_builder_test.rb +24 -14
- data/test/cli/runner_test.rb +15 -15
- data/test/cli_commands_test.rb +2 -0
- data/test/cli_test.rb +2 -0
- data/test/cli_version_test.rb +2 -0
- data/test/compile_book_test.rb +17 -102
- data/test/compile_workspace_test.rb +55 -0
- data/test/epub_initializer_test.rb +55 -27
- data/test/pack_ebook_test.rb +33 -9
- data/test/split_chapters_test.rb +27 -7
- data/test/style_finder_test.rb +2 -0
- data/test/test_helper.rb +2 -0
- data/test/unpack_ebook_test.rb +45 -20
- data/test/xhtml_cleaner_test.rb +2 -0
- data/test/xhtml_extractor_test.rb +3 -1
- metadata +13 -3
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'command_registry'
|
|
2
4
|
require_relative 'option_builder'
|
|
5
|
+
require_relative 'command_options_configurator'
|
|
3
6
|
|
|
4
7
|
module EpubTools
|
|
5
8
|
module CLI
|
|
@@ -12,24 +15,15 @@ module EpubTools
|
|
|
12
15
|
def initialize(program_name = nil)
|
|
13
16
|
@registry = CommandRegistry.new
|
|
14
17
|
@program_name = program_name || File.basename($PROGRAM_NAME)
|
|
18
|
+
@options_configurator = CommandOptionsConfigurator.new
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
# Run the CLI application
|
|
18
22
|
# @param args [Array<String>] Command line arguments
|
|
19
23
|
# @return [Boolean] true if the command was run successfully
|
|
20
24
|
def run(args = ARGV)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
puts EpubTools::VERSION
|
|
24
|
-
exit 0
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
commands = registry.available_commands
|
|
28
|
-
|
|
29
|
-
if args.empty? || !commands.include?(args[0])
|
|
30
|
-
print_usage(commands)
|
|
31
|
-
exit 1
|
|
32
|
-
end
|
|
25
|
+
handle_version_flag(args)
|
|
26
|
+
validate_command_args(args)
|
|
33
27
|
|
|
34
28
|
cmd = args.shift
|
|
35
29
|
handle_command(cmd, args)
|
|
@@ -43,6 +37,36 @@ module EpubTools
|
|
|
43
37
|
command_config = registry.get(cmd)
|
|
44
38
|
return false unless command_config
|
|
45
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)
|
|
46
70
|
options = command_config[:default_options].dup
|
|
47
71
|
required_keys = command_config[:required_keys]
|
|
48
72
|
|
|
@@ -50,114 +74,39 @@ module EpubTools
|
|
|
50
74
|
.with_banner("Usage: #{program_name} #{cmd} [options]")
|
|
51
75
|
.with_help_option
|
|
52
76
|
|
|
53
|
-
|
|
54
|
-
|
|
77
|
+
@options_configurator.configure(cmd, builder)
|
|
78
|
+
builder
|
|
79
|
+
end
|
|
55
80
|
|
|
56
|
-
|
|
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)
|
|
57
87
|
options = builder.parse(args)
|
|
58
88
|
command_class = command_config[:class]
|
|
59
|
-
command_class.new(options)
|
|
60
|
-
|
|
89
|
+
command_instance = command_class.new(options)
|
|
90
|
+
command_instance.run
|
|
91
|
+
command_instance
|
|
61
92
|
end
|
|
62
93
|
|
|
63
|
-
private
|
|
64
|
-
|
|
65
94
|
# Print usage information
|
|
66
95
|
# @param commands [Array<String>] Available commands
|
|
67
96
|
def print_usage(_commands)
|
|
68
|
-
puts
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
init Initialize a bare-bones EPUB
|
|
72
|
-
extract Extract XHTML files from EPUBs
|
|
73
|
-
split Split XHTML into separate XHTMLs per chapter
|
|
74
|
-
add Add chapter XHTML files into an EPUB
|
|
75
|
-
pack Package an EPUB directory into a .epub file
|
|
76
|
-
unpack Unpack an EPUB file into a directory
|
|
77
|
-
compile Takes EPUBs in a dir and splits, cleans, and compiles into a single EPUB.
|
|
78
|
-
USAGE
|
|
97
|
+
puts "Usage: #{program_name} COMMAND [options]"
|
|
98
|
+
puts 'Commands:'
|
|
99
|
+
print_command_list
|
|
79
100
|
end
|
|
80
101
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
opts.on('-e DIR', '--oebps-dir DIR', 'EPUB OEBPS directory (required)') do |v|
|
|
90
|
-
options[:oebps_dir] = v
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
when 'extract'
|
|
95
|
-
builder.with_custom_options do |opts, options|
|
|
96
|
-
opts.on('-s DIR', '--source-dir DIR', 'Directory with EPUBs to extract XHTMLs from (required)') do |v|
|
|
97
|
-
options[:source_dir] = v
|
|
98
|
-
end
|
|
99
|
-
opts.on('-t DIR', '--target-dir DIR',
|
|
100
|
-
'Directory where the XHTML files will be extracted to (required)') do |v|
|
|
101
|
-
options[:target_dir] = v
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
.with_verbose_option
|
|
105
|
-
|
|
106
|
-
when 'split'
|
|
107
|
-
builder.with_custom_options do |opts, options|
|
|
108
|
-
opts.on('-i FILE', '--input FILE', 'Source XHTML file (required)') { |v| options[:input_file] = v }
|
|
109
|
-
opts.on('-t TITLE', '--title TITLE', 'Book title for HTML <title> tags (required)') do |v|
|
|
110
|
-
options[:book_title] = v
|
|
111
|
-
end
|
|
112
|
-
opts.on('-o DIR', '--output-dir DIR',
|
|
113
|
-
"Output directory for chapter files (default: #{options[:output_dir]})") do |v|
|
|
114
|
-
options[:output_dir] = v
|
|
115
|
-
end
|
|
116
|
-
opts.on('-p PREFIX', '--prefix PREFIX',
|
|
117
|
-
"Filename prefix for chapters (default: #{options[:prefix]})") do |v|
|
|
118
|
-
options[:prefix] = v
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
.with_verbose_option
|
|
122
|
-
|
|
123
|
-
when 'init'
|
|
124
|
-
builder.with_title_option
|
|
125
|
-
.with_author_option
|
|
126
|
-
.with_custom_options do |opts, options|
|
|
127
|
-
opts.on('-o DIR', '--output-dir DIR', 'Destination EPUB directory (required)') do |v|
|
|
128
|
-
options[:destination] = v
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
.with_cover_option
|
|
132
|
-
|
|
133
|
-
when 'pack'
|
|
134
|
-
builder.with_input_dir('EPUB directory to package')
|
|
135
|
-
.with_output_file('Output EPUB file path')
|
|
136
|
-
.with_verbose_option
|
|
137
|
-
|
|
138
|
-
when 'unpack'
|
|
139
|
-
builder.with_custom_options do |opts, options|
|
|
140
|
-
opts.on('-i FILE', '--input-file FILE', 'EPUB file to unpack (required)') { |v| options[:epub_file] = v }
|
|
141
|
-
opts.on('-o DIR', '--output-dir DIR', 'Output directory to extract into (default: basename of epub)') do |v|
|
|
142
|
-
options[:output_dir] = v
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
.with_verbose_option
|
|
146
|
-
|
|
147
|
-
when 'compile'
|
|
148
|
-
builder.with_title_option
|
|
149
|
-
.with_author_option
|
|
150
|
-
.with_custom_options do |opts, options|
|
|
151
|
-
opts.on('-s DIR', '--source-dir DIR', 'Directory with EPUBs to extract XHTMLs from (required)') do |v|
|
|
152
|
-
options[:source_dir] = v
|
|
153
|
-
end
|
|
154
|
-
opts.on('-o FILE', '--output FILE', 'EPUB to create (default: book title in source dir)') do |v|
|
|
155
|
-
options[:output_file] = v
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
.with_cover_option
|
|
159
|
-
.with_verbose_option
|
|
160
|
-
end
|
|
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.'
|
|
161
110
|
end
|
|
162
111
|
end
|
|
163
112
|
end
|
data/lib/epub_tools/cli.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative 'cli/command_registry'
|
|
2
4
|
require_relative 'cli/option_builder'
|
|
3
5
|
require_relative 'cli/runner'
|
|
@@ -10,36 +12,21 @@ module EpubTools
|
|
|
10
12
|
# @return [CLI::Runner] A configured runner instance
|
|
11
13
|
def self.create_runner(program_name = nil)
|
|
12
14
|
runner = Runner.new(program_name)
|
|
13
|
-
|
|
14
|
-
# Register all commands
|
|
15
|
-
runner.registry.register('add', EpubTools::AddChapters,
|
|
16
|
-
%i[chapters_dir oebps_dir])
|
|
17
|
-
|
|
18
|
-
runner.registry.register('extract', EpubTools::XHTMLExtractor,
|
|
19
|
-
%i[source_dir target_dir],
|
|
20
|
-
{ verbose: true })
|
|
21
|
-
|
|
22
|
-
runner.registry.register('split', EpubTools::SplitChapters,
|
|
23
|
-
%i[input_file book_title],
|
|
24
|
-
{ output_dir: './chapters', prefix: 'chapter', verbose: true })
|
|
25
|
-
|
|
26
|
-
runner.registry.register('init', EpubTools::EpubInitializer,
|
|
27
|
-
%i[title author destination],
|
|
28
|
-
{ verbose: true })
|
|
29
|
-
|
|
30
|
-
runner.registry.register('pack', EpubTools::PackEbook,
|
|
31
|
-
%i[input_dir output_file],
|
|
32
|
-
{ verbose: true })
|
|
33
|
-
|
|
34
|
-
runner.registry.register('unpack', EpubTools::UnpackEbook,
|
|
35
|
-
[:epub_file],
|
|
36
|
-
{ verbose: true })
|
|
37
|
-
|
|
38
|
-
runner.registry.register('compile', EpubTools::CompileBook,
|
|
39
|
-
%i[title author source_dir],
|
|
40
|
-
{ verbose: true })
|
|
41
|
-
|
|
15
|
+
register_all_commands(runner.registry)
|
|
42
16
|
runner
|
|
43
17
|
end
|
|
18
|
+
|
|
19
|
+
# Register all available commands with their configurations
|
|
20
|
+
# @param registry [CommandRegistry] The command registry to populate
|
|
21
|
+
def self.register_all_commands(registry)
|
|
22
|
+
registry.register('add', EpubTools::AddChapters, %i[chapters_dir oebps_dir])
|
|
23
|
+
registry.register('extract', EpubTools::XHTMLExtractor, %i[source_dir target_dir], { verbose: true })
|
|
24
|
+
registry.register('split', EpubTools::SplitChapters, %i[input_file book_title],
|
|
25
|
+
{ output_dir: './chapters', prefix: 'chapter', verbose: true })
|
|
26
|
+
registry.register('init', EpubTools::EpubInitializer, %i[title author destination], { verbose: true })
|
|
27
|
+
registry.register('pack', EpubTools::PackEbook, %i[input_dir output_file], { verbose: true })
|
|
28
|
+
registry.register('unpack', EpubTools::UnpackEbook, [:epub_file], { verbose: true })
|
|
29
|
+
registry.register('compile', EpubTools::CompileBook, %i[title author source_dir], { verbose: true })
|
|
30
|
+
end
|
|
44
31
|
end
|
|
45
32
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
2
4
|
require 'fileutils'
|
|
3
5
|
require_relative 'loggable'
|
|
4
6
|
require_relative 'xhtml_extractor'
|
|
@@ -6,11 +8,14 @@ require_relative 'split_chapters'
|
|
|
6
8
|
require_relative 'epub_initializer'
|
|
7
9
|
require_relative 'add_chapters'
|
|
8
10
|
require_relative 'pack_ebook'
|
|
11
|
+
require_relative 'compile_workspace'
|
|
12
|
+
require_relative 'chapter_validator'
|
|
9
13
|
|
|
10
14
|
module EpubTools
|
|
11
15
|
# Orchestrates extraction, splitting, validation, and packaging of book EPUBs
|
|
12
16
|
class CompileBook
|
|
13
17
|
include Loggable
|
|
18
|
+
|
|
14
19
|
# Book title
|
|
15
20
|
attr_reader :title
|
|
16
21
|
# Book author
|
|
@@ -43,114 +48,92 @@ module EpubTools
|
|
|
43
48
|
@output_file = options[:output_file] || default_output_file
|
|
44
49
|
@build_dir = options[:build_dir] || File.join(Dir.pwd, '.epub_tools_build')
|
|
45
50
|
@verbose = options[:verbose] || false
|
|
51
|
+
@workspace = CompileWorkspace.new(@build_dir)
|
|
46
52
|
end
|
|
47
53
|
|
|
48
54
|
# Run the full compile workflow
|
|
49
55
|
def run
|
|
50
|
-
|
|
51
|
-
prepare_dirs
|
|
56
|
+
setup_workspace
|
|
52
57
|
extract_xhtmls
|
|
53
58
|
split_xhtmls
|
|
54
|
-
|
|
59
|
+
validate_chapters
|
|
55
60
|
initialize_epub
|
|
56
61
|
add_chapters
|
|
57
62
|
pack_epub
|
|
58
|
-
|
|
59
|
-
clean_build_dir
|
|
63
|
+
finalize_and_cleanup
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
private
|
|
63
67
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def clean_build_dir
|
|
69
|
-
log "Cleaning build directory #{build_dir}..."
|
|
70
|
-
FileUtils.rm_rf(build_dir)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def prepare_dirs
|
|
68
|
+
def setup_workspace
|
|
69
|
+
@workspace.clean
|
|
70
|
+
log "Cleaning build directory #{@build_dir}..."
|
|
71
|
+
@workspace.prepare_directories
|
|
74
72
|
log 'Preparing build directories...'
|
|
75
|
-
FileUtils.mkdir_p(xhtml_dir)
|
|
76
|
-
FileUtils.mkdir_p(chapters_dir)
|
|
77
73
|
end
|
|
78
74
|
|
|
79
|
-
def
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def chapters_dir
|
|
84
|
-
@chapters_dir ||= File.join(build_dir, 'chapters')
|
|
75
|
+
def finalize_and_cleanup
|
|
76
|
+
log "Done. Output EPUB: #{File.expand_path(output_file)}"
|
|
77
|
+
@workspace.clean
|
|
78
|
+
output_file
|
|
85
79
|
end
|
|
86
80
|
|
|
87
|
-
def
|
|
88
|
-
|
|
81
|
+
def default_output_file
|
|
82
|
+
"#{title.gsub(' ', '_')}.epub"
|
|
89
83
|
end
|
|
90
84
|
|
|
91
85
|
def extract_xhtmls
|
|
92
86
|
log "Extracting XHTML files from epubs in '#{source_dir}'..."
|
|
93
87
|
XHTMLExtractor.new({
|
|
94
88
|
source_dir: source_dir,
|
|
95
|
-
target_dir: xhtml_dir,
|
|
89
|
+
target_dir: @workspace.xhtml_dir,
|
|
96
90
|
verbose: verbose
|
|
97
91
|
}).run
|
|
98
92
|
end
|
|
99
93
|
|
|
100
94
|
def split_xhtmls
|
|
101
95
|
log 'Splitting XHTML files into chapters...'
|
|
102
|
-
Dir.glob(File.join(xhtml_dir, '*.xhtml')).each do |xhtml_file|
|
|
103
|
-
|
|
104
|
-
log "Splitting '#{base}'..."
|
|
105
|
-
SplitChapters.new({
|
|
106
|
-
input_file: xhtml_file,
|
|
107
|
-
book_title: title,
|
|
108
|
-
output_dir: chapters_dir,
|
|
109
|
-
output_prefix: 'chapter',
|
|
110
|
-
verbose: verbose
|
|
111
|
-
}).run
|
|
96
|
+
Dir.glob(File.join(@workspace.xhtml_dir, '*.xhtml')).each do |xhtml_file|
|
|
97
|
+
split_xhtml_file(xhtml_file)
|
|
112
98
|
end
|
|
113
99
|
end
|
|
114
100
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
end
|
|
121
|
-
end.compact
|
|
122
|
-
raise "No chapter files found in #{chapters_dir}" if nums.empty?
|
|
101
|
+
def split_xhtml_file(xhtml_file)
|
|
102
|
+
base = File.basename(xhtml_file, '.xhtml')
|
|
103
|
+
log "Splitting '#{base}'..."
|
|
104
|
+
SplitChapters.new(build_split_options(xhtml_file)).run
|
|
105
|
+
end
|
|
123
106
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
107
|
+
def build_split_options(xhtml_file)
|
|
108
|
+
{
|
|
109
|
+
input_file: xhtml_file,
|
|
110
|
+
book_title: title,
|
|
111
|
+
output_dir: @workspace.chapters_dir,
|
|
112
|
+
output_prefix: 'chapter',
|
|
113
|
+
verbose: verbose
|
|
114
|
+
}
|
|
115
|
+
end
|
|
127
116
|
|
|
128
|
-
|
|
117
|
+
def validate_chapters
|
|
118
|
+
ChapterValidator.new(chapters_dir: @workspace.chapters_dir, verbose: verbose).validate
|
|
129
119
|
end
|
|
130
120
|
|
|
131
121
|
def initialize_epub
|
|
132
122
|
log 'Initializing new EPUB...'
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else
|
|
141
|
-
EpubInitializer.new({
|
|
142
|
-
title: title,
|
|
143
|
-
author: author,
|
|
144
|
-
destination: epub_dir
|
|
145
|
-
}).run
|
|
146
|
-
end
|
|
123
|
+
EpubInitializer.new(build_epub_options).run
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_epub_options
|
|
127
|
+
options = { title: title, author: author, destination: @workspace.epub_dir }
|
|
128
|
+
options[:cover_image] = cover_image if cover_image
|
|
129
|
+
options
|
|
147
130
|
end
|
|
148
131
|
|
|
149
132
|
def add_chapters
|
|
150
133
|
log 'Adding chapters to EPUB...'
|
|
151
134
|
AddChapters.new({
|
|
152
|
-
chapters_dir: chapters_dir,
|
|
153
|
-
epub_dir: File.join(epub_dir, 'OEBPS'),
|
|
135
|
+
chapters_dir: @workspace.chapters_dir,
|
|
136
|
+
epub_dir: File.join(@workspace.epub_dir, 'OEBPS'),
|
|
154
137
|
verbose: verbose
|
|
155
138
|
}).run
|
|
156
139
|
end
|
|
@@ -158,7 +141,7 @@ module EpubTools
|
|
|
158
141
|
def pack_epub
|
|
159
142
|
log "Building final EPUB '#{output_file}'..."
|
|
160
143
|
PackEbook.new({
|
|
161
|
-
input_dir: epub_dir,
|
|
144
|
+
input_dir: @workspace.epub_dir,
|
|
162
145
|
output_file: output_file,
|
|
163
146
|
verbose: verbose
|
|
164
147
|
}).run
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
module EpubTools
|
|
7
|
+
# Handles configuration parsing and setup for EPUB initialization
|
|
8
|
+
class EpubConfiguration
|
|
9
|
+
attr_reader :title, :author, :destination, :uuid, :modified,
|
|
10
|
+
:cover_image_path, :cover_image_fname, :cover_image_media_type, :verbose
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@title = options.fetch(:title)
|
|
14
|
+
@author = options.fetch(:author)
|
|
15
|
+
@destination = File.expand_path(options.fetch(:destination))
|
|
16
|
+
@uuid = "urn:uuid:#{SecureRandom.uuid}"
|
|
17
|
+
@modified = Time.now.utc.iso8601
|
|
18
|
+
@cover_image_path = options[:cover_image]
|
|
19
|
+
@cover_image_fname = nil
|
|
20
|
+
@cover_image_media_type = nil
|
|
21
|
+
@verbose = options[:verbose] || false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def cover_image?
|
|
25
|
+
!@cover_image_path.nil?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update_cover_info(fname, media_type)
|
|
29
|
+
@cover_image_fname = fname
|
|
30
|
+
@cover_image_media_type = media_type
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|