epuber 0.3.12 → 0.4.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/epuber.gemspec +13 -15
  4. data/lib/epuber/book.rb +35 -4
  5. data/lib/epuber/book/target.rb +41 -2
  6. data/lib/epuber/command.rb +5 -0
  7. data/lib/epuber/command/build.rb +155 -0
  8. data/lib/epuber/command/compile.rb +5 -131
  9. data/lib/epuber/command/init.rb +19 -7
  10. data/lib/epuber/command/server.rb +1 -1
  11. data/lib/epuber/compiler.rb +56 -2
  12. data/lib/epuber/compiler/compilation_context.rb +27 -1
  13. data/lib/epuber/compiler/file_database.rb +138 -0
  14. data/lib/epuber/compiler/file_finders/abstract.rb +3 -1
  15. data/lib/epuber/compiler/file_resolver.rb +6 -2
  16. data/lib/epuber/compiler/file_stat.rb +76 -0
  17. data/lib/epuber/compiler/file_types/abstract_file.rb +3 -37
  18. data/lib/epuber/compiler/file_types/bade_file.rb +63 -4
  19. data/lib/epuber/compiler/file_types/coffee_script_file.rb +24 -0
  20. data/lib/epuber/compiler/file_types/generated_file.rb +3 -1
  21. data/lib/epuber/compiler/file_types/image_file.rb +6 -4
  22. data/lib/epuber/compiler/file_types/source_file.rb +63 -1
  23. data/lib/epuber/compiler/file_types/static_file.rb +2 -2
  24. data/lib/epuber/compiler/file_types/stylus_file.rb +15 -0
  25. data/lib/epuber/compiler/file_types/xhtml_file.rb +71 -13
  26. data/lib/epuber/compiler/opf_generator.rb +1 -0
  27. data/lib/epuber/compiler/xhtml_processor.rb +94 -17
  28. data/lib/epuber/config.rb +47 -14
  29. data/lib/epuber/plugin.rb +6 -1
  30. data/lib/epuber/server.rb +35 -11
  31. data/lib/epuber/server/handlers.rb +2 -1
  32. data/lib/epuber/server/pages/book.bade +6 -2
  33. data/lib/epuber/server/pages/common.bade +5 -7
  34. data/lib/epuber/user_interface.rb +36 -4
  35. data/lib/epuber/vendor/version.rb +2 -2
  36. data/lib/epuber/version.rb +3 -1
  37. metadata +49 -57
@@ -59,6 +59,7 @@ module Epuber
59
59
  navigation: 'nav',
60
60
  scripted: 'scripted',
61
61
  remote_resources: 'remote-resources',
62
+ mathml: 'mathml',
62
63
  }.freeze
63
64
 
64
65
  OPF_UNIQUE_ID = 'bookid'
@@ -26,23 +26,41 @@ module Epuber
26
26
  text = text.lstrip
27
27
  end
28
28
 
29
- doc = Nokogiri::XML(text)
29
+ xml_header = ''
30
+ if /\A\s*(<\?xml[^>]*\?>)/ =~ text
31
+ match = Regexp.last_match
32
+ xml_header = text[match.begin(1)...match.end(1)]
33
+ text[match.begin(1)...match.end(1)] = ''
34
+ end
35
+
36
+ doctypes = []
37
+ while /(\n|\?>|\A)?(<![^>]*>\n*)/ =~ text
38
+ doctypes << $2.strip
39
+
40
+ match = Regexp.last_match
41
+ text[match.begin(2)...match.end(2)] = ''
42
+ end
43
+
44
+ before = ([xml_header] + doctypes).compact.join("\n")
45
+ unless before.empty?
46
+ before = before + "\n"
47
+ end
48
+
49
+ parse_options = Nokogiri::XML::ParseOptions::DEFAULT_XML |
50
+ Nokogiri::XML::ParseOptions::NOERROR | # to silence any errors or warnings printing into console
51
+ Nokogiri::XML::ParseOptions::NOWARNING
52
+
53
+ doc = Nokogiri::XML("#{before}<root>#{text}</root>", nil, nil, parse_options)
30
54
  doc.encoding = 'UTF-8'
31
55
  doc.file_path = file_path
32
56
 
33
- fragment = Nokogiri::XML.fragment(text)
34
- root_elements = fragment.children.select { |el| el.element? }
57
+ root = root_node = doc.root
58
+ root_elements = root.children.select { |a| a.element? || a.comment? }
35
59
 
36
60
  if root_elements.count == 1
37
61
  doc.root = root_elements.first
