epuber 0.9.4 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdced1dd2ebd0dc9d67e06757560ed5acd26d93303db343b53985b3c7aeebe20
4
- data.tar.gz: e979d0957ff1a554bc4f29cb4e2bbe6cd0a113a83a42ed46a6e80c8f049e8008
3
+ metadata.gz: 6190500e356c8d6fd0d642f6e39f2f606948933bee1b17f1285add446e5bdd6d
4
+ data.tar.gz: 63719103a8cce152f0cbb63ba72ab086064e0f5cbaf03c926fad293342be1c42
5
5
  SHA512:
6
- metadata.gz: b2ea5b2448b893e8c1c6cb47d4868cdfcd8e16aefeb54733c880c686107439ba51ec50aa89f0f571b6322998d5fe1d4862d4b8a4e78df003fbe4d1d849ebcbc9
7
- data.tar.gz: 6492a8277ff6d0179a5fd91e3758cc0edc5ff73faf584237336395a52d151654a7c451085ddde6e40532010b3e6d5f9046a3f058237c7879b6da184141b63877
6
+ metadata.gz: dd5056baa29e1b15c5ecad2e570969a21247dec9df18d1f0e18575e4a801c0427b66b20a20a6af44a17889e5b455cc17fb55e042c203e6f154046e648630fa6b
7
+ data.tar.gz: 07a96a039ff2a06774725ca1187bc7e8c09a938513893c52b8f2d7bf6d467f7febd45529cd7beabe1ed746c7dfc4118a7936677b890681c7840edbfe4c31e8dd
@@ -61,8 +61,7 @@ module Epuber
61
61
  matches = text.to_enum(:scan, regexp).map { Regexp.last_match }
62
62
  matches.each do |match|
63
63
  # @type match [MatchData]
64
- UI.print_processing_problem MatchProblem.new(match, message,
65
- Config.instance.pretty_path_from_project(file_path))
64
+ UI.warning MatchProblem.new(match, message, Config.instance.pretty_path_from_project(file_path))
66
65
  end
67
66
  end
68
67
 
@@ -17,16 +17,12 @@ module Epuber
17
17
  }.merge(super)
18
18
  end
19
19
 
20
- def warning(messsage, location: nil)
20
+ def warning(messsage, location: caller_locations.first)
21
21
  UI.warning(messsage, location: location)
22
22
  end
23
23
 
24
- def error(messsage, location: nil)
25
- if Config.instance.release_build
26
- UI.error!(messsage, location: location)
27
- else
28
- UI.error(messsage, location: location)
29
- end
24
+ def error(messsage, location: caller_locations.first)
25
+ UI.error(messsage, location: location)
30
26
  end
31
27
  end
32
28
  end
@@ -40,7 +40,7 @@ module Epuber
40
40
  @release_version = argv.flag?('release', false)
41
41
  @use_cache = argv.flag?('cache', true)
42
42
 
43
- self.debug_steps_times = argv.flag?('debug-steps-times', false)
43
+ UI.logger.debug_steps_times = argv.flag?('debug-steps-times', false)
44
44
 
45
45
  super(argv)
46
46
  end
@@ -56,7 +56,7 @@ module Epuber
56
56
  def run
57
57
  super
58
58
 
59
- UI.puts "building book `#{Config.instance.pretty_path_from_project(book.file_path)}`"
59
+ UI.info "building book `#{Config.instance.pretty_path_from_project(book.file_path)}`"
60
60
 
61
61
  if @release_version
62
62
  # Remove all previous versions of compiled files
@@ -77,9 +77,7 @@ module Epuber
77
77
  FileUtils.remove_file(archive_name) if ::File.exist?(archive_name)
78
78
 
79
79
  archive_path = compiler.archive(archive_name)
80
-
81
- Epubcheck.check(archive_path)
82
-
80
+ run_epubcheck(archive_path, build_path)
83
81
  convert_epub_to_mobi(archive_path, "#{::File.basename(archive_path, '.epub')}.mobi") if target.create_mobi
