jekyll 1.2.1 → 1.3.0.rc

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/History.markdown +58 -0
  3. data/README.markdown +2 -1
  4. data/Rakefile +8 -1
  5. data/bin/jekyll +14 -17
  6. data/features/create_sites.feature +11 -0
  7. data/features/data.feature +65 -0
  8. data/features/include_tag.feature +13 -0
  9. data/features/site_configuration.feature +29 -0
  10. data/features/step_definitions/jekyll_steps.rb +27 -12
  11. data/features/support/env.rb +36 -0
  12. data/jekyll.gemspec +22 -10
  13. data/lib/jekyll.rb +2 -1
  14. data/lib/jekyll/cleaner.rb +8 -8
  15. data/lib/jekyll/commands/build.rb +14 -8
  16. data/lib/jekyll/commands/serve.rb +2 -0
  17. data/lib/jekyll/configuration.rb +5 -1
  18. data/lib/jekyll/converters/markdown/kramdown_parser.rb +1 -1
  19. data/lib/jekyll/convertible.rb +17 -6
  20. data/lib/jekyll/core_ext.rb +15 -0
  21. data/lib/jekyll/filters.rb +10 -0
  22. data/lib/jekyll/post.rb +2 -2
  23. data/lib/jekyll/site.rb +68 -19
  24. data/lib/jekyll/tags/gist.rb +9 -1
  25. data/lib/jekyll/tags/highlight.rb +1 -1
  26. data/lib/jekyll/tags/include.rb +72 -28
  27. data/lib/jekyll/tags/post_url.rb +4 -2
  28. data/lib/site_template/css/main.css +15 -15
  29. data/site/_includes/docs_contents.html +1 -1
  30. data/site/_includes/docs_contents_mobile.html +1 -1
  31. data/site/_posts/2013-10-28-jekyll-1-3-0-rc1-released.markdown +19 -0
  32. data/site/docs/configuration.md +18 -0
  33. data/site/docs/datafiles.md +63 -0
  34. data/site/docs/installation.md +10 -0
  35. data/site/docs/migrations.md +12 -3
  36. data/site/docs/pagination.md +16 -37
  37. data/site/docs/plugins.md +66 -6
  38. data/site/docs/structure.md +17 -0
  39. data/site/docs/templates.md +31 -7
  40. data/site/docs/upgrading.md +3 -3
  41. data/site/docs/usage.md +1 -1
  42. data/site/docs/variables.md +2 -2
  43. data/test/helper.rb +4 -1
  44. data/test/source/_data/languages.yml +2 -0
  45. data/test/source/_data/members.yaml +7 -0
  46. data/test/source/_data/products.yml +4 -0
  47. data/test/source/_layouts/post/simple.html +1 -0
  48. data/test/source/products.yml +4 -0
  49. data/test/test_convertible.rb +1 -1
  50. data/test/test_filters.rb +11 -0
  51. data/test/test_kramdown.rb +32 -5
  52. data/test/test_site.rb +58 -1
  53. data/test/test_tags.rb +21 -23
  54. metadata +71 -27
@@ -20,6 +20,7 @@ require 'fileutils'
20
20
  require 'time'
21
21
  require 'safe_yaml'
22
22
  require 'English'
23
+ require 'pathname'
23
24
 
24
25
  # 3rd party
25
26
  require 'liquid'
@@ -60,7 +61,7 @@ require_all 'jekyll/tags'
60
61
  SafeYAML::OPTIONS[:suppress_warnings] = true
61
62
 
62
63
  module Jekyll
63
- VERSION = '1.2.1'
64
+ VERSION = '1.3.0.rc'
64
65
 
65
66
  # Public: Generate a Jekyll configuration Hash by merging the default
66
67
  # options with anything in _config.yml, and adding the given options on top.
@@ -2,7 +2,7 @@ require 'set'
2
2
 
3
3
  module Jekyll
4
4
  class Site
5
- # Handles the cleanup of a site's destination before the site is built.
5
+ # Handles the cleanup of a site's destination before it is built.
6
6
  class Cleaner
7
7
  def initialize(site)
8
8
  @site = site
@@ -15,14 +15,14 @@ module Jekyll
15
15
 
16
16
  private
17
17
 
18
- # Private: The list of files and directories to be deleted during the cleanup process
18
+ # Private: The list of files and directories to be deleted during cleanup process
19
19
  #
