jekyll 2.5.3 → 3.0.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +3 -3
  3. data/README.markdown +4 -4
  4. data/bin/jekyll +4 -6
  5. data/lib/jekyll.rb +8 -1
  6. data/lib/jekyll/cleaner.rb +8 -0
  7. data/lib/jekyll/collection.rb +1 -2
  8. data/lib/jekyll/command.rb +1 -0
  9. data/lib/jekyll/commands/build.rb +3 -1
  10. data/lib/jekyll/commands/clean.rb +42 -0
  11. data/lib/jekyll/configuration.rb +13 -11
  12. data/lib/jekyll/converters/markdown.rb +3 -6
  13. data/lib/jekyll/converters/markdown/kramdown_parser.rb +2 -2
  14. data/lib/jekyll/converters/markdown/rdiscount_parser.rb +1 -1
  15. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +3 -3
  16. data/lib/jekyll/convertible.rb +6 -0
  17. data/lib/jekyll/deprecator.rb +0 -17
  18. data/lib/jekyll/document.rb +5 -3
  19. data/lib/jekyll/excerpt.rb +1 -2
  20. data/lib/jekyll/external.rb +59 -0
  21. data/lib/jekyll/filters.rb +12 -20
  22. data/lib/jekyll/frontmatter_defaults.rb +1 -1
  23. data/lib/jekyll/layout.rb +4 -0
  24. data/lib/jekyll/log_adapter.rb +5 -2
  25. data/lib/jekyll/post.rb +15 -6
  26. data/lib/jekyll/regenerator.rb +141 -0
  27. data/lib/jekyll/renderer.rb +12 -5
  28. data/lib/jekyll/site.rb +32 -17
  29. data/lib/jekyll/static_file.rb +4 -5
  30. data/lib/jekyll/tags/highlight.rb +1 -1
  31. data/lib/jekyll/tags/include.rb +10 -1
  32. data/lib/jekyll/utils.rb +52 -15
  33. data/lib/jekyll/version.rb +1 -1
  34. data/lib/site_template/_includes/footer.html +5 -5
  35. data/lib/site_template/_includes/head.html +1 -1
  36. data/lib/site_template/_layouts/page.html +4 -4
  37. data/lib/site_template/_layouts/post.html +6 -6
  38. data/lib/site_template/_sass/_layout.scss +4 -4
  39. metadata +11 -248
  40. data/lib/jekyll/commands/docs.rb +0 -30
  41. data/lib/jekyll/converters/textile.rb +0 -56
@@ -1,19 +1,9 @@
1
1
  require 'uri'
2
2
  require 'json'
3
+ require 'date'
3
4
 
4
5
  module Jekyll
5
6
  module Filters
6
- # Convert a Textile string into HTML output.
7
- #
8
- # input - The Textile String to convert.
9
- #
10
- # Returns the HTML formatted String.
11
- def textilize(input)
12
- site = @context.registers[:site]
13
- converter = site.getConverterImpl(Jekyll::Converters::Textile)
14
- converter.convert(input)
15
- end
16
-
17
7
  # Convert a Markdown string into HTML output.
18
8
  #
19
9
  # input - The Markdown String to convert.
@@ -21,7 +11,7 @@ module Jekyll
21
11
  # Returns the HTML formatted String.
22
12
  def markdownify(input)
23
13
  site = @context.registers[:site]
24
- converter = site.getConverterImpl(Jekyll::Converters::Markdown)
14
+ converter = site.find_converter_instance(Jekyll::Converters::Markdown)
25
15
  converter.convert(input)
26
16
  end
27
17
 
@@ -32,7 +22,7 @@ module Jekyll
32
22
  # Returns the CSS formatted String.
33
23
  def sassify(input)
34
24
  site = @context.registers[:site]
35
- converter = site.getConverterImpl(Jekyll::Converters::Sass)
25
+ converter = site.find_converter_instance(Jekyll::Converters::Sass)
36
26
  converter.convert(input)
37
27
  end
38
28
 
@@ -43,19 +33,19 @@ module Jekyll
43
33
  # Returns the CSS formatted String.
44
34
  def scssify(input)
45
35
  site = @context.registers[:site]
46
- converter = site.getConverterImpl(Jekyll::Converters::Scss)
36
+ converter = site.find_converter_instance(Jekyll::Converters::Scss)
47
37
  converter.convert(input)
