epuber 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +46 -0
  5. data/bin/epuber +10 -0
  6. data/epuber.gemspec +51 -0
  7. data/lib/epuber.rb +14 -0
  8. data/lib/epuber/book.rb +267 -0
  9. data/lib/epuber/book/contributor.rb +90 -0
  10. data/lib/epuber/book/file_request.rb +68 -0
  11. data/lib/epuber/book/target.rb +284 -0
  12. data/lib/epuber/book/toc_item.rb +130 -0
  13. data/lib/epuber/checker.rb +19 -0
  14. data/lib/epuber/checker/text_checker.rb +129 -0
  15. data/lib/epuber/checker_transformer_base.rb +71 -0
  16. data/lib/epuber/command.rb +65 -0
  17. data/lib/epuber/command/compile.rb +142 -0
  18. data/lib/epuber/command/init.rb +145 -0
  19. data/lib/epuber/command/server.rb +59 -0
  20. data/lib/epuber/compiler.rb +255 -0
  21. data/lib/epuber/compiler/compilation_context.rb +86 -0
  22. data/lib/epuber/compiler/file_finders/abstract.rb +270 -0
  23. data/lib/epuber/compiler/file_finders/imaginary.rb +167 -0
  24. data/lib/epuber/compiler/file_finders/normal.rb +22 -0
  25. data/lib/epuber/compiler/file_resolver.rb +316 -0
  26. data/lib/epuber/compiler/file_types/abstract_file.rb +119 -0
  27. data/lib/epuber/compiler/file_types/bade_file.rb +39 -0
  28. data/lib/epuber/compiler/file_types/container_xml_file.rb +26 -0
  29. data/lib/epuber/compiler/file_types/generated_file.rb +29 -0
  30. data/lib/epuber/compiler/file_types/ibooks_display_options_file.rb +31 -0
  31. data/lib/epuber/compiler/file_types/image_file.rb +42 -0
  32. data/lib/epuber/compiler/file_types/mime_type_file.rb +20 -0
  33. data/lib/epuber/compiler/file_types/nav_file.rb +42 -0
  34. data/lib/epuber/compiler/file_types/opf_file.rb +27 -0
  35. data/lib/epuber/compiler/file_types/source_file.rb +52 -0
  36. data/lib/epuber/compiler/file_types/static_file.rb +18 -0
  37. data/lib/epuber/compiler/file_types/stylus_file.rb +20 -0
  38. data/lib/epuber/compiler/file_types/xhtml_file.rb +102 -0
  39. data/lib/epuber/compiler/generator.rb +67 -0
  40. data/lib/epuber/compiler/meta_inf_generator.rb +41 -0
  41. data/lib/epuber/compiler/nav_generator.rb +201 -0
  42. data/lib/epuber/compiler/opf_generator.rb +284 -0
  43. data/lib/epuber/compiler/xhtml_processor.rb +254 -0
  44. data/lib/epuber/config.rb +133 -0
  45. data/lib/epuber/dsl/attribute.rb +248 -0
  46. data/lib/epuber/dsl/attribute_support.rb +130 -0
  47. data/lib/epuber/dsl/object.rb +145 -0
  48. data/lib/epuber/dsl/tree_object.rb +101 -0
  49. data/lib/epuber/helper.rb +19 -0
  50. data/lib/epuber/lockfile.rb +55 -0
  51. data/lib/epuber/plugin.rb +108 -0
  52. data/lib/epuber/ruby_extensions/match_data.rb +40 -0
  53. data/lib/epuber/ruby_extensions/thread.rb +13 -0
  54. data/lib/epuber/server.rb +614 -0
  55. data/lib/epuber/server/auto_refresh/auto_refresh.coffee +97 -0
  56. data/lib/epuber/server/auto_refresh/connector.coffee +125 -0
  57. data/lib/epuber/server/auto_refresh/protocol.coffee +41 -0
  58. data/lib/epuber/server/auto_refresh/reloader.coffee +261 -0
  59. data/lib/epuber/server/base.styl +3 -0
  60. data/lib/epuber/server/basic.styl +211 -0
  61. data/lib/epuber/server/book_content.styl +39 -0
  62. data/lib/epuber/server/default_cover.png +0 -0
  63. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Bold.ttf +0 -0
  64. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-BoldItalic.ttf +0 -0
  65. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Italic.ttf +0 -0
  66. data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Regular.ttf +0 -0
  67. data/lib/epuber/server/handlers.rb +67 -0
  68. data/lib/epuber/server/keyboard_control.coffee +6 -0
  69. data/lib/epuber/server/pages/book.bade +75 -0
  70. data/lib/epuber/server/pages/common.bade +59 -0
  71. data/lib/epuber/server/pages/files.bade +17 -0
  72. data/lib/epuber/server/pages/toc.bade +29 -0
  73. data/lib/epuber/server/support.coffee +10 -0
  74. data/lib/epuber/templates/template.bookspec +143 -0
  75. data/lib/epuber/third_party/bower.rb +22 -0
  76. data/lib/epuber/third_party/bower/bower.json +10 -0
  77. data/lib/epuber/third_party/bower/bower_components/cookies-js/bower.json +10 -0
  78. data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.d.ts +33 -0
  79. data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.js +173 -0
  80. data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.min.js +6 -0
  81. data/lib/epuber/third_party/bower/bower_components/jquery/MIT-LICENSE.txt +21 -0
  82. data/lib/epuber/third_party/bower/bower_components/jquery/bower.json +28 -0
  83. data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.js +9210 -0
  84. data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.min.js +5 -0
  85. data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.min.map +1 -0
  86. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax.js +786 -0
  87. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/jsonp.js +89 -0
  88. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/load.js +75 -0
  89. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/parseJSON.js +13 -0
  90. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/parseXML.js +28 -0
  91. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/script.js +64 -0
  92. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/var/nonce.js +5 -0
  93. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/var/rquery.js +3 -0
  94. data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/xhr.js +136 -0
  95. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes.js +11 -0
  96. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/attr.js +141 -0
  97. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/classes.js +158 -0
  98. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/prop.js +94 -0
  99. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/support.js +35 -0
  100. data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/val.js +161 -0
  101. data/lib/epuber/third_party/bower/bower_components/jquery/src/callbacks.js +205 -0
  102. data/lib/epuber/third_party/bower/bower_components/jquery/src/core.js +502 -0
  103. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/access.js +60 -0
  104. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/init.js +123 -0
  105. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/parseHTML.js +39 -0
  106. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/ready.js +97 -0
  107. data/lib/epuber/third_party/bower/bower_components/jquery/src/core/var/rsingleTag.js +4 -0
  108. data/lib/epuber/third_party/bower/bower_components/jquery/src/css.js +450 -0
  109. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/addGetHookIf.js +22 -0
  110. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/curCSS.js +57 -0
  111. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/defaultDisplay.js +70 -0
  112. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  113. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/support.js +96 -0
  114. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/swap.js +28 -0
  115. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/cssExpand.js +3 -0
  116. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/getStyles.js +12 -0
  117. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/isHidden.js +13 -0
  118. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/rmargin.js +3 -0
  119. data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
  120. data/lib/epuber/third_party/bower/bower_components/jquery/src/data.js +178 -0
  121. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/Data.js +181 -0
  122. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/accepts.js +20 -0
  123. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/var/data_priv.js +5 -0
  124. data/lib/epuber/third_party/bower/bower_components/jquery/src/data/var/data_user.js +5 -0
  125. data/lib/epuber/third_party/bower/bower_components/jquery/src/deferred.js +149 -0
  126. data/lib/epuber/third_party/bower/bower_components/jquery/src/deprecated.js +13 -0
  127. data/lib/epuber/third_party/bower/bower_components/jquery/src/dimensions.js +50 -0
  128. data/lib/epuber/third_party/bower/bower_components/jquery/src/effects.js +648 -0
  129. data/lib/epuber/third_party/bower/bower_components/jquery/src/effects/Tween.js +114 -0
  130. data/lib/epuber/third_party/bower/bower_components/jquery/src/effects/animatedSelector.js +13 -0
  131. data/lib/epuber/third_party/bower/bower_components/jquery/src/event.js +868 -0
  132. data/lib/epuber/third_party/bower/bower_components/jquery/src/event/ajax.js +13 -0
  133. data/lib/epuber/third_party/bower/bower_components/jquery/src/event/alias.js +39 -0
  134. data/lib/epuber/third_party/bower/bower_components/jquery/src/event/support.js +9 -0
  135. data/lib/epuber/third_party/bower/bower_components/jquery/src/exports/amd.js +24 -0
  136. data/lib/epuber/third_party/bower/bower_components/jquery/src/exports/global.js +32 -0
  137. data/lib/epuber/third_party/bower/bower_components/jquery/src/intro.js +44 -0
  138. data/lib/epuber/third_party/bower/bower_components/jquery/src/jquery.js +37 -0
  139. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation.js +580 -0
  140. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/_evalUrl.js +18 -0
  141. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/support.js +32 -0
  142. data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
  143. data/lib/epuber/third_party/bower/bower_components/jquery/src/offset.js +207 -0
  144. data/lib/epuber/third_party/bower/bower_components/jquery/src/outro.js +1 -0
  145. data/lib/epuber/third_party/bower/bower_components/jquery/src/queue.js +142 -0
  146. data/lib/epuber/third_party/bower/bower_components/jquery/src/queue/delay.js +22 -0
  147. data/lib/epuber/third_party/bower/bower_components/jquery/src/selector-native.js +172 -0
  148. data/lib/epuber/third_party/bower/bower_components/jquery/src/selector-sizzle.js +14 -0
  149. data/lib/epuber/third_party/bower/bower_components/jquery/src/selector.js +1 -0
  150. data/lib/epuber/third_party/bower/bower_components/jquery/src/serialize.js +111 -0
  151. data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.js +2067 -0
  152. data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
  153. data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
  154. data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing.js +199 -0
  155. data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing/findFilter.js +100 -0
  156. data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
  157. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/arr.js +3 -0
  158. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/class2type.js +4 -0
  159. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/concat.js +5 -0
  160. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/hasOwn.js +5 -0
  161. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/indexOf.js +5 -0
  162. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/pnum.js +3 -0
  163. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/push.js +5 -0
  164. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/rnotwhite.js +3 -0
  165. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/slice.js +5 -0
  166. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/strundefined.js +3 -0
  167. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/support.js +4 -0
  168. data/lib/epuber/third_party/bower/bower_components/jquery/src/var/toString.js +5 -0
  169. data/lib/epuber/third_party/bower/bower_components/jquery/src/wrap.js +79 -0
  170. data/lib/epuber/third_party/bower/bower_components/keymaster/MIT-LICENSE +20 -0
  171. data/lib/epuber/third_party/bower/bower_components/keymaster/Makefile +4 -0
  172. data/lib/epuber/third_party/bower/bower_components/keymaster/README.markdown +212 -0
  173. data/lib/epuber/third_party/bower/bower_components/keymaster/bower.json +27 -0
  174. data/lib/epuber/third_party/bower/bower_components/keymaster/keymaster.js +296 -0
  175. data/lib/epuber/third_party/bower/bower_components/keymaster/package.json +11 -0
  176. data/lib/epuber/third_party/bower/bower_components/keymaster/test.html +93 -0
  177. data/lib/epuber/third_party/bower/bower_components/spin.js/LICENSE.txt +21 -0
  178. data/lib/epuber/third_party/bower/bower_components/spin.js/README.md +21 -0
  179. data/lib/epuber/third_party/bower/bower_components/spin.js/bower.json +18 -0
  180. data/lib/epuber/third_party/bower/bower_components/spin.js/jquery.spin.js +80 -0
  181. data/lib/epuber/third_party/bower/bower_components/spin.js/spin.js +337 -0
  182. data/lib/epuber/third_party/bower/bower_components/uri.js/LICENSE.txt +21 -0
  183. data/lib/epuber/third_party/bower/bower_components/uri.js/README.md +534 -0
  184. data/lib/epuber/third_party/bower/bower_components/uri.js/bower.json +16 -0
  185. data/lib/epuber/third_party/bower/bower_components/uri.js/contributing.md +19 -0
  186. data/lib/epuber/third_party/bower/bower_components/uri.js/src/IPv6.js +188 -0
  187. data/lib/epuber/third_party/bower/bower_components/uri.js/src/SecondLevelDomains.js +241 -0
  188. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.fragmentQuery.js +104 -0
  189. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.fragmentURI.js +97 -0
  190. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.js +2115 -0
  191. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.min.js +86 -0
  192. data/lib/epuber/third_party/bower/bower_components/uri.js/src/URITemplate.js +499 -0
  193. data/lib/epuber/third_party/bower/bower_components/uri.js/src/jquery.URI.js +235 -0
  194. data/lib/epuber/third_party/bower/bower_components/uri.js/src/jquery.URI.min.js +7 -0
  195. data/lib/epuber/third_party/bower/bower_components/uri.js/src/punycode.js +508 -0
  196. data/lib/epuber/transformer.rb +19 -0
  197. data/lib/epuber/transformer/text_transformer.rb +60 -0
  198. data/lib/epuber/user_interface.rb +186 -0
  199. data/lib/epuber/vendor/globals_context.rb +26 -0
  200. data/lib/epuber/vendor/hash_binding.rb +26 -0
  201. data/lib/epuber/vendor/nokogiri_extensions.rb +30 -0
  202. data/lib/epuber/vendor/ruby_templater.rb +71 -0
  203. data/lib/epuber/vendor/size.rb +20 -0
  204. data/lib/epuber/vendor/version.rb +83 -0
  205. data/lib/epuber/version.rb +4 -0
  206. metadata +556 -0