84
82
 
85
83
  Epuber::Config.instance.release_build = false
@@ -94,13 +92,19 @@ module Epuber
94
92
  use_cache: @use_cache)
95
93
  archive_path = compiler.archive(configuration_suffix: 'debug')
96
94
 
97
- Epubcheck.check(archive_path) if @should_check
95
+ run_epubcheck(archive_path, build_path) if @should_check
98
96
 
99
97
  convert_epub_to_mobi(archive_path, "#{::File.basename(archive_path, '.epub')}.mobi") if target.create_mobi
100
98
  end
101
99
  end
102
100
 
103
- write_lockfile
101
+ # Exit with error if there are any errors
102
+ if (@release_version || @should_check) && Epuber::UI.logger.error?
103
+ exit(1)
104
+ else
105
+ UI.info('🎉 Build finished successfully.'.ansi.green)
106
+ write_lockfile
107
+ end
104
108
  end
105
109
 
106
110
  private
@@ -131,6 +135,38 @@ module Epuber
131
135
  path
132
136
  end
133
137
 
138
+ def run_epubcheck(archive_path, build_dir)
139
+ UI.info("Running Epubcheck for #{archive_path}")
140
+
141
+ report = Epubcheck.check(archive_path)
142
+ report.problems.each do |problem|
143
+ relative_path = problem.location.path.sub("#{archive_path}/", '')
144
+ file_path = ::File.join(build_dir, relative_path)
145
+
146
+ nice_path = Config.instance.pretty_path_from_project(file_path)
147
+ content = ::File.read(file_path)
148
+
149
+ log_level = case problem.level
150
+ when :fatal, :error then :error
151
+ when :warning then :warning
152
+ else :info
153
+ end
154
+ message = "#{problem.level}(#{problem.code}): #{problem.message}"
155
+
156
+ p = Compiler::Problem.new(problem.level, message, content, line: problem.location.lineno,
157
+ column: problem.location.column,
158
+ length: 1,
159
+ file_path: nice_path)
160
+ UI.send(log_level, p, backtrace: nil)
161
+ end
162
+
163
+ if report.error?
164
+ UI.error('Epubcheck found some errors in epub file.')
165
+ else
166
+ UI.info('Epubcheck finished successfully.')
167
+ end
168
+ end
169
+
134
170
  def find_calibre_app
135
171
  locations = `mdfind "kMDItemCFBundleIdentifier == net.kovidgoyal.calibre"`.split("\n")
136
172
  UI.error!("Can't find location of calibre.app to convert EPUB to MOBI.") if locations.empty?
@@ -52,7 +52,7 @@ module Epuber
52
52
  private
53
53
 
54
54
  def print_good_bye(book_id)
55
- UI.puts <<~TEXT.ansi.green
55
+ UI.info <<~TEXT.ansi.green
56
56
  Project initialized, please review #{book_id}.bookspec file, remove comments and fill some attributes like book title.
57
57
  TEXT
58
58
  end
@@ -124,7 +124,7 @@ module Epuber
124
124
  #
125
125
  def write(file_path, string)
126
126
  File.write(file_path, string)
127
- UI.puts " #{'create'.ansi.green} #{file_path}"
127
+ UI.info " #{'create'.ansi.green} #{file_path}"
128
128
  end
129
129
 
130
130
  # @param [String] string text to file
@@ -148,7 +148,7 @@ module Epuber
148
148
  existing_content << "\n"
149
149
 
150
150
  File.write(file_path, existing_content)
151
- UI.puts " #{'update'.ansi.green} #{file_path}"
151
+ UI.info " #{'update'.ansi.green} #{file_path}"
152
152
  end
153
153
 
154
154
  # @param [String] dir_path path to dir
@@ -157,7 +157,7 @@ module Epuber
157
157
  #