38
- elsif fragment.at_css('body').nil?
39
- doc.root = doc.create_element('body')
40
-
41
- fragment.children.select do |child|
42
- child.element? || child.comment? || child.text?
43
- end.each do |child|
44
- doc.root.add_child(child)
45
- end
62
+ elsif root_node.at_css('body').nil?
63
+ root_node.node_name = 'body'
46
64
  end
47
65
 
48
66
  doc
@@ -105,6 +123,24 @@ module Epuber
105
123
  end
106
124
  end
107
125
 
126
+ # Method for adding scripts with links, method will not add duplicate items
127
+ #
128
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
129
+ # @param [Array<String>] styles links to files
130
+ #
131
+ # @return nil
132
+ #
133
+ def self.add_scripts(xhtml_doc, scripts)
134
+ head = xhtml_doc.at_css('html > head')
135
+ old_links = head.css('script').map { |node| node['src'] }
136
+
137
+ links_to_add = scripts - old_links
138
+
139
+ links_to_add.each do |path|
140
+ head << xhtml_doc.create_element('script', src: path, type: 'text/javascript')
141
+ end
142
+ end
143
+
108
144
  # Adds viewport meta tag to head of some document, but only if there is not some existing tag
109
145
  #
110
146
  # @param [Nokogiri::XML::Document] xhtml_doc
@@ -222,6 +258,20 @@ module Epuber
222
258
  result
223
259
  end
224
260
 
261
+ # @param [Nokogiri::XML::Document] xhtml_doc input XML document to work with
262
+ #
263
+ # @return [Bool]
264
+ #
265
+ def self.using_mathml?(xhtml_doc)
266
+ !xhtml_doc.at_css('math|math', 'math' => 'http://www.w3.org/1998/Math/MathML').nil?
267
+ end
268
+
269
+ def self.resolve_mathml_namespace(xhtml_doc)
270
+ xhtml_doc.css('math').each do |math_node|
271
+ math_node.add_namespace('xmlns', 'http://www.w3.org/1998/Math/MathML')
272
+ end
273
+ end
274
+
225
275
  # @param [Nokogiri::XML::Document] xhtml_doc
226
276
  # @param [String] file_path path of referring file
227
277
  # @param [FileResolver] file_resolver
@@ -229,25 +279,52 @@ module Epuber
229
279
  # @return nil
230
280
  #
231
281
  def self.resolve_images(xhtml_doc, file_path, file_resolver)
282
+ resolve_resources_in('img', 'src', :image, xhtml_doc, file_path, file_resolver)
283
+ end
284
+
285
+ # @param [Nokogiri::XML::Document] xhtml_doc
286
+ # @param [String] file_path path of referring file
287
+ # @param [FileResolver] file_resolver
288
+ #
289
+ # @return nil
290
+ #
291
+ def self.resolve_scripts(xhtml_doc, file_path, file_resolver)
292
+ resolve_resources_in('script', 'src', :script, xhtml_doc, file_path, file_resolver)
293
+ end
294
+
295
+ # @param [Nokogiri::XML::Document] xhtml_doc
296
+ # @param [String] file_path path of referring file
297
+ # @param [FileResolver] file_resolver
298
+ #
299
+ # @return nil
300
+ #
301
+ def self.resolve_stylesheets(xhtml_doc, file_path, file_resolver)
302
+ resolve_resources_in('link[rel="stylesheet"]', 'href', :style, xhtml_doc, file_path, file_resolver)
303
+ end
304
+
305
+ def self.resolve_resources_in(node_css_query, attribute_name, resource_group, xhtml_doc, file_path, file_resolver)
232
306
  dirname = File.dirname(file_path)
233
307
 
234
- xhtml_doc.css('img').each do |img|
235
- path = img['src']
308
+ xhtml_doc.css(node_css_query).each do |img|
309
+ path = img[attribute_name]
236
310
  next if path.nil?
237
311
 
238
312
  begin
239
- new_path = file_resolver.dest_finder.find_file(path, groups: :image, context_path: dirname)
313
+ new_path = file_resolver.dest_finder.find_file(path, groups: resource_group, context_path: dirname)
240
314
 
241
315
  rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError
242
316
  begin
243
- new_path = resolved_link_to_file(path, :image, dirname, file_resolver.source_finder).to_s
317
+ new_path = resolved_link_to_file(path, resource_group, dirname, file_resolver.source_finder).to_s
244
318
  pkg_abs_path = File.expand_path(new_path, dirname).unicode_normalize
245
319
  pkg_new_path = Pathname.new(pkg_abs_path).relative_path_from(Pathname.new(file_resolver.source_path)).to_s
246
320
 