20
- # Returns an Array with the file and directory paths
20
+ # Returns an Array of the file and directory paths
21
21
  def obsolete_files
22
22
  (existing_files - new_files - new_dirs + replaced_files).to_a
23
23
  end
24
24
 
25
- # Private: The list of existing files, except those included in keep_files and hidden files.
25
+ # Private: The list of existing files, apart from those included in keep_files and hidden files.
26
26
  #
27
27
  # Returns a Set with the file paths
28
28
  def existing_files
@@ -33,7 +33,7 @@ module Jekyll
33
33
  files
34
34
  end
35
35
 
36
- # Private: The list of files to be created when the site is built.
36
+ # Private: The list of files to be created when site is built.
37
37
  #
38
38
  # Returns a Set with the file paths
39
39
  def new_files
@@ -42,7 +42,7 @@ module Jekyll
42
42
  files
43
43
  end
44
44
 
45
- # Private: The list of directories to be created when the site is built.
45
+ # Private: The list of directories to be created when site is built.
46
46
  # These are the parent directories of the files in #new_files.
47
47
  #
48
48
  # Returns a Set with the directory paths
@@ -57,7 +57,7 @@ module Jekyll
57
57
  new_dirs.select { |dir| File.file?(dir) }.to_set
58
58
  end
59
59
 
60
- # Private: creates a regular expression from the config's keep_files array
60
+ # Private: Creates a regular expression from the config's keep_files array
61
61
  #
62
62
  # Examples
63
63
  # ['.git','.svn'] creates the following regex: /\/(\.git|\/.svn)/
@@ -70,4 +70,4 @@ module Jekyll
70
70
  end
71
71
  end
72
72
  end
73
- end
73
+ end
@@ -31,27 +31,33 @@ module Jekyll
31
31
  #
32
32
  # Returns nothing.
33
33
  def self.watch(site, options)
34
- require 'directory_watcher'
34
+ require 'listen'
35
35
 
36
36
  source = options['source']
37
37
  destination = options['destination']
38
38
 
39
- Jekyll.logger.info "Auto-regeneration:", "enabled"
39
+ begin
40
+ dest = Pathname.new(destination).relative_path_from(Pathname.new(source)).to_path
41
+ ignored = Regexp.new(Regexp.escape(dest))
42
+ rescue ArgumentError
43
+ # Destination is outside the source, no need to ignore it.
44
+ ignored = nil
45
+ end
40
46
 
41
- dw = DirectoryWatcher.new(source, :glob => self.globs(source, destination), :pre_load => true)
42
- dw.interval = 1
47
+ Jekyll.logger.info "Auto-regeneration:", "enabled"
43
48
 
44
- dw.add_observer do |*args|
49
+ listener = Listen.to(source, :ignore => ignored) do |modified, added, removed|
45
50
  t = Time.now.strftime("%Y-%m-%d %H:%M:%S")
46
- print Jekyll.logger.formatted_topic("Regenerating:") + "#{args.size} files at #{t} "
51
+ n = modified.length + added.length + removed.length
52
+ print Jekyll.logger.formatted_topic("Regenerating:") + "#{n} files at #{t} "
47
53
  self.process_site(site)
48
54
  puts "...done."
49
55
  end
50
-
51
- dw.start
56
+ listener.start
52
57
 
53
58
  unless options['serving']
54
59
  trap("INT") do
60
+ listener.stop
55
61
  puts " Halting auto-regeneration."
56
62
  exit 0
57
63
  end
@@ -18,6 +18,8 @@ module Jekyll
18
18
 
19
19
  s.mount(options['baseurl'], HTTPServlet::FileHandler, destination, fh_option)
20
20
 
21
+ Jekyll.logger.info "Server address:", "http://#{s.config[:BindAddress]}:#{s.config[:Port]}"
22
+
21
23
  if options['detach'] # detach the server
22
24
  pid = Process.fork { s.start }
23
25
  Process.detach(pid)
@@ -10,10 +10,14 @@ module Jekyll
10
10
  'destination' => File.join(Dir.pwd, '_site'),
11
11
  'plugins' => '_plugins',
12
12
  'layouts' => '_layouts',
13
+ 'data_source' => '_data',
13
14
  'keep_files' => ['.git','.svn'],
15
+ 'gems' => [],
14
16
 
15
17
  'timezone' => nil, # use the local timezone
16
18
 
19
+ 'encoding' => nil, # use the system encoding
20
+
17
21
  'safe' => false,
