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
@@ -37,6 +37,9 @@ module Epuber
37
37
  #
38
38
  attr_accessor :path_type
39
39
 
40
+ # @return [Epuber::Compiler::CompilationContext] non-nil value only during #process() method
41
+ #
42
+ attr_accessor :compilation_context
40
43
 
41
44
  def ==(other)
42
45
  self.class == other.class && final_destination_path == other.final_destination_path
@@ -45,43 +48,6 @@ module Epuber
45
48
 
46
49
  ################################################################################################################
47
50
 
48
- # @param [String] source_path path to source file
49
- # @param [String] dest_path path to destination file
50
- # @param [Bool] identical whether the content of existing files should be compared or not (expensive operation)
51
- #
52
- # @return [Bool]
53
- #
54
- def self.file_uptodate?(source_path, dest_path, identical: true)
55
- return false unless File.exist?(dest_path)
56
- return false unless FileUtils.uptodate?(dest_path, [source_path])
57
-
58
- if identical
59
- return false unless FileUtils.identical?(dest_path, source_path)
60
- end
61
-
62
- true
63
- end
64
-
65
- # @param [String] source_path
66
- # @param [String] dest_path
67
- #
68
- # @return [Bool]
69
- #
70
- def self.file_copy?(source_path, dest_path)
71
- !file_uptodate?(source_path, dest_path)
72
- end
73
-
74
- # @param [String] source_path
75
- # @param [String] dest_path
76
- #
77
- # @return nil
78
- #
79
- def self.file_copy(source_path, dest_path)
80
- return unless file_copy?(source_path, dest_path)
81
-
82
- file_copy!(source_path, dest_path)
83
- end
84
-
85
51
  # @param [String] source_path
86
52
  # @param [String] dest_path
87
53
  #
@@ -9,6 +9,8 @@ module Epuber
9
9
  require_relative 'xhtml_file'
10
10
 
11
11
  class BadeFile < XHTMLFile
12
+ PRECOMPILED_CACHE_NAME = 'bade_precompiled'
13
+
12
14
  # @param [Epuber::Compiler::CompilationContext] compilation_context
13
15
  #
14
16
  def process(compilation_context)
@@ -16,7 +18,8 @@ module Epuber
16
18
  book = compilation_context.book
17
19
  file_resolver = compilation_context.file_resolver
18
20
 
19
- bade_content = load_source(compilation_context)
21
+ up_to_date = source_file_up_to_date?
22
+ precompiled_exists = File.exist?(precompiled_path)
20
23
 
21
24
  variables = {
22
25
  __book: book,
@@ -27,11 +30,67 @@ module Epuber
27
30
  __const: Hash.new { |_hash, key| UI.warning("Undefined constant with key `#{key}`", location: caller_locations[0]) }.merge!(target.constants),
28
31
  }
29
32
 
30
- xhtml_content = Bade::Renderer.from_source(bade_content, source_path)
31
- .with_locals(variables)
32
- .render(new_line: '', indent: '')
33
+ should_load_from_precompiled = up_to_date && precompiled_exists && compilation_context.incremental_build?
34
+
35
+ precompiled = if should_load_from_precompiled
36
+ begin
37
+ Bade::Precompiled.from_yaml_file(precompiled_path)
38
+ rescue LoadError
39
+ UI.warning("Empty precompiled file at path #{pretty_precompiled_path}", location: self)
40
+ nil
41
+ end
42
+ end
43
+
44
+ if !precompiled.nil?
45
+ xhtml_content = UI.print_step_processing_time('rendering precompiled Bade') do
46
+ renderer = Bade::Renderer.from_precompiled(precompiled)
47
+ .with_locals(variables)
48
+
49
+ renderer.render(new_line: '', indent: '')
50
+ end
51
+ else
52
+ UI.print_processing_debug_info('Parsing new version of source file') if compilation_context.incremental_build?
53
+
54
+ bade_content = load_source(compilation_context)
55
+
56
+ xhtml_content = UI.print_step_processing_time('rendering changed Bade') do
57
+ renderer = Bade::Renderer.from_source(bade_content, source_path)
58
+ .with_locals(variables)
59
+
60
+ # turn on optimizations when can
61
+ if renderer.respond_to?(:optimize=)
62
+ renderer.optimize = true
63
+ end
64
+
65
+ FileUtils.mkdir_p(File.dirname(precompiled_path))
66
+ renderer.precompiled.write_yaml_to_file(precompiled_path)
67
+
68
+ renderer.render(new_line: '', indent: '')
69
+ end
70
+ end
33
71
 
