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
@@ -36,12 +36,12 @@ module Epuber
36
36
  write_bookspec(@book_name)
37
37
  write_sublime_project(@book_name)
38
38
 
39
- FileUtils.mkdir_p('images')
40
- FileUtils.mkdir_p('fonts')
41
- FileUtils.mkdir_p('styles')
39
+ create_folder('images')
40
+ create_folder('fonts')
41
+ create_folder('styles')
42
42
  write_default_style(@book_name)
43
43
 
44
- FileUtils.mkdir_p('text')
44
+ create_folder('text')
45
45
 
46
46
  print_good_bye(@book_name)
47
47
  end
@@ -51,7 +51,7 @@ module Epuber
51
51
 
52
52
  def print_good_bye(book_id)
53
53
  puts <<-END.ansi.green
54
- Project initialized, please review #{book_id}.bookspec file, remove comments.
54
+ Project initialized, please review #{book_id}.bookspec file, remove comments and fill some attributes like book title.
55
55
  END
56
56
  end
57
57
 
@@ -101,8 +101,10 @@ END
101
101
  *.epub
102
102
  *.mobi
103
103
  !.epuber/
104
- .epuber/build
105
- .epuber/release_build
104
+ .epuber/build/
105
+ .epuber/release_build/
106
+ .epuber/build_cache/
107
+ .epuber/metadata/
106
108
 
107
109
  END
108
110
  )
@@ -122,6 +124,16 @@ END
122
124
  #
123
125
  def write(file_path, string)
124
126
  File.write(file_path, string)
127
+ puts " #{'create'.ansi.green} #{file_path}"
128
+ end
129
+
130
+ # @param [String] dir_path path to dir
131
+ #
132
+ # @return [nil]
133
+ #
134
+ def create_folder(dir_path)
135
+ FileUtils.mkdir_p(dir_path)
136
+ puts " #{'create'.ansi.green} #{dir_path}/"
125
137
  end
126
138
 
127
139
  # @param text [String]
@@ -37,7 +37,7 @@ module Epuber
37
37
  require_relative '../server'
38
38
 
39
39
  target = if @selected_target_name.nil?
40
- book.targets.first
40
+ book.all_targets.first
41
41
  else
42
42
  book.target_named(@selected_target_name)
43
43
  end
@@ -24,6 +24,8 @@ module Epuber
24
24
  require_relative 'compiler/file_resolver'
25
25
  require_relative 'compiler/file_finders/normal'
26
26
 
27
+ require_relative 'compiler/file_database'
28
+
27
29
 
28
30
  EPUB_CONTENT_FOLDER = 'OEBPS'
29
31
 
@@ -59,13 +61,14 @@ module Epuber
59
61
  #
60
62
  # @return [void]
61
63
  #
62
- def compile(build_folder, check: false, write: false, release: false, verbose: false)
64
+ def compile(build_folder, check: false, write: false, release: false, verbose: false, use_cache: true)
63
65
  @file_resolver = FileResolver.new(Config.instance.project_path, build_folder)
64
66
  compilation_context.file_resolver = @file_resolver
65
67
  compilation_context.should_check = check
66
68
  compilation_context.should_write = write
67
69
  compilation_context.release_build = release
68
70
  compilation_context.verbose = verbose
71
+ compilation_context.use_cache = use_cache
69
72
 
70
73
  self.class.globals_catcher.catch do
71
74
  @build_folder = build_folder
@@ -74,6 +77,9 @@ module Epuber
74
77
 
75
78
  puts " handling target #{@target.name.inspect} in build dir `#{Config.instance.pretty_path_from_project(build_folder)}`"
76
79
 
80
+ file_resolver.add_file(FileTypes::SourceFile.new(Config.instance.pretty_path_from_project(@book.file_path).to_s))
81
+ compilation_context.plugins
82
+
77
83
  parse_toc_item(@target.root_toc)
78
84
  parse_target_file_requests
79
85
 
@@ -83,6 +89,15 @@ module Epuber
83
89
  # build folder cleanup
84
90
  remove_unnecessary_files
85
91
  remove_empty_folders