158
158
  def create_folder(dir_path)
159
159
  FileUtils.mkdir_p(dir_path)
160
- UI.puts " #{'create'.ansi.green} #{dir_path}/"
160
+ UI.info " #{'create'.ansi.green} #{dir_path}/"
161
161
  end
162
162
 
163
163
  # @param [String] text
@@ -169,7 +169,7 @@ module Epuber
169
169
  result = $stdin.gets.chomp
170
170
 
171
171
  while result.empty?
172
- UI.puts 'Value cannot be empty, please fill it!'.ansi.red
172
+ UI.info 'Value cannot be empty, please fill it!'.ansi.red
173
173
  print text
174
174
  result = $stdin.gets.chomp
175
175
  end
@@ -51,7 +51,7 @@ module Epuber
51
51
  if @open_web_browser
52
52
  system "open #{uri}"
53
53
  else
54
- UI.puts 'Web browser can be automatically opened by adding --open flag, see --help'
54
+ UI.info 'Web browser can be automatically opened by adding --open flag, see --help'
55
55
  end
56
56
  end
57
57
  end
@@ -25,32 +25,23 @@ module Epuber
25
25
  self.plugin_prefixes = plugin_prefixes + %w[epuber]
26
26
 
27
27
  def self.run(argv = [])
28
- UI.current_command = self
29
28
  super
30
- UI.current_command = nil
31
29
  rescue Interrupt
32
30
  UI.error('[!] Cancelled')
33
31
  rescue StandardError => e
34
32
  UI.error!(e)
35
-
36
- UI.current_command = nil
37
33
  end
38
34
 
39
- def validate!
35
+ def initialize(argv)
40
36
  super
41
- UI.current_command = self
42
- end
43
37
 
44
- def run
45
- UI.current_command = self
38
+ UI.logger.verbose = verbose?
46
39
  end
47
40
 
48
- attr_reader :debug_steps_times
41
+ def run; end
49
42
 
50
43
  protected
51
44
 
52
- attr_writer :debug_steps_times
53
-
54
45
  # @return [Epuber::Book::Book]
55
46
  #
56
47
  def book
@@ -62,9 +53,16 @@ module Epuber
62
53
  # @raise PlainInformative if no .bookspec file don't exists or there are too many
63
54
  #
64
55
  def verify_one_bookspec_exists!
65
- bookspec_files = Config.instance.find_all_bookspecs
66
- raise PlainInformative, "No `.bookspec' found in the project directory." if bookspec_files.empty?
67
- raise PlainInformative, "Multiple `.bookspec' found in current directory" if bookspec_files.count > 1
56
+ project_path = Config.instance.project_path
57
+ bookspec_files = Config.find_bookspec_files(project_path)
58
+
59
+ if bookspec_files.empty?
60
+ raise PlainInformative, "No `.bookspec' found in the project directory (or in any parent folders)."
61
+ end
62
+
63
+ if bookspec_files.count > 1
64
+ raise PlainInformative, "Multiple `.bookspec' found in directory (directory: #{project_path})"
65
+ end
68
66
  end
69
67
 
70
68
  def write_lockfile
@@ -76,10 +74,17 @@ module Epuber
76
74
  def pre_build_checks
77
75
  Config.instance.warn_for_outdated_versions!
78
76
 
79
- return unless !Config.instance.same_version_as_last_run? && File.exist?(Config.instance.working_path)
80
-
81
- UI.warning('Using different version of Epuber or Bade, removing all build caches')
82
- Config.instance.remove_build_caches
77
+ # remove build caches if we are using different version of Epuber or Bade
78
+ if !Config.instance.same_version_as_last_run? && File.exist?(Config.instance.working_path)
79
+ UI.warning('Using different version of Epuber or Bade, removing all build caches')
80
+ Config.instance.remove_build_caches
81
+ end
82
+
83
+ # ensure we are in the project directory
84
+ if Dir.pwd != Config.instance.project_path
85
+ UI.debug("Changing directory to project directory: #{Config.instance.project_path}")
86
+ Dir.chdir(Config.instance.project_path)
87
+ end
83
88
  end
