lib/jekyll/commands/serve.rb in jekyll-3.0.5 vs lib/jekyll/commands/serve.rb in jekyll-3.1.0.pre.beta1

- old
+ new

@@ -1,151 +1,196 @@ -# -*- encoding: utf-8 -*- module Jekyll module Commands class Serve < Command - class << self + COMMAND_OPTIONS = { + "ssl_cert" => ["--ssl-cert [CERT]", "X.509 (SSL) certificate."], + "host" => ["host", "-H", "--host [HOST]", "Host to bind to"], + "open_url" => ["-o", "--open-url", "Launch your browser with your site."], + "detach" => ["-B", "--detach", "Run the server in the background (detach)"], + "ssl_key" => ["--ssl-key [KEY]", "X.509 (SSL) Private Key."], + "port" => ["-P", "--port [PORT]", "Port to listen on"], + "baseurl" => ["-b", "--baseurl [URL]", "Base URL"], + "skip_initial_build" => ["skip_initial_build", "--skip-initial-build", + "Skips the initial site build which occurs before the server is started."] + } + # + def init_with_program(prog) - prog.command(:serve) do |c| - c.syntax 'serve [options]' - c.description 'Serve your site locally' - c.alias :server - c.alias :s + prog.command(:serve) do |cmd| + cmd.description "Serve your site locally" + cmd.syntax "serve [options]" + cmd.alias :server + cmd.alias :s - add_build_options(c) + add_build_options(cmd) + COMMAND_OPTIONS.each do |key, val| + cmd.option key, *val + end - c.option 'detach', '-B', '--detach', 'Run the server in the background (detach)' - c.option 'port', '-P', '--port [PORT]', 'Port to listen on' - c.option 'host', '-H', '--host [HOST]', 'Host to bind to' - c.option 'baseurl', '-b', '--baseurl [URL]', 'Base URL' - c.option 'skip_initial_build', '--skip-initial-build', 'Skips the initial site build which occurs before the server is started.' - - c.action do |args, options| - options["serving"] = true - options["watch"] = true unless options.key?("watch") - Jekyll::Commands::Build.process(options) - Jekyll::Commands::Serve.process(options) + cmd.action do |_, opts| + opts["serving"] = true + opts["watch" ] = true unless opts.key?("watch") + Build.process(opts) + Serve.process(opts) end end end - # Boot up a WEBrick server which points to the compiled site's root. - def process(options) - options = configuration_from_options(options) - destination = options['destination'] + # + + def process(opts) + opts = configuration_from_options(opts) + destination = opts["destination"] setup(destination) - s = WEBrick::HTTPServer.new(webrick_options(options)) - s.unmount("") - - s.mount( - options['baseurl'], - custom_file_handler, - destination, - file_handler_options - ) - - Jekyll.logger.info "Server address:", server_address(s, options) - - if options['detach'] # detach the server - pid = Process.fork { s.start } - Process.detach(pid) - Jekyll.logger.info "Server detached with pid '#{pid}'.", "Run `pkill -f jekyll' or `kill -9 #{pid}' to stop the server." - else # create a new server thread, then join it with current terminal - t = Thread.new { s.start } - trap("INT") { s.shutdown } - t.join - end + server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") } + server.mount(opts["baseurl"], Servlet, destination, file_handler_opts) + Jekyll.logger.info "Server address:", server_address(server, opts) + launch_browser server, opts if opts["open_url"] + boot_or_detach server, opts end + # Do a base pre-setup of WEBRick so that everything is in place + # when we get ready to party, checking for an setting up an error page + # and making sure our destination exists. + + private def setup(destination) - require 'webrick' + require_relative "serve/servlet" FileUtils.mkdir_p(destination) - - # monkey patch WEBrick using custom 404 page (/404.html) - if File.exist?(File.join(destination, '404.html')) + if File.exist?(File.join(destination, "404.html")) WEBrick::HTTPResponse.class_eval do def create_error_page - @header['content-type'] = "text/html; charset=UTF-8" - @body = IO.read(File.join(@config[:DocumentRoot], '404.html')) + @header["Content-Type"] = "text/html; charset=UTF-8" + @body = IO.read(File.join(@config[:DocumentRoot], "404.html")) end end end end - def webrick_options(config) + # + + private + def webrick_opts(opts) opts = { - :BindAddress => config['host'], - :DirectoryIndex => %w(index.html index.htm index.cgi index.rhtml index.xml), - :DocumentRoot => config['destination'], + :JekyllOptions => opts, :DoNotReverseLookup => true, :MimeTypes => mime_types, - :Port => config['port'], - :StartCallback => start_callback(config['detach']) + :DocumentRoot => opts["destination"], + :StartCallback => start_callback(opts["detach"]), + :BindAddress => opts["host"], + :Port => opts["port"], + :DirectoryIndex => %W( + index.htm + index.html + index.rhtml + index.cgi + index.xml + ) } - if config['verbose'] - opts.merge!({ - :Logger => WEBrick::Log.new($stdout, WEBrick::Log::DEBUG) - }) + enable_ssl(opts) + enable_logging(opts) + opts + end + + # Recreate NondisclosureName under utf-8 circumstance + + private + def file_handler_opts + WEBrick::Config::FileHandler.merge({ + :FancyIndexing => true, + :NondisclosureName => [ + '.ht*','~*' + ] + }) + end + + # + + private + def server_address(server, opts) + address = server.config[:BindAddress] + baseurl = "#{opts["baseurl"]}/" if opts["baseurl"] + port = server.config[:Port] + + "http://#{address}:#{port}#{baseurl}" + end + + # + + private + def launch_browser(server, opts) + command = Utils::Platforms.windows?? "start" : Utils::Platforms.osx?? "open" : "xdg-open" + system command, server_address(server, opts) + end + + # Keep in our area with a thread or detach the server as requested + # by the user. This method determines what we do based on what you + # ask us to do. + + private + def boot_or_detach(server, opts) + if opts["detach"] + pid = Process.fork do + server.start + end + + Process.detach(pid) + Jekyll.logger.info "Server detached with pid '#{pid}'.", \ + "Run `pkill -f jekyll' or `kill -9 #{pid}' to stop the server." else - opts.merge!({ - :AccessLog => [], - :Logger => WEBrick::Log.new([], WEBrick::Log::WARN) - }) + t = Thread.new { server.start } + trap("INT") { server.shutdown } + t.join end + end - opts + # Make the stack verbose if the user requests it. + + private + def enable_logging(opts) + opts[:AccessLog] = [] + level = WEBrick::Log.const_get(opts[:JekyllOptions]["verbose"] ? :DEBUG : :WARN) + opts[:Logger] = WEBrick::Log.new($stdout, level) end - # Custom WEBrick FileHandler servlet for serving "/file.html" at "/file" - # when no exact match is found. This mirrors the behavior of GitHub - # Pages and many static web server configs. - def custom_file_handler - Class.new WEBrick::HTTPServlet::FileHandler do - def search_file(req, res, basename) - if file = super - file - else - super(req, res, "#{basename}.html") - end - end + # Add SSL to the stack if the user triggers --enable-ssl and they + # provide both types of certificates commonly needed. Raise if they + # forget to add one of the certificates. + + private + def enable_ssl(opts) + return if !opts[:JekyllOptions]["ssl_cert"] && !opts[:JekyllOptions]["ssl_key"] + if !opts[:JekyllOptions]["ssl_cert"] || !opts[:JekyllOptions]["ssl_key"] + raise RuntimeError, "--ssl-cert or --ssl-key missing." end + + require "openssl"; require "webrick/https" + source_key = Jekyll.sanitized_path(opts[:JekyllOptions]["source"], opts[:JekyllOptions]["ssl_key" ]) + source_certificate = Jekyll.sanitized_path(opts[:JekyllOptions]["source"], opts[:JekyllOptions]["ssl_cert"]) + opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(source_certificate)) + opts[:SSLPrivateKey ] = OpenSSL::PKey::RSA.new(File.read(source_key)) + opts[:EnableSSL] = true end + private def start_callback(detached) unless detached - Proc.new { Jekyll.logger.info "Server running...", "press ctrl-c to stop." } + proc do + Jekyll.logger.info("Server running...", "press ctrl-c to stop.") + end end end + private def mime_types - mime_types_file = File.expand_path('../mime.types', File.dirname(__FILE__)) - WEBrick::HTTPUtils::load_mime_types(mime_types_file) + file = File.expand_path('../mime.types', File.dirname(__FILE__)) + WEBrick::HTTPUtils.load_mime_types(file) end - - def server_address(server, options) - baseurl = "#{options['baseurl']}/" if options['baseurl'] - [ - "http://", - server.config[:BindAddress], - ":", - server.config[:Port], - baseurl || "" - ].map(&:to_s).join("") - end - - # recreate NondisclosureName under utf-8 circumstance - def file_handler_options - WEBrick::Config::FileHandler.merge({ - :FancyIndexing => true, - :NondisclosureName => ['.ht*','~*'] - }) - end - end - end end end