jekyll 4.2.1 → 4.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +86 -86
  5. data/exe/jekyll +57 -57
  6. data/lib/blank_template/_config.yml +3 -3
  7. data/lib/blank_template/_layouts/default.html +12 -12
  8. data/lib/blank_template/_sass/main.scss +9 -9
  9. data/lib/blank_template/assets/css/main.scss +4 -4
  10. data/lib/blank_template/index.md +8 -8
  11. data/lib/jekyll/cache.rb +190 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +309 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +93 -93
  16. data/lib/jekyll/commands/clean.rb +45 -45
  17. data/lib/jekyll/commands/doctor.rb +177 -177
  18. data/lib/jekyll/commands/help.rb +34 -34
  19. data/lib/jekyll/commands/new.rb +172 -169
  20. data/lib/jekyll/commands/new_theme.rb +40 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +122 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/servlet.rb +202 -202
  24. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  25. data/lib/jekyll/commands/serve.rb +362 -362
  26. data/lib/jekyll/configuration.rb +313 -313
  27. data/lib/jekyll/converter.rb +54 -54
  28. data/lib/jekyll/converters/identity.rb +41 -41
  29. data/lib/jekyll/converters/markdown/kramdown_parser.rb +199 -199
  30. data/lib/jekyll/converters/markdown.rb +113 -113
  31. data/lib/jekyll/converters/smartypants.rb +70 -70
  32. data/lib/jekyll/convertible.rb +257 -257
  33. data/lib/jekyll/deprecator.rb +50 -50
  34. data/lib/jekyll/document.rb +544 -544
  35. data/lib/jekyll/drops/collection_drop.rb +20 -20
  36. data/lib/jekyll/drops/document_drop.rb +70 -70
  37. data/lib/jekyll/drops/drop.rb +293 -293
  38. data/lib/jekyll/drops/excerpt_drop.rb +19 -19
  39. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  40. data/lib/jekyll/drops/site_drop.rb +66 -66
  41. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  42. data/lib/jekyll/drops/unified_payload_drop.rb +26 -26
  43. data/lib/jekyll/drops/url_drop.rb +140 -140
  44. data/lib/jekyll/entry_filter.rb +121 -121
  45. data/lib/jekyll/errors.rb +20 -20
  46. data/lib/jekyll/excerpt.rb +201 -201
  47. data/lib/jekyll/external.rb +79 -79
  48. data/lib/jekyll/filters/date_filters.rb +110 -110
  49. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  50. data/lib/jekyll/filters/url_filters.rb +98 -98
  51. data/lib/jekyll/filters.rb +535 -535
  52. data/lib/jekyll/frontmatter_defaults.rb +240 -240
  53. data/lib/jekyll/generator.rb +5 -5
  54. data/lib/jekyll/hooks.rb +107 -107
  55. data/lib/jekyll/inclusion.rb +32 -32
  56. data/lib/jekyll/layout.rb +67 -67
  57. data/lib/jekyll/liquid_extensions.rb +22 -22
  58. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  59. data/lib/jekyll/liquid_renderer/table.rb +55 -55
  60. data/lib/jekyll/liquid_renderer.rb +80 -80
  61. data/lib/jekyll/log_adapter.rb +151 -151
  62. data/lib/jekyll/mime.types +866 -866
  63. data/lib/jekyll/page.rb +217 -217
  64. data/lib/jekyll/page_excerpt.rb +25 -25
  65. data/lib/jekyll/page_without_a_file.rb +14 -14
  66. data/lib/jekyll/path_manager.rb +74 -74
  67. data/lib/jekyll/plugin.rb +92 -92
  68. data/lib/jekyll/plugin_manager.rb +115 -115
  69. data/lib/jekyll/profiler.rb +58 -58
  70. data/lib/jekyll/publisher.rb +23 -23
  71. data/lib/jekyll/reader.rb +192 -192
  72. data/lib/jekyll/readers/collection_reader.rb +23 -23
  73. data/lib/jekyll/readers/data_reader.rb +79 -79
  74. data/lib/jekyll/readers/layout_reader.rb +62 -62
  75. data/lib/jekyll/readers/page_reader.rb +25 -25
  76. data/lib/jekyll/readers/post_reader.rb +85 -85
  77. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  78. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  79. data/lib/jekyll/regenerator.rb +195 -195
  80. data/lib/jekyll/related_posts.rb +52 -52
  81. data/lib/jekyll/renderer.rb +265 -265
  82. data/lib/jekyll/site.rb +551 -551
  83. data/lib/jekyll/static_file.rb +208 -208
  84. data/lib/jekyll/stevenson.rb +60 -60
  85. data/lib/jekyll/tags/highlight.rb +110 -110
  86. data/lib/jekyll/tags/include.rb +275 -275
  87. data/lib/jekyll/tags/link.rb +42 -42
  88. data/lib/jekyll/tags/post_url.rb +106 -106
  89. data/lib/jekyll/theme.rb +86 -86
  90. data/lib/jekyll/theme_builder.rb +121 -121
  91. data/lib/jekyll/url.rb +167 -167
  92. data/lib/jekyll/utils/ansi.rb +57 -57
  93. data/lib/jekyll/utils/exec.rb +26 -26
  94. data/lib/jekyll/utils/internet.rb +37 -37
  95. data/lib/jekyll/utils/platforms.rb +67 -67
  96. data/lib/jekyll/utils/thread_event.rb +31 -31
  97. data/lib/jekyll/utils/win_tz.rb +75 -75
  98. data/lib/jekyll/utils.rb +367 -367
  99. data/lib/jekyll/version.rb +5 -5
  100. data/lib/jekyll.rb +195 -195
  101. data/lib/site_template/.gitignore +5 -5
  102. data/lib/site_template/404.html +25 -25
  103. data/lib/site_template/_config.yml +55 -55
  104. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  105. data/lib/site_template/about.markdown +18 -18
  106. data/lib/site_template/index.markdown +6 -6
  107. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  108. data/lib/theme_template/Gemfile +4 -4
  109. data/lib/theme_template/LICENSE.txt.erb +21 -21
  110. data/lib/theme_template/README.md.erb +52 -52
  111. data/lib/theme_template/_layouts/default.html +1 -1
  112. data/lib/theme_template/_layouts/page.html +5 -5
  113. data/lib/theme_template/_layouts/post.html +5 -5
  114. data/lib/theme_template/example/_config.yml.erb +1 -1
  115. data/lib/theme_template/example/_post.md +12 -12
  116. data/lib/theme_template/example/index.html +14 -14
  117. data/lib/theme_template/example/style.scss +7 -7
  118. data/lib/theme_template/gitignore.erb +6 -6
  119. data/lib/theme_template/theme.gemspec.erb +16 -16
  120. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  121. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  122. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  123. data/rubocop/jekyll.rb +5 -5
  124. metadata +3 -3