48
38
  end
49
39
 
50
40
  # Slugify a filename or title.
51
41
  #
52
42
  # input - The filename or title to slugify.
43
+ # mode - how string is slugified
53
44
  #
54
- # Returns the given filename or title as a lowercase String, with every
55
- # sequence of spaces and non-alphanumeric characters replaced with a
56
- # hyphen.
57
- def slugify(input)
58
- Utils.slugify(input)
45
+ # Returns the given filename or title as a lowercase URL String.
46
+ # See Utils.slugify for more detail.
47
+ def slugify(input, mode=nil)
48
+ Utils.slugify(input, mode)
59
49
  end
60
50
 
61
51
  # Format a date in short format e.g. "27 Jan 2011".
@@ -302,6 +292,8 @@ module Jekyll
302
292
  case input
303
293
  when Time
304
294
  input
295
+ when Date
296
+ input.to_time
305
297
  when String
306
298
  Time.parse(input) rescue Time.at(input.to_i)
307
299
  when Numeric
@@ -309,7 +301,7 @@ module Jekyll
309
301
  else
310
302
  Jekyll.logger.error "Invalid Date:", "'#{input}' is not a valid datetime."
311
303
  exit(1)
312
- end
304
+ end.localtime
313
305
  end
314
306
 
315
307
  def groupable?(element)
@@ -168,7 +168,7 @@ module Jekyll
168
168
  end.compact
169
169
  end
170
170
 
171
- # Sanitizes the given path by removing a leading and addding a trailing slash
171
+ # Sanitizes the given path by removing a leading and adding a trailing slash
172
172
  def sanitize_path(path)
173
173
  if path.nil? || path.empty?
174
174
  ""
@@ -8,6 +8,9 @@ module Jekyll
8
8
  # Gets the name of this layout.
9
9
  attr_reader :name
10
10
 
11
+ # Gets the path to this layout.
12
+ attr_reader :path
13
+
11
14
  # Gets/Sets the extension of this layout.
12
15
  attr_accessor :ext
13
16
 
@@ -26,6 +29,7 @@ module Jekyll
26
29
  @site = site
27
30
  @base = base
28
31
  @name = name
32
+ @path = site.in_source_dir(base, name)
29
33
 
30
34
  self.data = {}
31
35
 
@@ -1,6 +1,6 @@
1
1
  module Jekyll
2
2
  class LogAdapter
3
- attr_reader :writer
3
+ attr_reader :writer, :messages
4
4
 
5
5
  LOG_LEVELS = {
6
6
  :debug => ::Logger::DEBUG,
@@ -16,6 +16,7 @@ module Jekyll
16
16
  #
17
17
  # Returns nothing
18
18
  def initialize(writer, level = :info)
19
+ @messages = []
19
20
  @writer = writer
20
21
  self.log_level = level
21
22
  end
@@ -87,7 +88,9 @@ module Jekyll
87
88
  #
88
89
  # Returns the formatted message
89
90
  def message(topic, message)
90
- formatted_topic(topic) + message.to_s.gsub(/\s+/, ' ')
91
+ msg = formatted_topic(topic) + message.to_s.gsub(/\s+/, ' ')
92
+ messages << msg
93
+ msg
91
94
  end
92
95
 
93
96
  # Internal: Format the topic
@@ -23,6 +23,7 @@ module Jekyll
23
23
  ATTRIBUTES_FOR_LIQUID = EXCERPT_ATTRIBUTES_FOR_LIQUID + %w[
24
24
  content
25
25
  excerpt
26
+ excerpt_separator
26
27
  ]
27
28
 
28
29
  # Post name validator. Post filenames must be like:
@@ -52,12 +53,12 @@ module Jekyll
52
53
  @base = containing_dir(dir)
53
54
  @name = name
54
55
 
55
- self.categories = dir.downcase.split('/').reject { |x| x.empty? }
56
+ self.categories = dir.split('/').reject { |x| x.empty? }
56
57
  process(name)
57
58
  read_yaml(@base, name)
58
59
 
59
60
  data.default_proc = proc do |hash, key|
60
- site.frontmatter_defaults.find(File.join(dir, name), type, key)
61
+ site.frontmatter_defaults.find(relative_path, type, key)
61
62
  end
62
63
 
63
64
  if data.key?('date')
@@ -80,7 +81,7 @@ module Jekyll
80
81
  categories_from_data = Utils.pluralized_array_from_hash(data, 'category', 'categories')
81
82
  self.categories = (
82
83
  Array(categories) + categories_from_data
83
- ).map {|c| c.to_s.downcase}.flatten.uniq
84
+ ).map { |c| c.to_s }.flatten.uniq
84
85
  end