92
+
93
+ source_paths = file_resolver.files.select { |a| a.is_a?(FileTypes::SourceFile) }.map { |a| a.source_path }
94
+ compilation_context.source_file_database.cleanup(source_paths)
95
+ compilation_context.source_file_database.update_all_metadata
96
+ compilation_context.source_file_database.save_to_file
97
+
98
+ compilation_context.target_file_database.cleanup(source_paths)
99
+ compilation_context.target_file_database.update_all_metadata
100
+ compilation_context.target_file_database.save_to_file
86
101
  end
87
102
  ensure
88
103
  self.class.globals_catcher.clear_all
@@ -205,10 +220,49 @@ module Epuber
205
220
  end
206
221
  end
207
222
 
208
- # @param [FileTypes::AbstractFile] file
223
+ # @param [Epuber::Compiler::FileTypes::AbstractFile] file
209
224
  #
210
225
  def process_file(file)
226
+ file.compilation_context = compilation_context
227
+
228
+ resolve_dependencies(file) if file.is_a?(FileTypes::SourceFile)
211
229
  file.process(compilation_context)
230
+
231
+ file.compilation_context = nil
232
+ end
233
+
234
+ # @param [FileTypes::SourceFile] file
235
+ #
236
+ def resolve_dependencies(file)
237
+ deps = file.find_dependencies
238
+
239
+ # compute better paths for FileDatabase
240
+ dirname = File.dirname(file.source_path)
241
+ paths = deps.map { |relative| Config.instance.pretty_path_from_project(File.expand_path(relative, dirname)) }.uniq
242
+
243
+ # add missing files to file_resolver
244
+ paths.each do |path|
245
+ next if file_resolver.file_with_source_path(path)
246
+ file_resolver.add_file(FileTypes::SourceFile.new(path))
247
+ end
248
+
249
+ # add .bookspec file
250
+ paths += [Config.instance.pretty_path_from_project(@book.file_path).to_s]
251
+
252
+ # add all activated plugin files
253
+ paths += compilation_context.plugins.map do |plugin|
254
+ plugin.files.map { |p_file| p_file.source_path }
255
+ end.flatten
256
+
257
+ # add dependencies to databases
258
+ source_db = compilation_context.source_file_database
259
+ source_db.update_metadata(file.source_path) unless source_db.file_stat_for(file.source_path)
260
+ source_db.add_dependency(paths, to: file.source_path)
261
+
262
+ # add dependencies to databases
263
+ target_db = compilation_context.target_file_database
264
+ target_db.update_metadata(file.source_path) unless target_db.file_stat_for(file.source_path)
265
+ target_db.add_dependency(paths, to: file.source_path)
212
266
  end
213
267
 
214
268
  # @return nil
@@ -16,12 +16,24 @@ module Epuber
16
16
  #
17
17
  attr_accessor :file_resolver
18
18
 
19
+ # This will track source files regardless of current target
20
+ #
21
+ # @return [Epuber::Compiler::FileDatabase]
22
+ #
23
+ attr_reader :source_file_database
24
+
25
+ # This will track source files depend on current target
26
+ #
27
+ # @return [Epuber::Compiler::FileDatabase]
28
+ #
29
+ attr_reader :target_file_database
30
+
19
31
  # @return [Array<Epuber::Plugin>]
20
32
  #
21
33
  def plugins
22
34
  @plugins ||= @target.plugins.map do |path|
23
35
  begin
24
- plugin = Plugin.new(File.expand_path(path, Config.instance.project_path))
36
+ plugin = Plugin.new(path)
25
37
  plugin.files.each do |file|
26
38
  file_resolver.add_file(file)
27
39
  end
@@ -68,6 +80,10 @@ module Epuber
68
80
  #
69
81
  attr_accessor :release_build
70
82
 
83
+ # @return [Bool]
84
+ #
85
+ attr_accessor :use_cache
86
+
71
87
  # @return [Bool]
72
88
  #
73
89
  attr_accessor :verbose
@@ -76,10 +92,20 @@ module Epuber
76
92
  verbose
77
93
  end
78
94
 
95
+ def debug?
96
+ !release_build
97
+ end
98
+
99
+ def incremental_build?
100
+ use_cache
101
+ end
79
102
 
80
103
  def initialize(book, target)
81
104
  @book = book
82
105
  @target = target
106
+
107
+ @source_file_database = FileDatabase.new(Config.instance.file_stat_database_path)
108
+ @target_file_database = FileDatabase.new(Config.instance.target_file_stat_database_path(target))
83
109
  end
