jekyll 2.0.0.alpha.2 → 2.0.0.alpha.3

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/History.markdown +35 -0
  3. data/Rakefile +1 -1
  4. data/features/collections.feature +38 -0
  5. data/features/create_sites.feature +17 -0
  6. data/features/step_definitions/jekyll_steps.rb +15 -7
  7. data/features/support/env.rb +6 -1
  8. data/lib/jekyll.rb +6 -1
  9. data/lib/jekyll/cleaner.rb +5 -3
  10. data/lib/jekyll/collection.rb +121 -0
  11. data/lib/jekyll/command.rb +2 -0
  12. data/lib/jekyll/commands/build.rb +5 -1
  13. data/lib/jekyll/commands/serve.rb +1 -1
  14. data/lib/jekyll/configuration.rb +2 -0
  15. data/lib/jekyll/converters/markdown/redcarpet_parser.rb +11 -12
  16. data/lib/jekyll/convertible.rb +1 -1
  17. data/lib/jekyll/document.rb +228 -0
  18. data/lib/jekyll/entry_filter.rb +4 -1
  19. data/lib/jekyll/layout_reader.rb +1 -1
  20. data/lib/jekyll/page.rb +1 -1
  21. data/lib/jekyll/plugin_manager.rb +76 -0
  22. data/lib/jekyll/post.rb +3 -3
  23. data/lib/jekyll/publisher.rb +21 -0
  24. data/lib/jekyll/renderer.rb +132 -0
  25. data/lib/jekyll/site.rb +84 -68
  26. data/lib/jekyll/tags/highlight.rb +8 -6
  27. data/lib/jekyll/url.rb +43 -3
  28. data/lib/jekyll/version.rb +1 -1
  29. data/lib/site_template/_includes/head.html +3 -3
  30. data/script/console +38 -0
  31. data/site/_config.yml +1 -0
  32. data/site/_data/docs.yml +1 -0
  33. data/site/_includes/css/style.css +12 -12
  34. data/site/_includes/news_item.html +3 -3
  35. data/site/_includes/primary-nav-items.html +4 -1
  36. data/site/_includes/top.html +7 -3
  37. data/site/_layouts/news_item.html +3 -3
  38. data/site/_posts/2014-03-27-jekyll-1-5-1-released.markdown +26 -0
  39. data/site/docs/collections.md +126 -0
  40. data/site/docs/configuration.md +3 -2
  41. data/site/docs/datafiles.md +1 -1
  42. data/site/docs/history.md +6 -0
  43. data/site/docs/plugins.md +1 -1
  44. data/site/docs/troubleshooting.md +7 -3
  45. data/site/docs/variables.md +1 -1
  46. data/site/favicon.ico +0 -0
  47. data/site/feed.xml +27 -14
  48. data/site/img/logo-rss.png +0 -0
  49. data/site/js/html5shiv.js +8 -0
  50. data/site/js/respond.min.js +5 -0
  51. data/test/source/+/%# +.md +6 -0
  52. data/test/source/_methods/_do_not_read_me.md +5 -0
  53. data/test/source/_methods/configuration.md +8 -0
  54. data/test/source/_methods/sanitized_path.md +5 -0
  55. data/test/source/_methods/site/_dont_include_me_either.md +5 -0
  56. data/test/source/_methods/site/generate.md +6 -0
  57. data/test/source/_methods/site/initialize.md +5 -0
  58. data/test/source/_methods/um_hi.md +6 -0
  59. data/test/source/_posts/2014-03-03-yaml-with-dots.md +5 -0
  60. data/test/source/_posts/2014-03-22-escape-+ %20[].markdown +6 -0
  61. data/test/source/pgp.key +2 -0
  62. data/test/test_coffeescript.rb +1 -1
  63. data/test/test_collections.rb +129 -0
  64. data/test/test_command.rb +17 -0
  65. data/test/test_document.rb +48 -0
  66. data/test/test_filters.rb +1 -1
  67. data/test/test_generated_site.rb +5 -4
  68. data/test/test_new_command.rb +4 -4
  69. data/test/test_page.rb +22 -8
  70. data/test/test_path_sanitization.rb +4 -0
  71. data/test/test_post.rb +42 -13
  72. data/test/test_site.rb +11 -17
  73. data/test/test_tags.rb +10 -6
  74. metadata +42 -7
  75. data/lib/site_template/_posts/0000-00-00-this-post-demonstrates-post-content-styles.md +0 -88
  76. data/site/favicon.png +0 -0
  77. data/site/img/tube.png +0 -0
  78. data/site/img/tube1x.png +0 -0
  79. data/site/js/modernizr-2.7.1.min.js +0 -4