85
86
 
86
87
  def populate_tags
@@ -118,6 +119,14 @@ module Jekyll
118
119
  data.fetch('title') { titleized_slug }
119
120
  end
120
121
 
122
+ # Public: the Post excerpt_separator, from the YAML Front-Matter or site default
123
+ # excerpt_separator value
124
+ #
125
+ # Returns the post excerpt_separator
126
+ def excerpt_separator
127
+ (data['excerpt_separator'] || site.config['excerpt_separator']).to_s
128
+ end
129
+
121
130
  # Turns the post slug into a suitable title
122
131
  def titleized_slug
123
132
  slug.split('-').select {|w| w.capitalize! || w }.join(' ')
@@ -218,7 +227,7 @@ module Jekyll
218
227
  :title => slug,
219
228
  :i_day => date.strftime("%-d"),
220
229
  :i_month => date.strftime("%-m"),
221
- :categories => (categories || []).map { |c| c.to_s }.join('/'),
230
+ :categories => (categories || []).map { |c| c.to_s.downcase }.uniq.join('/'),
222
231
  :short_month => date.strftime("%b"),
223
232
  :short_year => date.strftime("%y"),
224
233
  :y_day => date.strftime("%j"),
@@ -269,7 +278,7 @@ module Jekyll
269
278
  def destination(dest)
270
279
  # The url needs to be unescaped in order to preserve the correct filename
271
280
  path = site.in_dest_dir(dest, URL.unescape_path(url))
272
- path = File.join(path, "index.html") if path[/\.html?$/].nil?
281
+ path = File.join(path, "index.html") if self.url =~ /\/$/
273
282
  path
274
283
  end
275
284
 
@@ -307,7 +316,7 @@ module Jekyll
307
316
  end
308
317
 
309
318
  def generate_excerpt?
310
- !(site.config['excerpt_separator'].to_s.empty?)
319
+ !excerpt_separator.empty?
311
320
  end
312
321
  end
313
322
  end