84
110
  end
85
111
  end
@@ -0,0 +1,138 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'yaml'
5
+
6
+
7
+ module Epuber
8
+ class Compiler
9
+ require_relative 'file_stat'
10
+
11
+ class FileDatabase
12
+
13
+ # @return [Hash<String, Epuber::Compiler::FileStat>]
14
+ #
15
+ attr_accessor :all_files
16
+
17
+ # @return [String]
18
+ #
19
+ attr_reader :store_file_path
20
+
21
+ # @param [String] path
22
+ #
23
+ def initialize(path)
24
+ @store_file_path = path
25
+ @all_files = YAML.load_file(path) || {}
26
+ rescue
27
+ @all_files = {}
28
+ end
29
+
30
+ # @param [String] file_path
31
+ #
32
+ def changed?(file_path, transitive: true, default_value: true)
33
+ stat = @all_files[file_path]
34
+ return default_value if stat.nil?
35
+
36
+ result = (stat != FileStat.new(file_path))
37
+
38
+ if transitive
39
+ result ||= stat.dependency_paths.any? do |path|
40
+ changed?(path, transitive: transitive, default_value: false)
41
+ end
42
+ end
43
+
44
+ result
45
+ end
46
+
47
+ # @param [String] file_path
48
+ #
49
+ # @return [FileStat]
50
+ #
51
+ def file_stat_for(file_path)
52
+ @all_files[file_path]
53
+ end
54
+
55
+ # @param [String] file_path
56
+ #
57
+ def up_to_date?(file_path, transitive: true)
58
+ !changed?(file_path, transitive: transitive)
59
+ end
60
+
61
+ # @param [String] file_path
62
+ #
63
+ def update_metadata(file_path, load_stats: true)
64
+ old_stat = @all_files[file_path]
65
+ old_dependencies = old_stat ? old_stat.dependency_paths : []
66
+
67
+ @all_files[file_path] = FileStat.new(file_path, load_stats: load_stats, dependency_paths: old_dependencies)
68
+ end
69
+
70
+ def update_all_metadata
71
+ @all_files.each do |file_path, _|
72
+ update_metadata(file_path)
73
+ end
74
+ end
75
+
76
+ # @param [Array<String>, String] file_path path to file that will be dependent on
77
+ # @param [String] to path to original file, that will has new dependency
78
+ #
79
+ def add_dependency(file_path, to: nil)
80
+ raise ArgumentError, ':to is required' if to.nil?
81
+
82
+ file_paths = Array(file_path)
83
+
84
+ to_stat = @all_files[to]
85
+ raise ArgumentError, ":to (#{to}) file is not in database" if to_stat.nil?
86
+
87
+ to_stat.add_dependency!(file_paths)
88
+
89
+ begin
90
+ file_paths.each do |path|
91
+ update_metadata(path, load_stats: false) if @all_files[path].nil?
92
+ end
93
+ rescue Errno::ENOENT
94
+ # no action, valid case where dependant file does not exist
95
+ end
96
+ end
97
+
98
+ # @param [Array<String>] file_paths
99
+ #
100
+ def cleanup(file_paths)
101
+ to_remove = @all_files.keys - file_paths
102
+ to_remove.each { |key| @all_files.delete(key) }
103
+
104
+ @all_files.each do |_, stat|
105
+ _cleanup_stat_dependency_list(file_paths, stat)
106
+ end
107
+ end
108
+
109
+ # @param [String] path
110
+ #
111
+ def save_to_file(path = store_file_path)
112
+ FileUtils.mkdir_p(File.dirname(path))
113
+
114
+ File.write(path, @all_files.to_yaml)
115
+ end
116
+
117
+
118
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
119
+
120
+ private
121
+
122
+ # @param [Array<String>] file_paths
123
+ # @param [FileStat] stat
124
+ #
125
+ def _cleanup_stat_dependency_list(file_paths, stat)
126
+ stat.keep_dependencies!(file_paths)
127
+
128
+ stat.dependency_paths.each do |path|
129
+ next_stat = @all_files[path]
130
+ next if next_stat.nil?
131
+
132
+ _cleanup_stat_dependency_list(file_paths, next_stat)
133
+ end
134
+ end
135
+
136
+ end
137
+ end
138
+ end
@@ -69,13 +69,15 @@ module Epuber
69
69
  image: %w(.png .jpg .jpeg),