@@ -0,0 +1,132 @@
1
+ module Jekyll
2
+ class Renderer
3
+
4
+ attr_reader :document, :site
5
+
6
+ def initialize(site, document)
7
+ @site = site
8
+ @document = document
9
+ end
10
+
11
+ # Determine which converters to use based on this document's
12
+ # extension.
13
+ #
14
+ # Returns an array of Converter instances.
15
+ def converters
16
+ @converters ||= site.converters.select { |c| c.matches(document.extname) }
17
+ end
18
+
19
+ # Determine the extname the outputted file should have
20
+ #
21
+ # Returns the output extname including the leading period.
22
+ def output_ext
23
+ converters.first.output_ext(document.extname)
24
+ end
25
+
26
+ ######################
27
+ ## DAT RENDER THO
28
+ ######################
29
+
30
+ def run
31
+ payload = Utils.deep_merge_hashes({
32
+ "page" => document.to_liquid
33
+ }, site.site_payload)
34
+
35
+ info = {
36
+ filters: [Jekyll::Filters],
37
+ registers: { :site => site, :page => payload['page'] }
38
+ }
39
+
40
+ # render and transform content (this becomes the final content of the object)
41
+ payload["highlighter_prefix"] = converters.first.highlighter_prefix
42
+ payload["highlighter_suffix"] = converters.first.highlighter_suffix
43
+
44
+ output = document.content
45
+
46
+ if document.render_with_liquid?
47
+ output = render_liquid(output, payload, info)
48
+ end
49
+
50
+ place_in_layouts(
51
+ convert(output),
52
+ payload,
53
+ info
54
+ )
55
+ end
56
+
57
+ # Convert the given content using the converters which match this renderer's document.
58
+ #
59
+ # content - the raw, unconverted content
60
+ #
61
+ # Returns the converted content.
62
+ def convert(content)
63
+ converters.reduce(content) do |output, converter|
64
+ begin
65
+ converter.convert output
66
+ rescue => e
67
+ Jekyll.logger.error "Conversion error:", "#{converter.class} encountered an error converting '#{document.relative_path}'."
68
+ raise e
69
+ end
70
+ end
71
+ end
72
+
73
+ # Render the given content with the payload and info
74
+ #
75
+ # content -
76
+ # payload -
77
+ # info -
78
+ # path - (optional) the path to the file, for use in ex
79
+ #
80
+ # Returns the content, rendered by Liquid.
81
+ def render_liquid(content, payload, info, path = nil)
82
+ Liquid::Template.parse(content).render!(payload, info)
83
+ rescue Tags::IncludeTagError => e
84
+ Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{e.path}, included in #{path || document.relative_path}"
85
+ raise e
86
+ rescue Exception => e
87
+ Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{path || document.relative_path}"
88
+ raise e
89
+ end
90
+
91
+ # Render layouts and place given content inside.
92
+ #
93
+ # content - the content to be placed in the layout
94
+ #
95
+ #
96
+ # Returns the content placed in the Liquid-rendered layouts
97
+ def place_in_layouts(content, payload, info)
98
+ output = content.dup
99
+ layout = site.layouts[document.data["layout"]]
100
+ used = Set.new([layout])
101
+
102
+ while layout
103
+ payload = Utils.deep_merge_hashes(
104
+ payload,
105
+ {
106
+ "content" => output,
107
+ "page" => document.to_liquid,
108
+ "layout" => layout.data
109
+ }
110
+ )
111
+
112
+ output = render_liquid(
113
+ layout.content,
114
+ payload,
115
+ info,
116
+ File.join(site.config['layouts'], layout.name)
117
+ )
118
+
119
+ if layout = site.layouts[layout.data["layout"]]
120
+ if used.include?(layout)
121
+ layout = nil # avoid recursive chain
122
+ else
123
+ used << layout
124
+ end
125
+ end
126
+ end
127
+
128
+ output
129
+ end
130
+
131
+ end
132
+ end
@@ -1,9 +1,10 @@
1
1
  module Jekyll
2
2
  class Site