18
22
  'detach' => false, # default to not detaching the server
19
23
  'show_drafts' => nil,
@@ -23,7 +27,7 @@ module Jekyll
23
27
  'pygments' => true,
24
28
 
25
29
  'relative_permalinks' => true, # backwards-compatibility with < 1.0
26
- # will be set to false once 1.1 hits
30
+ # will be set to false once 2.0 hits
27
31
 
28
32
  'markdown' => 'maruku',
29
33
  'permalink' => 'date',
@@ -16,7 +16,7 @@ module Jekyll
16
16
  if @config['kramdown']['use_coderay']
17
17
  %w[wrap line_numbers line_numbers_start tab_width bold_every css default_lang].each do |opt|
18
18
  key = "coderay_#{opt}"
19
- @config['kramdown'][key.to_sym] = @config['kramdown']['coderay'][key] unless @config['kramdown'].has_key?(key)
19
+ @config['kramdown'][key] = @config['kramdown']['coderay'][key] unless @config['kramdown'].has_key?(key)
20
20
  end
21
21
  end
22
22
 
@@ -20,16 +20,23 @@ module Jekyll
20
20
  self.content || ''
21
21
  end
22
22
 
23
+ # Returns merged optin hash for File.read of self.site (if exists)
24
+ # and a given param
25
+ def merged_file_read_opts(opts)
26
+ (self.site ? self.site.file_read_opts : {}).merge(opts)
27
+ end
28
+
23
29
  # Read the YAML frontmatter.
24
30
  #
25
31
  # base - The String path to the dir containing the file.
26
32
  # name - The String filename of the file.
33
+ # opts - optional parameter to File.read, default at site configs
27
34
  #
28
35
  # Returns nothing.
29
- def read_yaml(base, name)
36
+ def read_yaml(base, name, opts = {})
30
37
  begin
31
- self.content = File.read(File.join(base, name))
32
-
38
+ self.content = File.read_with_options(File.join(base, name),
39
+ merged_file_read_opts(opts))
33
40
  if self.content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
34
41
  self.content = $POSTMATCH
35
42
  self.data = YAML.safe_load($1)
@@ -77,10 +84,13 @@ module Jekyll
77
84
  # info - the info for Liquid
78
85
  #
79
86
  # Returns the converted content
80
- def render_liquid(content, payload, info)
87
+ def render_liquid(content, payload, info, path = nil)
81
88
  Liquid::Template.parse(content).render!(payload, info)
89
+ rescue Tags::IncludeTagError => e
90
+ Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{e.path}"
91
+ raise e
82
92
  rescue Exception => e
83
- Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{self.path}"
93
+ Jekyll.logger.error "Liquid Exception:", "#{e.message} in #{path || self.path}"
84
94
  raise e
85
95
  end
86
96
 
@@ -111,7 +121,8 @@ module Jekyll
111
121
 
112
122
  self.output = self.render_liquid(layout.content,
113
123
  payload,
114
- info)
124
+ info,
125
+ File.join(self.site.config['layouts'], layout.name))
115
126
 
116
127
  if layout = layouts[layout.data["layout"]]
117
128
  if used.include?(layout)
@@ -69,3 +69,18 @@ module Enumerable
69
69
  any? { |exp| File.fnmatch?(exp, e) }
70
70
  end
71
71
  end
72
+
73
+ # Ruby 1.8's File.read don't support option.
74
+ # read_with_options ignore optional parameter for 1.8,
75
+ # and act as alias for 1.9 or later.
76
+ class File
77
+ if RUBY_VERSION < '1.9'
78
+ def self.read_with_options(path, opts = {})
79
+ self.read(path)
80
+ end
81
+ else
82
+ def self.read_with_options(path, opts = {})
83
+ self.read(path, opts)
84
+ end
85
+ end
86
+ end
@@ -1,4 +1,5 @@
1
1
  require 'uri'
2
+ require 'json'
2
3
 
3
4
  module Jekyll
4
5
  module Filters
@@ -148,6 +149,15 @@ module Jekyll
148
149
  end
149
150
  end
150
151
 
152
+ # Convert the input into json string
153
+ #
154
+ # input - The Array or Hash to be converted
155
+ #
156
+ # Returns the converted json string
157
+ def jsonify(input)
158
+ input.to_json
159
+ end
160
+
151
161
  private
152
162
  def time(input)
153
163
  case input
@@ -19,10 +19,10 @@ module Jekyll
19
19
  ]
20
20
 
