jekyll 3.1.6 → 3.2.0.pre.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of jekyll might be problematic. Click here for more details.

Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +200 -74
  3. data/README.markdown +13 -13
  4. data/lib/jekyll.rb +12 -8
  5. data/lib/jekyll/cleaner.rb +11 -8
  6. data/lib/jekyll/collection.rb +3 -3
  7. data/lib/jekyll/commands/build.rb +15 -12
  8. data/lib/jekyll/commands/clean.rb +15 -16
  9. data/lib/jekyll/commands/doctor.rb +7 -7
  10. data/lib/jekyll/commands/help.rb +4 -3
  11. data/lib/jekyll/commands/new.rb +49 -14
  12. data/lib/jekyll/commands/new_theme.rb +33 -0
  13. data/lib/jekyll/commands/serve.rb +33 -27
  14. data/lib/jekyll/commands/serve/servlet.rb +2 -3
  15. data/lib/jekyll/configuration.rb +10 -34
  16. data/lib/jekyll/converter.rb +6 -2
  17. data/lib/jekyll/converters/markdown.rb +1 -1
  18. data/lib/jekyll/converters/markdown/kramdown_parser.rb +1 -0
  19. data/lib/jekyll/convertible.rb +6 -9
  20. data/lib/jekyll/document.rb +5 -1
  21. data/lib/jekyll/drops/document_drop.rb +7 -33
  22. data/lib/jekyll/drops/drop.rb +2 -26
  23. data/lib/jekyll/drops/jekyll_drop.rb +0 -12
  24. data/lib/jekyll/drops/site_drop.rb +1 -1
  25. data/lib/jekyll/entry_filter.rb +61 -15
  26. data/lib/jekyll/errors.rb +6 -0
  27. data/lib/jekyll/excerpt.rb +5 -2
  28. data/lib/jekyll/filters.rb +49 -8
  29. data/lib/jekyll/frontmatter_defaults.rb +2 -2
  30. data/lib/jekyll/hooks.rb +1 -0
  31. data/lib/jekyll/layout.rb +16 -1
  32. data/lib/jekyll/page.rb +1 -0
  33. data/lib/jekyll/plugin.rb +1 -1
  34. data/lib/jekyll/plugin_manager.rb +5 -5
  35. data/lib/jekyll/publisher.rb +4 -4
  36. data/lib/jekyll/readers/data_reader.rb +3 -2
  37. data/lib/jekyll/readers/layout_reader.rb +19 -3
  38. data/lib/jekyll/readers/post_reader.rb +5 -1
  39. data/lib/jekyll/regenerator.rb +5 -3
  40. data/lib/jekyll/renderer.rb +4 -6
  41. data/lib/jekyll/site.rb +47 -18
  42. data/lib/jekyll/static_file.rb +5 -1
  43. data/lib/jekyll/tags/include.rb +33 -31
  44. data/lib/jekyll/tags/link.rb +26 -0
  45. data/lib/jekyll/tags/post_url.rb +18 -8
  46. data/lib/jekyll/theme.rb +56 -0
  47. data/lib/jekyll/theme_builder.rb +117 -0
  48. data/lib/jekyll/utils.rb +2 -14
  49. data/lib/jekyll/version.rb +1 -1
  50. data/lib/site_template/_config.yml +8 -2
  51. data/lib/site_template/_includes/footer.html +3 -3
  52. data/lib/site_template/_includes/head.html +2 -2
  53. data/lib/site_template/_includes/header.html +3 -3
  54. data/lib/site_template/_layouts/default.html +3 -3
  55. data/lib/site_template/_layouts/page.html +1 -1
  56. data/lib/site_template/_layouts/post.html +1 -1
  57. data/lib/site_template/_sass/_base.scss +11 -17
  58. data/lib/site_template/index.html +1 -1
  59. data/lib/theme_template/CODE_OF_CONDUCT.md.erb +74 -0
  60. data/lib/theme_template/Gemfile +2 -0
  61. data/lib/theme_template/LICENSE.txt.erb +21 -0
  62. data/lib/theme_template/README.md.erb +46 -0
  63. data/lib/theme_template/Rakefile.erb +74 -0
  64. data/lib/theme_template/example/_config.yml.erb +1 -0
  65. data/lib/theme_template/example/_post.md +13 -0
  66. data/lib/theme_template/example/index.html +14 -0
  67. data/lib/theme_template/example/style.scss +7 -0
  68. data/lib/theme_template/theme.gemspec.erb +22 -0
  69. metadata +34 -7
  70. data/lib/jekyll/drops/excerpt_drop.rb +0 -15
