class Epuber::Server
API:
- LATER
-
/file/<path-or-pattern> – displays pretty file (image, text file) (for example: /file/text/s01.xhtml or
/file/text/s01.bade)
Public Class Methods
# File lib/epuber/server.rb, line 341 def self._compile_book compiler = Epuber::Compiler.new(book, target) compiler.compile(build_path) self.file_resolver = compiler.file_resolver true rescue StandardError => e self.file_resolver = compiler.file_resolver Epuber::UI.error("Compile error: #{e}", location: e) false end
@param [Symbol] level @param [String] message
@return nil
# File lib/epuber/server.rb, line 187 def self._log(level, message) case level when :ui UI.info message when :info UI.info "INFO: #{message}" if verbose when :get UI.info " GET: #{message}" if verbose when :ws UI.info " WS: #{message}" if verbose else raise "Unknown log level #{level}" end end
@return [String] base path
# File lib/epuber/server.rb, line 172 def self.build_path Epuber::Config.instance.build_path(target) end
@param [Array<String>] modified @param [Array<String>] added @param [Array<String>] removed
# File lib/epuber/server.rb, line 408 def self.changes_detected(modified, added, removed) all_changed = (modified + added + removed).uniq.map(&:unicode_normalize) reload_bookspec if all_changed.any? { |file| file == book.file_path } changed = filter_not_project_files(all_changed) || [] return if changed.count.zero? notify_clients(:compile_start) _log :ui, "#{Time.now} Compiling" compile_book do |success| unless success _log :ui, 'Skipping other steps' notify_clients(:compile_end) next end _log :ui, 'Notifying clients' # transform all paths to relatives to the server changed.map! do |file| relative = relative_path_to_book_file(file) File.join('', 'book', relative) unless relative.nil? end # remove nil paths (for example bookspec can't be found so the relative path is nil) changed.compact! changed_only_styles = changed.all? do |file| file.end_with?(*Epuber::Compiler::FileFinders::GROUP_EXTENSIONS[:style]) end if changed.size.positive? && changed_only_styles notify_clients(:styles, changed) else notify_clients(:reload, changed) end end end
# File lib/epuber/server.rb, line 355 def self.compile_book(&completion) if !@compilation_thread.nil? && @compilation_thread.status != false @compilation_thread.kill @compilation_thread = nil end @compilation_thread = Thread.new do result = _compile_book completion&.call(result) end return unless completion.nil? @compilation_thread.join end
@param [Array<String>] files_paths
@return [Array<String>]
# File lib/epuber/server.rb, line 398 def self.filter_not_project_files(files_paths) return nil if file_resolver.nil? files_paths.select { |file| file_resolver.file_with_source_path(file) || book.file_path == file } end
# File lib/epuber/server.rb, line 53 def self.instance_class_accessor(name) instance_name = "@#{name}" define_singleton_method(name) do instance_variable_get(instance_name) end define_singleton_method("#{name}=") do |new_value| instance_variable_set(instance_name, new_value) end define_method(name) do self.class.send(name) end define_method("#{name}=") do |new_value| self.class.send("#{name}=", new_value) end end
# File lib/epuber/server.rb, line 145 def initialize super _log :ui, 'Init compile' self.class.compile_book end
@param [Symbol] type
# File lib/epuber/server.rb, line 382 def self.notify_clients(type, data = nil) _log :info, "Notifying clients with type #{type.inspect}" raise "Not known type `#{type}`" unless %i[styles reload compile_start compile_end].include?(type) message = { name: type, } message[:data] = data unless data.nil? send_to_clients(message.to_json) end
@param [String] path
@return [String]
# File lib/epuber/server.rb, line 233 def self.relative_path_to_book_file(path) file = file_resolver.file_with_source_path(path) return if file.nil? file.pkg_destination_path end
# File lib/epuber/server.rb, line 328 def self.reload_bookspec last_target = target Config.instance.bookspec = nil self.book = Config.instance.bookspec if (new_target = book.target_named(last_target.name)) self.target = new_target else self.target = book.targets.first _log :ui, "[!] Not found previous target after reloading bookspec file, jumping to first #{target.name}" end end
@param [String] file_path path to bade file to render
@return [String]
# File lib/epuber/server/handlers.rb, line 61 def self.render_bade(file_path) renderer = Bade::Renderer.from_file(file_path) .with_locals(book: book, target: target, file_resolver: file_resolver) renderer.render(new_line: '', indent: '') end
@return nil
# File lib/epuber/server.rb, line 105 def self.run!(book, target, verbose: false) Encoding.default_internal = Encoding::UTF_8 if Encoding.default_internal.nil? self.book = book self.target = target self.verbose = verbose start_listening_if_needed old_stderr = $stderr $stderr = StringIO.new unless verbose super() do |server| $stderr = old_stderr UI.info "Started development server on #{server.host}:#{server.port}" host = if server.host == '0.0.0.0' 'localhost' else server.host end yield URI("http://#{host}:#{server.port}") if block_given? end end
@param [String] message
# File lib/epuber/server.rb, line 373 def self.send_to_clients(message) _log :info, "sending message to clients #{message.inspect}" sockets.each do |ws| ws.send(message) end end
# File lib/epuber/server.rb, line 95 def self.sockets @sockets ||= [] end
# File lib/epuber/server.rb, line 152 def self.start_listening_if_needed return unless listener.nil? self.listener = Listen.to(Config.instance.project_path, debug: true) do |modified, added, removed| changes_detected(modified, added, removed) rescue StandardError => e # print error, do not send error further, listener will die otherwise warn e warn e.backtrace end listener.ignore(/\.idea/) listener.ignore(/#{Config.instance.working_path}/) listener.ignore(%r{#{Config::WORKING_PATH}/}) listener.start end
# File lib/epuber/server.rb, line 131 def self.verbose=(verbose) @verbose = verbose @default_thin_logger ||= Thin::Logging.logger if verbose Thin::Logging.logger = @default_thin_logger else Thin::Logging.logger = ::Logger.new(nil) Thin::Logging.logger.level = :fatal end set :logging, verbose end
Public Instance Methods
# File lib/epuber/server.rb, line 202 def _log(level, message) self.class._log(level, message) end
@param [Nokogiri::HTML::Document] html_doc
# File lib/epuber/server.rb, line 277 def add_auto_refresh_script(html_doc) add_file_to_head(:js, html_doc, 'auto_refresh/reloader.coffee') add_file_to_head(:js, html_doc, 'auto_refresh/connector.coffee') add_file_to_head(:js, html_doc, 'auto_refresh/protocol.coffee') add_file_to_head(:js, html_doc, 'auto_refresh/auto_refresh.coffee') add_script_to_head(html_doc, 'var auto_refresh = new AutoRefresh(window, console);') end
@param [Nokogiri::HTML::Document] html_doc
# File lib/epuber/server.rb, line 295 def add_file_to_head(type, html_doc, file_path) head = html_doc.at_css('head') node = case type when :style html_doc.create_element('link', href: "/server/raw/#{file_path}", rel: 'stylesheet', type: 'text/css') when :js html_doc.create_element('script', src: "/server/raw/#{file_path}", type: 'text/javascript') else raise "Unknown file type `#{type}`" end return if head.css('script, link').any? do |n| (!n['href'].nil? && n['href'] == node['href']) || (!n['src'].nil? && n['src'] == node['src']) end head.add_child(node) end
@param [Nokogiri::HTML::Document] html_doc
# File lib/epuber/server.rb, line 287 def add_keyboard_control_script(html_doc, previous_path, next_path) add_script_file_to_head(html_doc, 'keyboard_control.coffee', '$previous_path' => previous_path, '$next_path' => next_path) end
@param [Nokogiri::HTML::Document] html_doc @param [String] key @param [String] value
# File lib/epuber/server.rb, line 317 def add_meta_to_head(name, content, html_doc, force: false) head = html_doc.at_css('head') meta = head.at_css("meta[name=\"#{name}\"]") return if force == false && !meta.nil? meta ||= html_doc.create_element('meta') meta['name'] = name meta['content'] = content head.add_child(meta) end
# File lib/epuber/server.rb, line 240 def add_script_file_to_head(html_doc, file_name, *args) source = File.read(File.expand_path("server/#{file_name}", File.dirname(__FILE__))) source = CoffeeScript.compile(source) if File.extname(file_name) == '.coffee' args.each do |hash| hash.each do |key, value| opt_value = if value "'#{value}'" else 'null' end source.gsub!(key, opt_value) end end source = yield source if block_given? add_script_to_head(html_doc, source) end
@param [Nokogiri::HTML::Document] html_doc @param [String] script_text
# File lib/epuber/server.rb, line 263 def add_script_to_head(html_doc, script_text) script_node = html_doc.create_element('script', script_text, type: 'text/javascript') head = html_doc.at_css('head') if head.nil? head = html_doc.create_element('head') html_doc.at_css('html').add_child(head) end head.add_child(script_node) end
@return [String] base path
# File lib/epuber/server.rb, line 178 def build_path self.class.build_path end
@param [String] pattern
@return [String] path to file
# File lib/epuber/server.rb, line 215 def find_file(pattern = params[:splat].first, source_path: build_path) finder = Compiler::FileFinders::Normal.new(source_path) finder.find_files(pattern).first end
@param [String] file_path path to bade file to render
@return [(Fixnum, String)]
# File lib/epuber/server/handlers.rb, line 50 def handle_bade(file_path) [200, self.class.render_bade(file_path)] rescue StandardError => e env['sinatra.error'] = e ShowExceptions.new(self).call(env) end
@param [String] file_path
# File lib/epuber/server.rb, line 486 def handle_file(file_path) return not_found unless File.exist?(file_path) mtime = File.mtime(file_path) last_modified(mtime) etag(mtime.to_s) cache_control :public, :must_revalidate case File.extname(file_path) when '.styl' content_type('text/css') body(Stylus.compile(::File.new(file_path))) when '.coffee' content_type('text/javascript') body(CoffeeScript.compile(::File.read(file_path))) else extname = File.extname(file_path) type = unless Compiler::FileFinders::BINARY_EXTENSIONS.include?(extname) mime_type = MIME::Types.of(file_path).first if mime_type.nil? 'text/plain' else content_type end end send_file(file_path, type: type, last_modified: mtime) end end
@param [String] file_name name of the file located in ./pages/
@return [(Fixnum, String)]
# File lib/epuber/server/handlers.rb, line 42 def handle_server_bade(file_name) handle_bade(File.expand_path("pages/#{file_name}", File.dirname(__FILE__))) end
@param [String] path
# File lib/epuber/server.rb, line 454 def handle_websocket(path) _log :ws, "#{path}: start" request.websocket do |ws| thread = nil ws.onopen do sockets << ws ws.send({ name: :hello }.to_json) thread = Thread.new do loop do sleep(10) ws.send({ name: :heartbeat }.to_json) end end end ws.onmessage do |msg| _log :ws, "#{path}: received message: #{msg.inspect}" end ws.onclose do _log :ws, "#{path}: socket closed" sockets.delete(ws) thread.kill end end end
@param [String] file_path absolute path to xhtml file
@return [(Fixnum, String)]
# File lib/epuber/server/handlers.rb, line 11 def handle_xhtml_file(file_path) html_doc = Nokogiri::XML(File.open(file_path)) add_file_to_head(:js, html_doc, 'vendor/bower/jquery/jquery.min.js') add_file_to_head(:js, html_doc, 'vendor/bower/spin/spin.js') add_file_to_head(:js, html_doc, 'vendor/bower/cookies/cookies.min.js') add_file_to_head(:js, html_doc, 'vendor/bower/uri/URI.min.js') add_file_to_head(:js, html_doc, 'vendor/bower/keymaster/keymaster.js') add_file_to_head(:style, html_doc, 'book_content.styl') add_file_to_head(:js, html_doc, 'support.coffee') add_meta_to_head(:viewport, 'width=device-width, initial-scale=1.0', html_doc) add_auto_refresh_script(html_doc) unless file_resolver.nil? current_index = file_resolver.spine_files.index { |file| file.final_destination_path == file_path } unless current_index.nil? previous_path = spine_file_at(current_index - 1).try(:pkg_destination_path) next_path = spine_file_at(current_index + 1).try(:pkg_destination_path) add_keyboard_control_script(html_doc, previous_path, next_path) end end [200, html_doc.to_xhtml] end
@param [Fixnum] index @return [Epuber::Book::File, nil]
# File lib/epuber/server.rb, line 223 def spine_file_at(index) return unless !file_resolver.nil? && index >= 0 && index < file_resolver.spine_files.count file_resolver.spine_files[index] end