@@ -0,0 +1,141 @@
1
+ module Jekyll
2
+ class Regenerator
3
+ attr_reader :site, :metadata, :cache
4
+
5
+ def initialize(site)
6
+ @site = site
7
+
8
+ # Read metadata from file
9
+ read_metadata
10
+
11
+ # Initialize cache to an empty hash
12
+ @cache = {}
13
+ end
14
+
15
+ # Checks if a renderable object needs to be regenerated
16
+ #
17
+ # Returns a boolean.
18
+ def regenerate?(document)
19
+ case document
20
+ when Post, Page
21
+ document.asset_file? || document.data['regenerate'] ||
22
+ modified?(site.in_source_dir(document.relative_path))
23
+ when Document
24
+ !document.write? || document.data['regenerate'] || modified?(document.path)
25
+ else
26
+ if document.respond_to?(:path)
27
+ modified?(document.path)
28
+ else
29
+ true
30
+ end
31
+ end
32
+ end
33
+
34
+ # Add a path to the metadata
35
+ #
36
+ # Returns true, also on failure.
37
+ def add(path)
38
+ return true unless File.exist?(path)
39
+
40
+ metadata[path] = {
41
+ "mtime" => File.mtime(path),
42
+ "deps" => []
43
+ }
44
+ cache[path] = true
45
+ end
46
+
47
+ # Force a path to regenerate
48
+ #
49
+ # Returns true.
50
+ def force(path)
51
+ cache[path] = true
52
+ end
53
+
54
+ # Clear the metadata and cache
55
+ #
56
+ # Returns nothing
57
+ def clear
58
+ @metadata = {}
59
+ @cache = {}
60
+ end
61
+
62
+ # Checks if a path's (or one of its dependencies)
63
+ # mtime has changed
64
+ #
65
+ # Returns a boolean.
66
+ def modified?(path)
67
+ return true if disabled?
68
+
69
+ # Check for path in cache
70
+ if cache.has_key? path
71
+ return cache[path]
72
+ end
73
+
74
+ # Check path that exists in metadata
75
+ data = metadata[path]
76
+ if data
77
+ data["deps"].each do |dependency|
78
+ if modified?(dependency)
79
+ return cache[dependency] = cache[path] = true
80
+ end
81
+ end
82
+ if data["mtime"].eql? File.mtime(path)
83
+ return cache[path] = false
84
+ else
85
+ return add(path)
86
+ end
87
+ end
88
+
89
+ # Path does not exist in metadata, add it
90
+ return add(path)
91
+ end
92
+
93
+ # Add a dependency of a path
94
+ #
95
+ # Returns nothing.
96
+ def add_dependency(path, dependency)
97
+ return if (metadata[path].nil? || @disabled)
98
+
99
+ metadata[path]["deps"] << dependency unless metadata[path]["deps"].include? dependency
100
+ regenerate? dependency
101
+ end
102
+
103
+ # Write the metadata to disk
104
+ #
105
+ # Returns nothing.
106
+ def write_metadata
107
+ File.open(metadata_file, 'w') do |f|
108
+ f.write(metadata.to_yaml)
109
+ end
110
+ end
111
+
112
+ # Produce the absolute path of the metadata file
113
+ #
114
+ # Returns the String path of the file.
115
+ def metadata_file
116
+ site.in_source_dir('.jekyll-metadata')
117
+ end
118
+
119
+ # Check if metadata has been disabled
120
+ #
121
+ # Returns a Boolean (true for disabled, false for enabled).
122
+ def disabled?
123
+ @disabled = site.full_rebuild? if @disabled.nil?
124
+ @disabled
125
+ end
126
+
127
+ private
128
+
129
+ # Read metadata from the metadata file, if no file is found,
130
+ # initialize with an empty hash
131
+ #
132
+ # Returns the read metadata.
133
+ def read_metadata
134
+ @metadata = if !disabled? && File.file?(metadata_file)
135
+ SafeYAML.load(File.read(metadata_file))
136
+ else
137
+ {}
138
+ end
139
+ end
140
+ end
141
+ end
@@ -3,11 +3,12 @@
3
3
  module Jekyll
4
4
  class Renderer
5
5
 
6
- attr_reader :document, :site
6
+ attr_reader :document, :site, :site_payload
7
7
 
8
- def initialize(site, document)
9
- @site = site
10
- @document = document
8
+ def initialize(site, document, site_payload = nil)
9
+ @site = site
10
+ @document = document
11
+ @site_payload = site_payload
11
12
  end
12
13
 
13
14
  # Determine which converters to use based on this document's
@@ -32,7 +33,7 @@ module Jekyll
32
33
  def run
33
34
  payload = Utils.deep_merge_hashes({
34
35
  "page" => document.to_liquid
35
- }, site.site_payload)
36
+ }, site_payload || site.site_payload)
36
37
 
37
38
  info = {
38
39
  filters: [Jekyll::Filters],
@@ -138,6 +139,12 @@ module Jekyll
138
139
  File.join(site.config['layouts'], layout.name)
139
140
  )
140
141
 
142
+ # Add layout to dependency tree
143
+ site.regenerator.add_dependency(
144
+ site.in_source_dir(document.path),
145
+ site.in_source_dir(layout.path)
146
+ ) if document.write?
147
+
141
148
  if layout = site.layouts[layout.data["layout"]]
142
149
  if used.include?(layout)
143
150
  layout = nil # avoid recursive chain
@@ -11,6 +11,7 @@ module Jekyll
11
11
  :gems, :plugin_manager
12
12
 
13
13
  attr_accessor :converters, :generators
14
+ attr_reader :regenerator
14
15
 
15
16
  # Public: Initialize a new Site.
16
17
  #
@@ -27,6 +28,9 @@ module Jekyll
27
28
  @source = File.expand_path(config['source']).freeze
28
29
  @dest = File.expand_path(config['destination']).freeze
29
30
 
31
+ # Initialize incremental regenerator
32
+ @regenerator = Regenerator.new(self)
33
+
30
34
  self.plugin_manager = Jekyll::PluginManager.new(self)
31
35
  self.plugins = plugin_manager.plugins_path
32
36
 
@@ -183,6 +187,7 @@ module Jekyll
183
187
  end
184
188
 
185
189
  pages.sort_by!(&:name)