247
- file = FileTypes::ImageFile.new(pkg_new_path)
321
+ file_class = FileResolver.file_class_for(File.extname(new_path))
322
+ file = file_class.new(pkg_new_path)
248
323
  file.path_type = :manifest
249
324
  file_resolver.add_file(file)
250
325
 
326
+ new_path = FileResolver::renamed_file_with_path(new_path)
327
+
251
328
  rescue UnparseableLinkError, FileFinders::FileNotFoundError, FileFinders::MultipleFilesFoundError => e
252
329
  UI.warning(e.to_s, location: img)
253
330
 
@@ -255,7 +332,7 @@ module Epuber
255
332
  end
256
333
  end
257
334
 
258
- img['src'] = new_path
335
+ img[attribute_name] = new_path
259
336
  end
260
337
  end
261
338
  end
data/lib/epuber/config.rb CHANGED
@@ -19,7 +19,7 @@ module Epuber
19
19
  # @return [String] relative path to file from root of project
20
20
  #
21
21
  def pretty_path_from_project(of_file)
22
- Pathname.new(of_file.unicode_normalize).relative_path_from(Pathname.new(project_path))
22
+ Pathname.new(of_file.unicode_normalize).relative_path_from(Pathname.new(project_path)).to_s
23
23
  end
24
24
 
25
25
  # @return [String]
@@ -53,14 +53,7 @@ module Epuber
53
53
  # @return [Epuber::Book]
54
54
  #
55
55
  def bookspec
56
- require_relative 'book'
57
- @bookspec ||= (
58
- book = Epuber::Book.from_file(bookspec_path)
59
- book.finish_toc
60
- book.validate
61
- book.freeze
62
- book
63
- )
56
+ @bookspec ||= self.class.load_bookspec(bookspec_path)
64
57
  end
65
58
 
66
59
  # @param [Epuber::Book] bookspec
@@ -103,22 +96,62 @@ module Epuber
103
96
  File.join(working_path, 'release_build', target.name.to_s)
104
97
  end
105
98
 
106
- # ---------------------------------------------------------------------------------------------------------------- #
99
+ # @param [String] cache_name
100
+ #
101
+ # @return [String]
102
+ #
103
+ def build_cache_path(cache_name)
104
+ File.join(working_path, 'build_cache', cache_name)
105
+ end
107
106
 
108
- # Singleton
107
+ # @return [String]
109
108
  #
110
- # @return [Epuber::Config]
109
+ def file_stat_database_path
110
+ File.join(working_path, 'metadata', 'source_file_stats.yml')
111
+ end
112
+
113
+ # @param [Epuber::Book::Target] target
114
+ #
115
+ # @return [String]
111
116
  #
112
- def self.instance
113
- @instance ||= new
117
+ def target_file_stat_database_path(target)
118
+ File.join(working_path, 'metadata', 'target_stats', target.name.to_s, 'file_stats.yml')
114
119
  end
115
120
 
121
+ # ---------------------------------------------------------------------------------------------------------------- #
122
+
116
123
  class << self
117
124
  attr_accessor :test
118
125
 
119
126
  def test?
120
127
  test
121
128
  end
129
+
130
+ # Singleton
131
+ #
132
+ # @return [Epuber::Config]
133
+ #
134
+ def instance
135
+ @instance ||= new
136
+ end
137
+
138
+ def clear_instance!
139
+ @instance = nil
140
+ end
141
+
142
+ # @return [Epuber::Book]
143
+ #
144
+ def load_bookspec(path, frozen: true)
145
+ require_relative 'book'
146
+
147
+ book = Epuber::Book.from_file(path)
148
+ book.finish_toc
149
+ book.validate
150
+
151
+ book.freeze if frozen
152
+
153
+ book
154
+ end
122
155
  end
123
156
 
124
157
  self.test = false
data/lib/epuber/plugin.rb CHANGED
@@ -75,11 +75,16 @@ module Epuber
75
75
  [PluginFile.new(path)]
76
76
  elsif ::File.directory?(path)
77
77
  Dir.glob(File.expand_path('**/*.rb', path)).map do |file_path|
78
- PluginFile.new(file_path)
78
+ PluginFile.new(Config.instance.pretty_path_from_project(file_path))
79
79
  end
80
80
  else
81
81
  raise LoadError, "#{self}: Can't find anything for #{path}"
82
82
  end
83
+
84
+ # expand abs_source_paths to every file
85
+ @files.each do |file|
86
+ file.abs_source_path = File.expand_path(file.source_path, Config.instance.project_path)
87
+ end
83
88
  end
84
89
 
