lib/hanami/commands/server.rb in hanami-0.7.3 vs lib/hanami/commands/server.rb in hanami-0.8.0

- old
+ new

@@ -1,80 +1,156 @@ require 'rack' +require 'hanami/server' module Hanami module Commands - # Rack compatible server. - # - # It is run with: - # - # `bundle exec hanami server` - # - # It runs the application, by using the server specified in your `Gemfile` - # (eg. Puma or Unicorn). - # - # It enables code reloading by default. - # This feature is implemented via process fork and requires `shotgun` gem. - # - # @since 0.1.0 - # @api private - class Server < ::Rack::Server - attr_reader :options + class Server - # @param options [Hash] Environment's options + # Message text when Shotgun enabled but interpreter does not support `fork` # - # @since 0.1.0 - # @see Hanami::Environment#initialize + # @since 0.8.0 + # @api private + WARNING_MESSAGE = 'Your platform doesn\'t support code reloading.'.freeze + + ENTR_EXECUTE_COMMAND = "find %{paths} -type f | entr -r bundle exec hanami rackserver %{args}".freeze + + attr_reader :server + def initialize(options) - @_env = Hanami::Environment.new(options) - @options = _extract_options(@_env) + @options = options + detect_strategy! + prepare_server! + end - if code_reloading? - require 'shotgun' - @app = Shotgun::Loader.new(@_env.rackup.to_s) + def start + preload_applications! + + case @strategy + when :entr + exec ENTR_EXECUTE_COMMAND % {paths: project_paths, args: server_options} + when :shotgun + Shotgun.enable_copy_on_write + Shotgun.preload + @server.start + when :rackup + @server.start end end - # Primarily this removes the ::Rack::Chunked middleware - # which is the cause of Safari content-length bugs. - # - # @since 0.1.0 - def middleware - mw = Hash.new { |e, m| e[m] = [] } - mw["deployment"].concat([::Rack::ContentLength, ::Rack::CommonLogger]) - mw["development"].concat(mw["deployment"] + [::Rack::ShowExceptions, ::Rack::Lint]) - mw["development"].push(::Shotgun::Static) if code_reloading? - mw + private + + def server_options + _options = @options.dup + _options.delete(:code_reloading) + _options.inject([]) {|res, (k, v)| res << "--#{k}=#{v}" ; res}.join(" ") end - # Kickstart shotgun preloader if code reloading is supported + def project_paths + applications = Hanami::Environment.new.container? ? 'apps' : 'app' + "#{ applications } config db lib" + end + + def prepare_server! + case @strategy + when :rackup + @server = Hanami::Server.new(@options) + when :shotgun + @server = Hanami::Server.new(@options) + @server.app = Shotgun::Loader.new(@server.rackup_config) + end + end + + # Determine server strategy # - # @since 0.1.0 - def start - if code_reloading? - Shotgun.enable_copy_on_write - Shotgun.preload + # In order to decide the value, it looks up the following sources: + # + # * CLI option `code_reloading` + # + # If those are missing it falls back to the following defaults: + # + # * :shotgun for development and if Shotgun enabled and `fork supported + # * :entr for development and Shotgun disabled but `entr` installed + # * :rackup for all other cases + # + # @return [:shotgun,:entr, :rackup] the result of the check + # + # @since 0.8.0 + # + # @see Hanami::Environment::CODE_RELOADING + def detect_strategy! + @strategy = :rackup + if Hanami::Environment.new(@options).code_reloading? + if shotgun_enabled? + if fork_supported? + @strategy = :shotgun + else + puts WARNING_MESSAGE + end + elsif entr_enabled? + @strategy = :entr + end end - super + @strategy end - private + def preload_applications! + Hanami::Environment.new(@options).require_application_environment + Hanami::Application.preload! + end - # @since 0.1.0 + # Check if entr(1) is installed + # + # @return [Boolean] + # + # @since 0.8.0 # @api private - def _extract_options(env) - env.to_options.merge( - config: env.rackup.to_s, - Host: env.host, - Port: env.port, - AccessLog: [] - ) + def entr_enabled? + !!which('entr') end - # @since 0.1.0 + + # Check if Shotgun is enabled + # + # @return [Boolean] + # + # @since 0.8.0 # @api private - def code_reloading? - @_env.code_reloading? + def shotgun_enabled? + begin + require 'shotgun' + true + rescue LoadError + false + end + end + + # Check if ruby interpreter supports `fork` + # + # @return [Boolean] + # + # @since 0.8.0 + # @api private + def fork_supported? + Kernel.respond_to?(:fork) + end + + # Cross-platform way of finding an executable in the $PATH. + # + # Usage: + # which('ruby') #=> /usr/bin/ruby + # + # @since 0.8.0 + # @api private + def which(cmd) + exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] + ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| + exts.each { |ext| + exe = File.join(path, "#{cmd}#{ext}") + return exe if File.executable?(exe) && !File.directory?(exe) + } + end + return nil end end end end