epub_tools 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -0
  3. data/.gitignore +1 -1
  4. data/.rubocop.yml +10 -17
  5. data/.tool-versions +1 -0
  6. data/CLAUDE.md +124 -0
  7. data/Gemfile +4 -4
  8. data/Gemfile.lock +44 -37
  9. data/README.md +10 -6
  10. data/Rakefile +2 -0
  11. data/bin/epub-tools +2 -0
  12. data/epub_tools.gemspec +3 -1
  13. data/lib/epub_tools/add_chapters.rb +48 -30
  14. data/lib/epub_tools/chapter_validator.rb +40 -0
  15. data/lib/epub_tools/cli/command_options_configurator.rb +115 -0
  16. data/lib/epub_tools/cli/command_registry.rb +2 -0
  17. data/lib/epub_tools/cli/option_builder.rb +5 -3
  18. data/lib/epub_tools/cli/runner.rb +59 -110
  19. data/lib/epub_tools/cli.rb +16 -29
  20. data/lib/epub_tools/compile_book.rb +48 -65
  21. data/lib/epub_tools/compile_workspace.rb +40 -0
  22. data/lib/epub_tools/epub_configuration.rb +33 -0
  23. data/lib/epub_tools/epub_file_writer.rb +57 -0
  24. data/lib/epub_tools/epub_initializer.rb +83 -162
  25. data/lib/epub_tools/epub_metadata_builder.rb +92 -0
  26. data/lib/epub_tools/loggable.rb +2 -0
  27. data/lib/epub_tools/pack_ebook.rb +28 -14
  28. data/lib/epub_tools/split_chapters.rb +42 -17
  29. data/lib/epub_tools/style_finder.rb +17 -6
  30. data/lib/epub_tools/unpack_ebook.rb +20 -10
  31. data/lib/epub_tools/version.rb +3 -1
  32. data/lib/epub_tools/xhtml_cleaner.rb +1 -0
  33. data/lib/epub_tools/xhtml_extractor.rb +20 -10
  34. data/lib/epub_tools/xhtml_generator.rb +71 -0
  35. data/lib/epub_tools.rb +2 -0
  36. data/test/add_chapters_test.rb +50 -26
  37. data/test/chapter_validator_test.rb +47 -0
  38. data/test/cli/command_registry_test.rb +2 -0
  39. data/test/cli/option_builder_test.rb +24 -14
  40. data/test/cli/runner_test.rb +15 -15
  41. data/test/cli_commands_test.rb +3 -1
  42. data/test/cli_test.rb +2 -0
  43. data/test/cli_version_test.rb +2 -0
  44. data/test/compile_book_test.rb +17 -102
  45. data/test/compile_workspace_test.rb +55 -0
  46. data/test/epub_initializer_test.rb +55 -27
  47. data/test/pack_ebook_test.rb +33 -9
  48. data/test/split_chapters_test.rb +27 -7
  49. data/test/style_finder_test.rb +2 -0
  50. data/test/test_helper.rb +2 -0
  51. data/test/unpack_ebook_test.rb +45 -20
  52. data/test/xhtml_cleaner_test.rb +2 -0
  53. data/test/xhtml_extractor_test.rb +3 -1
  54. metadata +15 -5
  55. data/.ruby-version +0 -1
@@ -0,0 +1,115 @@
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 'compile' command
100
+ # @param builder [OptionBuilder] Option builder instance
101
+ def configure_compile_options(builder)
102
+ builder.with_title_option
103
+ .with_author_option
104
+ .with_custom_options do |opts, options|
105
+ opts.on('-s DIR', '--source-dir DIR', 'Directory with EPUBs to extract XHTMLs from (required)') do |v|
106
+ options[:source_dir] = v
107
+ end
108
+ opts.on('-o FILE', '--output FILE', 'EPUB to create (default: book title in source dir)') do |v|
109
+ options[:output_file] = v
110
+ end
111
+ end.with_cover_option.with_verbose_option
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EpubTools
2
4
  module CLI
3
5
  # Manages the registration and retrieval of commands
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
 
3
5
  module EpubTools
@@ -45,7 +47,7 @@ module EpubTools
45
47
  # @param description [String] Option description
