epuber 0.7.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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