84
89
  end
85
90
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module Epuber
5
4
  class Compiler
6
5
  class CompilationContext
@@ -37,13 +36,14 @@ module Epuber
37
36
  file_resolver.add_file(file)
38
37
  end
39
38
  plugin
40
- rescue LoadError
41
- UI.error "Can't find plugin at path #{path}"
39
+ rescue LoadError => e
40
+ UI.error "Can't find plugin at path #{path}, #{e}"
42
41
  end.compact
43
42
  end
44
43
 
45
44
  # @param [Class] klass class of thing you want to perform (Checker or Transformer)
46
45
  # @param [Symbol] source_type source type of that thing (Checker or Transformer)
46
+ # @param [String] processing_time_step_name name of step for processing time
47
47
  #
48
48
  # @yield
49
49
  # @yieldparam [Epuber::CheckerTransformerBase] instance of checker or transformer
@@ -58,7 +58,11 @@ module Epuber
58
58
  next if instance.source_type != source_type
59
59
  next if instance.options.include?(:run_only_before_release) && !release_build
60
60
 
61
- yield instance
61
+ location = instance.block.source_location.map(&:to_s).join(':')
62
+ message = "performing #{source_type.inspect} from plugin #{location}"
63
+ UI.print_step_processing_time(message) do
64
+ yield instance
65
+ end
62
66
  end
63
67
  end
64
68
  end
@@ -27,7 +27,7 @@ module Epuber
27
27
 
28
28
  PATH_TYPES = [:spine, :manifest, :package, nil].freeze
29
29
 
30
- # @return [String] path where should look for source files
30
+ # @return [String] path where should look for source files (relative to project root)
31
31
  #
32
32
  attr_reader :source_path
33
33
 
@@ -6,7 +6,7 @@ module Epuber
6
6
  require_relative 'abstract_file'
7
7
 
8
8
  class SourceFile < AbstractFile
9
- # @return [String] relative source path
9
+ # @return [String] relative source path (from project root)
10
10
  #
11
11
  attr_reader :source_path
12
12
 
@@ -133,7 +133,7 @@ module Epuber
133
133
  rescue XHTMLProcessor::UnparseableLinkError,
134
134
  FileFinders::FileNotFoundError,
135
135
  FileFinders::MultipleFilesFoundError => e
136
- UI.warning(e.to_s, location: location)
136
+ UI.error(e.to_s, location: location)
137
137
  return nil
138
138
  end
139
139
  end
@@ -142,18 +142,14 @@ module Epuber
142
142
  end
143
143
 
144
144
  # perform transformations
145
- UI.print_step_processing_time('performing final transformations') do
146
- compilation_context.perform_plugin_things(Transformer, :result_text_xhtml_string) do |transformer|
147
- xhtml_string = transformer.call(final_destination_path, xhtml_string, compilation_context)
148
- end
145
+ compilation_context.perform_plugin_things(Transformer, :result_text_xhtml_string) do |transformer|
146
+ xhtml_string = transformer.call(final_destination_path, xhtml_string, compilation_context)
149
147
  end
150
148
 
151
149
  # perform custom validation
152
150
  if compilation_context.should_check
153
- UI.print_step_processing_time('performing final validations') do
154
- compilation_context.perform_plugin_things(Checker, :result_text_xhtml_string) do |checker|
155
- checker.call(final_destination_path, xhtml_string, compilation_context)
156
- end
151
+ compilation_context.perform_plugin_things(Checker, :result_text_xhtml_string) do |checker|
152
+ checker.call(final_destination_path, xhtml_string, compilation_context)
157
153
  end
158
154
  end
159
155
 
