epuber 0.7.4 → 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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -2
  3. data/LICENSE.txt +1 -1
  4. data/README.md +4 -3
  5. data/epuber.gemspec +11 -16
  6. data/lib/epuber/book/contributor.rb +10 -7
  7. data/lib/epuber/book/file_request.rb +3 -3
  8. data/lib/epuber/book/target.rb +30 -33
  9. data/lib/epuber/book/toc_item.rb +2 -4
  10. data/lib/epuber/book.rb +21 -21
  11. data/lib/epuber/checker/bookspec_checker.rb +26 -0
  12. data/lib/epuber/checker/text_checker.rb +16 -7
  13. data/lib/epuber/checker.rb +16 -2
  14. data/lib/epuber/checker_transformer_base.rb +3 -6
  15. data/lib/epuber/command/build.rb +40 -25
  16. data/lib/epuber/command/from_file.rb +39 -0
  17. data/lib/epuber/command/init.rb +34 -32
  18. data/lib/epuber/command/server.rb +3 -3
  19. data/lib/epuber/command.rb +18 -20
  20. data/lib/epuber/compiler/compilation_context.rb +10 -8
  21. data/lib/epuber/compiler/file_database.rb +2 -4
  22. data/lib/epuber/compiler/file_finders/abstract.rb +36 -26
  23. data/lib/epuber/compiler/file_finders/imaginary.rb +40 -35
  24. data/lib/epuber/compiler/file_resolver.rb +79 -89
  25. data/lib/epuber/compiler/file_stat.rb +4 -4
  26. data/lib/epuber/compiler/file_types/abstract_file.rb +4 -7
  27. data/lib/epuber/compiler/file_types/bade_file.rb +20 -15
  28. data/lib/epuber/compiler/file_types/coffee_script_file.rb +1 -1
  29. data/lib/epuber/compiler/file_types/css_file.rb +103 -0
  30. data/lib/epuber/compiler/file_types/generated_file.rb +1 -1
  31. data/lib/epuber/compiler/file_types/image_file.rb +4 -2
  32. data/lib/epuber/compiler/file_types/nav_file.rb +0 -1
  33. data/lib/epuber/compiler/file_types/opf_file.rb +0 -1
  34. data/lib/epuber/compiler/file_types/source_file.rb +8 -3
  35. data/lib/epuber/compiler/file_types/stylus_file.rb +4 -3
  36. data/lib/epuber/compiler/file_types/xhtml_file.rb +67 -13
  37. data/lib/epuber/compiler/generator.rb +1 -2
  38. data/lib/epuber/compiler/meta_inf_generator.rb +1 -1
  39. data/lib/epuber/compiler/nav_generator.rb +10 -11
  40. data/lib/epuber/compiler/opf_generator.rb +26 -27
  41. data/lib/epuber/compiler/problem.rb +12 -21
  42. data/lib/epuber/compiler/xhtml_processor.rb +63 -32
  43. data/lib/epuber/compiler.rb +77 -25
  44. data/lib/epuber/config.rb +16 -10
  45. data/lib/epuber/dsl/attribute.rb +17 -18
  46. data/lib/epuber/dsl/attribute_support.rb +7 -7
  47. data/lib/epuber/dsl/object.rb +19 -17
  48. data/lib/epuber/dsl/tree_object.rb +2 -3
  49. data/lib/epuber/epubcheck.rb +15 -0
  50. data/lib/epuber/from_file/bookspec_generator.rb +371 -0
  51. data/lib/epuber/from_file/encryption_handler.rb +146 -0
  52. data/lib/epuber/from_file/from_file_executor.rb +140 -0
  53. data/lib/epuber/from_file/nav_file.rb +163 -0
  54. data/lib/epuber/from_file/opf_file.rb +219 -0
  55. data/lib/epuber/helper.rb +0 -1
  56. data/lib/epuber/lockfile.rb +7 -9
  57. data/lib/epuber/plugin.rb +2 -3
  58. data/lib/epuber/ruby_extensions/match_data.rb +1 -1
  59. data/lib/epuber/ruby_extensions/thread.rb +1 -0
  60. data/lib/epuber/server/base.styl +0 -1
  61. data/lib/epuber/server/basic.styl +1 -30
  62. data/lib/epuber/server/handlers.rb +1 -1
  63. data/lib/epuber/server.rb +81 -80
  64. data/lib/epuber/third_party/bower.rb +5 -5
  65. data/lib/epuber/transformer/book_transformer.rb +108 -0
  66. data/lib/epuber/transformer/text_transformer.rb +4 -2
  67. data/lib/epuber/transformer.rb +4 -2
  68. data/lib/epuber/user_interface.rb +49 -38
  69. data/lib/epuber/vendor/hash_binding.rb +9 -2
  70. data/lib/epuber/vendor/ruby_templater.rb +4 -8
  71. data/lib/epuber/vendor/version.rb +12 -12
  72. data/lib/epuber/version.rb +1 -1
  73. metadata +79 -100
  74. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Bold.ttf +0 -0
  75. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-BoldItalic.ttf +0 -0
  76. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Italic.ttf +0 -0
  77. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Regular.ttf +0 -0