21
21
  # Attributes for Liquid templates
22
- ATTRIBUTES_FOR_LIQUID = EXCERPT_ATTRIBUTES_FOR_LIQUID.concat(%w[
22
+ ATTRIBUTES_FOR_LIQUID = EXCERPT_ATTRIBUTES_FOR_LIQUID + %w[
23
23
  content
24
24
  excerpt
25
- ])
25
+ ]
26
26
 
27
27
  # Post name validator. Post filenames must be like:
28
28
  # 2008-11-05-my-awesome-post.textile
@@ -3,7 +3,7 @@ module Jekyll
3
3
  attr_accessor :config, :layouts, :posts, :pages, :static_files,
4
4
  :categories, :exclude, :include, :source, :dest, :lsi, :pygments,
5
5
  :permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts,
6
- :show_drafts, :keep_files, :baseurl
6
+ :show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems
7
7
 
8
8
  attr_accessor :converters, :generators
9
9
 
@@ -13,7 +13,7 @@ module Jekyll
13
13
  def initialize(config)
14
14
  self.config = config.clone
15
15
 
16
- %w[safe lsi pygments baseurl exclude include future show_drafts limit_posts keep_files].each do |opt|
16
+ %w[safe lsi pygments baseurl exclude include future show_drafts limit_posts keep_files gems].each do |opt|
17
17
  self.send("#{opt}=", config[opt])
18
18
  end
19
19
 
@@ -22,6 +22,9 @@ module Jekyll
22
22
  self.plugins = plugins_path
23
23
  self.permalink_style = config['permalink'].to_sym
24
24
 
25
+ self.file_read_opts = {}
26
+ self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
27
+
25
28
  self.reset
26
29
  self.setup
27
30
  end
@@ -53,6 +56,7 @@ module Jekyll
53
56
  self.static_files = []
54
57
  self.categories = Hash.new { |hash, key| hash[key] = [] }
55
58
  self.tags = Hash.new { |hash, key| hash[key] = [] }
59
+ self.data = {}
56
60
 
57
61
  if self.limit_posts < 0
58
62
  raise ArgumentError, "limit_posts must be a non-negative number"
@@ -63,11 +67,7 @@ module Jekyll
63
67
  #
64
68
  # Returns nothing.
65
69
  def setup
66
- # Check that the destination dir isn't the source dir or a directory
67
- # parent to the source dir.
68
- if self.source =~ /^#{self.dest}/
69
- raise FatalException.new "Destination directory cannot be or contain the Source directory."
70
- end
70
+ ensure_not_in_dest
71
71
 
72
72
  # If safe mode is off, load in any Ruby files under the plugins
73
73
  # directory.
@@ -77,12 +77,26 @@ module Jekyll
77
77
  require f
78
78
  end
79
79
  end
80
+ self.gems.each do |gem|
81
+ require gem
82
+ end
80
83
  end
81
84
 
82
85
  self.converters = instantiate_subclasses(Jekyll::Converter)
83
86
  self.generators = instantiate_subclasses(Jekyll::Generator)
84
87
  end
85
88
 
89
+ # Check that the destination dir isn't the source dir or a directory
90
+ # parent to the source dir.
91
+ def ensure_not_in_dest
92
+ dest = Pathname.new(self.dest)
93
+ Pathname.new(self.source).ascend do |path|
94
+ if path == dest
95
+ raise FatalException.new "Destination directory cannot be or contain the Source directory."
96
+ end
97
+ end
98
+ end
99
+
86
100
  # Internal: Setup the plugin search path
87
101
  #
88
102
  # Returns an Array of plugin search paths
@@ -100,6 +114,7 @@ module Jekyll
100
114
  def read
101
115
  self.read_layouts
102
116
  self.read_directories
117
+ self.read_data(config['data_source'])
103
118
  end
104
119
 
105
120
  # Read all the files in <source>/<layouts> and create a new Layout object
@@ -110,7 +125,7 @@ module Jekyll
110
125
  base = File.join(self.source, self.config['layouts'])
111
126
  return unless File.exists?(base)
112
127
  entries = []
113
- Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
128
+ Dir.chdir(base) { entries = filter_entries(Dir['**/*.*']) }
114
129
 
115
130
  entries.each do |f|
116
131
  name = f.split(".")[0..-2].join(".")
@@ -187,6 +202,25 @@ module Jekyll
187
202
  end
188
203
  end
189
204
 