@@ -175,7 +171,7 @@ module Epuber
175
171
  # @param [Compiler::CompilationContext] compilation_context
176
172
  # @param [Hash<String, XHTMLFile>] global_ids
177
173
  #
178
- def process_global_ids(compilation_context, global_ids)
174
+ def process_global_ids(_compilation_context, global_ids)
179
175
  return if self.global_ids.empty? && global_links.empty?
180
176
 
181
177
  xhtml_doc = XHTMLProcessor.xml_document_from_string(File.read(final_destination_path), final_destination_path)
@@ -196,12 +192,8 @@ module Epuber
196
192
  node['href'] = "#{rel_path}##{href}"
197
193
  else
198
194
  message = "Can't find global id '#{href}' from link in file #{source_path}"
199
- location = UserInterface::Location.new(path: final_destination_path, lineno: node.line)
200
- if compilation_context.release_build?
201
- UI.error!(message, location: location)
202
- else
203
- UI.warning(message, location: location)
204
- end
195
+ location = Epuber::Location.new(path: final_destination_path, lineno: node.line)
196
+ UI.error(message, location: location)
205
197
  end
206
198
  end
207
199
 
@@ -25,7 +25,7 @@ module Epuber
25
25
 
26
26
  if /\A[\n\r ]+(<\?xml)/ =~ text
27
27
  UI.warning('XML header must be at the beginning of document',
28
- location: UI::Location.new(path: file_path, lineno: 1))
28
+ location: Epuber::Location.new(path: file_path, lineno: 1))
29
29
 
30
30
  text = text.lstrip
31
31
  end
@@ -198,7 +198,7 @@ module Epuber
198
198
  # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching
199
199
  # for file from tag <img>
200
200
  # @param [String] file_path path to file from which is searching for other file
201
- # @param [Epuber::Compiler::FileFinder] file_finder finder for searching for files
201
+ # @param [Epuber::Compiler::FileFinders::Abstract] file_finder finder for searching for files
202
202
  #
203
203
  # @raise UnparseableLinkError, FileFinder::FileNotFoundError, FileFinder::MultipleFilesFoundError
204
204
  #
@@ -71,7 +71,7 @@ module Epuber
71
71
 
72
72
  FileUtils.mkdir_p(build_folder)
73
73
 
74
- UI.puts " #{<<~MSG}"
74
+ UI.info " #{<<~MSG}"
75
75
  building target #{@target.name.inspect} (build dir: #{Config.instance.pretty_path_from_project(build_folder)})
76
76
  MSG
77
77
 
@@ -89,6 +89,7 @@ module Epuber
89
89
  process_all_target_files
90
90
  generate_other_files
91
91
 
92
+ # run :after_all_text_files transformers
92
93
  compilation_context.perform_plugin_things(Transformer, :after_all_text_files) do |transformer|
93
94
  transformer.call(@book, compilation_context)
94
95
  end
@@ -131,7 +132,7 @@ module Epuber
131
132
  old_paths = zip_file.instance_eval { @entry_set.entries.map(&:name) }
132
133
  diff = old_paths - new_paths
133
134
  diff.each do |file_to_remove|
134
- UI.puts "DEBUG: removing file from result EPUB: #{file_to_remove}" if compilation_context.verbose?
135
+ UI.debug "removing file from result EPUB: #{file_to_remove}"
135
136
  zip_file.remove(file_to_remove)
136
137
  end
137
138
  end
@@ -174,7 +175,7 @@ module Epuber
174
175
  .select { |d| File.directory?(d) }
175
176
  .select { |d| (Dir.entries(d) - %w[. ..]).empty? }
176
177
  .each do |d|
177
- UI.puts "DEBUG: removing empty folder `#{d}`" if compilation_context.verbose?
178
+ UI.debug "removing empty folder `#{d}`"
178
179
  Dir.rmdir(d)
179
180
  end
180
181
  end
