jekyll 4.2.1 → 4.3.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 (126) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +474 -350
  3. data/LICENSE +21 -21
  4. data/README.markdown +83 -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 → base.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 +186 -190
  12. data/lib/jekyll/cleaner.rb +111 -111
  13. data/lib/jekyll/collection.rb +310 -309
  14. data/lib/jekyll/command.rb +105 -105
  15. data/lib/jekyll/commands/build.rb +82 -93
  16. data/lib/jekyll/commands/clean.rb +44 -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 +168 -169
  20. data/lib/jekyll/commands/new_theme.rb +39 -40
  21. data/lib/jekyll/commands/serve/live_reload_reactor.rb +119 -122
  22. data/lib/jekyll/commands/serve/livereload_assets/livereload.js +1183 -1183
  23. data/lib/jekyll/commands/serve/mime_types_charset.json +71 -0
  24. data/lib/jekyll/commands/serve/servlet.rb +206 -202
  25. data/lib/jekyll/commands/serve/websockets.rb +81 -81
  26. data/lib/jekyll/commands/serve.rb +367 -362
  27. data/lib/jekyll/configuration.rb +313 -313
  28. data/lib/jekyll/converter.rb +54 -54
  29. data/lib/jekyll/converters/identity.rb +41 -41
  30. data/lib/jekyll/converters/markdown/kramdown_parser.rb +197 -199
  31. data/lib/jekyll/converters/markdown.rb +113 -113
  32. data/lib/jekyll/converters/smartypants.rb +70 -70
  33. data/lib/jekyll/convertible.rb +257 -257
  34. data/lib/jekyll/deprecator.rb +50 -50
  35. data/lib/jekyll/document.rb +543 -544
  36. data/lib/jekyll/drops/collection_drop.rb +20 -20
  37. data/lib/jekyll/drops/document_drop.rb +74 -70
  38. data/lib/jekyll/drops/drop.rb +293 -293
  39. data/lib/jekyll/drops/excerpt_drop.rb +23 -19
  40. data/lib/jekyll/drops/jekyll_drop.rb +32 -32
  41. data/lib/jekyll/drops/site_drop.rb +66 -66
  42. data/lib/jekyll/drops/static_file_drop.rb +14 -14
  43. data/lib/jekyll/drops/theme_drop.rb +36 -0
  44. data/lib/jekyll/drops/unified_payload_drop.rb +30 -26
  45. data/lib/jekyll/drops/url_drop.rb +140 -140
  46. data/lib/jekyll/entry_filter.rb +117 -121
  47. data/lib/jekyll/errors.rb +20 -20
  48. data/lib/jekyll/excerpt.rb +200 -201
  49. data/lib/jekyll/external.rb +75 -79
  50. data/lib/jekyll/filters/date_filters.rb +110 -110
  51. data/lib/jekyll/filters/grouping_filters.rb +64 -64
  52. data/lib/jekyll/filters/url_filters.rb +98 -98
  53. data/lib/jekyll/filters.rb +532 -535
  54. data/lib/jekyll/frontmatter_defaults.rb +238 -240
  55. data/lib/jekyll/generator.rb +5 -5
  56. data/lib/jekyll/hooks.rb +107 -107
  57. data/lib/jekyll/inclusion.rb +32 -32
  58. data/lib/jekyll/layout.rb +55 -67
  59. data/lib/jekyll/liquid_extensions.rb +22 -22
  60. data/lib/jekyll/liquid_renderer/file.rb +77 -77
  61. data/lib/jekyll/liquid_renderer/table.rb +45 -55
  62. data/lib/jekyll/liquid_renderer.rb +80 -80
  63. data/lib/jekyll/log_adapter.rb +151 -151
  64. data/lib/jekyll/mime.types +939 -866
  65. data/lib/jekyll/page.rb +215 -217
  66. data/lib/jekyll/page_excerpt.rb +25 -25
  67. data/lib/jekyll/page_without_a_file.rb +14 -14
  68. data/lib/jekyll/path_manager.rb +74 -74
  69. data/lib/jekyll/plugin.rb +92 -92
  70. data/lib/jekyll/plugin_manager.rb +123 -115
  71. data/lib/jekyll/profiler.rb +51 -58
  72. data/lib/jekyll/publisher.rb +23 -23
  73. data/lib/jekyll/reader.rb +209 -192
  74. data/lib/jekyll/readers/collection_reader.rb +23 -23
  75. data/lib/jekyll/readers/data_reader.rb +113 -79
  76. data/lib/jekyll/readers/layout_reader.rb +62 -62
  77. data/lib/jekyll/readers/page_reader.rb +25 -25
  78. data/lib/jekyll/readers/post_reader.rb +85 -85
  79. data/lib/jekyll/readers/static_file_reader.rb +25 -25
  80. data/lib/jekyll/readers/theme_assets_reader.rb +52 -52
  81. data/lib/jekyll/regenerator.rb +195 -195
  82. data/lib/jekyll/related_posts.rb +52 -52
  83. data/lib/jekyll/renderer.rb +263 -265
  84. data/lib/jekyll/site.rb +576 -551
  85. data/lib/jekyll/static_file.rb +205 -208
  86. data/lib/jekyll/stevenson.rb +60 -60
  87. data/lib/jekyll/tags/highlight.rb +114 -110
  88. data/lib/jekyll/tags/include.rb +275 -275
  89. data/lib/jekyll/tags/link.rb +42 -42
  90. data/lib/jekyll/tags/post_url.rb +106 -106
  91. data/lib/jekyll/theme.rb +90 -86
  92. data/lib/jekyll/theme_builder.rb +121 -121
  93. data/lib/jekyll/url.rb +167 -167
  94. data/lib/jekyll/utils/ansi.rb +57 -57
  95. data/lib/jekyll/utils/exec.rb +26 -26
  96. data/lib/jekyll/utils/internet.rb +37 -37
  97. data/lib/jekyll/utils/platforms.rb +67 -67
  98. data/lib/jekyll/utils/thread_event.rb +31 -31
  99. data/lib/jekyll/utils/win_tz.rb +46 -75
  100. data/lib/jekyll/utils.rb +371 -367
  101. data/lib/jekyll/version.rb +5 -5
  102. data/lib/jekyll.rb +195 -195
  103. data/lib/site_template/.gitignore +5 -5
  104. data/lib/site_template/404.html +25 -25
  105. data/lib/site_template/_config.yml +55 -55
  106. data/lib/site_template/_posts/0000-00-00-welcome-to-jekyll.markdown.erb +29 -29
  107. data/lib/site_template/about.markdown +18 -18
  108. data/lib/site_template/index.markdown +6 -6
  109. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -74
  110. data/lib/theme_template/Gemfile +4 -4
  111. data/lib/theme_template/LICENSE.txt.erb +21 -21
  112. data/lib/theme_template/README.md.erb +50 -52
  113. data/lib/theme_template/_layouts/default.html +1 -1
  114. data/lib/theme_template/_layouts/page.html +5 -5
  115. data/lib/theme_template/_layouts/post.html +5 -5
  116. data/lib/theme_template/example/_config.yml.erb +1 -1
  117. data/lib/theme_template/example/_post.md +12 -12
  118. data/lib/theme_template/example/index.html +14 -14
  119. data/lib/theme_template/example/style.scss +7 -7
  120. data/lib/theme_template/gitignore.erb +6 -6
  121. data/lib/theme_template/theme.gemspec.erb +16 -16
  122. data/rubocop/jekyll/assert_equal_literal_actual.rb +149 -149
  123. data/rubocop/jekyll/no_p_allowed.rb +23 -23
  124. data/rubocop/jekyll/no_puts_allowed.rb +23 -23
  125. data/rubocop/jekyll.rb +5 -5
  126. metadata +64 -18