205
+ # Read and parse all yaml files under <source>/<dir>
206
+ #
207
+ # Returns nothing
208
+ def read_data(dir)
209
+ base = File.join(self.source, dir)
210
+ return unless File.directory?(base) && (!self.safe || !File.symlink?(base))
211
+
212
+ entries = Dir.chdir(base) { Dir['*.{yaml,yml}'] }
213
+ entries.delete_if { |e| File.directory?(File.join(base, e)) }
214
+
215
+ entries.each do |entry|
216
+ path = File.join(self.source, dir, entry)
217
+ next if File.symlink?(path) && self.safe
218
+
219
+ key = sanitize_filename(File.basename(entry, '.*'))
220
+ self.data[key] = YAML.safe_load_file(path)
221
+ end
222
+ end
223
+
190
224
  # Run each of the Generators.
191
225
  #
192
226
  # Returns nothing.
@@ -200,14 +234,11 @@ module Jekyll
200
234
  #
201
235
  # Returns nothing.
202
236
  def render
203
- payload = site_payload
204
- self.posts.each do |post|
205
- post.render(self.layouts, payload)
206
- end
237
+ relative_permalinks_deprecation_method
207
238
 
208
- self.pages.each do |page|
209
- relative_permalinks_deprecation_method if page.uses_relative_permalinks
210
- page.render(self.layouts, payload)
239
+ payload = site_payload
240
+ [self.posts, self.pages].flatten.each do |page_or_post|
241
+ page_or_post.render(self.layouts, payload)
211
242
  end
212
243
 
213
244
  self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a } }
@@ -252,6 +283,14 @@ module Jekyll
252
283
  hash
253
284
  end
254
285
 
286
+ # Prepare site data for site payload. The method maintains backward compatibility
287
+ # if the key 'data' is already used in _config.yml.
288
+ #
289
+ # Returns the Hash to be hooked to site.data.
290
+ def site_data
291
+ self.config['data'] || self.data
292
+ end
293
+
255
294
  # The Hash payload containing site-wide data.
256
295
  #
257
296
  # Returns the Hash: { "site" => data } where data is a Hash with keys:
@@ -273,7 +312,8 @@ module Jekyll
273
312
  "pages" => self.pages,
274
313
  "html_pages" => self.pages.reject { |page| !page.html? },
275
314
  "categories" => post_attr_hash('categories'),
276
- "tags" => post_attr_hash('tags')})}
315
+ "tags" => post_attr_hash('tags'),
316
+ "data" => site_data})}
277
317
  end
278
318
 
279
319
  # Filter out any files/directories that are hidden or backup files (start
@@ -349,15 +389,14 @@ module Jekyll
349
389
  end
350
390
 
351
391
  def relative_permalinks_deprecation_method
352
- if config['relative_permalinks'] && !@deprecated_relative_permalinks
392
+ if config['relative_permalinks'] && has_relative_page?
353
393
  $stderr.puts # Places newline after "Generating..."
354
- Jekyll.logger.warn "Deprecation:", "Starting in 1.1, permalinks for pages" +
394
+ Jekyll.logger.warn "Deprecation:", "Starting in 2.0, permalinks for pages" +
355
395
  " in subfolders must be relative to the" +
356
396
  " site source directory, not the parent" +
357
397
  " directory. Check http://jekyllrb.com/docs/upgrading/"+
358
398
  " for more info."
359
399
  $stderr.print Jekyll.logger.formatted_topic("") + "..." # for "done."
360
- @deprecated_relative_permalinks = true
361
400
  end
362
401
  end
363
402
 
@@ -371,6 +410,10 @@ module Jekyll
371
410
 
372
411
  private
373
412
 
413
+ def has_relative_page?
414
+ self.pages.any? { |page| page.uses_relative_permalinks }
415
+ end
416
+
374
417
  def has_yaml_header?(file)
375
418
  "---" == File.open(file) { |fd| fd.read(3) }
376
419
  end
@@ -383,5 +426,11 @@ module Jekyll
383
426
  def site_cleaner
384
427
  @site_cleaner ||= Cleaner.new(self)
385
428
  end
429
+
430
+ def sanitize_filename(name)
431
+ name = name.gsub(/[^\w\s_-]+/, '')
432
+ name = name.gsub(/(^|\b\s)\s+($|\s?\b)/, '\\1\\2')
433
+ name = name.gsub(/\s+/, '_')
434
+ end
386
435
  end
387
436
  end