@@ -2,9 +2,15 @@ module Jekyll
2
2
  module Errors
3
3
  FatalException = Class.new(::RuntimeError)
4
4
 
5
+ InvalidThemeName = Class.new(FatalException)
6
+
5
7
  DropMutationException = Class.new(FatalException)
6
8
  InvalidPermalinkError = Class.new(FatalException)
7
9
  InvalidYAMLFrontMatterError = Class.new(FatalException)
8
10
  MissingDependencyException = Class.new(FatalException)
11
+
12
+ InvalidDateError = Class.new(FatalException)
13
+ InvalidPostNameError = Class.new(FatalException)
14
+ PostURLError = Class.new(FatalException)
9
15
  end
10
16
  end
@@ -7,7 +7,7 @@ module Jekyll
7
7
  attr_writer :output
8
8
 
9
9
  def_delegators :@doc, :site, :name, :ext, :relative_path, :extname,
10
- :render_with_liquid?, :collection, :related_posts, :url
10
+ :render_with_liquid?, :collection, :related_posts
11
11
 
12
12
  # Initialize this Excerpt instance.
13
13
  #
@@ -59,7 +59,10 @@ module Jekyll
59
59
  end
60
60
 
61
61
  def to_liquid
62
- Jekyll::Drops::ExcerptDrop.new(self)
62
+ doc.data['excerpt'] = nil
63
+ @to_liquid ||= doc.to_liquid
64
+ doc.data['excerpt'] = self
65
+ @to_liquid
63
66
  end
64
67
 
65
68
  # Returns the shorthand String identifier of this doc.
@@ -1,6 +1,7 @@
1
1
  require 'uri'
2
2
  require 'json'
3
3
  require 'date'
4
+ require 'liquid'
4
5
 
5
6
  module Jekyll
6
7
  module Filters
@@ -15,11 +16,11 @@ module Jekyll
15
16
  converter.convert(input)
16
17
  end
17
18
 
18
- # Convert a Markdown string into HTML output.
19
+ # Convert quotes into smart quotes.
19
20
  #
20
- # input - The Markdown String to convert.
21
+ # input - The String to convert.
21
22
  #
22
- # Returns the HTML formatted String.
23
+ # Returns the smart-quotified String.
23
24
  def smartify(input)
24
25
  site = @context.registers[:site]
25
26
  converter = site.find_converter_instance(Jekyll::Converters::SmartyPants)
@@ -117,7 +118,7 @@ module Jekyll
117
118
  #
118
119
  # Returns the escaped String.
119
120
  def xml_escape(input)
120
- CGI.escapeHTML(input.to_s)
121
+ input.to_s.encode(:xml => :attr).gsub(/\A"|"\Z/, "")
121
122
  end
122
123
 
123
124
  # CGI escape a string for use in a URL. Replaces any special characters
@@ -205,7 +206,7 @@ module Jekyll
205
206
  input.group_by do |item|
206
207
  item_property(item, property).to_s
207
208
  end.inject([]) do |memo, i|
208
- memo << { "name" => i.first, "items" => i.last }
209
+ memo << { "name" => i.first, "items" => i.last, "size" => i.last.size }
209
210
  end
210
211
  else
211
212
  input
@@ -222,7 +223,27 @@ module Jekyll
222
223
  def where(input, property, value)
223
224
  return input unless input.is_a?(Enumerable)
224
225
  input = input.values if input.is_a?(Hash)