34
72
  write_compiled(common_process(xhtml_content, compilation_context))
73
+ update_metadata!
74
+ end
75
+
76
+ # return [Array<String>]
77
+ #
78
+ def find_dependencies
79
+ (super + self.class.find_imports(File.read(abs_source_path))).uniq
80
+ end
81
+
82
+ # @return [String]
83
+ #
84
+ def precompiled_path
85
+ File.join(Config.instance.build_cache_path(PRECOMPILED_CACHE_NAME), source_path + '.precompiled.yml')
86
+ end
87
+
88
+ def pretty_precompiled_path
89
+ Config.instance.pretty_path_from_project(precompiled_path)
90
+ end
91
+
92
+ def self.find_imports(content)
93
+ content.to_enum(:scan, /^\s*import ("|')([^'"]*)("|')/).map { Regexp.last_match[2] }
35
94
  end
36
95
  end
37
96
  end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require 'coffee-script'
4
+
5
+
6
+ module Epuber
7
+ class Compiler
8
+ module FileTypes
9
+ require_relative 'source_file'
10
+
11
+ class CoffeeScriptFile < SourceFile
12
+ # @param [Compiler::CompilationContext] compilation_context
13
+ #
14
+ def process(compilation_context)
15
+ return if destination_file_up_to_date?
16
+
17
+ write_compiled(CoffeeScript.compile(File.new(abs_source_path)))
18
+
19
+ update_metadata!
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -19,8 +19,10 @@ module Epuber
19
19
 
20
20
  def write_generate(content)
21
21
  if self.class.write_to_file?(content, final_destination_path)
22
- UI.print_processing_debug_info("Writing generated content to #{pkg_destination_path}")
22
+ UI.print_processing_debug_info("#{pkg_destination_path}: Writing generated content")
23
23
  self.class.write_to_file!(content, final_destination_path)
24
+ else
25
+ UI.print_processing_debug_info("#{pkg_destination_path}: Not writing to disk ... generated content is same")
24
26
  end
25
27
  end
26
28
  end
@@ -9,14 +9,14 @@ module Epuber
9
9
  require_relative 'source_file'
10
10
 
11
11
  class ImageFile < SourceFile
12
- # @param [Compiler::CompilationContext] compilation_context
12
+ # @param [Compiler::CompilationContext] _compilation_context
13
13
  #
14
- def process(compilation_context)
14
+ def process(_compilation_context)
15
+ return if destination_file_up_to_date?
16
+
15
17
  dest = final_destination_path
16
18
  source = abs_source_path
17
19
 
18
- return if self.class.file_uptodate?(source, dest)
19
-
20
20
  img = Magick::Image::read(source).first
21
21
 
22
22
  resolution = img.columns * img.rows
@@ -35,6 +35,8 @@ module Epuber
35
35
  # file is already old
36
36
  self.class.file_copy!(source, dest)
37
37
  end
38
+
39
+ update_metadata!
38
40
  end
39
41
  end
40
42
  end
@@ -26,17 +26,77 @@ module Epuber
26
26
  @source_path = source_path
27
27
  end
28
28
 
29
+ # return [Array<String>]
30
+ #
31
+ def find_dependencies
32
+ []
33
+ end
34
+
35
+ def process(_compilation_context)
36
+ # do nothing
37
+ end
38
+
39
+ # Source file does not change from last build
40
+ # @warning Using only this method can cause not updating files that are different for targets
41
+ #
42
+ # @return [Bool]
43
+ #
44
+ def source_file_up_to_date?
45
+ return false unless compilation_context.incremental_build?
46
+
47
+ source_db = compilation_context.source_file_database
48
+ source_db.up_to_date?(source_path)
49
+ end
50
+
51
+ # Source file does not change from last build of this target
52
+ #
53
+ # @return [Bool]
54
+ #
55
+ def destination_file_up_to_date?
56
+ return false unless compilation_context.incremental_build?
57
+
58
+ source_db = compilation_context.source_file_database
59
+ target_db = compilation_context.target_file_database
60
+
61
+ destination_file_exist? && # destination file must exist
62
+ target_db.up_to_date?(source_path) && # source file must be up-to-date from last build of this target
63
+ source_db.file_stat_for(source_path) == target_db.file_stat_for(source_path)
64
+ end
65
+
66
+ # Final destination path exist
67
+ #
68
+ # @return [Bool]
69
+ #
70
+ def destination_file_exist?
71
+ File.exist?(final_destination_path)
72
+ end
73
+
74
+ # Updates information about source file in file databases
75
+ #
76
+ # @return [nil]
77
+ #
78
+ def update_metadata!
79
+ compilation_context.source_file_database.update_metadata(source_path)
80
+ compilation_context.target_file_database.update_metadata(source_path)
81
+ end
82
+
29
83
  def default_file_copy
30
- if self.class.file_copy?(abs_source_path, final_destination_path)
84
+ if destination_file_up_to_date?
85
+ UI.print_processing_debug_info("Destination path #{pkg_destination_path} is up-to-date")
86
+ else
31
87
  UI.print_processing_debug_info("Copying to #{pkg_destination_path}")
32
88
  self.class.file_copy!(abs_source_path, final_destination_path)
33
89
  end
90
+
91
+ update_metadata!
34
92
  end
35
93
 
36
94
  def write_compiled(content)
37
95
  if self.class.write_to_file?(content, final_destination_path)
38
96
  UI.print_processing_debug_info("Writing compiled version to #{pkg_destination_path}")
39
97
  self.class.write_to_file!(content, final_destination_path)
98
+ else
99
+ UI.print_processing_debug_info("Not writing to disk ... compiled version at #{pkg_destination_path} is same")
40
100
  end
41
101
  end
42
102
 
@@ -44,6 +104,8 @@ module Epuber
44
104
  if self.class.write_to_file?(content, final_destination_path)
45
105
  UI.print_processing_debug_info("Writing processed version to #{pkg_destination_path}")
46
106
  self.class.write_to_file!(content, final_destination_path)
107
+ else
108
+ UI.print_processing_debug_info("Not writing to disk ... processed version at #{pkg_destination_path} is same")
47
109
  end
48
110
  end
49
111
  end
@@ -7,9 +7,9 @@ module Epuber
7
7
  require_relative 'source_file'
8
8
 
9
9
  class StaticFile < SourceFile
10
- # @param [Compiler::CompilationContext] compilation_context
10
+ # @param [Compiler::CompilationContext] _compilation_context
11
11
  #
12
- def process(compilation_context)
12
+ def process(_compilation_context)
13
13
  default_file_copy
14
14
  end
15
15
  end
@@ -12,12 +12,27 @@ module Epuber
12
12
  # @param [Compiler::CompilationContext] compilation_context
13
13
  #
14
14
  def process(compilation_context)
15
+ return if destination_file_up_to_date?
16
+
15
17
  Stylus.define('__is_debug', !compilation_context.release_build)
16
18
  Stylus.define('__is_verbose_mode', compilation_context.verbose?)
17
19
  Stylus.define('__target_name', compilation_context.target.name)
18
20
  Stylus.define('__book_title', compilation_context.book.title)
21
+ Stylus.define('__const', compilation_context.target.constants)
19
22
 
20
23
  write_compiled(Stylus.compile(File.new(abs_source_path)))
24
+
25
+ update_metadata!
26
+ end
27
+
28
+ def find_dependencies
29
+ self.class.find_imports(File.read(abs_source_path))
30
+ end
31
+
32
+ # @return [Array<String>]
33
+ #
34
+ def self.find_imports(content)
35
+ content.to_enum(:scan, /^\s*@import ("|')([^'"]*)("|')/).map { Regexp.last_match[2] }
21
36
  end
22
37
  end
23
38
  end
@@ -29,6 +29,21 @@ module Epuber
29
29
  end
30
30
  end
31
31
 
32
+ # @param [Book::Target] target
33
+ # @param [FileResolver] file_resolver
34
+ #
35
+ # @return [Array<String>] list of paths to styles relative to this file
36
+ #
37
+ def default_scripts(target, file_resolver)
38
+ default_scripts = target.default_scripts.map do |default_style_request|
39
+ Array(file_resolver.file_from_request(default_style_request))
40
+ end.flatten
41
+
42
+ default_scripts.map do |style|
43
+ Pathname.new(style.final_destination_path).relative_path_from(Pathname.new(File.dirname(final_destination_path))).to_s
44
+ end
45
+ end
46
+
32
47
  # @param [Compiler::CompilationContext] compilation_context
33
48
  #
34
49
  def load_source(compilation_context)
@@ -53,6 +68,14 @@ module Epuber
53
68
  xhtml_content
54
69
  end
55
70
 
71
+ # @param [Array] errors
72
+ #
73
+ def process_nokogiri_errors(errors)
74
+ errors.each do |e|
75
+ UI.warning(e)
76
+ end
77
+ end
78
+
56
79
  # @param [String] content xhtml in string
57
80
  # @param [Compiler::CompilationContext] compilation_context
58
81
  #
@@ -63,29 +86,64 @@ module Epuber
63
86
  book = compilation_context.book
64
87
  file_resolver = compilation_context.file_resolver
65
88
 
66
- xhtml_doc = XHTMLProcessor.xml_document_from_string(content, source_path)
67
- XHTMLProcessor.add_missing_root_elements(xhtml_doc, book.title, target.epub_version)
89
+ xhtml_doc = UI.print_step_processing_time('parsing XHTML file') do
90
+ XHTMLProcessor.xml_document_from_string(content, source_path)
91
+ end
68
92
 
69
- XHTMLProcessor.add_styles(xhtml_doc, default_styles(target, file_resolver))
93
+ if compilation_context.release_build && xhtml_doc.errors.count > 0
94
+ process_nokogiri_errors(xhtml_doc.errors)
95
+ end
70
96
 
71
- XHTMLProcessor.add_viewport(xhtml_doc, target.default_viewport) unless target.default_viewport.nil?
72
- self.properties << :scripted if XHTMLProcessor.using_javascript?(xhtml_doc)
73
- self.properties << :remote_resources if XHTMLProcessor.using_remote_resources?(xhtml_doc)
97
+ UI.print_step_processing_time('adding missing elements') do
98
+ XHTMLProcessor.add_missing_root_elements(xhtml_doc, book.title, target.epub_version)
99
+ end
74
100
 
75
- XHTMLProcessor.resolve_links(xhtml_doc, destination_path, file_resolver.dest_finder)
76
- XHTMLProcessor.resolve_images(xhtml_doc, destination_path, file_resolver)
101
+ UI.print_step_processing_time('adding default things') do
102
+ XHTMLProcessor.add_styles(xhtml_doc, default_styles(target, file_resolver))
103
+ XHTMLProcessor.add_scripts(xhtml_doc, default_scripts(target, file_resolver))
77
104
 
78
- xhtml_string = xhtml_doc.to_s
105
+ XHTMLProcessor.add_viewport(xhtml_doc, target.default_viewport) unless target.default_viewport.nil?
106
+ end
107
+
108
+ # resolve links to files, add other linked resources and compute correct path
109
+ UI.print_step_processing_time('resolving links') do
110
+ XHTMLProcessor.resolve_links(xhtml_doc, destination_path, file_resolver.dest_finder)
111
+ end
112
+ UI.print_step_processing_time('resolving image resources') do
113
+ XHTMLProcessor.resolve_images(xhtml_doc, destination_path, file_resolver)
114
+ end
115
+ UI.print_step_processing_time('resolving scripts') do
116
+ XHTMLProcessor.resolve_scripts(xhtml_doc, destination_path, file_resolver)
117
+ end
118
+ UI.print_step_processing_time('resolving stylesheets') do
119
+ XHTMLProcessor.resolve_stylesheets(xhtml_doc, destination_path, file_resolver)
120
+ end
121
+
122
+ XHTMLProcessor.resolve_mathml_namespace(xhtml_doc)
123
+
124
+ UI.print_step_processing_time('investigating properties') do
125
+ self.properties << :remote_resources if XHTMLProcessor.using_remote_resources?(xhtml_doc)
126
+ self.properties << :scripted if XHTMLProcessor.using_javascript?(xhtml_doc)
127
+ self.properties << :mathml if XHTMLProcessor.using_mathml?(xhtml_doc)
128
+ end
129
+
130
+ xhtml_string = UI.print_step_processing_time('converting to XHTML') do
131
+ xhtml_doc.to_s
132
+ end
79
133
 
80
134
  # perform transformations
81
- compilation_context.perform_plugin_things(Transformer, :result_text_xhtml_string) do |transformer|
82
- xhtml_string = transformer.call(final_destination_path, xhtml_string, compilation_context)
135
+ UI.print_step_processing_time('performing final transformations') do
136
+ compilation_context.perform_plugin_things(Transformer, :result_text_xhtml_string) do |transformer|
137
+ xhtml_string = transformer.call(final_destination_path, xhtml_string, compilation_context)
138
+ end
83
139
  end
84
140
 
85
141
  # perform custom validation
86
142
  if compilation_context.should_check
87
- compilation_context.perform_plugin_things(Checker, :result_text_xhtml_string) do |checker|
88
- checker.call(final_destination_path, xhtml_string, compilation_context)
143
+ UI.print_step_processing_time('performing final validations') do
144
+ compilation_context.perform_plugin_things(Checker, :result_text_xhtml_string) do |checker|
145
+ checker.call(final_destination_path, xhtml_string, compilation_context)
146
+ end
89
147
  end
90
148
  end
91
149