85
90
  # @param [Class] klass base class of all instances
data/lib/epuber/server.rb CHANGED
@@ -105,6 +105,8 @@ module Epuber
105
105
  # @return nil
106
106
  #
107
107
  def self.run!(book, target, verbose: false)
108
+ Encoding.default_internal = Encoding::UTF_8 if Encoding.default_internal.nil?
109
+
108
110
  self.book = book
109
111
  self.target = target
110
112
  self.verbose = verbose
@@ -118,7 +120,13 @@ module Epuber
118
120
  $stderr = old_stderr
119
121
  puts "Started development server on #{server.host}:#{server.port}"
120
122
 
121
- yield URI("http://#{server.host}:#{server.port}") if block_given?
123
+ host = if server.host == '0.0.0.0'
124
+ 'localhost'
125
+ else
126
+ server.host
127
+ end
128
+
129
+ yield URI("http://#{host}:#{server.port}") if block_given?
122
130
  end
123
131
  end
124
132
 
@@ -306,6 +314,21 @@ module Epuber
306
314
  head.add_child(node)
307
315
  end
308
316
 
317
+ # @param html_doc [Nokogiri::HTML::Document]
318
+ # @param key [String]
319
+ # @param value [String]
320
+ #
321
+ def add_meta_to_head(name, content, html_doc, force: false)
322
+ head = html_doc.at_css('head')
323
+ meta = head.at_css("meta[name=\"#{name}\"]")
324
+ return if force == false && !meta.nil?
325
+
326
+ meta ||= html_doc.create_element('meta')
327
+ meta['name'] = name
328
+ meta['content'] = content
329
+ head.add_child(meta)
330
+ end
331
+
309
332
  def self.reload_bookspec
310
333
  last_target = target
311
334
  Config.instance.bookspec = nil
@@ -341,12 +364,13 @@ module Epuber
341
364
  @compilation_thread = nil
342
365
  end
343
366
 
367
+ @compilation_thread = Thread.new do
368
+ result = _compile_book
369
+ completion.call(result) unless completion.nil?
370
+ end
371
+
344
372
  if completion.nil?
345
- _compile_book
346
- else
347
- @compilation_thread = Thread.new do
348
- completion.call(_compile_book)
349
- end
373
+ @compilation_thread.join
350
374
  end
351
375
  end
352
376
 
@@ -387,7 +411,7 @@ module Epuber
387
411
  # @param _removed [Array<String>]
388
412
  #
389
413
  def self.changes_detected(_modified, _added, _removed)
390
- all_changed = (_modified + _added + _removed).uniq
414
+ all_changed = (_modified + _added + _removed).uniq.map { |path| path.unicode_normalize }
391
415
 
392
416
  reload_bookspec if all_changed.any? { |file| file == book.file_path }
393
417
 
@@ -416,11 +440,11 @@ module Epuber
416
440
  # remove nil paths (for example bookspec can't be found so the relative path is nil)
417
441
  changed.compact!
418
442
 
419
- if changed.size > 0 && changed.all? { |file| file.end_with?(*Epuber::Compiler::FileFinders::GROUP_EXTENSIONS[:style]) }
420
- notify_clients(:styles, changed)
421
- else
443
+ # if changed.size > 0 && changed.all? { |file| file.end_with?(*Epuber::Compiler::FileFinders::GROUP_EXTENSIONS[:style]) }
444
+ # notify_clients(:styles, changed)
445
+ # else
422
446
  notify_clients(:reload, changed)
423
- end
447
+ # end
424
448
  end
425
449
  end
426
450
 
@@ -19,6 +19,7 @@ module Epuber
19
19
  add_file_to_head(:style, html_doc, 'book_content.styl')
20
20
 
21
21
  add_file_to_head(:js, html_doc, 'support.coffee')
22
+ add_meta_to_head(:viewport, 'width=device-width, initial-scale=1.0', html_doc)
22
23
  add_auto_refresh_script(html_doc)
23
24
 
24
25
  unless file_resolver.nil?
@@ -31,7 +32,7 @@ module Epuber
31
32
  end
32
33
  end
33
34
 
34
- [200, html_doc.to_html]
35
+ [200, html_doc.to_xhtml]
35
36
  end
36
37
 
37
38
  # @param [String] file_name name of the file located in ./pages/
@@ -5,11 +5,15 @@ mixin book_meta(key, format: nil)
5
5
  tr
6
6
  - value = book.send(key.to_sym)
7
7
  - if value.is_a?(Array)
8
- - value = value.map(&format).join(', ')
8
+ - value = value.compact.map(&format).join(', ')
9
9
  - end