3
3
  attr_accessor :config, :layouts, :posts, :pages, :static_files,
4
- :categories, :exclude, :include, :source, :dest, :lsi, :highlighter,
5
- :permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
6
- :show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems
4
+ :exclude, :include, :source, :dest, :lsi, :highlighter,
5
+ :permalink_style, :time, :future, :unpublished, :safe, :plugins, :limit_posts,
6
+ :show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems,
7
+ :plugin_manager, :collections
7
8
 
8
9
  attr_accessor :converters, :generators
9
10
 
@@ -13,15 +14,18 @@ module Jekyll
13
14
  def initialize(config)
14
15
  self.config = config.clone
15
16
 
16
- %w[safe lsi highlighter baseurl exclude include future show_drafts limit_posts keep_files gems].each do |opt|
17
+ %w[safe lsi highlighter baseurl exclude include future unpublished
18
+ show_drafts limit_posts keep_files gems].each do |opt|
17
19
  self.send("#{opt}=", config[opt])
18
20
  end
19
21
 
20
- self.source = File.expand_path(config['source'])
21
- self.dest = File.expand_path(config['destination'])
22
- self.plugins = plugins_path
22
+ self.source = File.expand_path(config['source'])
23
+ self.dest = File.expand_path(config['destination'])
23
24
  self.permalink_style = config['permalink'].to_sym
24
25
 
26
+ self.plugin_manager = Jekyll::PluginManager.new(self)
27
+ self.plugins = plugin_manager.plugins_path
28
+
25
29
  self.file_read_opts = {}
26
30
  self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
27
31
 
@@ -50,8 +54,6 @@ module Jekyll
50
54
  self.posts = []
51
55
  self.pages = []
52
56
  self.static_files = []
53
- self.categories = Hash.new { |hash, key| hash[key] = [] }
54
- self.tags = Hash.new { |hash, key| hash[key] = [] }
55
57
  self.data = {}
56
58
 
57
59
  if limit_posts < 0
@@ -65,17 +67,7 @@ module Jekyll
65
67
  def setup
66
68
  ensure_not_in_dest
67
69
 
68
- # If safe mode is off, load in any Ruby files under the plugins
69
- # directory.
70
- unless safe
71
- plugins.each do |plugins|
72
- Dir[File.join(plugins, "**/*.rb")].sort.each do |f|
73
- require f
74
- end
75
- end
76
- end
77
-
78
- require_gems
70
+ plugin_manager.conscientious_require
79
71
 
80
72
  self.converters = instantiate_subclasses(Jekyll::Converter)
81
73
  self.generators = instantiate_subclasses(Jekyll::Generator)
@@ -92,31 +84,24 @@ module Jekyll
92
84
  end
93
85
  end
94
86
 
95
- def require_gems
96
- gems.each do |gem|
97
- if plugin_allowed?(gem)
98
- require gem
99
- end
87
+ # The list of collections and their corresponding Jekyll::Collection instances.
88
+ # If config['collections'] is set, a new instance is created for each item in the collection.
89
+ # If config['collections'] is not set, a new hash is returned.
90
+ #
91
+ # Returns a Hash containing collection name-to-instance pairs.
92
+ def collections
93
+ @collections ||= if config['collections']
94
+ Hash[config['collections'].map { |coll| [coll, Jekyll::Collection.new(self, coll)] } ]
95
+ else
96
+ Hash.new
100
97
  end
101
98
  end
102
99
 
103
- def plugin_allowed?(gem_name)
104
- whitelist.include?(gem_name) || !safe
105
- end
106
-
107
- def whitelist
108
- @whitelist ||= Array[config['whitelist']].flatten
109
- end
110
-
111
- # Internal: Setup the plugin search path
100
+ # The list of collections to render.
112
101
  #
113
- # Returns an Array of plugin search paths
114
- def plugins_path
115
- if (config['plugins'] == Jekyll::Configuration::DEFAULTS['plugins'])
116
- [File.join(source, config['plugins'])]
117
- else
118
- Array(config['plugins']).map { |d| File.expand_path(d) }
119
- end
102
+ # The array of collection labels to render.
103
+ def to_render
104
+ @to_render ||= (config['render'] || Array.new)
120
105
  end
121
106
 
122
107
  # Read Site data from disk and load it into internal data structures.
@@ -126,6 +111,7 @@ module Jekyll
126
111
  self.layouts = LayoutReader.new(self).read