70
70
  font: %w(.otf .ttf),
71
71
  style: %w(.css .styl),
72
- script: %w(.js),
72
+ script: %w(.js .coffee),
73
73
  }
74
74
 
75
75
  EXTENSIONS_RENAME = {
76
76
  '.styl' => '.css',
77
77
 
78
78
  '.bade' => '.xhtml',
79
+
80
+ '.coffee' => '.js',
79
81
  }
80
82
 
81
83
  class Abstract
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require_relative '../ruby_extensions/string'
4
+ require 'active_support/core_ext/object/try'
4
5
 
5
6
  module Epuber
6
7
  class Compiler
@@ -19,6 +20,7 @@ module Epuber
19
20
  require_relative 'file_types/mime_type_file'
20
21
  require_relative 'file_types/container_xml_file'
21
22
  require_relative 'file_types/ibooks_display_options_file'
23
+ require_relative 'file_types/coffee_script_file'
22
24
 
23
25
 
24
26
  class FileResolver
@@ -242,7 +244,7 @@ module Epuber
242
244
  #
243
245
  # @return [String] path with changed extension
244
246
  #
245
- def renamed_file_with_path(path)
247
+ def self.renamed_file_with_path(path)
246
248
  extname = File.extname(path)
247
249
  new_extname = FileFinders::EXTENSIONS_RENAME[extname]
248
250
 
@@ -261,7 +263,7 @@ module Epuber
261
263
  if file.final_destination_path.nil?
262
264
  dest_path = if file.respond_to?(:source_path) && !file.source_path.nil?
263
265
  file.abs_source_path = File.expand_path(file.source_path, source_path)
264
- renamed_file_with_path(file.source_path)
266
+ self.class.renamed_file_with_path(file.source_path)
265
267
  elsif !file.destination_path.nil?
266
268
  file.destination_path
267
269
  else
@@ -282,6 +284,8 @@ module Epuber
282
284
  mapping = {
283
285
  '.styl' => FileTypes::StylusFile,
284
286
 
287
+ '.coffee' => FileTypes::CoffeeScriptFile,
288
+
285
289
  '.bade' => FileTypes::BadeFile,
286
290
  '.xhtml' => FileTypes::XHTMLFile,
287
291
  '.html' => FileTypes::XHTMLFile,
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Epuber
5
+ class Compiler
6
+ class FileStat
7
+ # @return [Date]
8
+ #
9
+ attr_reader :mtime
10
+
11
+ # @return [Date]
12
+ #
13
+ attr_reader :ctime
14
+
15
+ # @return [Fixnum]
16
+ #
17
+ attr_reader :size
18
+
19
+ # @return [String]
20
+ #
21
+ attr_reader :file_path
22
+
23
+ # @return [String]
24
+ #
25
+ attr_reader :dependency_paths
26
+
27
+ # @param [String] path
28
+ # @param [File::Stat] stat
29
+ # @param [Bool] load_stats
30
+ #
31
+ def initialize(path, stat = nil, load_stats: true, dependency_paths: [])
32
+ @file_path = path
33
+
34
+ if load_stats
35
+ begin
36
+ stat ||= File.stat(path)
37
+ @mtime = stat.mtime
38
+ @ctime = stat.ctime
39
+ @size = stat.size
40
+ rescue
41
+ # noop
42
+ end
43
+ end
44
+
45
+ @dependency_paths = dependency_paths
46
+ end
47
+
48
+ # @param [Array<String>, String] path
49
+ #
50
+ def add_dependency!(path)
51
+ @dependency_paths += Array(path)
52
+ @dependency_paths.uniq!
53
+ end
54
+
55
+ # @param [Array<String>] paths
56
+ #
57
+ def keep_dependencies!(paths)
58
+ to_delete = (dependency_paths - paths)
59
+ @dependency_paths -= to_delete
60
+ end
61
+
62
+ # @param [FileStat] other
63
+ #
64
+ # @return [Bool]
65
+ #
66
+ def ==(other)
67
+ raise AttributeError, "other must be class of #{self.class}" unless other.is_a?(FileStat)
68
+
69
+ file_path == other.file_path &&
70
+ size == other.size &&
71
+ mtime == other.mtime &&
72
+ ctime == other.ctime
73
+ end
74
+ end
75
+ end
76
+ end