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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/epuber.gemspec +13 -15
- data/lib/epuber/book.rb +35 -4
- data/lib/epuber/book/target.rb +41 -2
- data/lib/epuber/command.rb +5 -0
- data/lib/epuber/command/build.rb +155 -0
- data/lib/epuber/command/compile.rb +5 -131
- data/lib/epuber/command/init.rb +19 -7
- data/lib/epuber/command/server.rb +1 -1
- data/lib/epuber/compiler.rb +56 -2
- data/lib/epuber/compiler/compilation_context.rb +27 -1
- data/lib/epuber/compiler/file_database.rb +138 -0
- data/lib/epuber/compiler/file_finders/abstract.rb +3 -1
- data/lib/epuber/compiler/file_resolver.rb +6 -2
- data/lib/epuber/compiler/file_stat.rb +76 -0
- data/lib/epuber/compiler/file_types/abstract_file.rb +3 -37
- data/lib/epuber/compiler/file_types/bade_file.rb +63 -4
- data/lib/epuber/compiler/file_types/coffee_script_file.rb +24 -0
- data/lib/epuber/compiler/file_types/generated_file.rb +3 -1
- data/lib/epuber/compiler/file_types/image_file.rb +6 -4
- data/lib/epuber/compiler/file_types/source_file.rb +63 -1
- data/lib/epuber/compiler/file_types/static_file.rb +2 -2
- data/lib/epuber/compiler/file_types/stylus_file.rb +15 -0
- data/lib/epuber/compiler/file_types/xhtml_file.rb +71 -13
- data/lib/epuber/compiler/opf_generator.rb +1 -0
- data/lib/epuber/compiler/xhtml_processor.rb +94 -17
- data/lib/epuber/config.rb +47 -14
- data/lib/epuber/plugin.rb +6 -1
- data/lib/epuber/server.rb +35 -11
- data/lib/epuber/server/handlers.rb +2 -1
- data/lib/epuber/server/pages/book.bade +6 -2
- data/lib/epuber/server/pages/common.bade +5 -7
- data/lib/epuber/user_interface.rb +36 -4
- data/lib/epuber/vendor/version.rb +2 -2
- data/lib/epuber/version.rb +3 -1
- 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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
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]
|
12
|
+
# @param [Compiler::CompilationContext] _compilation_context
|
13
13
|
#
|
14
|
-
def process(
|
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
|
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]
|
10
|
+
# @param [Compiler::CompilationContext] _compilation_context
|
11
11
|
#
|
12
|
-
def process(
|
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 =
|
67
|
-
|
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
|
-
|
93
|
+
if compilation_context.release_build && xhtml_doc.errors.count > 0
|
94
|
+
process_nokogiri_errors(xhtml_doc.errors)
|
95
|
+
end
|
70
96
|
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
88
|
-
|
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
|
|