@@ -0,0 +1,108 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'checker'
4
+ require_relative 'transformer'
5
+ require_relative 'compiler/file_types/source_file'
6
+
7
+
8
+ module Epuber
9
+ class Plugin
10
+ class PluginFile < Compiler::FileTypes::SourceFile
11
+ # @return [Array]
12
+ #
13
+ attr_reader :instances
14
+
15
+ # @param [String] file_path path to plugin file
16
+ #
17
+ def initialize(file_path)
18
+ super(file_path)
19
+ @instances = []
20
+
21
+ eval(::File.read(file_path), binding, file_path)
22
+ end
23
+
24
+ # @param [Symbol] name name of the plugin function
25
+ # @param [Class] klass class of what it should create
26
+ #
27
+ # @return nil
28
+ #
29
+ def self.plugin_instance_function(name, klass)
30
+ define_method(name) do |source_type, *options, &block|
31
+ checker_class = klass.class_for_source_type(source_type)
32
+ checker = checker_class.new(source_type, *options, &block)
33
+ instances << checker
34
+ checker
35
+ end
36
+ end
37
+
38
+
39
+ # Method for creating new instance of checker in packages
40
+ #
41
+ # @param [Symbol] source_type source type of checker, see Checker#source_type
42
+ # @param [Array<Symbol>] options list of other arguments, usually flags
43
+ # @yield value for checking, depends on type of checker
44
+ #
45
+ # @return [Checker]
46
+ #
47
+ plugin_instance_function(:check, Checker)
48
+
49
+ # Method for creating new instance of checker in packages
50
+ #
51
+ # @param [Symbol] source_type source type of transformer, see Transformer#source_type
52
+ # @param [Array<Symbol>] options list of other arguments, usually flags
53
+ # @yield value for checking, depends on type of checker
54
+ #
55
+ # @return [Transformer]
56
+ #
57
+ plugin_instance_function(:transform, Transformer)
58
+ end
59
+
60
+
61
+ # @return [String]
62
+ #
63
+ attr_reader :path
64
+
65
+ # @return [Array<PluginFile>]
66
+ #
67
+ attr_reader :files
68
+
69
+ # @param path [String]
70
+ #
71
+ def initialize(path)
72
+ @path = path
73
+
74
+ @files = if ::File.file?(path)
75
+ [PluginFile.new(path)]
76
+ elsif ::File.directory?(path)
77
+ Dir.glob(File.expand_path('**/*.rb', path)).map do |file_path|
78
+ PluginFile.new(file_path)
79
+ end
80
+ else
81
+ raise LoadError, "#{self}: Can't find anything for #{path}"
82
+ end
83
+ end
84
+
85
+ # @param [Class] klass base class of all instances
86
+ #
87
+ # @return [Array<CheckerTransformerBase>]
88
+ #
89
+ def instances(klass)
90
+ files.map do |plugin_file|
91
+ plugin_file.instances.select { |inst| inst.is_a?(klass) }
92
+ end.flatten
93
+ end
94
+
95
+
96
+ # @return [Array<Checker>]
97
+ #
98
+ def checkers
99
+ instances(Checker)
100
+ end
101
+
102
+ # @return [Array<Transformer>]
103
+ #
104
+ def transformers
105
+ instances(Transformer)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,40 @@
1
+
2
+ class MatchData
3
+ # @return [Array<String>]
4
+ #
5
+ def pre_match_lines
6
+ @pre_match_lines ||= pre_match.split(/\r?\n/)
7
+ end
8
+
9
+ # @return [Array<String>]
10
+ #
11
+ def post_match_lines
12
+ @post_match_lines ||= post_match.split(/\r?\n/)
13
+ end
14
+
15
+ # @return [Fixnum]
16
+ #
17
+ def line_number
18
+ n = pre_match_lines.length
19
+ n += 1 if n == 0 # it can't be zero, this happens only when the match is at the beginning of file or string
20
+ n
21
+ end
22
+
23
+ # @return [Fixnum]
24
+ #
25
+ def line_index
26
+ pre_match_lines.length - 1
27
+ end
28
+
29
+ # @return [String]
30
+ #
31
+ def matched_line
32
+ (pre_match_lines.last || '') + matched_string + (post_match_lines.first || '')
33
+ end
34
+
35
+ # @return [String]
36
+ #
37
+ def matched_string
38
+ self[0]
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class Thread
3
+ class Backtrace
4
+ class Location
5
+ def hash
6
+ to_s.hash
7
+ end
8
+ def ==(other)
9
+ to_s == other.to_s
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,614 @@
1
+ # encoding: utf-8
2
+
3
+ require 'stringio'
4
+
5
+ require 'sinatra/base'
6
+ require 'sinatra/namespace'
7
+ require 'sinatra-websocket'
8
+
9
+ require 'nokogiri'
10
+ require 'listen'
11
+
12
+ require 'active_support'
13
+
14
+ require_relative 'vendor/hash_binding'
15
+ require_relative 'helper'
16
+
17
+ require 'bade'
18
+ require 'stylus'
19
+ require 'coffee-script'
20
+
21
+ require_relative 'third_party/bower'
22
+
23
+
24
+ module Epuber
25
+
26
+ # API:
27
+ # [LATER] /file/<path-or-pattern> -- displays pretty file (image, text file) (for example: /file/text/s01.xhtml or /file/text/s01.bade)
28
+ #
29
+ class Server < Sinatra::Base
30
+ require_relative 'server/handlers'
31
+
32
+ class ShowExceptions < Sinatra::ShowExceptions
33
+ def call(env)
34
+ e = env['sinatra.error']
35
+
36
+ if prefers_plain_text?(env)
37
+ content_type = 'text/plain'
38
+ body = [dump_exception(e)]
39
+ else
40
+ content_type = 'text/html'
41
+ body = pretty(env, e)
42
+ end
43
+
44
+ unless body.is_a?(Array)
45
+ body = [body]
46
+ end
47
+
48
+ [500, { 'Content-Type' => content_type,
49
+ 'Content-Length' => Rack::Utils.bytesize(body.join).to_s },
50
+ body]
51
+ end
52
+ end
53
+
54
+ def self.instance_class_accessor(name)
55
+ instance_name = "@#{name}"
56
+
57
+ define_singleton_method(name) do
58
+ instance_variable_get(instance_name)
59
+ end
60
+ define_singleton_method("#{name}=") do |new_value|
61
+ instance_variable_set(instance_name, new_value)
62
+ end
63
+
64
+ define_method(name) do
65
+ self.class.send(name)
66
+ end
67
+ define_method("#{name}=") do |new_value|
68
+ self.class.send("#{name}=", new_value)
69
+ end
70
+ end
71
+
72
+ # @return [Epuber::Book::Book]
73
+ #
74
+ instance_class_accessor :book
75
+
76
+ # @return [Epuber::Book::Target]
77
+ #
78
+ instance_class_accessor :target
79
+
80
+ # @return [FalseClass, TrueClass]
81
+ #
82
+ instance_class_accessor :verbose
83
+
84
+ # @return [Epuber::Compiler::FileResolver]
85
+ #
86
+ instance_class_accessor :file_resolver
87
+
88
+ # @return [Array<Epuber::Compiler::File>]
89
+ #
90
+ instance_class_accessor :spine
91
+
92
+ # @return [Array<SinatraWebsocket::Connection>]
93
+ #
94
+ instance_class_accessor :sockets
95
+
96
+ def self.sockets
97
+ @sockets ||= []
98
+ end
99
+
100
+ # @return [Listener]
101
+ #
102
+ instance_class_accessor :listener
103
+
104
+ # @return nil
105
+ #
106
+ def self.run!(book, target, verbose: false)
107
+ self.book = book
108
+ self.target = target
109
+ self.verbose = verbose
110
+
111
+ start_listening_if_needed
112
+
113
+ old_stderr = $stderr
114
+ $stderr = StringIO.new unless verbose
115
+
116
+ super() do |server|
117
+ $stderr = old_stderr
118
+ puts "Started development server on #{server.host}:#{server.port}"
119
+
120
+ yield URI("http://#{server.host}:#{server.port}") if block_given?
121
+ end
122
+ end
123
+
124
+ def self.verbose=(verbose)
125
+ @verbose = verbose
126
+ @default_thin_logger ||= Thin::Logging.logger
127
+
128
+ unless verbose
129
+ Thin::Logging.logger = Logger.new(nil)
130
+ Thin::Logging.logger.level = nil
131
+ else
132
+ Thin::Logging.logger = @default_thin_logger
133
+ end
134
+
135
+ set :logging, verbose
136
+ end
137
+
138
+ def initialize
139
+ super
140
+ _log :ui, 'Init compile'
141
+
142
+ self.class.compile_book
143
+ end
144
+
145
+ def self.start_listening_if_needed
146
+ return unless self.listener.nil?
147
+
148
+ self.listener = Listen.to(Config.instance.project_path, debug: true) do |modified, added, removed|
149
+ begin
150
+ changes_detected(modified, added, removed)
151
+ rescue => e
152
+ # print error, do not send error further, listener will die otherwise
153
+ $stderr.puts e
154
+ $stderr.puts e.backtrace
155
+ end
156
+ end
157
+
158
+ listener.ignore(%r{\.idea})
159
+ listener.ignore(%r{#{Config.instance.working_path}})
160
+ listener.ignore(%r{#{Config::WORKING_PATH}/})
161
+
162
+ listener.start
163
+ end
164
+
165
+ # @return [String] base path
166
+ #
167
+ def self.build_path
168
+ Epuber::Config.instance.build_path(target)
169
+ end
170
+
171
+ # @return [String] base path
172
+ #
173
+ def build_path
174
+ self.class.build_path
175
+ end
176
+
177
+
178
+ # @param level [Symbol]
179
+ # @param message [String]
180
+ #
181
+ # @return nil
182
+ #
183
+ def self._log(level, message)
184
+ case level
185
+ when :ui
186
+ puts message
187
+ when :info
188
+ puts "INFO: #{message}" if verbose
189
+ when :get
190
+ puts " GET: #{message}" if verbose
191
+ when :ws
192
+ puts " WS: #{message}" if verbose
193
+ else
194
+ raise "Unknown log level #{level}"
195
+ end
196
+ end
197
+
198
+ def _log(level, message)
199
+ self.class._log(level, message)
200
+ end
201
+
202
+
203
+ # -------------------------------------------------- #
204
+
205
+ # @!group Helpers
206
+
207
+ # @param pattern [String]
208
+ #
209
+ # @return [String] path to file
210
+ #
211
+ def find_file(pattern = params[:splat].first, source_path: build_path)
212
+ finder = Compiler::FileFinders::Normal.new(source_path)
213
+ finder.find_files(pattern).first
214
+ end
215
+
216
+ # @param index [Fixnum]
217
+ # @return [Epuber::Book::File, nil]
218
+ #
219
+ def spine_file_at(index)
220
+ if !file_resolver.nil? && index >= 0 && index < file_resolver.spine_files.count
221
+ file_resolver.spine_files[index]
222
+ end
223
+ end
224
+
225
+ # @param [String] path
226
+ #
227
+ # @return [String]
228
+ #
229
+ def self.relative_path_to_book_file(path)
230
+ file = file_resolver.file_with_source_path(path)
231
+ return if file.nil?
232
+ file.pkg_destination_path
233
+ end
234
+
235
+ def add_script_file_to_head(html_doc, file_name, *args)
236
+ source = File.read(File.expand_path("server/#{file_name}", File.dirname(__FILE__)))
237
+
238
+ if File.extname(file_name) == '.coffee'
239
+ source = CoffeeScript.compile(source)
240
+ end
241
+
242
+ args.each do |hash|
243
+ hash.each do |key, value|
244
+ opt_value = if value
245
+ "'#{value}'"
246
+ else
247
+ 'null'
248
+ end
249
+ source.gsub!(key, opt_value)
250
+ end
251
+ end
252
+
253
+ source = yield source if block_given?
254
+ add_script_to_head(html_doc, source)
255
+ end
256
+
257
+ # @param html_doc [Nokogiri::HTML::Document]
258
+ # @param script_text [String]
259
+ #
260
+ def add_script_to_head(html_doc, script_text)
261
+ script_node = html_doc.create_element('script', script_text, type: 'text/javascript')
262
+
263
+ head = html_doc.at_css('head')
264
+
265
+ if head.nil?
266
+ head = html_doc.create_element('head')
267
+ html_doc.at_css('html').add_child(head)
268
+ end
269
+ head.add_child(script_node)
270
+ end
271
+
272
+ # @param html_doc [Nokogiri::HTML::Document]
273
+ #
274
+ def add_auto_refresh_script(html_doc)
275
+ add_file_to_head(:js, html_doc, 'auto_refresh/reloader.coffee')
276
+ add_file_to_head(:js, html_doc, 'auto_refresh/connector.coffee')
277
+ add_file_to_head(:js, html_doc, 'auto_refresh/protocol.coffee')
278
+ add_file_to_head(:js, html_doc, 'auto_refresh/auto_refresh.coffee')
279
+ add_script_to_head(html_doc, 'var auto_refresh = new AutoRefresh(window, console);')
280
+ end
281
+
282
+ # @param html_doc [Nokogiri::HTML::Document]
283
+ #
284
+ def add_keyboard_control_script(html_doc, previous_path, next_path)
285
+ add_script_file_to_head(html_doc, 'keyboard_control.coffee',
286
+ '$previous_path' => previous_path,
287
+ '$next_path' => next_path)
288
+ end
289
+
290
+ # @param html_doc [Nokogiri::HTML::Document]
291
+ #
292
+ def add_file_to_head(type, html_doc, file_path)
293
+ head = html_doc.at_css('head')
294
+ node = case type
295
+ when :style
296
+ html_doc.create_element('link', href: "/server/raw/#{file_path}" ,rel: 'stylesheet', type: 'text/css')
297
+ when :js
298
+ html_doc.create_element('script', src: "/server/raw/#{file_path}", type: 'text/javascript')
299
+ else
300
+ raise "Unknown file type `#{type}`"
301
+ end
302
+
303
+ return if head.css('script, link').any? { |n| (!n['href'].nil? && n['href'] == node['href']) || (!n['src'].nil? && n['src'] == node['src']) }
304
+
305
+ head.add_child(node)
306
+ end
307
+
308
+ def self.reload_bookspec
309
+ last_target = target
310
+ Config.instance.bookspec = nil
311
+ self.book = Config.instance.bookspec
312
+
313
+ if (new_target = book.target_named(last_target.name))
314
+ self.target = new_target
315
+ else
316
+ self.target = book.targets.first
317
+ _log :ui, "[!] Not found previous target after reloading bookspec file, jumping to first #{self.target.name}"
318
+ end
319
+ end
320
+
321
+ def self._compile_book
322
+ begin
323
+ compiler = Epuber::Compiler.new(book, target)
324
+ compiler.compile(build_path)
325
+ self.file_resolver = compiler.file_resolver
326
+
327
+ true
328
+ rescue => e
329
+ self.file_resolver = compiler.file_resolver
330
+
331
+ Epuber::UI.error("Compile error: #{e}", location: e)
332
+
333
+ false
334
+ end
335
+ end
336
+
337
+ def self.compile_book(&completion)
338
+ if !@compilation_thread.nil? && @compilation_thread.status != false
339
+ @compilation_thread.kill
340
+ @compilation_thread = nil
341
+ end
342
+
343
+ if completion.nil?
344
+ _compile_book
345
+ else
346
+ @compilation_thread = Thread.new do
347
+ completion.call(_compile_book)
348
+ end
349
+ end
350
+ end
351
+
352
+ # @param message [String]
353
+ #
354
+ def self.send_to_clients(message)
355
+ _log :info, "sending message to clients #{message.inspect}"
356
+
357
+ sockets.each do |ws|
358
+ ws.send(message)
359
+ end
360
+ end
361
+
362
+ # @param type [Symbol]
363
+ def self.notify_clients(type, data = nil)
364
+ _log :info, "Notifying clients with type #{type.inspect}"
365
+ raise "Not known type `#{type}`" unless [:styles, :reload, :compile_start, :compile_end].include?(type)
366
+ message = {
367
+ name: type,
368
+ }
369
+ message[:data] = data unless data.nil?
370
+
371
+ send_to_clients(message.to_json)
372
+ end
373
+
374
+ # @param [Array<String>] files_paths
375
+ #
376
+ # @return [Array<String>]
377
+ #
378
+ def self.filter_not_project_files(files_paths)
379
+ return nil if file_resolver.nil?
380
+
381
+ files_paths.select { |file| file_resolver.file_with_source_path(file) || book.file_path == file }
382
+ end
383
+
384
+ # @param _modified [Array<String>]
385
+ # @param _added [Array<String>]
386
+ # @param _removed [Array<String>]
387
+ #
388
+ def self.changes_detected(_modified, _added, _removed)
389
+ all_changed = (_modified + _added + _removed).uniq
390
+
391
+ reload_bookspec if all_changed.any? { |file| file == book.file_path }
392
+
393
+ changed = filter_not_project_files(all_changed) || []
394
+ return if changed.count == 0
395
+
396
+ notify_clients(:compile_start)
397
+
398
+
399
+ _log :ui, "#{Time.now} Compiling"
400
+ compile_book do |success|
401
+ unless success
402
+ _log :ui, 'Skipping other steps'
403
+ notify_clients(:compile_end)
404
+ next
405
+ end
406
+
407
+ _log :ui, 'Notifying clients'
408
+
409
+ # transform all paths to relatives to the server
410
+ changed.map! do |file|
411
+ relative = relative_path_to_book_file(file)
412
+ File.join('', 'book', relative) unless relative.nil?
413
+ end
414
+
415
+ # remove nil paths (for example bookspec can't be found so the relative path is nil)
416
+ changed.compact!
417
+
418
+ if changed.size > 0 && changed.all? { |file| file.end_with?(*Epuber::Compiler::FileFinders::GROUP_EXTENSIONS[:style]) }
419
+ notify_clients(:styles, changed)
420
+ else
421
+ notify_clients(:reload, changed)
422
+ end
423
+ end
424
+ end
425
+
426
+ # -------------------------------------------------- #
427
+
428
+ # @param [String] path
429
+ #
430
+ def handle_websocket(path)
431
+ _log :ws, "#{path}: start"
432
+ request.websocket do |ws|
433
+ thread = nil
434
+
435
+ ws.onopen do
436
+ sockets << ws
437
+
438
+ ws.send({name: :hello}.to_json)
439
+
440
+ thread = Thread.new do
441
+ loop do
442
+ sleep(10)
443
+ ws.send({name: :heartbeat}.to_json)
444
+ end
445
+ end
446
+ end
447
+
448
+ ws.onmessage do |msg|
449
+ _log :ws, "#{path}: received message: #{msg.inspect}"
450
+ end
451
+
452
+ ws.onclose do
453
+ _log :ws, "#{path}: socket closed"
454
+ sockets.delete(ws)
455
+ thread.kill
456
+ end
457
+ end
458
+ end
459
+
460
+ # @param [String] file_path
461
+ #
462
+ def handle_file(file_path)
463
+ return not_found unless File.exists?(file_path)
464
+
465
+ last_modified(File.mtime(file_path))
466
+
467
+ case File.extname(file_path)
468
+ when '.styl'
469
+ content_type('text/css')
470
+ body(Stylus.compile(::File.new(file_path)))
471
+ when '.coffee'
472
+ content_type('text/javascript')
473
+ body(CoffeeScript.compile(::File.read(file_path)))
474
+ else
475
+ extname = File.extname(file_path)
476
+ type = unless Compiler::FileFinders::BINARY_EXTENSIONS.include?(extname)
477
+ mime_type = MIME::Types.of(file_path).first
478
+ if mime_type.nil?
479
+ 'text/plain'
480
+ else
481
+ content_type
482
+ end
483
+ end
484
+
485
+ send_file(file_path, type: type)
486
+ end
487
+ end
488
+
489
+ # -------------------------------------------------- #
490
+
491
+ # @!group Sinatra things
492
+
493
+ register Sinatra::Namespace
494
+
495
+ enable :sessions
496
+ disable :show_exceptions
497
+ disable :dump_errors
498
+ enable :raise_errors
499
+
500
+ set :bind, '0.0.0.0'
501
+
502
+ # uncomment following line to enable accessing from remote devices
503
+ # set :port, '8080'
504
+
505
+ error do
506
+ ShowExceptions.new(self).call(env)
507
+ end
508
+
509
+ not_found do
510
+ [404, 'Epuber: Not found']
511
+ end
512
+
513
+
514
+ # ----------------------------------
515
+ # @group Home page
516
+ #
517
+ namespace '' do
518
+ get '/?' do
519
+ if request.websocket?
520
+ handle_websocket('/')
521
+ else
522
+ handle_server_bade('book.bade')
523
+ end
524
+ end
525
+
526
+ get '/change_target/:target_name' do |target_name|
527
+ selected_target = book.target_named(target_name)
528
+
529
+ next [404, 'Target not found'] if selected_target.nil?
530
+
531
+ self.target = selected_target
532
+ self.class.compile_book
533
+ redirect '/'
534
+ end
535
+ end
536
+
537
+ namespace '/book' do
538
+ get '/*' do
539
+ next handle_websocket("/book/#{params[:splat].first}") if request.websocket?
540
+
541
+ path = find_file
542
+ next not_found if path.nil?
543
+
544
+ full_path = File.expand_path(path, build_path)
545
+
546
+ case File.extname(full_path)
547
+ when '.xhtml'
548
+ handle_xhtml_file(full_path)
549
+ else
550
+ handle_file(full_path)
551
+ end
552
+ end
553
+ end
554
+
555
+ # ------------------------------------------
556
+ # @group TOC
557
+ #
558
+ get '/toc/?' do
559
+ if request.websocket?
560
+ handle_websocket('/toc')
561
+ else
562
+ handle_server_bade('toc.bade')
563
+ end
564
+ end
565
+
566
+ # ----------------------------------
567
+ # @group Pretty files
568
+ #
569
+ namespace '/files' do
570
+ get '/?' do
571
+ if request.websocket?
572
+ handle_websocket('/files')
573
+ else
574
+ handle_server_bade('files.bade')
575
+ end
576
+ end
577
+
578
+ get '/*' do
579
+ path = find_file
580
+ next not_found if path.nil?
581
+
582
+ _log :get, "/files/#{params[:splat].first}: founded file #{path}"
583
+
584
+ handle_file(File.expand_path(path, build_path))
585
+ end
586
+ end
587
+
588
+ # ----------------------------------
589
+ # @group Raw files
590
+
591
+ # Returns file with path or pattern, base_path is epub root
592
+ #
593
+ get '/raw/*' do
594
+ path = find_file
595
+ next not_found if path.nil?
596
+ handle_file(File.expand_path(path, build_path))
597
+ end
598
+
599
+ # ----------------------------------
600
+ # @group Server files
601
+ #
602
+ namespace '/server' do
603
+ get '/raw/vendor/bower/:name/*' do |name, rest|
604
+ path = File.join(ThirdParty::Bower.path_to_js(name.to_sym), rest)
605
+ handle_file(path)
606
+ end
607
+
608
+ get '/raw/*' do
609
+ file_path = File.expand_path("server/#{params[:splat].first}", File.dirname(__FILE__))
610
+ handle_file(file_path)
611
+ end
612
+ end
613
+ end
614
+ end