@@ -188,7 +189,7 @@ module Epuber
188
189
  end
189
190
  unnecessary_paths.each do |path|
190
191
  if compilation_context.verbose?
191
- UI.puts "DEBUG: removing unnecessary file: `#{Config.instance.pretty_path_from_project(path)}`"
192
+ UI.debug "removing unnecessary file: `#{Config.instance.pretty_path_from_project(path)}`"
192
193
  end
193
194
 
194
195
  File.delete(path)
@@ -283,11 +284,11 @@ module Epuber
283
284
  #
284
285
  def process_all_target_files
285
286
  @file_resolver.manifest_files.each_with_index do |file, idx|
286
- UI.print_processing_file(file, idx, @file_resolver.manifest_files.count)
287
+ UI.start_processing_file(file, idx, @file_resolver.manifest_files.count)
287
288
  process_file(file)
288
289
  end
289
290
 
290
- UI.processing_files_done
291
+ UI.end_processing
291
292
  end
292
293
 
293
294
  # @param [Epuber::Book::TocItem] toc_item
@@ -305,11 +306,13 @@ module Epuber
305
306
  end
306
307
 
307
308
  def process_global_ids
308
- xhtml_files = @file_resolver.files.select { |file| file.is_a?(FileTypes::XHTMLFile) }
309
- global_ids = validate_global_ids(xhtml_files)
309
+ UI.print_step_processing_time('Processing global ids') do
310
+ xhtml_files = @file_resolver.files.select { |file| file.is_a?(FileTypes::XHTMLFile) }
311
+ global_ids = validate_global_ids(xhtml_files)
310
312
 
311
- xhtml_files.each do |file|
312
- file.process_global_ids(compilation_context, global_ids)
313
+ xhtml_files.each do |file|
314
+ file.process_global_ids(compilation_context, global_ids)
315
+ end
313
316
  end
314
317
  end
315
318
 
data/lib/epuber/config.rb CHANGED
@@ -8,10 +8,13 @@ module Epuber
8
8
  class Config
9
9
  WORKING_PATH = '.epuber'
10
10
 
11
- # @return [String]
11
+ # @return [String] path to project directory (where .bookspec file is located or current directory if not found)
12
12
  #
13
13
  def project_path
14
- @project_path ||= Dir.pwd.unicode_normalize
14
+ @project_path ||= begin
15
+ path = self.class.find_project_dir(Dir.pwd) || Dir.pwd
16
+ path.unicode_normalize
17
+ end
15
18
  end
16
19
 
17
20
  # @param [String] of_file absolute path to file
@@ -19,7 +22,9 @@ module Epuber
19
22
  # @return [String] relative path to file from root of project
20
23
  #
21
24
  def pretty_path_from_project(of_file)
22
- Pathname.new(of_file.unicode_normalize).relative_path_from(Pathname.new(project_path)).to_s
25
+ Pathname.new(of_file.unicode_normalize)
26
+ .relative_path_from(Pathname.new(project_path))
27
+ .to_s
23
28
  end
24
29
 
25
30
  # @return [String]
@@ -31,7 +36,7 @@ module Epuber
31
36
  # @return [String]
32
37
  #
33
38
  def bookspec_path
34
- @bookspec_path ||= find_all_bookspecs.first
39
+ @bookspec_path ||= self.class.find_bookspec_files(project_path).first
35
40
  end
36
41
 
37
42
  # @return [String]
@@ -40,16 +45,6 @@ module Epuber
40
45
  "#{bookspec_path}.lock"
41
46
  end
42
47
 
43
- # @return [Array<String>]
44
- #
45
- def find_all_bookspecs
46
- Dir.chdir(project_path) do
47
- Dir.glob('*.bookspec').map do |path|
48
- File.expand_path(path)
49
- end
50
- end
51
- end
52
-
53
48
  # @return [Epuber::Book]
54
49
  #
55
50
  def bookspec