225
- input.select { |object| item_property(object, property).to_s == value.to_s }
226
+ input.select { |object| Array(item_property(object, property)).map(&:to_s).include?(value.to_s) }
227
+ end
228
+
229
+ # Filters an array of objects against an expression
230
+ #
231
+ # input - the object array
232
+ # variable - the variable to assign each item to in the expression
233
+ # expression - a Liquid comparison expression passed in as a string
234
+ #
235
+ # Returns the filtered array of objects
236
+ def where_exp(input, variable, expression)
237
+ return input unless input.is_a?(Enumerable)
238
+ input = input.values if input.is_a?(Hash) # FIXME
239
+
240
+ condition = parse_condition(expression)
241
+ @context.stack do
242
+ input.select do |object|
243
+ @context[variable] = object
244
+ condition.evaluate(@context)
245
+ end
246
+ end
226
247
  end
227
248
 
228
249
  # Sort an array of objects
@@ -308,14 +329,14 @@ module Jekyll
308
329
  #
309
330
  # Returns a String representation of the object.
310
331
  def inspect(input)
311
- CGI.escapeHTML(input.inspect)
332
+ xml_escape(input.inspect)
312
333
  end
313
334
 
314
335
  private
315
336
  def time(input)
316
337
  case input
317
338
  when Time
318
- input
339
+ input.clone
319
340
  when Date
320
341
  input.to_time
321
342
  when String
@@ -363,5 +384,25 @@ module Jekyll
363
384
  end
364
385
  end
365
386
  end
387
+
388
+ # Parse a string to a Liquid Condition
389
+ def parse_condition(exp)
390
+ parser = Liquid::Parser.new(exp)
391
+ left_expr = parser.expression
392
+ operator = parser.consume?(:comparison)
393
+ condition =
394
+ if operator
395
+ Liquid::Condition.new(left_expr, operator, parser.expression)
396
+ else
397
+ Liquid::Condition.new(left_expr)
398
+ end
399
+ parser.consume(:end_of_string)
400
+
401
+ condition
402
+ end
366
403
  end
367
404
  end
405
+
406
+ Liquid::Template.register_filter(
407
+ Jekyll::Filters
408
+ )
@@ -94,8 +94,8 @@ module Jekyll
94
94
  return true if !scope.key?('path') || scope['path'].empty?
95
95
 
96
96
  scope_path = Pathname.new(scope['path'])
97
- Pathname.new(sanitize_path(path)).ascend do |path|
98
- if path.to_s == scope_path.to_s
97
+ Pathname.new(sanitize_path(path)).ascend do |ascended_path|
98
+ if ascended_path.to_s == scope_path.to_s
99
99
  return true
100
100
  end
101
101
  end
@@ -12,6 +12,7 @@ module Jekyll
12
12
  # initial empty hooks