@@ -1,362 +1,367 @@
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, --livereload-max-delay, " \
136
+ "--livereload-ignore, and --livereload-port require " \
137
+ "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
+ @changed_pages = []
148
+ site.each_site_file do |item|
149
+ @changed_pages << item if site.regenerator.regenerate?(item)
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
+ # rubocop:disable Security/IoMethods
179
+ def setup(destination)
180
+ require_relative "serve/servlet"
181
+
182
+ FileUtils.mkdir_p(destination)
183
+ if File.exist?(File.join(destination, "404.html"))
184
+ WEBrick::HTTPResponse.class_eval do
185
+ def create_error_page
186
+ @header["Content-Type"] = "text/html; charset=UTF-8"
187
+ @body = IO.read(File.join(@config[:DocumentRoot], "404.html"))
188
+ end
189
+ end
190
+ end
191
+ end
192
+ # rubocop:enable Security/IoMethods
193
+
194
+ def webrick_opts(opts)
195
+ opts = {
196
+ :JekyllOptions => opts,
197
+ :DoNotReverseLookup => true,
198
+ :MimeTypes => mime_types,
199
+ :MimeTypesCharset => mime_types_charset,
200
+ :DocumentRoot => opts["destination"],
201
+ :StartCallback => start_callback(opts["detach"]),
202
+ :StopCallback => stop_callback(opts["detach"]),
203
+ :BindAddress => opts["host"],
204
+ :Port => opts["port"],
205
+ :DirectoryIndex => DIRECTORY_INDEX,
206
+ }
207
+
208
+ opts[:DirectoryIndex] = [] if opts[:JekyllOptions]["show_dir_listing"]
209
+
210
+ enable_ssl(opts)
211
+ enable_logging(opts)
212
+ opts
213
+ end
214
+
215
+ def start_up_webrick(opts, destination)
216
+ @reload_reactor.start(opts) if opts["livereload"]
217
+
218
+ @server = WEBrick::HTTPServer.new(webrick_opts(opts)).tap { |o| o.unmount("") }
219
+ @server.mount(opts["baseurl"].to_s, Servlet, destination, file_handler_opts)
220
+
221
+ Jekyll.logger.info "Server address:", server_address(@server, opts)
222
+ launch_browser @server, opts if opts["open_url"]
223
+ boot_or_detach @server, opts
224
+ end
225
+
226
+ # Recreate NondisclosureName under utf-8 circumstance
227
+ def file_handler_opts
228
+ WEBrick::Config::FileHandler.merge(
229
+ :FancyIndexing => true,
230
+ :NondisclosureName => [
231
+ ".ht*", "~*",
232
+ ]
233
+ )
234
+ end
235
+
236
+ def server_address(server, options = {})
237
+ format_url(
238
+ server.config[:SSLEnable],
239
+ server.config[:BindAddress],
240
+ server.config[:Port],
241
+ options["baseurl"]
242
+ )
243
+ end
244
+
245
+ def format_url(ssl_enabled, address, port, baseurl = nil)
246
+ format("%<prefix>s://%<address>s:%<port>i%<baseurl>s",
247
+ :prefix => ssl_enabled ? "https" : "http",
248
+ :address => address,
249
+ :port => port,
250
+ :baseurl => baseurl ? "#{baseurl}/" : "")
251
+ end
252
+
253
+ def default_url(opts)
254
+ config = configuration_from_options(opts)
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. Platform launcher unknown."
269
+ end
270
+
271
+ # Keep in our area with a thread or detach the server as requested
272
+ # by the user. This method determines what we do based on what you
273
+ # ask us to do.
274
+ def boot_or_detach(server, opts)
275
+ if opts["detach"]
276
+ pid = Process.fork do
277
+ server.start
278
+ end
279
+
280
+ Process.detach(pid)
281
+ Jekyll.logger.info "Server detached with pid '#{pid}'.",
282
+ "Run `pkill -f jekyll' or `kill -9 #{pid}' to stop the server."
283
+ else
284
+ t = Thread.new { server.start }
285
+ trap("INT") { server.shutdown }
286
+ t.join
287
+ end
288
+ end
289
+
290
+ # Make the stack verbose if the user requests it.
291
+ def enable_logging(opts)
292
+ opts[:AccessLog] = []
293
+ level = WEBrick::Log.const_get(opts[:JekyllOptions]["verbose"] ? :DEBUG : :WARN)
294
+ opts[:Logger] = WEBrick::Log.new($stdout, level)
295
+ end
296
+
297
+ # Add SSL to the stack if the user triggers --enable-ssl and they
298
+ # provide both types of certificates commonly needed. Raise if they
299
+ # forget to add one of the certificates.
300
+ def enable_ssl(opts)
301
+ cert, key, src =
302
+ opts[:JekyllOptions].values_at("ssl_cert", "ssl_key", "source")
303
+
304
+ return if cert.nil? && key.nil?
305
+ raise "Missing --ssl_cert or --ssl_key. Both are required." unless cert && key
306
+
307
+ require "openssl"
308
+ require "webrick/https"
309
+
310
+ opts[:SSLCertificate] = OpenSSL::X509::Certificate.new(read_file(src, cert))
311
+ begin
312
+ opts[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(read_file(src, key))
313
+ rescue StandardError
314
+ if defined?(OpenSSL::PKey::EC)
315
+ opts[:SSLPrivateKey] = OpenSSL::PKey::EC.new(read_file(src, key))
316
+ else
317
+ raise
318
+ end
319
+ end
320
+ opts[:SSLEnable] = true
321
+ end
322
+
323
+ def start_callback(detached)
324
+ unless detached
325
+ proc do
326
+ mutex.synchronize do
327
+ # Block until EventMachine reactor starts
328
+ @reload_reactor&.started_event&.wait
329
+ @running = true
330
+ Jekyll.logger.info("Server running...", "press ctrl-c to stop.")
331
+ @run_cond.broadcast
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ def stop_callback(detached)
338
+ unless detached
339
+ proc do
340
+ mutex.synchronize do
341
+ unless @reload_reactor.nil?
342
+ @reload_reactor.stop
343
+ @reload_reactor.stopped_event.wait
344
+ end
345
+ @running = false
346
+ @run_cond.broadcast
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ def mime_types
353
+ file = File.expand_path("../mime.types", __dir__)
354
+ WEBrick::HTTPUtils.load_mime_types(file)
355
+ end
356
+
357
+ def mime_types_charset
358
+ SafeYAML.load_file(File.expand_path("serve/mime_types_charset.json", __dir__))
359
+ end
360
+
361
+ def read_file(source_dir, file_path)
362
+ File.read(Jekyll.sanitized_path(source_dir, file_path))
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end