epuber 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +46 -0
- data/bin/epuber +10 -0
- data/epuber.gemspec +51 -0
- data/lib/epuber.rb +14 -0
- data/lib/epuber/book.rb +267 -0
- data/lib/epuber/book/contributor.rb +90 -0
- data/lib/epuber/book/file_request.rb +68 -0
- data/lib/epuber/book/target.rb +284 -0
- data/lib/epuber/book/toc_item.rb +130 -0
- data/lib/epuber/checker.rb +19 -0
- data/lib/epuber/checker/text_checker.rb +129 -0
- data/lib/epuber/checker_transformer_base.rb +71 -0
- data/lib/epuber/command.rb +65 -0
- data/lib/epuber/command/compile.rb +142 -0
- data/lib/epuber/command/init.rb +145 -0
- data/lib/epuber/command/server.rb +59 -0
- data/lib/epuber/compiler.rb +255 -0
- data/lib/epuber/compiler/compilation_context.rb +86 -0
- data/lib/epuber/compiler/file_finders/abstract.rb +270 -0
- data/lib/epuber/compiler/file_finders/imaginary.rb +167 -0
- data/lib/epuber/compiler/file_finders/normal.rb +22 -0
- data/lib/epuber/compiler/file_resolver.rb +316 -0
- data/lib/epuber/compiler/file_types/abstract_file.rb +119 -0
- data/lib/epuber/compiler/file_types/bade_file.rb +39 -0
- data/lib/epuber/compiler/file_types/container_xml_file.rb +26 -0
- data/lib/epuber/compiler/file_types/generated_file.rb +29 -0
- data/lib/epuber/compiler/file_types/ibooks_display_options_file.rb +31 -0
- data/lib/epuber/compiler/file_types/image_file.rb +42 -0
- data/lib/epuber/compiler/file_types/mime_type_file.rb +20 -0
- data/lib/epuber/compiler/file_types/nav_file.rb +42 -0
- data/lib/epuber/compiler/file_types/opf_file.rb +27 -0
- data/lib/epuber/compiler/file_types/source_file.rb +52 -0
- data/lib/epuber/compiler/file_types/static_file.rb +18 -0
- data/lib/epuber/compiler/file_types/stylus_file.rb +20 -0
- data/lib/epuber/compiler/file_types/xhtml_file.rb +102 -0
- data/lib/epuber/compiler/generator.rb +67 -0
- data/lib/epuber/compiler/meta_inf_generator.rb +41 -0
- data/lib/epuber/compiler/nav_generator.rb +201 -0
- data/lib/epuber/compiler/opf_generator.rb +284 -0
- data/lib/epuber/compiler/xhtml_processor.rb +254 -0
- data/lib/epuber/config.rb +133 -0
- data/lib/epuber/dsl/attribute.rb +248 -0
- data/lib/epuber/dsl/attribute_support.rb +130 -0
- data/lib/epuber/dsl/object.rb +145 -0
- data/lib/epuber/dsl/tree_object.rb +101 -0
- data/lib/epuber/helper.rb +19 -0
- data/lib/epuber/lockfile.rb +55 -0
- data/lib/epuber/plugin.rb +108 -0
- data/lib/epuber/ruby_extensions/match_data.rb +40 -0
- data/lib/epuber/ruby_extensions/thread.rb +13 -0
- data/lib/epuber/server.rb +614 -0
- data/lib/epuber/server/auto_refresh/auto_refresh.coffee +97 -0
- data/lib/epuber/server/auto_refresh/connector.coffee +125 -0
- data/lib/epuber/server/auto_refresh/protocol.coffee +41 -0
- data/lib/epuber/server/auto_refresh/reloader.coffee +261 -0
- data/lib/epuber/server/base.styl +3 -0
- data/lib/epuber/server/basic.styl +211 -0
- data/lib/epuber/server/book_content.styl +39 -0
- data/lib/epuber/server/default_cover.png +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Bold.ttf +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-BoldItalic.ttf +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Italic.ttf +0 -0
- data/lib/epuber/server/fonts/AvenirNext/AvenirNext-Regular.ttf +0 -0
- data/lib/epuber/server/handlers.rb +67 -0
- data/lib/epuber/server/keyboard_control.coffee +6 -0
- data/lib/epuber/server/pages/book.bade +75 -0
- data/lib/epuber/server/pages/common.bade +59 -0
- data/lib/epuber/server/pages/files.bade +17 -0
- data/lib/epuber/server/pages/toc.bade +29 -0
- data/lib/epuber/server/support.coffee +10 -0
- data/lib/epuber/templates/template.bookspec +143 -0
- data/lib/epuber/third_party/bower.rb +22 -0
- data/lib/epuber/third_party/bower/bower.json +10 -0
- data/lib/epuber/third_party/bower/bower_components/cookies-js/bower.json +10 -0
- data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.d.ts +33 -0
- data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.js +173 -0
- data/lib/epuber/third_party/bower/bower_components/cookies-js/dist/cookies.min.js +6 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/MIT-LICENSE.txt +21 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/bower.json +28 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.js +9210 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.min.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/dist/jquery.min.map +1 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax.js +786 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/jsonp.js +89 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/load.js +75 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/parseJSON.js +13 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/parseXML.js +28 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/script.js +64 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/var/nonce.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/var/rquery.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/ajax/xhr.js +136 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes.js +11 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/attr.js +141 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/classes.js +158 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/prop.js +94 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/support.js +35 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/attributes/val.js +161 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/callbacks.js +205 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/core.js +502 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/core/access.js +60 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/core/init.js +123 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/core/parseHTML.js +39 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/core/ready.js +97 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/core/var/rsingleTag.js +4 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css.js +450 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/addGetHookIf.js +22 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/curCSS.js +57 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/defaultDisplay.js +70 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/support.js +96 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/swap.js +28 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/cssExpand.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/getStyles.js +12 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/isHidden.js +13 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/rmargin.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/data.js +178 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/data/Data.js +181 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/data/accepts.js +20 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/data/var/data_priv.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/data/var/data_user.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/deferred.js +149 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/deprecated.js +13 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/dimensions.js +50 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/effects.js +648 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/effects/Tween.js +114 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/effects/animatedSelector.js +13 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/event.js +868 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/event/ajax.js +13 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/event/alias.js +39 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/event/support.js +9 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/exports/amd.js +24 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/exports/global.js +32 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/intro.js +44 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/jquery.js +37 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation.js +580 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/_evalUrl.js +18 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/support.js +32 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/offset.js +207 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/outro.js +1 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/queue.js +142 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/queue/delay.js +22 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/selector-native.js +172 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/selector-sizzle.js +14 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/selector.js +1 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/serialize.js +111 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.js +2067 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing.js +199 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing/findFilter.js +100 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/arr.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/class2type.js +4 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/concat.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/hasOwn.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/indexOf.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/pnum.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/push.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/rnotwhite.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/slice.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/strundefined.js +3 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/support.js +4 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/var/toString.js +5 -0
- data/lib/epuber/third_party/bower/bower_components/jquery/src/wrap.js +79 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/MIT-LICENSE +20 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/Makefile +4 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/README.markdown +212 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/bower.json +27 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/keymaster.js +296 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/package.json +11 -0
- data/lib/epuber/third_party/bower/bower_components/keymaster/test.html +93 -0
- data/lib/epuber/third_party/bower/bower_components/spin.js/LICENSE.txt +21 -0
- data/lib/epuber/third_party/bower/bower_components/spin.js/README.md +21 -0
- data/lib/epuber/third_party/bower/bower_components/spin.js/bower.json +18 -0
- data/lib/epuber/third_party/bower/bower_components/spin.js/jquery.spin.js +80 -0
- data/lib/epuber/third_party/bower/bower_components/spin.js/spin.js +337 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/LICENSE.txt +21 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/README.md +534 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/bower.json +16 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/contributing.md +19 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/IPv6.js +188 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/SecondLevelDomains.js +241 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.fragmentQuery.js +104 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.fragmentURI.js +97 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.js +2115 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/URI.min.js +86 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/URITemplate.js +499 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/jquery.URI.js +235 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/jquery.URI.min.js +7 -0
- data/lib/epuber/third_party/bower/bower_components/uri.js/src/punycode.js +508 -0
- data/lib/epuber/transformer.rb +19 -0
- data/lib/epuber/transformer/text_transformer.rb +60 -0
- data/lib/epuber/user_interface.rb +186 -0
- data/lib/epuber/vendor/globals_context.rb +26 -0
- data/lib/epuber/vendor/hash_binding.rb +26 -0
- data/lib/epuber/vendor/nokogiri_extensions.rb +30 -0
- data/lib/epuber/vendor/ruby_templater.rb +71 -0
- data/lib/epuber/vendor/size.rb +20 -0
- data/lib/epuber/vendor/version.rb +83 -0
- data/lib/epuber/version.rb +4 -0
- 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,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
|