@@ -1,362 +1,362 @@
1
- # frozen_string_literal: true
2
-
3
- module Jekyll
4
- module Commands
5
- class Serve < Command
6
- # Similar to the pattern in Utils::ThreadEvent except we are maintaining the
7
- # state of @running instead of just signaling an event. We have to maintain this
8
- # state since Serve is just called via class methods instead of an instance
9
- # being created each time.
10
- @mutex = Mutex.new
11
- @run_cond = ConditionVariable.new
12
- @running = false
13
-
14
- class << self
15
- COMMAND_OPTIONS = {
16
- "ssl_cert" => ["--ssl-cert [CERT]", "X.509 (SSL) certificate."],
17
- "host" => ["host", "-H", "--host [HOST]", "Host to bind to"],
18
- "open_url" => ["-o", "--open-url", "Launch your site in a browser"],
19
- "detach" => ["-B", "--detach",
20
- "Run the server in the background",],
21
- "ssl_key" => ["--ssl-key [KEY]", "X.509 (SSL) Private Key."],
22
- "port" => ["-P", "--port [PORT]", "Port to listen on"],
23
- "show_dir_listing" => ["--show-dir-listing",
24
- "Show a directory listing instead of loading" \
25
- " your index file.",],
26
- "skip_initial_build" => ["skip_initial_build", "--skip-initial-build",
27
- "Skips the initial site build which occurs before" \
28
- " the server is started.",],
29
- "livereload" => ["-l", "--livereload",
30
- "Use LiveReload to automatically refresh browsers",],
31
- "livereload_ignore" => ["--livereload-ignore ignore GLOB1[,GLOB2[,...]]",
32
- Array,
33
- "Files for LiveReload to ignore. " \
34
- "Remember to quote the values so your shell " \
35
- "won't expand them",],
36
- "livereload_min_delay" => ["--livereload-min-delay [SECONDS]",
37
- "Minimum reload delay",],
38
- "livereload_max_delay" => ["--livereload-max-delay [SECONDS]",
39
- "Maximum reload delay",],
40
- "livereload_port" => ["--livereload-port [PORT]", Integer,
41
- "Port for LiveReload to listen on",],
42
- }.freeze
43
-
44
- DIRECTORY_INDEX = %w(
45
- index.htm
46
- index.html
47
- index.rhtml
48
- index.xht
49
- index.xhtml
50
- index.cgi
51
- index.xml
52
- index.json
53
- ).freeze
54
-
55
- LIVERELOAD_PORT = 35_729
56
- LIVERELOAD_DIR = File.join(__dir__, "serve", "livereload_assets")
57
-
58
- attr_reader :mutex, :run_cond, :running
59
- alias_method :running?, :running
60
-
61
- def init_with_program(prog)
62
- prog.command(:serve) do |cmd|
63
- cmd.description "Serve your site locally"
64
- cmd.syntax "serve [options]"
65
- cmd.alias :server
66
- cmd.alias :s
67
-
68
- add_build_options(cmd)
69
- COMMAND_OPTIONS.each do |key, val|
70
- cmd.option key, *val
71
- end
72
-
73
- cmd.action do |_, opts|
74
- opts["livereload_port"] ||= LIVERELOAD_PORT
75
- opts["serving"] = true
76
- opts["watch"] = true unless opts.key?("watch")
77
-
78
- # Set the reactor to nil so any old reactor will be GCed.
79
- # We can't unregister a hook so while running tests we don't want to
80
- # inadvertently keep using a reactor created by a previous test.
81
- @reload_reactor = nil
82
-
83
- config = configuration_from_options(opts)
84
- config["url"] = default_url(config) if Jekyll.env == "development"
85
-
86
- process_with_graceful_fail(cmd, config, Build, Serve)
87
- end
88
- end
89
- end
90
-
91
- #
92
-
93
- def process(opts)
94
- opts = configuration_from_options(opts)
95
- destination = opts["destination"]
96
- if opts["livereload"]
97
- validate_options(opts)
98
- register_reload_hooks(opts)
99
- end
100
- setup(destination)
101
-
102
- start_up_webrick(opts, destination)
103
- end
104
-
105
- def shutdown
106
- @server.shutdown if running?
107
- end
108
-
109
- # Perform logical validation of CLI options
110
-
111
- private
112
-
113
- def validate_options(opts)
114
- if opts["livereload"]
115
- if opts["detach"]
116
- Jekyll.logger.warn "Warning:", "--detach and --livereload are mutually exclusive." \
117
- " Choosing --livereload"
118
- opts["detach"] = false
119
- end
120
- if opts["ssl_cert"] || opts["ssl_key"]
121
- # This is not technically true. LiveReload works fine over SSL, but
122
- # EventMachine's SSL support in Windows requires building the gem's
123
- # native extensions against OpenSSL and that proved to be a process
124
- # so tedious that expecting users to do it is a non-starter.
125
- Jekyll.logger.abort_with "Error:", "LiveReload does not support SSL"
126
- end
127
- unless opts["watch"]
128
- # Using livereload logically implies you want to watch the files
129
- opts["watch"] = true
130
- end
131
- elsif %w(livereload_min_delay
132
- livereload_max_delay
133
- livereload_ignore
134
- livereload_port).any? { |o| opts[o] }
135
- Jekyll.logger.abort_with "--livereload-min-delay, "\
136
- "--livereload-max-delay, --livereload-ignore, and "\
137
- "--livereload-port require the --livereload option."
138
- end
139
- end
140
-
141
- # rubocop:disable Metrics/AbcSize
142
- def register_reload_hooks(opts)
143
- require_relative "serve/live_reload_reactor"
144
- @reload_reactor = LiveReloadReactor.new
145
-
146
- Jekyll::Hooks.register(:site, :post_render) do |site|
147
- regenerator = Jekyll::Regenerator.new(site)
148
- @changed_pages = site.pages.select do |p|
149
- regenerator.regenerate?(p)
150
- end
151
- end
152
-
153
- # A note on ignoring files: LiveReload errs on the side of reloading when it
154
- # comes to the message it gets. If, for example, a page is ignored but a CSS
155
- # file linked in the page isn't, the page will still be reloaded if the CSS
156
- # file is contained in the message sent to LiveReload. Additionally, the
157
- # path matching is very loose so that a message to reload "/" will always
158
- # lead the page to reload since every page starts with "/".
159
- Jekyll::Hooks.register(:site, :post_write) do
160
- if @changed_pages && @reload_reactor && @reload_reactor.running?
161
- ignore, @changed_pages = @changed_pages.partition do |p|
162
- Array(opts["livereload_ignore"]).any? do |filter|
163
- File.fnmatch(filter, Jekyll.sanitized_path(p.relative_path))
164
- end
165
- end
166
- Jekyll.logger.debug "LiveReload:", "Ignoring #{ignore.map(&:relative_path)}"
167
- @reload_reactor.reload(@changed_pages)
168
- end
169
- @changed_pages = nil
170
- end
171
- end
172
- # rubocop:enable Metrics/AbcSize
173
-
174
- # Do a base pre-setup of WEBRick so that everything is in place
175
- # when we get ready to party, checking for an setting up an error page
176
- # and making sure our destination exists.
177
-
178
- def setup(destination)
179
- require_relative "serve/servlet"
180
-
181
- FileUtils.mkdir_p(destination)
182
- if File.exist?(File.join(destination, "404.html"))
183
- WEBrick::HTTPResponse.class_eval do
184
- def create_error_page
185
- @header["Content-Type"] = "text/html; charset=UTF-8"
186
- @body = IO.read(File.join(@config[:DocumentRoot], "404.html"))
187
- end
188
- end
189
- end
190
- end
191
-
192
- def webrick_opts(opts)
193
- opts = {
194
- :JekyllOptions => opts,
195
- :DoNotReverseLookup => true,
196
- :MimeTypes => mime_types,
197
- :DocumentRoot => opts["destination"],
198
- :StartCallback => start_callback(opts["detach"]),
199
- :StopCallback => stop_callback(opts["detach"]),
200
- :BindAddress => opts["host"],
201
- :Port => opts["port"],
202
- :DirectoryIndex => DIRECTORY_INDEX,
203
- }
204
-
205
- opts[:DirectoryIndex] = [] if opts[:JekyllOptions]["show_dir_listing"]
206
-
207
- enable_ssl(opts)
208
- enable_logging(opts)
209
- opts
210
- end
211
-
212
- def start_up_webrick(opts, destination)
213
- @reload_reactor.start(opts) if opts["livereload"]
214
-
215
- @server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
216
- @server.mount(opts["baseurl"].to_s, Servlet, destination, file_handler_opts)
217
-
218
- Jekyll.logger.info "Server address:", server_address(@server, opts)
219
- launch_browser @server, opts if opts["open_url"]
220
- boot_or_detach @server, opts
221
- end
222
-
223
- # Recreate NondisclosureName under utf-8 circumstance
224
- def file_handler_opts
225
- WEBrick::Config::FileHandler.merge(
226
- :FancyIndexing => true,
227
- :NondisclosureName => [
228
- ".ht*", "~*",
229
- ]
230
- )
231
- end
232
-
233
- def server_address(server, options = {})
234
- format_url(
235
- server.config[:SSLEnable],
236
- server.config[:BindAddress],
237
- server.config[:Port],
238
- options["baseurl"]
239
- )
240
- end
241
-
242
- def format_url(ssl_enabled, address, port, baseurl = nil)
243
- format("%<prefix>s://%<address>s:%<port>i%<baseurl>s",
244
- :prefix => ssl_enabled ? "https" : "http",
245
- :address => address,
246
- :port => port,
247
- :baseurl => baseurl ? "#{baseurl}/" : "")
248
- end
249
-
250
- def default_url(opts)
251
- config = configuration_from_options(opts)
252
- format_url(
253
- config["ssl_cert"] && config["ssl_key"],
254
- config["host"] == "127.0.0.1" ? "localhost" : config["host"],
255
- config["port"]
256
- )
257
- end
258
-
259
- def launch_browser(server, opts)
260
- address = server_address(server, opts)
261
- return system "start", address if Utils::Platforms.windows?
262
- return system "xdg-open", address if Utils::Platforms.linux?
263
- return system "open", address if Utils::Platforms.osx?
264
-
265
- Jekyll.logger.error "Refusing to launch browser; " \
266
- "Platform launcher unknown."
267
- end
268
-
269
- # Keep in our area with a thread or detach the server as requested
270
- # by the user. This method determines what we do based on what you
271
- # ask us to do.
272
- def boot_or_detach(server, opts)
273
- if opts["detach"]
274
- pid = Process.fork do
275
- server.start
276
- end
277
-
278
- Process.detach(pid)
279
- Jekyll.logger.info "Server detached with pid '#{pid}'.", \
280
- "Run `pkill -f jekyll' or `kill -9 #{pid}'" \
281
- " to stop the server."
282
- else
283
- t = Thread.new { server.start }
284
- trap("INT") { server.shutdown }
285
- t.join
286
- end
287
- end
288
-
289
- # Make the stack verbose if the user requests it.
290
- def enable_logging(opts)
291
- opts[:AccessLog] = []
292
- level = WEBrick::Log.const_get(opts[:JekyllOptions]["verbose"] ? :DEBUG : :WARN)
293
- opts[:Logger] = WEBrick::Log.new($stdout, level)
294
- end
295
-
296
- # Add SSL to the stack if the user triggers --enable-ssl and they
297
- # provide both types of certificates commonly needed. Raise if they
298
- # forget to add one of the certificates.
299
- def enable_ssl(opts)
300
- cert, key, src =
301
- opts[:JekyllOptions].values_at("ssl_cert", "ssl_key", "source")
302
-
303
- return if cert.nil? && key.nil?
304
- raise "Missing --ssl_cert or --ssl_key. Both are required." unless cert && key
305
-
306
- require "openssl"
307
- require "webrick/https"
308
-
309
- opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(read_file(src, cert))
310
- begin
311
- opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(read_file(src, key))
312
- rescue StandardError
313
- if defined?(OpenSSL::PKey::EC)
314
- opts[:SSLPrivateKey] = OpenSSL::PKey::EC.new(read_file(src, key))
315
- else
316
- raise
317
- end
318
- end
319
- opts[:SSLEnable] = true
320
- end
321
-
322
- def start_callback(detached)
323
- unless detached
324
- proc do
325
- mutex.synchronize do
326
- # Block until EventMachine reactor starts
327
- @reload_reactor&.started_event&.wait
328
- @running = true
329
- Jekyll.logger.info("Server running...", "press ctrl-c to stop.")
330
- @run_cond.broadcast
331
- end
332
- end
333
- end
334
- end
335
-
336
- def stop_callback(detached)
337
- unless detached
338
- proc do
339
- mutex.synchronize do
340
- unless @reload_reactor.nil?
341
- @reload_reactor.stop
342
- @reload_reactor.stopped_event.wait
343
- end
344
- @running = false
345
- @run_cond.broadcast
346
- end
347
- end
348
- end
349
- end
350
-
351
- def mime_types
352
- file = File.expand_path("../mime.types", __dir__)
353
- WEBrick::HTTPUtils.load_mime_types(file)
354
- end
355
-
356
- def read_file(source_dir, file_path)
357
- File.read(Jekyll.sanitized_path(source_dir, file_path))
358
- end
359
- end
360
- end
361
- end
362
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module Commands
5
+ class Serve < Command
6
+ # Similar to the pattern in Utils::ThreadEvent except we are maintaining the
7
+ # state of @running instead of just signaling an event. We have to maintain this
8
+ # state since Serve is just called via class methods instead of an instance
9
+ # being created each time.
10
+ @mutex = Mutex.new
11
+ @run_cond = ConditionVariable.new
12
+ @running = false
13
+
14
+ class << self
15
+ COMMAND_OPTIONS = {
16
+ "ssl_cert" => ["--ssl-cert [CERT]", "X.509 (SSL) certificate."],
17
+ "host" => ["host", "-H", "--host [HOST]", "Host to bind to"],
18
+ "open_url" => ["-o", "--open-url", "Launch your site in a browser"],
19
+ "detach" => ["-B", "--detach",
20
+ "Run the server in the background",],
21
+ "ssl_key" => ["--ssl-key [KEY]", "X.509 (SSL) Private Key."],
22
+ "port" => ["-P", "--port [PORT]", "Port to listen on"],
23
+ "show_dir_listing" => ["--show-dir-listing",
24
+ "Show a directory listing instead of loading" \
25
+ " your index file.",],
26
+ "skip_initial_build" => ["skip_initial_build", "--skip-initial-build",
27
+ "Skips the initial site build which occurs before" \
28
+ " the server is started.",],
29
+ "livereload" => ["-l", "--livereload",
30
+ "Use LiveReload to automatically refresh browsers",],
31
+ "livereload_ignore" => ["--livereload-ignore ignore GLOB1[,GLOB2[,...]]",
32
+ Array,
33
+ "Files for LiveReload to ignore. " \
34
+ "Remember to quote the values so your shell " \
35
+ "won't expand them",],
36
+ "livereload_min_delay" => ["--livereload-min-delay [SECONDS]",
37
+ "Minimum reload delay",],
38
+ "livereload_max_delay" => ["--livereload-max-delay [SECONDS]",
39
+ "Maximum reload delay",],
40
+ "livereload_port" => ["--livereload-port [PORT]", Integer,
41
+ "Port for LiveReload to listen on",],
42
+ }.freeze
43
+
44
+ DIRECTORY_INDEX = %w(
45
+ index.htm
46
+ index.html
47
+ index.rhtml
48
+ index.xht
49
+ index.xhtml
50
+ index.cgi
51
+ index.xml
52
+ index.json
53
+ ).freeze
54
+
55
+ LIVERELOAD_PORT = 35_729
56
+ LIVERELOAD_DIR = File.join(__dir__, "serve", "livereload_assets")
57
+
58
+ attr_reader :mutex, :run_cond, :running
59
+ alias_method :running?, :running
60
+
61
+ def init_with_program(prog)
62
+ prog.command(:serve) do |cmd|
63
+ cmd.description "Serve your site locally"
64
+ cmd.syntax "serve [options]"
65
+ cmd.alias :server
66
+ cmd.alias :s
67
+
68
+ add_build_options(cmd)
69
+ COMMAND_OPTIONS.each do |key, val|
70
+ cmd.option key, *val
71
+ end
72
+
73
+ cmd.action do |_, opts|
74
+ opts["livereload_port"] ||= LIVERELOAD_PORT
75
+ opts["serving"] = true
76
+ opts["watch"] = true unless opts.key?("watch")
77
+
78
+ # Set the reactor to nil so any old reactor will be GCed.
79
+ # We can't unregister a hook so while running tests we don't want to
80
+ # inadvertently keep using a reactor created by a previous test.
81
+ @reload_reactor = nil
82
+
83
+ config = configuration_from_options(opts)
84
+ config["url"] = default_url(config) if Jekyll.env == "development"
85
+
86
+ process_with_graceful_fail(cmd, config, Build, Serve)
87
+ end
88
+ end
89
+ end
90
+
91
+ #
92
+
93
+ def process(opts)
94
+ opts = configuration_from_options(opts)
95
+ destination = opts["destination"]
96
+ if opts["livereload"]
97
+ validate_options(opts)
98
+ register_reload_hooks(opts)
99
+ end
100
+ setup(destination)
101
+
102
+ start_up_webrick(opts, destination)
103
+ end
104
+
105
+ def shutdown
106
+ @server.shutdown if running?
107
+ end
108
+
109
+ # Perform logical validation of CLI options
110
+
111
+ private
112
+
113
+ def validate_options(opts)
114
+ if opts["livereload"]
115
+ if opts["detach"]
116
+ Jekyll.logger.warn "Warning:", "--detach and --livereload are mutually exclusive." \
117
+ " Choosing --livereload"
118
+ opts["detach"] = false
119
+ end
120
+ if opts["ssl_cert"] || opts["ssl_key"]
121
+ # This is not technically true. LiveReload works fine over SSL, but
122
+ # EventMachine's SSL support in Windows requires building the gem's
123
+ # native extensions against OpenSSL and that proved to be a process
124
+ # so tedious that expecting users to do it is a non-starter.
125
+ Jekyll.logger.abort_with "Error:", "LiveReload does not support SSL"
126
+ end
127
+ unless opts["watch"]
128
+ # Using livereload logically implies you want to watch the files
129
+ opts["watch"] = true
130
+ end
131
+ elsif %w(livereload_min_delay
132
+ livereload_max_delay
133
+ livereload_ignore
134
+ livereload_port).any? { |o| opts[o] }
135
+ Jekyll.logger.abort_with "--livereload-min-delay, "\
136
+ "--livereload-max-delay, --livereload-ignore, and "\
137
+ "--livereload-port require the --livereload option."
138
+ end
139
+ end
140
+
141
+ # rubocop:disable Metrics/AbcSize
142
+ def register_reload_hooks(opts)
143
+ require_relative "serve/live_reload_reactor"
144
+ @reload_reactor = LiveReloadReactor.new
145
+
146
+ Jekyll::Hooks.register(:site, :post_render) do |site|
147
+ regenerator = Jekyll::Regenerator.new(site)
148
+ @changed_pages = site.pages.select do |p|
149
+ regenerator.regenerate?(p)
150
+ end
151
+ end
152
+
153
+ # A note on ignoring files: LiveReload errs on the side of reloading when it
154
+ # comes to the message it gets. If, for example, a page is ignored but a CSS
155
+ # file linked in the page isn't, the page will still be reloaded if the CSS
156
+ # file is contained in the message sent to LiveReload. Additionally, the
157
+ # path matching is very loose so that a message to reload "/" will always
158
+ # lead the page to reload since every page starts with "/".
159
+ Jekyll::Hooks.register(:site, :post_write) do
160
+ if @changed_pages && @reload_reactor && @reload_reactor.running?
161
+ ignore, @changed_pages = @changed_pages.partition do |p|
162
+ Array(opts["livereload_ignore"]).any? do |filter|
163
+ File.fnmatch(filter, Jekyll.sanitized_path(p.relative_path))
164
+ end
165
+ end
166
+ Jekyll.logger.debug "LiveReload:", "Ignoring #{ignore.map(&:relative_path)}"
167
+ @reload_reactor.reload(@changed_pages)
168
+ end
169
+ @changed_pages = nil
170
+ end
171
+ end
172
+ # rubocop:enable Metrics/AbcSize
173
+
174
+ # Do a base pre-setup of WEBRick so that everything is in place
175
+ # when we get ready to party, checking for an setting up an error page
176
+ # and making sure our destination exists.
177
+
178
+ def setup(destination)
179
+ require_relative "serve/servlet"
180
+
181
+ FileUtils.mkdir_p(destination)
182
+ if File.exist?(File.join(destination, "404.html"))
183
+ WEBrick::HTTPResponse.class_eval do
184
+ def create_error_page
185
+ @header["Content-Type"] = "text/html; charset=UTF-8"
186
+ @body = IO.read(File.join(@config[:DocumentRoot], "404.html"))
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def webrick_opts(opts)
193
+ opts = {
194
+ :JekyllOptions => opts,
195
+ :DoNotReverseLookup => true,
196
+ :MimeTypes => mime_types,
197
+ :DocumentRoot => opts["destination"],
198
+ :StartCallback => start_callback(opts["detach"]),
199
+ :StopCallback => stop_callback(opts["detach"]),
200
+ :BindAddress => opts["host"],
201
+ :Port => opts["port"],
202
+ :DirectoryIndex => DIRECTORY_INDEX,
203
+ }
204
+
205
+ opts[:DirectoryIndex] = [] if opts[:JekyllOptions]["show_dir_listing"]
206
+
207
+ enable_ssl(opts)
208
+ enable_logging(opts)
209
+ opts
210
+ end
211
+
212
+ def start_up_webrick(opts, destination)
213
+ @reload_reactor.start(opts) if opts["livereload"]
214
+
215
+ @server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
216
+ @server.mount(opts["baseurl"].to_s, Servlet, destination, file_handler_opts)
217
+
218
+ Jekyll.logger.info "Server address:", server_address(@server, opts)
219
+ launch_browser @server, opts if opts["open_url"]
220
+ boot_or_detach @server, opts
221
+ end
222
+
223
+ # Recreate NondisclosureName under utf-8 circumstance
224
+ def file_handler_opts
225
+ WEBrick::Config::FileHandler.merge(
226
+ :FancyIndexing => true,
227
+ :NondisclosureName => [
228
+ ".ht*", "~*",
229
+ ]
230
+ )
231
+ end
232
+
233
+ def server_address(server, options = {})
234
+ format_url(
235
+ server.config[:SSLEnable],
236
+ server.config[:BindAddress],
237
+ server.config[:Port],
238
+ options["baseurl"]
239
+ )
240
+ end
241
+
242
+ def format_url(ssl_enabled, address, port, baseurl = nil)
243
+ format("%<prefix>s://%<address>s:%<port>i%<baseurl>s",
244
+ :prefix => ssl_enabled ? "https" : "http",
245
+ :address => address,
246
+ :port => port,
247
+ :baseurl => baseurl ? "#{baseurl}/" : "")
248
+ end
249
+
250
+ def default_url(opts)
251
+ config = configuration_from_options(opts)
252
+ format_url(
253
+ config["ssl_cert"] && config["ssl_key"],
254
+ config["host"] == "127.0.0.1" ? "localhost" : config["host"],
255
+ config["port"]
256
+ )
257
+ end
258
+
259
+ def launch_browser(server, opts)
260
+ address = server_address(server, opts)
261
+ return system "start", address if Utils::Platforms.windows?
262
+ return system "xdg-open", address if Utils::Platforms.linux?
263
+ return system "open", address if Utils::Platforms.osx?
264
+
265
+ Jekyll.logger.error "Refusing to launch browser; " \
266
+ "Platform launcher unknown."
267
+ end
268
+
269
+ # Keep in our area with a thread or detach the server as requested
270
+ # by the user. This method determines what we do based on what you
271
+ # ask us to do.
272
+ def boot_or_detach(server, opts)
273
+ if opts["detach"]
274
+ pid = Process.fork do
275
+ server.start
276
+ end
277
+
278
+ Process.detach(pid)
279
+ Jekyll.logger.info "Server detached with pid '#{pid}'.", \
280
+ "Run `pkill -f jekyll' or `kill -9 #{pid}'" \
281
+ " to stop the server."
282
+ else
283
+ t = Thread.new { server.start }
284
+ trap("INT") { server.shutdown }
285
+ t.join
286
+ end
287
+ end
288
+
289
+ # Make the stack verbose if the user requests it.
290
+ def enable_logging(opts)
291
+ opts[:AccessLog] = []
292
+ level = WEBrick::Log.const_get(opts[:JekyllOptions]["verbose"] ? :DEBUG : :WARN)
293
+ opts[:Logger] = WEBrick::Log.new($stdout, level)
294
+ end
295
+
296
+ # Add SSL to the stack if the user triggers --enable-ssl and they
297
+ # provide both types of certificates commonly needed. Raise if they
298
+ # forget to add one of the certificates.
299
+ def enable_ssl(opts)
300
+ cert, key, src =
301
+ opts[:JekyllOptions].values_at("ssl_cert", "ssl_key", "source")
302
+
303
+ return if cert.nil? && key.nil?
304
+ raise "Missing --ssl_cert or --ssl_key. Both are required." unless cert && key
305
+
306
+ require "openssl"
307
+ require "webrick/https"
308
+
309
+ opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(read_file(src, cert))
310
+ begin
311
+ opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(read_file(src, key))
312
+ rescue StandardError
313
+ if defined?(OpenSSL::PKey::EC)
314
+ opts[:SSLPrivateKey] = OpenSSL::PKey::EC.new(read_file(src, key))
315
+ else
316
+ raise
317
+ end
318
+ end
319
+ opts[:SSLEnable] = true
320
+ end
321
+
322
+ def start_callback(detached)
323
+ unless detached
324
+ proc do
325
+ mutex.synchronize do
326
+ # Block until EventMachine reactor starts
327
+ @reload_reactor&.started_event&.wait
328
+ @running = true
329
+ Jekyll.logger.info("Server running...", "press ctrl-c to stop.")
330
+ @run_cond.broadcast
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ def stop_callback(detached)
337
+ unless detached
338
+ proc do
339
+ mutex.synchronize do
340
+ unless @reload_reactor.nil?
341
+ @reload_reactor.stop
342
+ @reload_reactor.stopped_event.wait
343
+ end
344
+ @running = false
345
+ @run_cond.broadcast
346
+ end
347
+ end
348
+ end
349
+ end
350
+
351
+ def mime_types
352
+ file = File.expand_path("../mime.types", __dir__)
353
+ WEBrick::HTTPUtils.load_mime_types(file)
354
+ end
355
+
356
+ def read_file(source_dir, file_path)
357
+ File.read(Jekyll.sanitized_path(source_dir, file_path))
358
+ end
359
+ end
360
+ end
361
+ end
362
+ end