jekyll 3.0.5 → 3.1.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +1 -1
  3. data/lib/jekyll.rb +6 -5
  4. data/lib/jekyll/cleaner.rb +1 -1
  5. data/lib/jekyll/collection.rb +8 -11
  6. data/lib/jekyll/commands/clean.rb +2 -2
  7. data/lib/jekyll/commands/doctor.rb +23 -1
  8. data/lib/jekyll/commands/serve.rb +148 -103
  9. data/lib/jekyll/commands/serve/servlet.rb +61 -0
  10. data/lib/jekyll/configuration.rb +26 -46
  11. data/lib/jekyll/converters/markdown.rb +51 -36
  12. data/lib/jekyll/converters/markdown/kramdown_parser.rb +70 -17
  13. data/lib/jekyll/convertible.rb +5 -4
  14. data/lib/jekyll/document.rb +19 -63
  15. data/lib/jekyll/drops/collection_drop.rb +24 -0
  16. data/lib/jekyll/drops/document_drop.rb +28 -0
  17. data/lib/jekyll/drops/drop.rb +128 -0
  18. data/lib/jekyll/drops/jekyll_drop.rb +21 -0
  19. data/lib/jekyll/drops/site_drop.rb +39 -0
  20. data/lib/jekyll/drops/unified_payload_drop.rb +26 -0
  21. data/lib/jekyll/drops/url_drop.rb +51 -0
  22. data/lib/jekyll/entry_filter.rb +1 -1
  23. data/lib/jekyll/errors.rb +3 -4
  24. data/lib/jekyll/excerpt.rb +0 -2
  25. data/lib/jekyll/external.rb +1 -0
  26. data/lib/jekyll/filters.rb +10 -0
  27. data/lib/jekyll/frontmatter_defaults.rb +8 -1
  28. data/lib/jekyll/liquid_renderer/file.rb +1 -1
  29. data/lib/jekyll/page.rb +15 -11
  30. data/lib/jekyll/plugin_manager.rb +4 -10
  31. data/lib/jekyll/renderer.rb +12 -19
  32. data/lib/jekyll/site.rb +2 -20
  33. data/lib/jekyll/tags/highlight.rb +5 -5
  34. data/lib/jekyll/tags/include.rb +13 -2
  35. data/lib/jekyll/url.rb +22 -12
  36. data/lib/jekyll/utils.rb +48 -8
  37. data/lib/jekyll/utils/ansi.rb +59 -0
  38. data/lib/jekyll/utils/platforms.rb +2 -1
  39. data/lib/jekyll/version.rb +1 -1
  40. metadata +14 -5
@@ -0,0 +1,51 @@
1
+ # encoding: UTF-8
2
+
3
+ module Jekyll
4
+ module Drops
5
+ class UrlDrop < Drop
6
+ extend Forwardable
7
+
8
+ mutable false
9
+
10
+ def_delegator :@obj, :cleaned_relative_path, :path
11
+ def_delegator :@obj, :output_ext, :output_ext
12
+
13
+ def collection
14
+ @obj.collection.label
15
+ end
16
+
17
+ def name
18
+ Utils.slugify(@obj.basename_without_ext)
19
+ end
20
+
21
+ def title
22
+ Utils.slugify(@obj.data['slug'], mode: "pretty", cased: true) ||
23
+ Utils.slugify(@obj.basename_without_ext, mode: "pretty", cased: true)
24
+ end
25
+
26
+ def slug
27
+ Utils.slugify(@obj.data['slug']) || Utils.slugify(@obj.basename_without_ext)
28
+ end
29
+
30
+ def categories
31
+ category_set = Set.new
32
+ Array(@obj.data['categories']).each do |category|
33
+ category_set << category.to_s.downcase
34
+ end
35
+ category_set.to_a.join('/')
36
+ end
37
+
38
+ def year; @obj.date.strftime("%Y"); end
39
+ def month; @obj.date.strftime("%m"); end
40
+ def day; @obj.date.strftime("%d"); end
41
+ def hour; @obj.date.strftime("%H"); end
42
+ def minute; @obj.date.strftime("%M"); end
43
+ def second; @obj.date.strftime("%S"); end
44
+ def i_day; @obj.date.strftime("%-d"); end
45
+ def i_month; @obj.date.strftime("%-m"); end
46
+ def short_month; @obj.date.strftime("%b"); end
47
+ def short_year; @obj.date.strftime("%y"); end
48
+ def y_day; @obj.date.strftime("%j"); end
49
+ end
50
+ end
51
+ end
@@ -47,7 +47,7 @@ module Jekyll
47
47
 