127
112
  read_directories
128
113
  read_data(config['data_source'])
114
+ read_collections
129
115
  end
130
116
 
131
117
  # Recursively traverse directories to find posts, pages and static files
@@ -151,7 +137,7 @@ module Jekyll
151
137
  read_directories(f_rel) unless dest.sub(/\/$/, '') == f_abs
152
138
  elsif has_yaml_header?(f_abs)
153
139
  page = Page.new(self, source, dir, f)
154
- pages << page if page.published?
140
+ pages << page if publisher.publish?(page)
155
141
  else
156
142
  static_files << StaticFile.new(self, source, dir, f)
157
143
  end
@@ -170,11 +156,9 @@ module Jekyll
170
156
  posts = read_content(dir, '_posts', Post)
171
157
 
172
158
  posts.each do |post|
173
- if post.published? && (future || post.date <= time)
174
- aggregate_post_info(post)
175
- end
159
+ aggregate_post_info(post) if publisher.publish?(post)
176
160
  end
177
- end
161
+ end
178
162
 
179
163
  # Read all the files in <source>/<dir>/_drafts and create a new Post
180
164
  # object with each one.
@@ -204,21 +188,27 @@ module Jekyll
204
188
  #
205
189
  # Returns nothing
206
190
  def read_data(dir)
207
- base = File.join(source, dir)
208
- return unless File.directory?(base) && (!safe || !File.symlink?(base))
209
-
210
- entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] }
211
- entries.delete_if { |e| File.directory?(File.join(base, e)) }
191
+ unless dir.to_s.eql?("_data")
192
+ Jekyll.logger.error "Error:", "Data source directories other than '_data' have been removed.\n" +
193
+ "Please move your YAML files to `_data` and remove the `data_source` key from your `_config.yml`."
194
+ end
212
195
 
213
- entries.each do |entry|
214
- path = File.join(source, dir, entry)
215
- next if File.symlink?(path) && safe
196
+ collections['data'] = Jekyll::Collection.new(self, "data")
197
+ collections['data'].read
216
198
 
217
- key = sanitize_filename(File.basename(entry, '.*'))
218
- self.data[key] = SafeYAML.load_file(path)
199
+ collections['data'].docs.each do |doc|
200
+ key = sanitize_filename(doc.basename(".*"))
201
+ self.data[key] = doc.data
219
202
  end
220
203
  end
221
204
 
205
+ # Read in all collections specified in the configuration
206
+ #
207
+ # Returns nothing.
208
+ def read_collections
209
+ collections.each { |_, collection| collection.read }
210
+ end
211
+
222
212
  # Run each of the Generators.
223
213
  #
224
214
  # Returns nothing.
@@ -234,13 +224,16 @@ module Jekyll
234
224
  def render
235
225
  relative_permalinks_deprecation_method
236
226
 
227
+ to_render.each do |label|
228
+ collections[label].docs.each do |document|
229
+ document.output = Jekyll::Renderer.new(self, document).run
230
+ end
231
+ end
232
+
237
233
  payload = site_payload
238
234
  [posts, pages].flatten.each do |page_or_post|
239
235
  page_or_post.render(layouts, payload)
240
236
  end
241
-
242
- categories.values.map { |ps| ps.sort! { |a, b| b <=> a } }
243
- tags.values.map { |ps| ps.sort! { |a, b| b <=> a } }
244
237
  rescue Errno::ENOENT => e
245
238
  # ignore missing layout dir
246
239
  end
@@ -275,12 +268,20 @@ module Jekyll
275
268
  def post_attr_hash(post_attr)
276
269
  # Build a hash map based on the specified post attribute ( post attr =>
277
270
  # array of posts ) then sort each array in reverse order.
278
- hash = Hash.new { |hash, key| hash[key] = [] }
271
+ hash = Hash.new { |h, key| h[key] = [] }
279
272
  posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
280
- hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a } }
273
+ hash.values.each { |posts| posts.sort!.reverse! }
281
274
  hash
282
275
  end
283
276
 
277
+ def tags
278
+ post_attr_hash('tags')
279
+ end
280
+
281
+ def categories
282
+ post_attr_hash('categories')
283
+ end
284
+
284
285
  # Prepare site data for site payload. The method maintains backward compatibility
285
286
  # if the key 'data' is already used in _config.yml.
286
287
  #