10
10
 
11
11
  - unless value.nil?
12
12
  td.book_meta_key= key.gsub('_', ' ')
13
+ - if value.to_s.empty?
14
+ - value = '&nbsp;'
15
+ - end
16
+
13
17
  td.book_meta_value= value
14
18
  - end
15
19
 
@@ -67,7 +71,7 @@ mixin iterate_spine(toc_item)
67
71
 
68
72
 
69
73
  +section('Targets')
70
- - book.targets.each do |b_target|
74
+ - book.all_targets.each do |b_target|
71
75
  p(class: 'target_selected' if target == b_target): a(href: "/change_target/#{b_target.name}")= b_target.name
72
76
  - end
73
77
 
@@ -19,22 +19,20 @@ mixin body_header(current)
19
19
  #header
20
20
  #header_content
21
21
  ul
22
- li(class: 'selected' if current == 'home' || current.nil?): a(href: '/')= book.title
22
+ li(class: 'selected' if current == 'home' || current.nil?): a(href: '/') Home
23
23
  li(class: 'selected' if current == 'toc'): a(href: '/toc/') Toc
24
24
  li(class: 'selected' if current == 'files'): a(href: '/files/') Files
25
25
 
26
26
  mixin body_footer
27
27
  #footer
28
- p Epuber v#{Epuber::VERSION} | © #{Time.now.year} Roman Kříž
28
+ p
29
+ a(href: Epuber::HOME_URL) Epuber v#{Epuber::VERSION}
30
+ | | © #{Time.now.year} Roman Kříž
29
31
 
30
32
  mixin page(title: nil, header_id: nil)
31
33
  html
32
34
  head
33
- - if title.nil?
34
- title #{book.title} | Epuber
35
- - else
36
- title #{title} | #{book.title} | Epuber
37
- - end
35
+ title= [title, book.title, 'Epuber'].compact.reject(&:empty?).join(' | ')
38
36
  link(rel: "stylesheet", type: "text/css", href: "/server/raw/basic.styl")
39
37
  <script type="text/javascript" charset="utf-8" src="/server/raw/vendor/bower/jquery/jquery.min.js"></script>
40
38
  <script type="text/javascript" charset="utf-8" src="/server/raw/vendor/bower/spin/spin.js"></script>
@@ -33,10 +33,10 @@ module Epuber
33
33
  # @param [Bool] backtrace output backtrace locations, nil == automatic, true == always and false == never
34
34
  #
35
35
  def self.error(message, location: nil)
36
- $stdout.puts unless @last_processing_file_line.nil?
37
-
38
- $stdout.puts(_format_message(:error, message, location: location))
39
- _print_backtrace(location.try(:backtrace_locations) || message.try(:backtrace_locations) || caller_locations, location: location) if current_command && current_command.verbose?
36
+ _clear_processing_line_for_new_output do
37
+ $stdout.puts(_format_message(:error, message, location: location))
38
+ _print_backtrace(location.try(:backtrace_locations) || message.try(:backtrace_locations) || caller_locations, location: location) if current_command && current_command.verbose?
39
+ end
40
40
  end
41
41
 
42
42
  # @param [String] message message of the error
@@ -107,6 +107,36 @@ module Epuber
107
107
  @current_file = nil
108
108
  end
109
109
 
110
+ # @param [Compiler::FileTypes::AbstractFile] file
111
+ # @param [String] step_name
112
+ # @param [Fixnum] time
113
+ #
114
+ def self.print_step_processing_time(step_name, time = nil)
115
+ if !current_command || !current_command.debug_steps_times
116
+ return yield
117
+ end
118
+
119
+ remove_processing_file_line
120
+
121
+ if block_given?
122
+ start = Time.now
123
+ returned_value = yield
124
+
125
+ time = Time.now - start
126
+ end
127
+
128
+ info_text = "Step #{step_name} took #{time * 1000} ms"
129
+ message = if @current_file.nil?
130
+ "▸ #{info_text}"
131
+ else
132
+ "▸ #{@current_file.source_path}: #{info_text}"
133
+ end
134
+
135
+ $stdout.puts(_format_message(:debug, message))
136
+
137
+ returned_value
138
+ end
139
+
110
140
  private
111
141
 
112
142
  def self._clear_processing_line_for_new_output
@@ -145,6 +175,8 @@ module Epuber
145
175
  Location.new(obj.document.file_path, obj.line)
146
176
  when Location
147
177
  obj
178
+ when Epuber::Compiler::FileTypes::AbstractFile
179
+ Location.new(obj.source_path, nil)
148
180
  end
149
181
  end
150
182