48
48
  def excluded?(entry)
49
49
  excluded = glob_include?(site.exclude, relative_to_source(entry))
50
- Jekyll.logger.debug "EntryFilter:", "excluded #{relative_to_source(entry)}" if excluded
50
+ Jekyll.logger.debug "EntryFilter:", "excluded?(#{relative_to_source(entry)}) ==> #{excluded}"
51
51
  excluded
52
52
  end
53
53
 
@@ -1,9 +1,8 @@
1
1
  module Jekyll
2
2
  module Errors
3
- class FatalException < RuntimeError
4
- end
3
+ FatalException = Class.new(::RuntimeError)
5
4
 
6
- class MissingDependencyException < FatalException
7
- end
5
+ MissingDependencyException = Class.new(FatalException)
6
+ DropMutationException = Class.new(FatalException)
8
7
  end
9
8
  end
@@ -1,5 +1,3 @@
1
- require 'forwardable'
2
-
3
1
  module Jekyll
4
2
  class Excerpt
5
3
  extend Forwardable
@@ -39,6 +39,7 @@ module Jekyll
39
39
  def require_with_graceful_fail(names)
40
40
  Array(names).each do |name|
41
41
  begin
42
+ Jekyll.logger.debug("Requiring #{name}")
42
43
  require name
43
44
  rescue LoadError => e
44
45
  Jekyll.logger.error "Dependency Error:", <<-MSG
@@ -281,6 +281,16 @@ module Jekyll
281
281
  new_ary
282
282
  end
283
283
 
284
+ def sample(input, num = 1)
285
+ return input unless input.respond_to?(:sample)
286
+ n = num.to_i rescue 1
287
+ if n == 1
288
+ input.sample
289
+ else
290
+ input.sample(n)
291
+ end
292
+ end
293
+
284
294
  # Convert an object into its String representation for debugging
285
295
  #
286
296
  # input - The Object to be converted
@@ -30,6 +30,13 @@ module Jekyll
30
30
  set
31
31
  end
32
32
 
33
+ def ensure_time!(set)
34
+ return set unless set.key?('values') && set['values'].key?('date')
35
+ return set if set['values']['date'].is_a?(Time)
36
+ set['values']['date'] = Utils.parse_date(set['values']['date'], "An invalid date format was found in a front-matter default set: #{set}")
37
+ set
38
+ end
39
+
33
40
  # Finds a default value for a given setting, filtered by path and type
34
41
  #
35
42
  # path - the path (relative to the source) of the page, post or :draft the default is used in
@@ -159,7 +166,7 @@ module Jekyll
159
166
 
160
167
  sets.map do |set|
161
168
  if valid?(set)
162
- update_deprecated_types(set)
169
+ ensure_time!(update_deprecated_types(set))
163
170
  else
164
171
  Jekyll.logger.warn "Defaults:", "An invalid front-matter default set was found:"
165
172
  Jekyll.logger.warn "#{set}"
@@ -8,7 +8,7 @@ module Jekyll
8
8
 
9
9
  def parse(content)
10
10
  measure_time do
11
- @template = Liquid::Template.parse(content, line_numbers: true)
11
+ @template = Liquid::Template.parse(content)
12
12
  end
13
13
 
14
14
  self
@@ -16,6 +16,15 @@ module Jekyll
16
16
  url
17
17
  ]
18
18
 
19
+ # A set of extensions that are considered HTML or HTML-like so we
20
+ # should not alter them, this includes .xhtml through XHTM5.
21
+
22
+ HTML_EXTENSIONS = %W(
23
+ .html
24
+ .xhtml
25
+ .htm
26
+ )
27
+
19
28
  # Initialize a new Page.
20
29
  #
21
30
  # site - The Site object.
