jekyll 4.2.0 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +350 -347
  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 +169 -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 -365
  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 -260
  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 -270
  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,365 +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
- auth = config.values_at("host", "port").join(":")
253
- return config["url"] if auth == "127.0.0.1:4000"
254
-
255
- format_url(
256
- config["ssl_cert"] && config["ssl_key"],
257
- config["host"] == "127.0.0.1" ? "localhost" : config["host"],
258
- config["port"]
259
- )
260
- end
261
-
262
- def launch_browser(server, opts)
263
- address = server_address(server, opts)
264
- return system "start", address if Utils::Platforms.windows?
265
- return system "xdg-open", address if Utils::Platforms.linux?
266
- return system "open", address if Utils::Platforms.osx?
267
-
268
- Jekyll.logger.error "Refusing to launch browser; " \
269
- "Platform launcher unknown."
270
- end
271
-
272
- # Keep in our area with a thread or detach the server as requested
273
- # by the user. This method determines what we do based on what you
274
- # ask us to do.
275
- def boot_or_detach(server, opts)
276
- if opts["detach"]
277
- pid = Process.fork do
278
- server.start
279
- end
280
-
281
- Process.detach(pid)
282
- Jekyll.logger.info "Server detached with pid '#{pid}'.", \
283
- "Run `pkill -f jekyll' or `kill -9 #{pid}'" \
284
- " to stop the server."
285
- else
286
- t = Thread.new { server.start }
287
- trap("INT") { server.shutdown }
288
- t.join
289
- end
290
- end
291
-
292
- # Make the stack verbose if the user requests it.
293
- def enable_logging(opts)
294
- opts[:AccessLog] = []
295
- level = WEBrick::Log.const_get(opts[:JekyllOptions]["verbose"] ? :DEBUG : :WARN)
296
- opts[:Logger] = WEBrick::Log.new($stdout, level)
297
- end
298
-
299
- # Add SSL to the stack if the user triggers --enable-ssl and they
300
- # provide both types of certificates commonly needed. Raise if they
301
- # forget to add one of the certificates.
302
- def enable_ssl(opts)
303
- cert, key, src =
304
- opts[:JekyllOptions].values_at("ssl_cert", "ssl_key", "source")
305
-
306
- return if cert.nil? && key.nil?
307
- raise "Missing --ssl_cert or --ssl_key. Both are required." unless cert && key
308
-
309
- require "openssl"
310
- require "webrick/https"
311
-
312
- opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(read_file(src, cert))
313
- begin
314
- opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(read_file(src, key))
315
- rescue StandardError
316
- if defined?(OpenSSL::PKey::EC)
317
- opts[:SSLPrivateKey] = OpenSSL::PKey::EC.new(read_file(src, key))
318
- else
319
- raise
320
- end
321
- end
322
- opts[:SSLEnable] = true
323
- end
324
-
325
- def start_callback(detached)
326
- unless detached
327
- proc do
328
- mutex.synchronize do
329
- # Block until EventMachine reactor starts
330
- @reload_reactor&.started_event&.wait
331
- @running = true
332
- Jekyll.logger.info("Server running...", "press ctrl-c to stop.")
333
- @run_cond.broadcast
334
- end
335
- end
336
- end
337
- end
338
-
339
- def stop_callback(detached)
340
- unless detached
341
- proc do
342
- mutex.synchronize do
343
- unless @reload_reactor.nil?
344
- @reload_reactor.stop
345
- @reload_reactor.stopped_event.wait
346
- end
347
- @running = false
348
- @run_cond.broadcast
349
- end
350
- end
351
- end
352
- end
353
-
354
- def mime_types
355
- file = File.expand_path("../mime.types", __dir__)
356
- WEBrick::HTTPUtils.load_mime_types(file)
357
- end
358
-
359
- def read_file(source_dir, file_path)
360
- File.read(Jekyll.sanitized_path(source_dir, file_path))
361
- end
362
- end
363
- end
364
- end
365
- 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