190
+ static_files.sort_by!(&:relative_path)
186
191
  end
187
192
 
188
193
  # Read all the files in <source>/<dir>/_posts and create a new Post
@@ -287,17 +292,22 @@ module Jekyll
287
292
  def render
288
293
  relative_permalinks_deprecation_method
289
294
 
295
+ payload = site_payload
290
296
  collections.each do |label, collection|
291
297
  collection.docs.each do |document|
292
- document.output = Jekyll::Renderer.new(self, document).run
298
+ if regenerator.regenerate?(document)
299
+ document.output = Jekyll::Renderer.new(self, document, payload).run
300
+ end
293
301
  end
294
302
  end
295
303
 
296
304
  payload = site_payload
297
305
  [posts, pages].flatten.each do |page_or_post|
298
- page_or_post.render(layouts, payload)
306
+ if regenerator.regenerate?(page_or_post)
307
+ page_or_post.render(layouts, payload)
308
+ end
299
309
  end
300
- rescue Errno::ENOENT => e
310
+ rescue Errno::ENOENT
301
311
  # ignore missing layout dir
302
312
  end
303
313
 
@@ -312,7 +322,10 @@ module Jekyll
312
322
  #
313
323
  # Returns nothing.
314
324
  def write
315
- each_site_file { |item| item.write(dest) }
325
+ each_site_file { |item|
326
+ item.write(dest) if regenerator.regenerate?(item)
327
+ }
328
+ regenerator.write_metadata unless full_rebuild?
316
329
  end
317
330
 
318
331
  # Construct a Hash of Posts indexed by the specified Post attribute.
@@ -377,7 +390,7 @@ module Jekyll
377
390
  "time" => time,
378
391
  "posts" => posts.sort { |a, b| b <=> a },
379
392
  "pages" => pages,
380
- "static_files" => static_files.sort { |a, b| a.relative_path <=> b.relative_path },
393
+ "static_files" => static_files,
381
394
  "html_pages" => pages.select { |page| page.html? || page.url.end_with?("/") },
382
395
  "categories" => post_attr_hash('categories'),
383
396
  "tags" => post_attr_hash('tags'),
@@ -405,13 +418,8 @@ module Jekyll
405
418
  # klass - The Class of the Converter to fetch.
406
419
  #
407
420
  # Returns the Converter instance implementing the given Converter.
408
- def getConverterImpl(klass)
409
- matches = converters.select { |c| c.class == klass }
410
- if impl = matches.first
411
- impl
412
- else
413
- raise "Converter implementation not found for #{klass}"
414
- end
421
+ def find_converter_instance(klass)
422
+ converters.find { |c| c.class == klass } || proc { raise "No converter for #{klass}" }.call
415
423
  end
416
424
 
417
425
  # Create array of instances of the subclasses of the class or module
@@ -453,7 +461,7 @@ module Jekyll
453
461
 
454
462
  def relative_permalinks_deprecation_method
455
463
  if config['relative_permalinks'] && has_relative_page?
456
- Jekyll.logger.warn "Deprecation:", "Starting in 2.0, permalinks for pages" +
464
+ Jekyll.logger.warn "Deprecation:", "Since v2.0, permalinks for pages" +
457
465
  " in subfolders must be relative to the" +
458
466
  " site source directory, not the parent" +
459
467
  " directory. Check http://jekyllrb.com/docs/upgrading/"+
@@ -483,6 +491,17 @@ module Jekyll
483
491
  @frontmatter_defaults ||= FrontmatterDefaults.new(self)
484
492
  end
485
493
 
494
+ # Whether to perform a full rebuild without incremental regeneration
495
+ #
496
+ # Returns a Boolean: true for a full rebuild, false for normal build
497
+ def full_rebuild?(override = {})
498
+ override['full_rebuild'] || config['full_rebuild']
499
+ end
500
+
501
+ def publisher
502
+ @publisher ||= Publisher.new(self)
503
+ end
504
+
486
505
  private
487
506
 
488
507
  def has_relative_page?
@@ -503,9 +522,5 @@ module Jekyll
503
522
  name.gsub!(/(^|\b\s)\s+($|\s?\b)/, '\\1\\2')
504
523
  name.gsub(/\s+/, '_')
505
524
  end
506
-
507
- def publisher
508
- @publisher ||= Publisher.new(self)
509
- end
510
525
  end
511
526
  end