@@ -108,12 +117,10 @@ module Jekyll
108
117
  #
109
118
  # Returns nothing.
110
119
  def render(layouts, site_payload)
111
- payload = Utils.deep_merge_hashes({
112
- "page" => to_liquid,
113
- 'paginator' => pager.to_liquid
114
- }, site_payload)
120
+ site_payload.page = to_liquid
121
+ site_payload.paginator = pager.to_liquid
115
122
 
116
- do_layout(payload, layouts)
123
+ do_layout(site_payload, layouts)
117
124
  end
118
125
 
119
126
  # The path to the source file
@@ -135,11 +142,8 @@ module Jekyll
135
142
  # Returns the destination file path String.
136
143
  def destination(dest)
137
144
  path = site.in_dest_dir(dest, URL.unescape_path(url))
138
- if url.end_with? "/"
139
- path = File.join(path, "index.html")
140
- else
141
- path << output_ext unless path.end_with?(output_ext)
142
- end
145
+ path = File.join(path, "index") if url.end_with?("/")
146
+ path << output_ext unless path.end_with?(output_ext)
143
147
  path
144
148
  end
145
149
 
@@ -150,7 +154,7 @@ module Jekyll
150
154
 
151
155
  # Returns the Boolean of whether this Page is HTML or not.
152
156
  def html?
153
- output_ext == '.html'
157
+ HTML_EXTENSIONS.include?(output_ext)
154
158
  end
155
159
 
156
160
  # Returns the Boolean of whether this Page is an index file or not.
@@ -24,12 +24,7 @@ module Jekyll
24
24
  #
25
25
  # Returns nothing.
26
26
  def require_gems
27
- site.gems.each do |gem|
28
- if plugin_allowed?(gem)
29
- Jekyll.logger.debug("PluginManager:", "Requiring #{gem}")
30
- require gem
31
- end
32
- end
27
+ Jekyll::External.require_with_graceful_fail(site.gems.select { |gem| plugin_allowed?(gem) })
33
28
  end
34
29
 
35
30
  def self.require_from_bundler
@@ -70,10 +65,9 @@ module Jekyll
70
65
  # Returns nothing.
71
66
  def require_plugin_files
72
67
  unless site.safe
73
- plugins_path.each do |plugins|
74
- Dir[File.join(plugins, "**", "*.rb")].sort.each do |f|
75
- require f
76
- end
68
+ plugins_path.each do |plugin_search_path|
69
+ plugin_files = Utils.safe_glob(plugin_search_path, File.join("**", "*.rb"))
70
+ Jekyll::External.require_with_graceful_fail(plugin_files)
77
71
  end
78
72
  end
79
73
  end
@@ -3,12 +3,12 @@
3
3
  module Jekyll
4
4
  class Renderer
5
5
 
6
- attr_reader :document, :site, :site_payload
6
+ attr_reader :document, :site, :payload
7
7
 
8
8
  def initialize(site, document, site_payload = nil)
9
- @site = site
10
- @document = document
11
- @site_payload = site_payload
9
+ @site = site
10
+ @document = document
11
+ @payload = site_payload || site.site_payload
12
12
  end
13
13
 
14
14
  # Determine which converters to use based on this document's
@@ -33,12 +33,10 @@ module Jekyll
33
33
  def run
34
34
  Jekyll.logger.debug "Rendering:", document.relative_path
35
35
 
36
- payload = Utils.deep_merge_hashes({
37
- "page" => document.to_liquid
38
- }, site_payload || site.site_payload)
36
+ payload.page = document.to_liquid
39
37
 
40
38
  if document.collection.label == 'posts' && document.is_a?(Document)
41
- payload['site']['related_posts'] = document.related_posts
39
+ payload.site['related_posts'] = document.related_posts
42
40
  end
43
41
 
44
42
  Jekyll.logger.debug "Pre-Render Hooks:", document.relative_path
@@ -46,12 +44,12 @@ module Jekyll
46
44
 
47
45
  info = {
48
46
  filters: [Jekyll::Filters],
49
- registers: { :site => site, :page => payload['page'] }
47
+ registers: { :site => site, :page => payload.page }
50
48
  }
51
49
 
52
50
  # render and transform content (this becomes the final content of the object)
