Files
news.gitea.happylizard.me/bin/gems/jekyll-4.4.1/lib/jekyll/commands/serve/servlet.rb
T
pizza2d1 4cee170d66
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s
Add bin and edit workflow
2026-09-16 13:11:16 -06:00

207 lines
6.4 KiB
Ruby

# frozen_string_literal: true
require "webrick"
module Jekyll
module Commands
class Serve
# This class is used to determine if the Servlet should modify a served file
# to insert the LiveReload script tags
class SkipAnalyzer
BAD_USER_AGENTS = [%r!MSIE!].freeze
def self.skip_processing?(request, response, options)
new(request, response, options).skip_processing?
end
def initialize(request, response, options)
@options = options
@request = request
@response = response
end
def skip_processing?
!html? || chunked? || inline? || bad_browser?
end
def chunked?
@response["Transfer-Encoding"] == "chunked"
end
def inline?
@response["Content-Disposition"].to_s.start_with?("inline")
end
def bad_browser?
BAD_USER_AGENTS.any? { |pattern| pattern.match?(@request["User-Agent"]) }
end
def html?
@response["Content-Type"].to_s.include?("text/html")
end
end
# This class inserts the LiveReload script tags into HTML as it is served
class BodyProcessor
HEAD_TAG_REGEX = %r!<head>|<head[^(er)][^<]*>!.freeze
attr_reader :content_length, :new_body, :livereload_added
def initialize(body, options)
@body = body
@options = options
@processed = false
end
def processed?
@processed
end
# rubocop:disable Metrics/MethodLength
def process!
@new_body = []
# @body will usually be a File object but Strings occur in rare cases
if @body.respond_to?(:each)
begin
@body.each { |line| @new_body << line.to_s }
ensure
@body.close
end
else
@new_body = @body.lines
end
@content_length = 0
@livereload_added = false
@new_body.each do |line|
if !@livereload_added && line["<head"]
line.gsub!(HEAD_TAG_REGEX) do |match|
%(#{match}#{template.result(binding)})
end
@livereload_added = true
end
@content_length += line.bytesize
@processed = true
end
@new_body = @new_body.join
end
# rubocop:enable Metrics/MethodLength
def template
# Unclear what "snipver" does. Doc at
# https://github.com/livereload/livereload-js states that the recommended
# setting is 1.
# Complicated JavaScript to ensure that livereload.js is loaded from the
# same origin as the page. Mostly useful for dealing with the browser's
# distinction between 'localhost' and 127.0.0.1
@template ||= ERB.new(<<~TEMPLATE)
<script>
document.write(
'<script src="' + location.protocol + '//' +
(location.host || 'localhost').split(':')[0] +
':<%=@options["livereload_port"] %>/livereload.js?snipver=1<%= livereload_args %>"' +
'></' +
'script>');
</script>
TEMPLATE
end
def livereload_args
# XHTML standard requires ampersands to be encoded as entities when in
# attributes. See http://stackoverflow.com/a/2190292
src = ""
if @options["livereload_min_delay"]
src += "&amp;mindelay=#{@options["livereload_min_delay"]}"
end
if @options["livereload_max_delay"]
src += "&amp;maxdelay=#{@options["livereload_max_delay"]}"
end
src += "&amp;port=#{@options["livereload_port"]}" if @options["livereload_port"]
src
end
end
class Servlet < WEBrick::HTTPServlet::FileHandler
DEFAULTS = {
"Cache-Control" => "private, max-age=0, proxy-revalidate, " \
"no-store, no-cache, must-revalidate",
}.freeze
def initialize(server, root, callbacks)
# So we can access them easily.
@jekyll_opts = server.config[:JekyllOptions]
@mime_types_charset = server.config[:MimeTypesCharset]
set_defaults
super
end
def search_index_file(req, res)
super ||
search_file(req, res, ".html") ||
search_file(req, res, ".xhtml")
end
# Add the ability to tap file.html the same way that Nginx does on our
# Docker images (or on GitHub Pages.) The difference is that we might end
# up with a different preference on which comes first.
def search_file(req, res, basename)
# /file.* > /file/index.html > /file.html
super ||
super(req, res, "#{basename}.html") ||
super(req, res, "#{basename}.xhtml")
end
# rubocop:disable Naming/MethodName
def do_GET(req, res)
rtn = super
if @jekyll_opts["livereload"]
return rtn if SkipAnalyzer.skip_processing?(req, res, @jekyll_opts)
processor = BodyProcessor.new(res.body, @jekyll_opts)
processor.process!
res.body = processor.new_body
res.content_length = processor.content_length.to_s
if processor.livereload_added
# Add a header to indicate that the page content has been modified
res["X-Rack-LiveReload"] = "1"
end
end
conditionally_inject_charset(res)
res.header.merge!(@headers)
rtn
end
# rubocop:enable Naming/MethodName
private
# Inject charset based on Jekyll config only if our mime-types database contains
# the charset metadata.
#
# Refer `script/vendor-mimes` in the repository for further details.
def conditionally_inject_charset(res)
typ = res.header["content-type"]
return unless @mime_types_charset.key?(typ)
return if %r!;\s*charset=!.match?(typ)
res.header["content-type"] = "#{typ}; charset=#{@jekyll_opts["encoding"]}"
end
def set_defaults
hash_ = @jekyll_opts.fetch("webrick", {}).fetch("headers", {})
DEFAULTS.each_with_object(@headers = hash_) do |(key, val), hash|
hash[key] = val unless hash.key?(key)
end
end
end
end
end
end