epuber 0.3.5

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 (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