46
48
  # @param required [Boolean] Whether this option is required
47
49
  # @return [self] for method chaining
48
- def with_input_file(description = 'Input file', required = true)
50
+ def with_input_file(description = 'Input file', required: true)
49
51
  desc = required ? "#{description} (required)" : description
50
52
  @parser.on('-i FILE', '--input-file FILE', desc) { |v| @options[:input_file] = v }
51
53
  self
@@ -55,7 +57,7 @@ module EpubTools
55
57
  # @param description [String] Option description
56
58
  # @param required [Boolean] Whether this option is required
57
59
  # @return [self] for method chaining
58
- def with_input_dir(description = 'Input directory', required = true)
60
+ def with_input_dir(description = 'Input directory', required: true)
59
61
  desc = required ? "#{description} (required)" : description
60
62
  @parser.on('-i DIR', '--input-dir DIR', desc) { |v| @options[:input_dir] = v }
61
63
  self
@@ -80,7 +82,7 @@ module EpubTools
80
82
  # @param description [String] Option description
81
83
  # @param required [Boolean] Whether this option is required
82
84
  # @return [self] for method chaining
83
- def with_output_file(description = 'Output file', required = true)
85
+ def with_output_file(description = 'Output file', required: true)
84
86
  desc = required ? "#{description} (required)" : description
85
87
  @parser.on('-o FILE', '--output-file FILE', desc) { |v| @options[:output_file] = v }
86
88
  self
@@ -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
- # Handle global version flag
22
- if ['-v', '--version'].include?(args[0])
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
- # Configure command-specific options
54
- configure_command_options(cmd, builder)
77
+ @options_configurator.configure(cmd, builder)
78
+ builder
79
+ end
55
80
 
56
- # Parse arguments and run the command
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).run
60
- true
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 <<~USAGE
69
- Usage: #{program_name} COMMAND [options]
70
- Commands:
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
- # Configure command-specific options
82
- # @param cmd [String] Command name
83
- # @param builder [OptionBuilder] Option builder instance
84
- def configure_command_options(cmd, builder)
85
- case cmd
86
- when 'add'
87
- builder.with_custom_options do |opts, options|
88
- opts.on('-c DIR', '--chapters-dir DIR', 'Chapters directory (required)') { |v| options[:chapters_dir] = v }
89
- opts.on('-e DIR', '--epub-oebps-dir DIR', 'EPUB OEBPS directory (required)') do |v|
90
- options[:epub_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
@@ -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 epub_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
- clean_build_dir
51
- prepare_dirs
56
+ setup_workspace
52
57
  extract_xhtmls
53
58
  split_xhtmls
54
- validate_sequence
59
+ validate_chapters
55
60
  initialize_epub
56
61
  add_chapters
57
62
  pack_epub
58
- log "Done. Output EPUB: #{File.expand_path(output_file)}"
59
- clean_build_dir
63
+ finalize_and_cleanup
60
64
  end
61
65
 
62
66
  private
63
67
 
64
- def default_output_file
65
- "#{title.gsub(' ', '_')}.epub"
66
- end
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 xhtml_dir
80
- @xhtml_dir ||= File.join(build_dir, 'xhtml')
81
- end
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 epub_dir
88
- @epub_dir ||= File.join(build_dir, 'epub')
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
- base = File.basename(xhtml_file, '.xhtml')
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 validate_sequence
116
- log 'Validating chapter sequence...'
117
- nums = Dir.glob(File.join(chapters_dir, '*.xhtml')).map do |file|
118
- if (m = File.basename(file, '.xhtml').match(/_(\d+)\z/))
119
- m[1].to_i
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
- sorted = nums.sort.uniq
125
- missing = (sorted.first..sorted.last).to_a - sorted
126
- raise "Missing chapter numbers: #{missing.join(' ')}" if missing.any?
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
- log "Chapter sequence is complete: #{sorted.first} to #{sorted.last}."
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
- if cover_image
134
- EpubInitializer.new({
135
- title: title,
136
- author: author,
137
- destination: epub_dir,
138
- cover_image: cover_image
139
- }).run
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