13
13
  @registry = {
14
14
  :site => {
15
+ :after_init => [],
15
16
  :after_reset => [],
16
17
  :post_read => [],
17
18
  :pre_render => [],
@@ -29,7 +29,14 @@ module Jekyll
29
29
  @site = site
30
30
  @base = base
31
31
  @name = name
32
- @path = site.in_source_dir(base, name)
32
+
33
+ if site.theme && site.theme.layouts_path.eql?(base)
34
+ @base_dir = site.theme.root
35
+ @path = site.in_theme_dir(base, name)
36
+ else
37
+ @base_dir = site.source
38
+ @path = site.in_source_dir(base, name)
39
+ end
33
40
 
34
41
  self.data = {}
35
42
 
@@ -45,5 +52,13 @@ module Jekyll
45
52
  def process(name)
46
53
  self.ext = File.extname(name)
47
54
  end
55
+
56
+ # The path to the layout, relative to the site source.
57
+ #
58
+ # Returns a String path which represents the relative path
59
+ # from the site source to this layout
60
+ def relative_path
61
+ @relative_path ||= Pathname.new(path).relative_path_from(Pathname.new(@base_dir)).to_s
62
+ end
48
63
  end
49
64
  end
@@ -40,6 +40,7 @@ module Jekyll
40
40
  @base = base
41
41
  @dir = dir
42
42
  @name = name
43
+ @path = site.in_source_dir(base, dir, name)
43
44
 
44
45
  process(name)
45
46
  read_yaml(File.join(base, dir), name)
@@ -60,7 +60,7 @@ module Jekyll
60
60
  #
61
61
  # Returns the safety Boolean.
62
62
  def self.safe(safe = nil)
63
- if safe
63
+ if !defined?(@safe) || !safe.nil?
64
64
  @safe = safe
65
65
  end
66
66
  @safe || false
@@ -30,16 +30,16 @@ module Jekyll
30
30
  def self.require_from_bundler
31
31
  if !ENV["JEKYLL_NO_BUNDLER_REQUIRE"] && File.file?("Gemfile")
32
32
  require "bundler"
33
- Bundler.setup # puts all groups on the load path
34
- required_gems = Bundler.require(:jekyll_plugins) # requires the gems in this group only
33
+
34
+ Bundler.setup
35
+ required_gems = Bundler.require(:jekyll_plugins)
35
36
  Jekyll.logger.debug("PluginManager:", "Required #{required_gems.map(&:name).join(', ')}")
36
37
  ENV["JEKYLL_NO_BUNDLER_REQUIRE"] = "true"
38
+
37
39
  true
38
40
  else
39
41
  false
40
42
  end
41
- rescue LoadError, Bundler::GemfileNotFound
42
- false
43
43
  end
44
44
 
45
45
  # Check whether a gem plugin is allowed to be used during this build.
@@ -76,7 +76,7 @@ module Jekyll
76
76
  #
77
77
  # Returns an Array of plugin search paths
78
78
  def plugins_path
79
- if site.config['plugins_dir'].eql? Jekyll::Configuration::DEFAULTS['plugins_dir']
79
+ if site.config['plugins_dir'] == Jekyll::Configuration::DEFAULTS['plugins_dir']
80
80
  [site.in_source_dir(site.config['plugins_dir'])]
81
81
  else
82
82
  Array(site.config['plugins_dir']).map { |d| File.expand_path(d) }
@@ -8,14 +8,14 @@ module Jekyll
8
8
  can_be_published?(thing) && !hidden_in_the_future?(thing)
9
9
  end
10
10
 
11
+ def hidden_in_the_future?(thing)
12
+ thing.respond_to?(:date) && !@site.future && thing.date.to_i > @site.time.to_i
13
+ end
14
+
11
15
  private
12
16
 
13
17
  def can_be_published?(thing)
14
18
  thing.data.fetch('published', true) || @site.unpublished
15
19
  end
16
-
17
- def hidden_in_the_future?(thing)
18
- thing.respond_to?(:date) && !@site.future && thing.date.to_i > @site.time.to_i
19
- end
20
20
  end
21
21
  end
@@ -4,6 +4,7 @@ module Jekyll
4
4
  def initialize(site)
5
5
  @site = site
6
6
  @content = {}
7
+ @entry_filter = EntryFilter.new(site)
7
8
  end
8
9
 
9
10
  # Read all the files in <source>/<dir>/_drafts and create a new Draft
@@ -26,7 +27,7 @@ module Jekyll
26
27
  #
27
28
  # Returns nothing
28
29
  def read_data_to(dir, data)
29
- return unless File.directory?(dir) && (!site.safe || !File.symlink?(dir))
30
+ return unless File.directory?(dir) && !@entry_filter.symlink?(dir)
30
31
 
31
32
  entries = Dir.chdir(dir) do
32
33
  Dir['*.{yaml,yml,json,csv}'] + Dir['*'].select { |fn| File.directory?(fn) }
@@ -34,7 +35,7 @@ module Jekyll
34
35
 
35
36
  entries.each do |entry|
36
37
  path = @site.in_source_dir(dir, entry)
37
- next if File.symlink?(path) && site.safe
38
+ next if @entry_filter.symlink?(path)
38
39
 
39
40
  key = sanitize_filename(File.basename(entry, '.*'))
40
41
  if File.directory?(path)
@@ -7,8 +7,12 @@ module Jekyll
7
7
  end
8
8
 
9
9
  def read
10
- layout_entries.each do |f|
11
- @layouts[layout_name(f)] = Layout.new(site, layout_directory, f)
10
+ layout_entries.each do |layout_file|
11
+ @layouts[layout_name(layout_file)] = Layout.new(site, layout_directory, layout_file)
12
+ end
13
+
14
+ theme_layout_entries.each do |layout_file|
15
+ @layouts[layout_name(layout_file)] ||= Layout.new(site, theme_layout_directory, layout_file)
12
16
  end
13
17
 
14
18
  @layouts
@@ -18,11 +22,23 @@ module Jekyll
18
22
  @layout_directory ||= (layout_directory_in_cwd || layout_directory_inside_source)
19
23
  end
20
24
 
25
+ def theme_layout_directory
26
+ @theme_layout_directory ||= site.theme.layouts_path if site.theme
27
+ end
28
+
21
29
  private
22
30
 
23
31
  def layout_entries
32
+ entries_in layout_directory
33
+ end
34
+
35
+ def theme_layout_entries
36
+ theme_layout_directory ? entries_in(theme_layout_directory) : []
37
+ end
38
+
39
+ def entries_in(dir)
24
40
  entries = []
25
- within(layout_directory) do
41
+ within(dir) do
26
42
  entries = EntryFilter.new(site).filter(Dir['**/*.*'])
27
43
  end
28
44
  entries
@@ -35,7 +35,11 @@ module Jekyll
35
35
  read_content(dir, magic_dir, matcher).tap do |docs|
36
36
  docs.each(&:read)
37
37
  end.select do |doc|
38
- site.publisher.publish?(doc)
38
+ site.publisher.publish?(doc).tap do |will_publish|
39
+ if !will_publish && site.publisher.hidden_in_the_future?(doc)
40
+ Jekyll.logger.debug "Skipping:", "#{doc.relative_path} has a future date"
41
+ end
42
+ end
39
43
  end
40
44
  end
41
45
 
@@ -1,6 +1,8 @@
1
1
  module Jekyll
2
2
  class Regenerator
3
3
  attr_reader :site, :metadata, :cache
4
+ attr_accessor :disabled
5
+ private :disabled, :disabled=
4
6
 
5
7
  def initialize(site)
6
8
  @site = site
@@ -115,7 +117,7 @@ module Jekyll
115
117
  #
116
118
  # Returns nothing.
117
119
  def add_dependency(path, dependency)
118
- return if metadata[path].nil? || @disabled
120
+ return if metadata[path].nil? || disabled
119
121
 
120
122
  unless metadata[path]["deps"].include? dependency
121
123
  metadata[path]["deps"] << dependency
@@ -144,8 +146,8 @@ module Jekyll
144
146
  #
145
147
  # Returns a Boolean (true for disabled, false for enabled).
146
148
  def disabled?
147
- @disabled = !site.incremental? if @disabled.nil?
148
- @disabled
149
+ self.disabled = !site.incremental? if disabled.nil?
150
+ disabled
149
151
  end
150
152
 
151
153
  private
@@ -40,6 +40,8 @@ module Jekyll
40
40
 
41
41
  if document.is_a?(Document) && document.collection.label == 'posts'
42
42
  payload['site']['related_posts'] = document.related_posts
43
+ else
44
+ payload['site']['related_posts'] = nil
43
45
  end
44
46
 
45
47
  # render and transform content (this becomes the final content of the object)
@@ -50,7 +52,6 @@ module Jekyll
50
52
  document.trigger_hooks(:pre_render, payload)
51
53
 
52
54
  info = {
53
- :filters => [Jekyll::Filters],
54
55
  :registers => { :site => site, :page => payload['page'] }
55
56
  }
56
57
 
@@ -135,19 +136,16 @@ module Jekyll
135
136
 
136
137
  used = Set.new([layout])
137
138
 
138
- # Reset the payload layout data to ensure it starts fresh for each page.
139
- payload["layout"] = nil
140
-
141
139
  while layout
142
140
  payload['content'] = output
143
141
  payload['page'] = document.to_liquid
144
- payload['layout'] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
142
+ payload['layout'] = Utils.deep_merge_hashes(payload['layout'] || {}, layout.data)
145
143
 
146
144
  output = render_liquid(
147
145
  layout.content,
148
146
  payload,
149
147
  info,
150
- File.join(site.config['layouts_dir'], layout.name)
148
+ layout.relative_path
151
149
  )
152
150
 
153
151
  # Add layout to dependency tree
@@ -8,15 +8,40 @@ module Jekyll
8
8
  :exclude, :include, :lsi, :highlighter, :permalink_style,
9
9
  :time, :future, :unpublished, :safe, :plugins, :limit_posts,
10
10
  :show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
11
- :gems, :plugin_manager
11
+ :gems, :plugin_manager, :theme
12
12
 
13
13
  attr_accessor :converters, :generators, :reader
14
- attr_reader :regenerator, :liquid_renderer
14
+ attr_reader :regenerator, :liquid_renderer, :includes_load_paths
15
15
 
16
16
  # Public: Initialize a new Site.
17
17
  #
18
18
  # config - A Hash containing site configuration details.
19
19
  def initialize(config)
20
+ # Source and destination may not be changed after the site has been created.
21
+ @source = File.expand_path(config['source']).freeze
22
+ @dest = File.expand_path(config['destination']).freeze
23
+
24
+ self.config = config
25
+
26
+ @reader = Reader.new(self)
27
+ @regenerator = Regenerator.new(self)
28
+ @liquid_renderer = LiquidRenderer.new(self)
29
+
30
+ Jekyll.sites << self
31
+
32
+ Jekyll::Hooks.trigger :site, :after_init, self
33
+
34
+ reset
35
+ setup
36
+ end
37
+
38
+ # Public: Set the site's configuration. This handles side-effects caused by
39
+ # changing values in the configuration.
40
+ #
41
+ # config - a Jekyll::Configuration, containing the new configuration.
42
+ #
43
+ # Returns the new configuration.
44
+ def config=(config)
20
45
  @config = config.clone
21
46
 
22
47
  %w(safe lsi highlighter baseurl exclude include future unpublished
@@ -24,29 +49,21 @@ module Jekyll
24
49
  self.send("#{opt}=", config[opt])
25
50
  end
26
51
 
27
- # Source and destination may not be changed after the site has been created.
28
- @source = File.expand_path(config['source']).freeze
29
- @dest = File.expand_path(config['destination']).freeze
30
-
31
- @reader = Jekyll::Reader.new(self)
32
-
33
- # Initialize incremental regenerator
34
- @regenerator = Regenerator.new(self)
35
-
36
- @liquid_renderer = LiquidRenderer.new(self)
37
-
38
52
  self.plugin_manager = Jekyll::PluginManager.new(self)
39
53
  self.plugins = plugin_manager.plugins_path
40
54
 
55
+ self.theme = nil
56
+ self.theme = Jekyll::Theme.new(config["theme"]) if config["theme"]
57
+
58
+ @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
59
+ @includes_load_paths << theme.includes_path if self.theme
60
+
41
61
  self.file_read_opts = {}
42
62
  self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
43
63
 
44
64
  self.permalink_style = config['permalink'].to_sym
45
65
 
46
- Jekyll.sites << self
47
-
48
- reset
49
- setup
66
+ @config
50
67
  end
51
68
 
52
69
  # Public: Read, process, and write this Site to output.
@@ -262,7 +279,6 @@ module Jekyll
262
279
  def site_payload
263
280
  Drops::UnifiedPayloadDrop.new self
264
281
  end
265
- alias_method :to_liquid, :site_payload
266
282
 
267
283
  # Get the implementation class for the given Converter.
268
284
  # Returns the Converter instance implementing the given Converter.
@@ -357,6 +373,19 @@ module Jekyll
357
373
  end
358
374
  end
359
375
 
376
+ # Public: Prefix a given path with the theme directory.
377
+ #
378
+ # paths - (optional) path elements to a file or directory within the
379
+ # theme directory
380
+ #
381
+ # Returns a path which is prefixed with the theme root directory.
382
+ def in_theme_dir(*paths)
383
+ return nil unless theme
384
+ paths.reduce(theme.root) do |base, path|
385
+ Jekyll.sanitized_path(base, path)
386
+ end
387
+ end
388
+
360
389
  # Public: Prefix a given path with the destination directory.
361
390
  #
362
391
  # paths - (optional) path elements to a file or directory within the