@@ -304,7 +305,8 @@ module Jekyll
304
305
  # See Site#post_attr_hash for type info.
305
306
  def site_payload
306
307
  {"jekyll" => { "version" => Jekyll::VERSION },
307
- "site" => config.merge({
308
+ "site" => Utils.deep_merge_hashes(config,
309
+ Utils.deep_merge_hashes(collections, {
308
310
  "time" => time,
309
311
  "posts" => posts.sort { |a, b| b <=> a },
310
312
  "pages" => pages,
@@ -312,7 +314,9 @@ module Jekyll
312
314
  "html_pages" => pages.reject { |page| !page.html? },
313
315
  "categories" => post_attr_hash('categories'),
314
316
  "tags" => post_attr_hash('tags'),
315
- "data" => site_data})}
317
+ "data" => site_data
318
+ }))
319
+ }
316
320
  end
317
321
 
318
322
  # Filter out any files/directories that are hidden or backup files (start
@@ -364,7 +368,7 @@ module Jekyll
364
368
  # Returns the list of entries to process
365
369
  def get_entries(dir, subfolder)
366
370
  base = File.join(source, dir, subfolder)
367
- return [] unless File.exists?(base)
371
+ return [] unless File.exist?(base)
368
372
  entries = Dir.chdir(base) { filter_entries(Dir['**/*'], base) }
369
373
  entries.delete_if { |e| File.directory?(File.join(base, e)) }
370
374
  end
@@ -376,8 +380,6 @@ module Jekyll
376
380
  # Returns nothing
377
381
  def aggregate_post_info(post)
378
382
  posts << post
379
- post.categories.each { |c| categories[c] << post }
380
- post.tags.each { |c| tags[c] << post }
381
383
  end
382
384
 
383
385
  def relative_permalinks_deprecation_method
@@ -392,8 +394,18 @@ module Jekyll
392
394
  end
393
395
  end
394
396
 
397
+ def documents
398
+ collections.reduce(Set.new) do |docs, (label, coll)|
399
+ if to_render.include?(label)
400
+ docs.merge(coll.docs)
401
+ else
402
+ docs
403
+ end
404
+ end
405
+ end
406
+
395
407
  def each_site_file
396
- %w(posts pages static_files).each do |type|
408
+ %w(posts pages static_files documents).each do |type|
397
409
  send(type).each do |item|
398
410
  yield item
399
411
  end
@@ -407,7 +419,7 @@ module Jekyll
407
419
  end
408
420
 
409
421
  def has_yaml_header?(file)
410
- "---" == File.open(file) { |fd| fd.read(3) }
422
+ !!(File.open(file).read =~ /\A---\r?\n/)
411
423
  end
412
424
 
413
425
  def limit_posts!
@@ -424,5 +436,9 @@ module Jekyll
424
436
  name.gsub!(/(^|\b\s)\s+($|\s?\b)/, '\\1\\2')
425
437
  name.gsub(/\s+/, '_')
426
438
  end
439
+
440
+ def publisher
441
+ @publisher ||= Publisher.new(self)
442
+ end
427
443
  end
428
444
  end
@@ -41,14 +41,15 @@ eos
41
41
  end
42
42
 
43
43
  def render(context)
44
+ code = super.to_s.strip
44
45
  case context.registers[:site].highlighter
45
46
  when 'pygments'
46
- render_pygments(context, super)
47
+ render_pygments(context, code)
47
48
  when 'rouge'
48
- render_rouge(context, super)
49
+ render_rouge(context, code)
49
50
  else
50
- render_codehighlighter(context, super)
51
- end
51
+ render_codehighlighter(context, code)
52
+ end.strip
52
53
  end
53
54
 
54
55
  def render_pygments(context, code)
@@ -106,8 +107,9 @@ eos
106
107
 
107
108
  def add_code_tags(code, lang)
108
109
  # Add nested <code> tags to code blocks
109
- code = code.sub(/<pre>/,'<pre><code class="' + lang.to_s.gsub("+", "-") + '">')
110
- code = code.sub(/<\/pre>/,"</code></pre>")
110
+ code = code.sub(/<pre>\n*/,'<pre><code class="' + lang.to_s.gsub("+", "-") + '">')
111
+ code = code.sub(/\n*<\/pre>/,"</code></pre>")
112
+ code.strip
111
113
  end
112
114
 
113
115
  end