@@ -13,7 +13,8 @@ module Epuber
13
13
 
14
14
  # Method for parsing incomplete XML, supports multiple root elements
15
15
  #
16
- # @warning Because of nature of XML, when input string don't contain root element, it will create own called `body`, since it will be used in next steps.
16
+ # @warning Because of nature of XML, when input string don't contain root element, it will create own called
17
+ # `body`, since it will be used in next steps.
17
18
  #
18
19
  # @param [String] text input XHTML text
19
20
  #
@@ -23,7 +24,8 @@ module Epuber
23
24
  text = text.dup
24
25
 
25
26
  if /\A[\n\r ]+(<\?xml)/ =~ text
26
- UI.warning('XML header must be at the beginning of document', location: UI::Location.new(file_path, 1))
27
+ UI.warning('XML header must be at the beginning of document',
28
+ location: UI::Location.new(path: file_path, lineno: 1))
27
29
 
28
30
  text = text.lstrip
29
31
  end
@@ -37,20 +39,19 @@ module Epuber
37
39
 
38
40
  doctypes = []
39
41
  while /(\n|\?>|\A)?(<!DOCTYPE [^>]*>\n*)/ =~ text
40
- doctypes << $2.strip
42
+ doctypes << ::Regexp.last_match(2).strip
41
43
 
42
44
  match = Regexp.last_match
43
45
  text[match.begin(2)...match.end(2)] = ''
44
46
  end
45
47
 
46
48
  before = ([xml_header] + doctypes).compact.join("\n")
47
- unless before.empty?
48
- before = before + "\n"
49
- end
49
+ before += "\n" unless before.empty?
50
50
 
51
51
  parse_options = Nokogiri::XML::ParseOptions::DEFAULT_XML |
52
52
  Nokogiri::XML::ParseOptions::NOERROR | # to silence any errors or warnings printing into console
53
- Nokogiri::XML::ParseOptions::NOWARNING
53
+ Nokogiri::XML::ParseOptions::NOWARNING |
54
+ Nokogiri::XML::ParseOptions::NOENT
54
55
 
55
56
  doc = Nokogiri::XML("#{before}<root>#{text}</root>", file_path, nil, parse_options)
56
57
  text_for_errors = before + text
@@ -82,11 +83,10 @@ module Epuber
82
83
  end
83
84
 
84
85
  def self.xml_document_from_string(text, file_path = nil)
85
- xml, errros = self.xml_doc_from_str_with_errors(text, file_path)
86
+ xml, = xml_doc_from_str_with_errors(text, file_path)
86
87
  xml
87
88
  end
88
89
 
89
-
90
90
  # Method to add all missing items in XML root
91
91
  #
92
92
  # Required items:
@@ -137,10 +137,10 @@ module Epuber
137
137
  end
138
138
 
139
139
  # https://github.com/IDPF/epubcheck/issues/631