53
- payload["highlighter_prefix"] = converters.first.highlighter_prefix
54
- payload["highlighter_suffix"] = converters.first.highlighter_suffix
51
+ payload.highlighter_prefix = converters.first.highlighter_prefix
52
+ payload.highlighter_suffix = converters.first.highlighter_suffix
55
53
 
56
54
  output = document.content
57
55
 
@@ -135,14 +133,9 @@ module Jekyll
135
133
  used = Set.new([layout])
136
134
 
137
135
  while layout
138
- payload = Utils.deep_merge_hashes(
139
- payload,
140
- {
141
- "content" => output,
142
- "page" => document.to_liquid,
143
- "layout" => layout.data
144
- }
145
- )
136
+ payload.content = output
137
+ payload.page = document.to_liquid
138
+ payload.layout = layout.data
146
139
 
147
140
  output = render_liquid(
148
141
  layout.content,
@@ -224,7 +224,7 @@ module Jekyll
224
224
  # Build a hash map based on the specified post attribute ( post attr =>
225
225
  # array of posts ) then sort each array in reverse order.
226
226
  hash = Hash.new { |h, key| h[key] = [] }
227
- posts.docs.each { |p| p.data[post_attr].each { |t| hash[t] << p } if p.data[post_attr] }
227
+ posts.docs.each { |p| p.data[post_attr].each { |t| hash[t] << p } }
228
228
  hash.values.each { |posts| posts.sort!.reverse! }
229
229
  hash
230
230
  end
@@ -259,25 +259,7 @@ module Jekyll
259
259
  # "tags" - The Hash of tag values and Posts.
260
260
  # See Site#post_attr_hash for type info.
261
261
  def site_payload
262
- {
263
- "jekyll" => {
264
- "version" => Jekyll::VERSION,
265
- "environment" => Jekyll.env
266
- },
267
- "site" => Utils.deep_merge_hashes(config,
268
- Utils.deep_merge_hashes(Hash[collections.map{|label, coll| [label, coll.docs]}], {
269
- "time" => time,
270
- "posts" => posts.docs.sort { |a, b| b <=> a },
271
- "pages" => pages,
272
- "static_files" => static_files,
273
- "html_pages" => pages.select { |page| page.html? || page.url.end_with?("/") },
274
- "categories" => post_attr_hash('categories'),
275
- "tags" => post_attr_hash('tags'),
276
- "collections" => collections.values.sort_by(&:label).map(&:to_liquid),
277
- "documents" => documents,
278
- "data" => site_data
279
- }))
280
- }
262
+ Drops::UnifiedPayloadDrop.new self
281
263
  end
282
264
 
283
265
  # Get the implementation class for the given Converter.
@@ -14,7 +14,7 @@ module Jekyll
14
14
  super
15
15
  if markup.strip =~ SYNTAX
16
16
  @lang = $1.downcase
17
- @options = {}
17
+ @highlight_options = {}
18
18
  if defined?($2) && $2 != ''
19
19
  # Split along 3 possible forms -- key="<quoted list>", key=value, or key
20
20
  $2.scan(/(?:\w="[^"]*"|\w=\w|\w)+/) do |opt|
@@ -24,10 +24,10 @@ module Jekyll
24
24
  value.gsub!(/"/, "")
25
25
  value = value.split
26
26
  end
27
- @options[key.to_sym] = value || true
27
+ @highlight_options[key.to_sym] = value || true
28
28
  end
29
29
  end
30
- @options[:linenos] = "inline" if @options.key?(:linenos) and @options[:linenos] == true
30
+ @highlight_options[:linenos] = "inline" if @highlight_options.key?(:linenos) and @highlight_options[:linenos] == true
31
31
  else
32
32
  raise SyntaxError.new <<-eos
33
33
  Syntax Error in tag 'highlight' while parsing the following markup:
@@ -80,7 +80,7 @@ eos
80
80
  highlighted_code = Pygments.highlight(
81
81
  code,
82
82
  :lexer => @lang,
83
- :options => sanitized_opts(@options, is_safe)
83
+ :options => sanitized_opts(@highlight_options, is_safe)
84
84
  )
85
85
 
86
86
  if highlighted_code.nil?
@@ -99,7 +99,7 @@ eos
99
99
 
100
100
  def render_rouge(code)
101
101
  Jekyll::External.require_with_graceful_fail('rouge')
102
- formatter = Rouge::Formatters::HTML.new(line_numbers: @options[:linenos], wrap: false)
102
+ formatter = Rouge::Formatters::HTML.new(line_numbers: @highlight_options[:linenos], wrap: false)
103
103
  lexer = Rouge::Lexer.find_fancy(@lang, code) || Rouge::Lexers::PlainText
104
104
  formatter.format(lexer.lex(code))
105
105
  end
@@ -16,7 +16,7 @@ module Jekyll
16
16
  attr_reader :includes_dir
17
17
 
18
18
  VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
19
- VARIABLE_SYNTAX = /(?<variable>[^{]*\{\{\s*(?<name>[\w\-\.]+)\s*(\|.*)?\}\}[^\s}]*)(?<params>.*)/
19
+ VARIABLE_SYNTAX = /(?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)(?<params>.*)/
20
20
 
21
21
  def initialize(tag_name, markup, tokens)
22
22
  super
@@ -123,7 +123,7 @@ eos
123
123
  end
124
124
 
125
125
  begin
126
- partial = site.liquid_renderer.file(path).parse(read_file(path, context))
126
+ partial = load_cached_partial(path, context)
127
127
 
128
128
  context.stack do
129
129
  context['include'] = parse_params(context) if @params
@@ -134,6 +134,17 @@ eos
134
134
  end
135
135
  end
136
136
 
137
+ def load_cached_partial(path, context)
138
+ context.registers[:cached_partials] ||= {}
139
+ cached_partial = context.registers[:cached_partials]
140
+
141
+ if cached_partial.has_key?(path)
142
+ cached_partial[path]
143
+ else
144
+ cached_partial[path] = context.registers[:site].liquid_renderer.file(path).parse(read_file(path, context))
145
+ end
146
+ end
147
+
137
148
  def resolved_includes_dir(context)
138
149
  context.registers[:site].in_source_dir(@includes_dir)
139
150
  end
@@ -59,6 +59,14 @@ module Jekyll
59
59
  #
60
60
  # Returns the unsanitized String URL
61
61
  def generate_url(template)
62
+ if @placeholders.is_a? Drops::UrlDrop
63
+ generate_url_from_drop(template)
64
+ else
65
+ generate_url_from_hash(template)
66
+ end
67
+ end
68
+
69
+ def generate_url_from_hash(template)
62
70
  @placeholders.inject(template) do |result, token|
63
71
  break result if result.index(':').nil?
64
72
  if token.last.nil?
@@ -70,20 +78,22 @@ module Jekyll
70
78
  end
71
79
  end
72
80
 
73
- # Returns a sanitized String URL
74
- def sanitize_url(in_url)
75
- url = in_url \
76
- # Remove all double slashes
77
- .gsub(/\/\//, '/') \
78
- # Remove every URL segment that consists solely of dots
79
- .split('/').reject{ |part| part =~ /^\.+$/ }.join('/') \
80
- # Always add a leading slash
81
- .gsub(/\A([^\/])/, '/\1')
81
+ def generate_url_from_drop(template)
82
+ template.gsub(/:([a-z_]+)/) do |match|
83
+ replacement = @placeholders.public_send(match.sub(':', ''))
84
+ if replacement.nil?
85
+ ''.freeze
86
+ else
87
+ self.class.escape_path(replacement)
88
+ end
89
+ end.gsub(/\/\//, '/')
90
+ end
82
91
 
83
- # Append a trailing slash to the URL if the unsanitized URL had one
84
- url << "/" if in_url.end_with?("/")
92
+ # Returns a sanitized String URL, stripping "../../" and multiples of "/",
93
+ # as well as the beginning "/" so we can enforce and ensure it.
85
94
 
86
- url
95
+ def sanitize_url(str)
96
+ "/" + str.gsub(/\/{2,}/, "/").gsub(%r!\.+\/|\A/+!, "")
87
97
  end
88
98
 
89
99
  # Escapes a path to be a valid URL path segment