@@ -182,6 +177,32 @@ module Epuber
182
177
 
183
178
  book
184
179
  end
180
+
181
+ # Find all bookspec files in given directory
182
+ #
183
+ # @param [String] dir
184
+ #
185
+ def find_bookspec_files(dir)
186
+ Dir.chdir(dir) do
187
+ Dir.glob('*.bookspec').map do |path|
188
+ File.expand_path(path)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Find project directory by searching for .bookspec files in current and parent directories
194
+ #
195
+ # @param [String] dir
196
+ # @return [String, nil]
197
+ #
198
+ def find_project_dir(dir)
199
+ return dir if find_bookspec_files(dir).any?
200
+
201
+ parent = File.dirname(dir)
202
+ return nil if parent == dir
203
+
204
+ find_project_dir(parent)
205
+ end
185
206
  end
186
207
 
187
208
  self.test = false
@@ -1,14 +1,94 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'open3'
4
+ require 'json'
5
+
6
+ require_relative 'utils/location'
7
+
3
8
  module Epuber
4
9
  class Epubcheck
10
+ Report = Struct.new(:problems, keyword_init: true) do
11
+ # !attribute [r] problems
12
+ # @return [Array<Problem>] problems found by epubcheck
13
+
14
+ def error?
15
+ problems.any?(&:error?)
16
+ end
17
+ end
18
+
19
+ Problem = Struct.new(:level, :code, :location, :message, keyword_init: true) do
20
+ # !attribute [r] level
21
+ # @return [Symbol] level of the problem (:fatal, :error, :warning, :info, :usage, :suppressed)
22
+
23
+ # !attribute [r] code
24
+ # @return [String] code of the problem (for example: RSC-005)
25
+
26
+ # !attribute [r] location
27
+ # @return [Epuber::Location, nil] location of the problem
28
+
29
+ # !attribute [r] message
30
+ # @return [String] message of the problem
31
+
32
+ def to_s
33
+ "#{level}(#{code}): #{location.path}(#{location.lineno},#{location.column}): #{message}"
34
+ end
35
+
36
+ def error?
37
+ level == :error || level == :fatal
38
+ end
39
+
40
+ def self.from_json(json)
41
+ json_location = json['locations'].first
42
+
43
+ location = if json_location
44
+ Epuber::Location.new(
45
+ path: json_location['path'],
46
+ lineno: json_location['line'],
47
+ column: json_location['column'],
48
+ )
49
+ end
50
+
51
+ new(
52
+ level: json['severity'].downcase.to_sym,
53
+ code: json['ID'],
54
+ message: json['message'],
55
+ location: location,
56
+ )
57
+ end
58
+ end
59
+
5
60
  class << self
6
61
  # @param [String] path path to file
7
62
  #
63
+ # @return [Report] report of the epubcheck
64
+ #
8
65
  def check(path)
9
- res = system('epubcheck', path)
66
+ report = nil
67
+
68
+ Dir.mktmpdir('epubcheck-') do |tmpdir|
69
+ json_path = File.join(tmpdir, 'epubcheck.json')
70
+ Open3.popen3('epubcheck', path, '--json', json_path) do |_stdin, _stdout, stderr, wait_thr|
71
+ exit_status = wait_thr.value
72
+
73
+ if exit_status.success?
74
+ report = _parse_json(File.read(json_path))
75
+ else
76
+ UI.error(stderr.gets.chomp)
77
+ end
78
+ end
79
+ end
80
+
81
+ report
82
+ end
83
+
84
+ # @param [String] string json string
85
+ # @return [Report]
86
+ #
87
+ def _parse_json(string)
88
+ json = JSON.parse(string)
89
+ messages = json['messages']
10
90
 
11
- UI.error!('Epubcheck failed') unless res
91
+ Report.new(problems: messages.map { |msg| Problem.from_json(msg) })
12
92
  end
13
93
  end
14
94
  end