140
- if epub_version < 3.0
141
- xhtml_doc.internal_subset.remove unless xhtml_doc.internal_subset.nil?
142
- xhtml_doc.create_internal_subset('html', "-//W3C//DTD XHTML 1.1//EN", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd")
143
- end
140
+ return unless epub_version < 3.0
141
+
142
+ xhtml_doc.internal_subset&.remove
143
+ xhtml_doc.create_internal_subset('html', '-//W3C//DTD XHTML 1.1//EN', 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd')
144
144
  end
145
145
 
146
146
  # Method for adding style sheets with links, method will not add duplicate items
@@ -151,7 +151,7 @@ module Epuber
151
151
  # @return nil
152
152
  #
153
153
  def self.add_styles(xhtml_doc, styles)
154
- head = xhtml_doc.at_css('html > head')
154
+ head = xhtml_doc.at_css('html > head')
155
155
  old_links = head.css('link[rel="stylesheet"]').map { |node| node['href'] }
156
156
 
157
157
  links_to_add = styles - old_links
@@ -169,7 +169,7 @@ module Epuber
169
169
  # @return nil
170
170
  #
171
171
  def self.add_scripts(xhtml_doc, scripts)
172
- head = xhtml_doc.at_css('html > head')
172
+ head = xhtml_doc.at_css('html > head')
173
173
  old_links = head.css('script').map { |node| node['src'] }
174
174
 
175
175
  links_to_add = scripts - old_links
@@ -195,7 +195,8 @@ module Epuber
195
195
  # Method which will resolve path to file from pattern
196
196
  #
197
197
  # @param [String] path pattern or path of the file
198
- # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching for file from tag <img>
198
+ # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching
199
+ # for file from tag <img>
199
200
  # @param [String] file_path path to file from which is searching for other file
200
201
  # @param [Epuber::Compiler::FileFinder] file_finder finder for searching for files
201
202
  #
@@ -233,7 +234,8 @@ module Epuber
233
234
  # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
234
235
  # @param [String] tag_name CSS selector for tag
235
236
  # @param [String] attribute_name name of attribute
236
- # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching for file from tag <img>
237
+ # @param [Symbol | Array<Symbol>] groups groups of the searching file, could be for example :image when searching
238
+ # for file from tag <img>
237
239
  # @param [String] file_path path to file from which is searching for other file
238
240
  # @param [Epuber::Compiler::FileFinder] file_finder finder for searching for files
239
241
  #
@@ -243,22 +245,21 @@ module Epuber
243
245
  founded_links = []
244
246
 
245
247
  xhtml_doc.css("#{tag_name}[#{attribute_name}]").each do |node|
246
- begin
247
- src = node[attribute_name]
248
- # @type [String] src
248
+ src = node[attribute_name]
249
+ # @type [String] src
249
250
 
250
- next if src.nil?
251
+ next if src.nil?
252
+ next if src.start_with?('$')
251
253
 
252
- target_file = resolved_link_to_file(src, groups, file_path, file_finder)
253
- founded_links << target_file
254
+ target_file = resolved_link_to_file(src, groups, file_path, file_finder)
255
+ founded_links << target_file
254
256
 
255
- node[attribute_name] = target_file.to_s
256
- rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError => e
257
- UI.warning(e.to_s, location: node)
257
+ node[attribute_name] = target_file.to_s
258
+ rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError => e
259
+ UI.warning(e.to_s, location: node)
258
260
 
259
- # skip not found files
260
- next
261
- end
261
+ # skip not found files
262
+ next
262
263
  end
263
264
 
264
265
  founded_links
@@ -349,7 +350,6 @@ module Epuber
349
350
 
350
351
  begin
351
352
  new_path = file_resolver.dest_finder.find_file(path, groups: resource_group, context_path: dirname)
352
-
353
353
  rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError
354
354
  begin
355
355
  new_path = resolved_link_to_file(path, resource_group, dirname, file_resolver.source_finder).to_s
@@ -361,8 +361,7 @@ module Epuber
361
361
  file.path_type = :manifest
362
362
  file_resolver.add_file(file)
363
363
 
364
- new_path = FileResolver::renamed_file_with_path(new_path)
365
-
364
+ new_path = FileResolver.renamed_file_with_path(new_path)
366
365
  rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError => e
367
366
  UI.warning(e.to_s, location: img)
368
367
 
@@ -373,6 +372,38 @@ module Epuber
373
372
  img[attribute_name] = new_path
374
373
  end
375
374
  end
375
+
376
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
377
+ # @return [Array<Nokogiri::XML::Node>] list of nodes with global ids
378
+ #
379
+ def self.find_global_ids_nodes(xhtml_doc)
380
+ xhtml_doc
381
+ .css('[id^="$"]')
382
+ end
383
+
384
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
385
+ # @return [Array<string>] list of global ids (without dollar signs)
386
+ #
387
+ def self.find_global_ids(xhtml_doc)
388
+ find_global_ids_nodes(xhtml_doc)
389
+ .map { |node| node['id'][1..-1] }
390
+ end
391
+
392
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
393
+ # @return [Array<Nokogiri::XML::Node>] list of nodes with global links
394
+ #
395
+ def self.find_global_links_nodes(xhtml_doc)
396
+ xhtml_doc
397
+ .css('[href^="$"]')
398
+ end
399
+
400
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
401
+ # @return [Array<string>] list of global ids (without dollar signs)
402
+ #
403
+ def self.find_global_links(xhtml_doc)
404
+ find_global_links_nodes(xhtml_doc)
405
+ .map { |node| node['href'][1..-1] }
406
+ end
376
407
  end
377
408
  end
378
409
  end
@@ -39,8 +39,8 @@ module Epuber
39
39
  #
40
40
  attr_reader :compilation_context
41
41
 
42
- # @param book [Epuber::Book::Book]
43
- # @param target [Epuber::Book::Target]
42
+ # @param [Epuber::Book::Book] book
43
+ # @param [Epuber::Book::Target] target
44
44
  #
45
45
  def initialize(book, target)
46
46
  @book = book
@@ -50,7 +50,7 @@ module Epuber
50
50
 
51
51
  # Compile target to build folder
52
52
  #
53
- # @param build_folder [String] path to folder, where will be stored all compiled files
53
+ # @param [String] build_folder path to folder, where will be stored all compiled files
54
54
  # @param [Bool] check should run non-release checkers
55
55
  # @param [Bool] write should perform transformations of source files and write them back
56
56
  # @param [Bool] release this is release build
@@ -71,22 +71,35 @@ module Epuber
71
71
 
72
72
  FileUtils.mkdir_p(build_folder)
73
73
 
74
- puts " handling target #{@target.name.inspect} in build dir `#{Config.instance.pretty_path_from_project(build_folder)}`"
74
+ UI.puts " #{<<~MSG}"
75
+ building target #{@target.name.inspect} (build dir: #{Config.instance.pretty_path_from_project(build_folder)})
76
+ MSG
75
77
 
76
- file_resolver.add_file(FileTypes::SourceFile.new(Config.instance.pretty_path_from_project(@book.file_path).to_s))
78
+ file_resolver.add_file(FileTypes::SourceFile.new(Config.instance.pretty_path_from_project(@book.file_path)))
77
79
  compilation_context.plugins
78
80
 
81
+ # validate bookspec
82
+ compilation_context.perform_plugin_things(Checker, :bookspec) do |checker|
83
+ checker.call(@book, compilation_context)
84
+ end
85
+
79
86
  parse_toc_item(@target.root_toc)
80
87
  parse_target_file_requests
81
88
 
82
89
  process_all_target_files
83
90
  generate_other_files
84
91
 
92
+ compilation_context.perform_plugin_things(Transformer, :after_all_text_files) do |transformer|
93
+ transformer.call(@book, compilation_context)
94
+ end
95
+
96
+ process_global_ids
97
+
85
98
  # build folder cleanup
86
99
  remove_unnecessary_files
87
100
  remove_empty_folders
88
101
 
89
- source_paths = file_resolver.files.select { |a| a.is_a?(FileTypes::SourceFile) }.map { |a| a.source_path }
102
+ source_paths = file_resolver.files.select { |a| a.is_a?(FileTypes::SourceFile) }.map(&:source_path)
90
103
  compilation_context.source_file_database.cleanup(source_paths)
91
104
  compilation_context.source_file_database.update_all_metadata
92
105
  compilation_context.source_file_database.save_to_file
@@ -101,7 +114,7 @@ module Epuber
101
114
 
102
115
  # Archives current target files to epub
103
116
  #
104
- # @param path [String] path to created archive
117
+ # @param [String] path path to created archive
105
118
  #
106
119
  # @return [String] path
107
120
  #
@@ -118,7 +131,7 @@ module Epuber
118
131
  old_paths = zip_file.instance_eval { @entry_set.entries.map(&:name) }
119
132
  diff = old_paths - new_paths
120
133
  diff.each do |file_to_remove|
121
- puts "DEBUG: removing file from result EPUB: #{file_to_remove}" if compilation_context.verbose?
134
+ UI.puts "DEBUG: removing file from result EPUB: #{file_to_remove}" if compilation_context.verbose?
122
135
  zip_file.remove(file_to_remove)
123
136
  end
124
137
  end
@@ -147,7 +160,7 @@ module Epuber
147
160
  epub_name += @book.build_version.to_s unless @book.build_version.nil?
148
161
  epub_name += "-#{@target.name}" if @target != @book.default_target
149
162
  epub_name += "-#{configuration_suffix}" unless configuration_suffix.nil?
150
- epub_name + '.epub'
163
+ "#{epub_name}.epub"
151
164
  end
152
165
 
153
166
 
@@ -158,21 +171,25 @@ module Epuber
158
171
  def remove_empty_folders
159
172
  Dir.chdir(@file_resolver.destination_path) do
160
173
  Dir.glob('**/*')
161
- .select { |d| File.directory?(d) }
162
- .select { |d| (Dir.entries(d) - %w(. ..)).empty? }
163
- .each do |d|
164
- puts "DEBUG: removing empty folder `#{d}`" if compilation_context.verbose?
165
- Dir.rmdir(d)
166
- end
174
+ .select { |d| File.directory?(d) }
175
+ .select { |d| (Dir.entries(d) - %w[. ..]).empty? }
176
+ .each do |d|
177
+ UI.puts "DEBUG: removing empty folder `#{d}`" if compilation_context.verbose?
178
+ Dir.rmdir(d)
179
+ end
167
180
  end
168
181
  end
169
182
 
170
183
  # @return nil
171
184
  #
172
185
  def remove_unnecessary_files
173
- unnecessary_paths = @file_resolver.unneeded_files_in_destination.map { |path| File.join(@file_resolver.destination_path, path) }
186
+ unnecessary_paths = @file_resolver.unneeded_files_in_destination.map do |path|
187
+ File.join(@file_resolver.destination_path, path)
188
+ end
174
189
  unnecessary_paths.each do |path|
175
- puts "DEBUG: removing unnecessary file: `#{Config.instance.pretty_path_from_project(path)}`" if compilation_context.verbose?
190
+ if compilation_context.verbose?
191
+ UI.puts "DEBUG: removing unnecessary file: `#{Config.instance.pretty_path_from_project(path)}`"
192
+ end
176
193
 
177
194
  File.delete(path)
178
195
  end
@@ -201,11 +218,11 @@ module Epuber
201
218
  @file_resolver.add_file(container_xml)
202
219
  process_file(container_xml)
203
220
 
204
- if @target.epub_version >= 2.0 && @target.epub_version < 3.0
205
- ibooks_options = FileTypes::IBooksDisplayOptionsFile.new
206
- @file_resolver.add_file(ibooks_options)
207
- process_file(ibooks_options)
208
- end
221
+ return unless @target.epub_version >= 2.0 && @target.epub_version < 3.0
222
+
223
+ ibooks_options = FileTypes::IBooksDisplayOptionsFile.new
224
+ @file_resolver.add_file(ibooks_options)
225
+ process_file(ibooks_options)
209
226
  end
210
227
 
211
228
  # @return nil
@@ -239,6 +256,7 @@ module Epuber
239
256
  # add missing files to file_resolver
240
257
  paths.each do |path|
241
258
  next if file_resolver.file_with_source_path(path)
259
+
242
260
  file_resolver.add_file(FileTypes::SourceFile.new(path))
243
261
  end
244
262
 
@@ -247,7 +265,7 @@ module Epuber
247
265
 
248
266
  # add all activated plugin files
249
267
  paths += compilation_context.plugins.map do |plugin|
250
- plugin.files.map { |p_file| p_file.source_path }
268
+ plugin.files.map(&:source_path)
251
269
  end.flatten
252
270
 
253
271
  # add dependencies to databases
@@ -272,7 +290,7 @@ module Epuber
272
290
  UI.processing_files_done
273
291
  end
274
292
 
275
- # @param toc_item [Epuber::Book::TocItem]
293
+ # @param [Epuber::Book::TocItem] toc_item
276
294
  #
277
295
  def parse_toc_item(toc_item)
278
296
  unless toc_item.file_request.nil?
@@ -286,7 +304,41 @@ module Epuber
286
304
  end
287
305
  end
288
306
 
289
- # @param cmd [String]
307
+ 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)
310
+
311
+ xhtml_files.each do |file|
312
+ file.process_global_ids(compilation_context, global_ids)
313
+ end
314
+ end
315
+
316
+ # Validates duplicity of global ids in all files + returns map of global ids to files
317
+ #
318
+ # @param [Array<FileTypes::XHTMLFile>] xhtml_files
319
+ # @return [Hash<String, FileTypes::XHTMLFile>]
320
+ #
321
+ def validate_global_ids(xhtml_files)
322
+ map = {}
323
+ xhtml_files.each do |file|
324
+ file.global_ids.each do |id|
325
+ if map.key?(id)
326
+ message = "Duplicate global id `#{id}` in file `#{file.source_path}`."
327
+ if compilation_context.release_build?
328
+ UI.error!(message)
329
+ else
330
+ UI.warning("#{message} Will fail during release build.")
331
+ end
332
+ end
333
+
334
+ map[id] = file
335
+ end
336
+ end
337
+
338
+ map
339
+ end
340
+
341
+ # @param [String] cmd
290
342
  #
291
343
  # @return [void]
292
344
  #
data/lib/epuber/config.rb CHANGED
@@ -14,7 +14,7 @@ module Epuber
14
14
  @project_path ||= Dir.pwd.unicode_normalize
15
15
  end
16
16
 
17
- # @param of_file [String] absolute path to file
17
+ # @param [String] of_file absolute path to file
18
18
  #
19
19
  # @return [String] relative path to file from root of project
20
20
  #
@@ -60,9 +60,11 @@ module Epuber
60
60
  #
61
61
  # @return [Epuber::Book]
62
62
  #
63
- def bookspec=(bookspec)
64
- @bookspec = bookspec
65
- end
63
+ attr_writer :bookspec
64
+
65
+ # @return [Boolean]
66
+ #
67
+ attr_accessor :release_build
66
68
 
67
69
  # @return [Epuber::Lockfile]
68
70
  #
@@ -82,7 +84,7 @@ module Epuber
82
84
  bookspec_lockfile.write_to_file
83
85
  end
84
86
 
85
- # @param target [Epuber::Book::Target]
87
+ # @param [Epuber::Book::Target] target
86
88
  #
87
89
  # @return [String]
88
90
  #
@@ -90,7 +92,7 @@ module Epuber
90
92
  File.join(working_path, 'build', target.name.to_s)
91
93
  end
92
94
 
93
- # @param target [Epuber::Book::Target]
95
+ # @param [Epuber::Book::Target] target
94
96
  #
95
97
  # @return [String]
96
98
  #
@@ -122,12 +124,16 @@ module Epuber
122
124
 
123
125
  def warn_for_outdated_versions!
124
126
  if bookspec_lockfile.epuber_version > Epuber::VERSION
125
- UI.warning('Warning: the running version of Epuber is older than the version that created the lockfile. We suggest you upgrade to the latest version of Epuber by running `gem install epuber`.')
127
+ UI.warning(<<~MSG.rstrip)
128
+ Warning: the running version of Epuber is older than the version that created the lockfile. We suggest you upgrade to the latest version of Epuber by running `gem install epuber`.
129
+ MSG
126
130
  end
127
131
 
128
- if bookspec_lockfile.bade_version && bookspec_lockfile.bade_version > Bade::VERSION
129
- UI.warning('Warning: the running version of Bade is older than the version that created the lockfile. We suggest you upgrade to the latest version of Bade by running `gem install bade`.')
130
- end
132
+ return unless bookspec_lockfile.bade_version && bookspec_lockfile.bade_version > Bade::VERSION
133
+
134
+ UI.warning(<<~MSG.rstrip)
135
+ Warning: the running version of Bade is older than the version that created the lockfile. We suggest you upgrade to the latest version of Bade by running `gem install bade`.
136
+ MSG
131
137
  end
132
138
 
133
139
  def same_version_as_last_run?
@@ -17,7 +17,7 @@ module Epuber
17
17
 
18
18
  # Returns a new attribute initialized with the given options.
19
19
  #
20
- # @param name [Symbol]
20
+ # @param [Symbol] name
21
21
  #
22
22
  # @see #name
23
23
  #
@@ -109,25 +109,25 @@ module Epuber
109
109
  # is not specified.
110
110
  #
111
111
  attr_reader :required
112
- alias_method :required?, :required
112
+ alias required? required
113
113
 
114
114
  # @return [Bool] whether the attribute should be specified only on the root specification.
115
115
  #
116
116
  attr_reader :root_only
117
- alias_method :root_only?, :root_only
117
+ alias root_only? root_only
118
118
 
119
119
 
120
120
  # @return [Bool] whether there should be a singular alias for the attribute writer.
121
121
  #
122
122
  attr_reader :singularize
123
- alias_method :singularize?, :singularize
123
+ alias singularize? singularize
124
124
 
125
125
  # @return [Bool] whether the attribute describes file patterns.
126
126
  #
127
127
  # @note This is mostly used by the linter.
128
128
  #
129
129
  attr_reader :file_patterns
130
- alias_method :file_patterns?, :file_patterns
130
+ alias file_patterns? file_patterns
131
131
 
132
132
  # @return [Bool] defines whether the attribute reader should join the values with the parent.
133
133
  #
@@ -184,25 +184,24 @@ module Epuber
184
184
  raise StandardError, "Can't set `#{name}` attribute for subspecs (in `#{spec.name}`)."
185
185
  end
186
186
 
187
- if keys
188
- value.keys.each do |key|
189
- unless allowed_keys.include?(key)
190
- raise StandardError, "Unknown key `#{key}` for #{self}. Allowed keys: `#{allowed_keys.inspect}`"
191
- end
192
- end
193
- end
187
+ return unless keys
194
188
 
195
189
  # @return [Array] the flattened list of the allowed keys for the hash of a given specification.
196
190
  #
197
- def allowed_keys
191
+ allowed_keys = lambda do
198
192
  if keys.is_a?(Hash)
199
193
  keys.keys.concat(keys.values.flatten.compact)
200
194
  else
201
195
  keys
202
196
  end
203
197
  end
204
- end
205
198
 
199
+ value.each_key do |key|
200
+ unless allowed_keys.include?(key)
201
+ raise StandardError, "Unknown key `#{key}` for #{self}. Allowed keys: `#{allowed_keys.inspect}`"
202
+ end
203
+ end
204
+ end
206
205
 
207
206
  #---------------------------------------------------------------------#
208
207
 
@@ -221,15 +220,14 @@ module Epuber
221
220
  validate_type(value)
222
221
  rescue StandardError
223
222
  raise if @auto_convert.nil?
223
+
224
224
  dest_class = @auto_convert[value.class]
225
225
 
226
226
  if dest_class.nil?
227
227
  array_keys = @auto_convert.select { |k, _v| k.is_a?(Array) }
228
228
  array_keys_with_type = array_keys.select { |k, _v| k.any? { |klass| value.class <= klass } }
229
229
 
230
- if array_keys_with_type.count > 0
231
- dest_class = array_keys_with_type.values.first
232
- end
230
+ dest_class = array_keys_with_type.values.first if array_keys_with_type.count.positive?
233
231
  end
234
232
 
235
233
  if dest_class.respond_to?(:call)
@@ -241,7 +239,8 @@ module Epuber
241
239
  elsif dest_class.respond_to?(:new)
242
240
  return dest_class.new(value)
243
241
  else
244
- raise StandardError, "Object/class #{dest_class} doesn't support any convert method (#call, .parse or implicit .new)"
242
+ raise StandardError,
243
+ "Object/class #{dest_class} doesn't support any convert method (#call, .parse or implicit .new)"
245
244
  end
246
245
  end
247
246
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  module Epuber
5
4
  module DSL
6
5
  module AttributeSupport
@@ -10,8 +9,8 @@ module Epuber
10
9
  # attribute :name
11
10
  # attribute :title, required: true, inherited: true
12
11
  #
13
- # @param name [Symbol] attribute name
14
- # @param options [Hash]
12
+ # @param [Symbol] name attribute name
13
+ # @param [Hash] options
15
14
  #
16
15
  # @see Attribute
17
16
  #
@@ -45,8 +44,8 @@ module Epuber
45
44
  end
46
45
  end
47
46
 
48
- # @param name [Symbol]
49
- # @param attr [Epuber::DSL::Attribute]
47
+ # @param [Symbol] name
48
+ # @param [Epuber::DSL::Attribute] attr
50
49
  #
51
50
  # @return nil
52
51
  #
@@ -86,8 +85,9 @@ module Epuber
86
85
  else
87
86
  begin
88
87
  @attributes_values[key] = attr.converted_value(value)
89
- rescue Exception => e
90
- UI.warning("Invalid value `#{value}` for attribute `#{name}`, original error `#{e}`", location: caller_locations[1])
88
+ rescue StandardError => e
89
+ UI.warning("Invalid value `#{value}` for attribute `#{name}`, original error `#{e}`",
90
+ location: caller_locations[1])
91
91
  end
92
92
  end
93
93
  end
@@ -49,17 +49,16 @@ module Epuber
49
49
 
50
50
  next unless attr.required? && value.nil?
51
51
 
52
- if attr.singularize?
53
- raise ValidationError, "missing required attribute `#{key.to_s.singularize}|#{key}`"
54
- else
55
- raise ValidationError, "missing required attribute `#{key}`"
56
- end
52
+ raise ValidationError, "missing required attribute `#{key.to_s.singularize}|#{key}`" if attr.singularize?
53
+
54
+
55
+ raise ValidationError, "missing required attribute `#{key}`"
57
56
  end
58
57
  end
59
58
 
60
59
  # Creates new instance by parsing ruby code from file
61
60
  #
62
- # @param file_path [String]
61
+ # @param [String] file_path
63
62
  #
64
63
  # @return [Self]
65
64
  #
@@ -69,26 +68,21 @@ module Epuber
69
68
 
70
69
  # Creates new instance by parsing ruby code from string
71
70
  #
72
- # @param string [String]
71
+ # @param [String] string
73
72
  #
74
73
  # @return [Self]
75
74
  #
76
75
  def self.from_string(string, file_path = nil)
77
- # rubocop:disable Lint/Eval
78
76
  obj = if file_path
79
- eval(string, nil, file_path)
77
+ eval(string, nil, file_path) # rubocop:disable Security/Eval
80
78
  else
81
- eval(string)
79
+ eval(string) # rubocop:disable Security/Eval
82
80
  end
83
- # rubocop:enable Lint/Eval
84
81
 
85
82
  unless obj.is_a?(self)
86
83
  msg = "Invalid object #{obj.class}, expected object of class #{self}"
87
84
 
88
- if file_path
89
- msg += ", loaded from file #{file_path}"
90
-
91
- end
85
+ msg += ", loaded from file #{file_path}" if file_path
92
86
 
93
87
  raise StandardError, msg
94
88
  end
@@ -121,6 +115,10 @@ module Epuber
121
115
  #
122
116
  attr_accessor :attributes_values
123
117
 
118
+ def respond_to_missing?(name, include_private = false)
119
+ @attributes_values.key?(name) || super
120
+ end
121
+
124
122
  # Raise exception when there is used some unknown method or attribute
125
123
  #
126
124
  # This is just for creating better message in raised exception
@@ -129,9 +127,13 @@ module Epuber
129
127
  #
130
128
  def method_missing(name, *args)
131
129
  if /([^=]+)=?/ =~ name
132
- attr_name = $1
130
+ attr_name = ::Regexp.last_match(1)
133
131
  location = caller_locations.first
134
- raise NameError, "Unknown attribute or method `#{attr_name}` for class `#{self.class}` in file `#{location.path}:#{location.lineno}`"
132
+ message = <<~MSG
133
+ Unknown attribute or method `#{attr_name}` for class `#{self.class}` in file `#{location.path}:#{location.lineno}`
134
+ MSG
135
+
136
+ raise NameError, message
135
137
  else
136
138
  super
137
139
  end