jekyll 1.0.4 → 1.1.1

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 (90) hide show
  1. checksums.yaml +14 -6
  2. data/{CONTRIBUTING.md → CONTRIBUTING.markdown} +19 -1
  3. data/History.markdown +172 -85
  4. data/README.markdown +45 -0
  5. data/Rakefile +43 -18
  6. data/bin/jekyll +31 -1
  7. data/features/create_sites.feature +18 -0
  8. data/features/include_tag.feature +35 -0
  9. data/features/pagination.feature +28 -0
  10. data/features/post_excerpts.feature +50 -0
  11. data/features/step_definitions/jekyll_steps.rb +27 -11
  12. data/features/support/env.rb +9 -0
  13. data/jekyll.gemspec +32 -7
  14. data/lib/jekyll.rb +2 -1
  15. data/lib/jekyll/commands/new.rb +15 -3
  16. data/lib/jekyll/configuration.rb +23 -22
  17. data/lib/jekyll/converters/markdown/kramdown_parser.rb +4 -15
  18. data/lib/jekyll/convertible.rb +4 -0
  19. data/lib/jekyll/core_ext.rb +11 -0
  20. data/lib/jekyll/excerpt.rb +113 -0
  21. data/lib/jekyll/generators/pagination.rb +93 -23
  22. data/lib/jekyll/page.rb +1 -3
  23. data/lib/jekyll/post.rb +15 -55
  24. data/lib/jekyll/related_posts.rb +2 -1
  25. data/lib/jekyll/site.rb +33 -41
  26. data/lib/jekyll/stevenson.rb +25 -4
  27. data/lib/jekyll/tags/include.rb +46 -2
  28. data/lib/site_template/_config.yml +1 -0
  29. data/lib/site_template/css/main.css +0 -5
  30. data/site/_config.yml +1 -0
  31. data/site/_includes/docs_contents.html +12 -3
  32. data/site/_includes/docs_contents_mobile.html +7 -1
  33. data/site/_includes/news_contents.html +23 -0
  34. data/site/_includes/news_contents_mobile.html +11 -0
  35. data/site/_includes/news_item.html +24 -0
  36. data/site/_includes/primary-nav-items.html +4 -1
  37. data/site/_includes/top.html +2 -0
  38. data/site/_layouts/news.html +19 -0
  39. data/site/_layouts/news_item.html +27 -0
  40. data/site/_posts/2013-05-06-jekyll-1-0-0-released.markdown +23 -0
  41. data/site/_posts/2013-05-08-jekyll-1-0-1-released.markdown +27 -0
  42. data/site/_posts/2013-05-12-jekyll-1-0-2-released.markdown +28 -0
  43. data/site/_posts/2013-06-07-jekyll-1-0-3-released.markdown +25 -0
  44. data/site/_posts/2013-07-14-jekyll-1-1-0-released.markdown +27 -0
  45. data/site/_posts/2013-07-24-jekyll-1-1-1-released.markdown +31 -0
  46. data/site/css/style.css +125 -17
  47. data/site/docs/configuration.md +10 -1
  48. data/site/docs/contributing.md +21 -3
  49. data/site/docs/deployment-methods.md +2 -2
  50. data/site/docs/drafts.md +20 -0
  51. data/site/docs/extras.md +24 -3
  52. data/site/docs/github-pages.md +25 -0
  53. data/site/docs/history.md +150 -85
  54. data/site/docs/index.md +1 -17
  55. data/site/docs/installation.md +3 -2
  56. data/site/docs/migrations.md +2 -2
  57. data/site/docs/pagination.md +12 -0
  58. data/site/docs/plugins.md +97 -76
  59. data/site/docs/quickstart.md +32 -0
  60. data/site/docs/resources.md +0 -1
  61. data/site/docs/structure.md +15 -0
  62. data/site/docs/templates.md +26 -4
  63. data/site/docs/troubleshooting.md +22 -7
  64. data/site/docs/upgrading.md +6 -1
  65. data/site/docs/variables.md +12 -2
  66. data/site/feed.xml +36 -0
  67. data/site/freenode.txt +1 -0
  68. data/site/img/article-footer.png +0 -0
  69. data/site/img/footer-arrow.png +0 -0
  70. data/site/img/footer-logo.png +0 -0
  71. data/site/img/logo-2x.png +0 -0
  72. data/site/img/octojekyll.png +0 -0
  73. data/site/img/tube.png +0 -0
  74. data/site/img/tube1x.png +0 -0
  75. data/site/index.html +1 -1
  76. data/site/news/index.md +10 -0
  77. data/site/news/releases/index.md +10 -0
  78. data/test/source/+/foo.md +7 -0
  79. data/test/source/_includes/params.html +7 -0
  80. data/test/source/_posts/2013-07-22-post-excerpt-with-layout.markdown +23 -0
  81. data/test/test_configuration.rb +9 -0
  82. data/test/test_excerpt.rb +62 -0
  83. data/test/test_generated_site.rb +1 -1
  84. data/test/test_page.rb +9 -0
  85. data/test/test_pager.rb +31 -37
  86. data/test/test_post.rb +2 -1
  87. data/test/test_related_posts.rb +6 -1
  88. data/test/test_tags.rb +90 -0
  89. metadata +62 -23
  90. data/README.textile +0 -45
@@ -36,6 +36,7 @@ require 'jekyll/convertible'
36
36
  require 'jekyll/layout'
37
37
  require 'jekyll/page'
38
38
  require 'jekyll/post'
39
+ require 'jekyll/excerpt'
39
40
  require 'jekyll/draft'
40
41
  require 'jekyll/filters'
41
42
  require 'jekyll/static_file'
@@ -57,7 +58,7 @@ require_all 'jekyll/tags'
57
58
  SafeYAML::OPTIONS[:suppress_warnings] = true
58
59
 
59
60
  module Jekyll
60
- VERSION = '1.0.4'
61
+ VERSION = '1.1.1'
61
62
 
62
63
  # Public: Generate a Jekyll configuration Hash by merging the default
63
64
  # options with anything in _config.yml, and adding the given options on top.
@@ -13,14 +13,26 @@ module Jekyll
13
13
  exit(1)
14
14
  end
15
15
 
16
- create_sample_files new_blog_path
16
+ if options[:blank]
17
+ create_blank_site new_blog_path
18
+ else
19
+ create_sample_files new_blog_path
17
20
 
18
- File.open(File.expand_path(self.initialized_post_name, new_blog_path), "w") do |f|
19
- f.write(self.scaffold_post_content(site_template))
21
+ File.open(File.expand_path(self.initialized_post_name, new_blog_path), "w") do |f|
22
+ f.write(self.scaffold_post_content(site_template))
23
+ end
20
24
  end
25
+
21
26
  puts "New jekyll site installed in #{new_blog_path}."
22
27
  end
23
28
 
29
+ def self.create_blank_site(path)
30
+ Dir.chdir(path) do
31
+ FileUtils.mkdir(%w(_layouts _posts _drafts))
32
+ FileUtils.touch("index.html")
33
+ end
34
+ end
35
+
24
36
  def self.scaffold_post_content(template_site)
25
37
  ERB.new(File.read(File.expand_path(scaffold_path, site_template))).result
26
38
  end
@@ -29,7 +29,7 @@ module Jekyll
29
29
  'baseurl' => '/',
30
30
  'include' => ['.htaccess'],
31
31
  'exclude' => [],
32
- 'paginate_path' => 'page:num',
32
+ 'paginate_path' => '/page:num',
33
33
 
34
34
  'markdown_ext' => 'markdown,mkd,mkdn,md',
35
35
  'textile_ext' => 'textile',
@@ -102,7 +102,10 @@ module Jekyll
102
102
  def config_files(override)
103
103
  # Get configuration from <source>/_config.yml or <source>/<config_file>
104
104
  config_files = override.delete('config')
105
- config_files = File.join(source(override), "_config.yml") if config_files.to_s.empty?
105
+ if config_files.to_s.empty?
106
+ config_files = File.join(source(override), "_config.yml")
107
+ @default_config_file = true
108
+ end
106
109
  config_files = [config_files] unless config_files.is_a? Array
107
110
  config_files
108
111
  end
@@ -114,9 +117,17 @@ module Jekyll
114
117
  # Returns this configuration, overridden by the values in the file
115
118
  def read_config_file(file)
116
119
  next_config = YAML.safe_load_file(file)
117
- raise "Configuration file: (INVALID) #{file}".yellow if !next_config.is_a?(Hash)
120
+ raise ArgumentError.new("Configuration file: (INVALID) #{file}".yellow) if !next_config.is_a?(Hash)
118
121
  Jekyll.logger.info "Configuration file:", file
119
122
  next_config
123
+ rescue SystemCallError
124
+ if @default_config_file
125
+ Jekyll.logger.warn "Configuration file:", "none"
126
+ {}
127
+ else
128
+ Jekyll.logger.error "Fatal:", "The configuration file '#{file}' could not be found."
129
+ raise LoadError
130
+ end
120
131
  end
121
132
 
122
133
  # Public: Read in a list of configuration files and merge with this hash
@@ -133,10 +144,7 @@ module Jekyll
133
144
  new_config = read_config_file(config_file)
134
145
  configuration = configuration.deep_merge(new_config)
135
146
  end
136
- rescue SystemCallError
137
- # Errno:ENOENT = file not found
138
- Jekyll.logger.warn "Configuration file:", "none"
139
- rescue => err
147
+ rescue ArgumentError => err
140
148
  Jekyll.logger.warn "WARNING:", "Error reading configuration. " +
141
149
  "Using defaults (and options)."
142
150
  $stderr.puts "#{err}"
@@ -185,22 +193,15 @@ module Jekyll
185
193
  config.delete('server_port')
186
194
  end
187
195
 
188
- if config.has_key?('exclude') && config['exclude'].is_a?(String)
189
- Jekyll.logger.warn "Deprecation:", "The 'exclude' configuration option" +
190
- " must now be specified as an array, but you specified" +
191
- " a string. For now, we've treated the string you provided" +
192
- " as a list of comma-separated values."
193
- config['exclude'] = csv_to_array(config['exclude'])
194
- end
195
-
196
- if config.has_key?('include') && config['include'].is_a?(String)
197
- Jekyll.logger.warn "Deprecation:", "The 'include' configuration option" +
198
- " must now be specified as an array, but you specified" +
199
- " a string. For now, we've treated the string you provided" +
200
- " as a list of comma-separated values."
201
- config['include'] = csv_to_array(config['include'])
196
+ %w[include exclude].each do |option|
197
+ if config.fetch(option, []).is_a?(String)
198
+ Jekyll.logger.warn "Deprecation:", "The '#{option}' configuration option" +
199
+ " must now be specified as an array, but you specified" +
200
+ " a string. For now, we've treated the string you provided" +
201
+ " as a list of comma-separated values."
202
+ config[option] = csv_to_array(config[option])
203
+ end
202
204
  end
203
-
204
205
  config
205
206
  end
206
207
 
@@ -13,8 +13,8 @@ module Jekyll
13
13
 
14
14
  def convert(content)
15
15
  # Check for use of coderay
16
- kramdown_configs = if @config['kramdown']['use_coderay']
17
- base_kramdown_configs.merge({
16
+ if @config['kramdown']['use_coderay']
17
+ @config['kramdown'].merge!({
18
18
  :coderay_wrap => @config['kramdown']['coderay']['coderay_wrap'],
19
19
  :coderay_line_numbers => @config['kramdown']['coderay']['coderay_line_numbers'],
20
20
  :coderay_line_number_start => @config['kramdown']['coderay']['coderay_line_number_start'],
@@ -22,22 +22,11 @@ module Jekyll
22
22
  :coderay_bold_every => @config['kramdown']['coderay']['coderay_bold_every'],
23
23
  :coderay_css => @config['kramdown']['coderay']['coderay_css']
24
24
  })
25
- else
26
- # not using coderay
27
- base_kramdown_configs
28
25
  end
29
- Kramdown::Document.new(content, kramdown_configs).to_html
30
- end
31
26
 
32
- def base_kramdown_configs
33
- {
34
- :auto_ids => @config['kramdown']['auto_ids'],
35
- :footnote_nr => @config['kramdown']['footnote_nr'],
36
- :entity_output => @config['kramdown']['entity_output'],
37
- :toc_levels => @config['kramdown']['toc_levels'],
38
- :smart_quotes => @config['kramdown']['smart_quotes']
39
- }
27
+ Kramdown::Document.new(content, @config["kramdown"].symbolize_keys).to_html
40
28
  end
29
+
41
30
  end
42
31
  end
43
32
  end
@@ -48,6 +48,10 @@ module Jekyll
48
48
  # Returns nothing.
49
49
  def transform
50
50
  self.content = converter.convert(self.content)
51
+ rescue => e
52
+ Jekyll.logger.error "Conversion error:", "There was an error converting" +
53
+ " '#{self.path}'."
54
+ raise e
51
55
  end
52
56
 
53
57
  # Determine the extension depending on content_type.
@@ -41,6 +41,17 @@ class Hash
41
41
  end
42
42
  array || []
43
43
  end
44
+
45
+ def symbolize_keys!
46
+ keys.each do |key|
47
+ self[(key.to_sym rescue key) || key] = delete(key)
48
+ end
49
+ self
50
+ end
51
+
52
+ def symbolize_keys
53
+ dup.symbolize_keys!
54
+ end
44
55
  end
45
56
 
46
57
  # Thanks, ActiveSupport!
@@ -0,0 +1,113 @@
1
+ module Jekyll
2
+ class Excerpt
3
+ include Convertible
4
+
5
+ attr_accessor :post
6
+ attr_accessor :content, :output, :ext
7
+
8
+ # Initialize this Post instance.
9
+ #
10
+ # site - The Site.
11
+ # base - The String path to the dir containing the post file.
12
+ # name - The String filename of the post file.
13
+ #
14
+ # Returns the new Post.
15
+ def initialize(post)
16
+ self.post = post
17
+ self.content = extract_excerpt(post.content)
18
+ end
19
+
20
+ %w[site name ext].each do |meth|
21
+ define_method(meth) do
22
+ post.send(meth)
23
+ end
24
+ end
25
+
26
+ def to_liquid
27
+ post.to_liquid(Post::EXCERPT_ATTRIBUTES_FOR_LIQUID)
28
+ end
29
+
30
+ # Fetch YAML front-matter data from related post, without layout key
31
+ #
32
+ # Returns Hash of post data
33
+ def data
34
+ @data ||= post.data.dup
35
+ @data.delete("layout")
36
+ @data
37
+ end
38
+
39
+ # 'Path' of the excerpt.
40
+ #
41
+ # Returns the path for the post this excerpt belongs to with #excerpt appended
42
+ def path
43
+ File.join(post.path, "#excerpt")
44
+ end
45
+
46
+ # Check if excerpt includes a string
47
+ #
48
+ # Returns true if the string passed in
49
+ def include?(something)
50
+ (self.output && self.output.include?(something)) || self.content.include?(something)
51
+ end
52
+
53
+ # The UID for this post (useful in feeds).
54
+ # e.g. /2008/11/05/my-awesome-post
55
+ #
56
+ # Returns the String UID.
57
+ def id
58
+ File.join(post.dir, post.slug, "#excerpt")
59
+ end
60
+
61
+ def to_s
62
+ self.output || self.content
63
+ end
64
+
65
+ # Returns the shorthand String identifier of this Post.
66
+ def inspect
67
+ "<Excerpt: #{self.id}>"
68
+ end
69
+
70
+ protected
71
+
72
+ # Internal: Extract excerpt from the content
73
+ #
74
+ # By default excerpt is your first paragraph of a post: everything before
75
+ # the first two new lines:
76
+ #
77
+ # ---
78
+ # title: Example
79
+ # ---
80
+ #
81
+ # First paragraph with [link][1].
82
+ #
83
+ # Second paragraph.
84
+ #
85
+ # [1]: http://example.com/
86
+ #
87
+ # This is fairly good option for Markdown and Textile files. But might cause
88
+ # problems for HTML posts (which is quite unusual for Jekyll). If default
89
+ # excerpt delimiter is not good for you, you might want to set your own via
90
+ # configuration option `excerpt_separator`. For example, following is a good
91
+ # alternative for HTML posts:
92
+ #
93
+ # # file: _config.yml
94
+ # excerpt_separator: "<!-- more -->"
95
+ #
96
+ # Notice that all markdown-style link references will be appended to the
97
+ # excerpt. So the example post above will have this excerpt source:
98
+ #
99
+ # First paragraph with [link][1].
100
+ #
101
+ # [1]: http://example.com/
102
+ #
103
+ # Excerpts are rendered same time as content is rendered.
104
+ #
105
+ # Returns excerpt String
106
+ def extract_excerpt(post_content)
107
+ separator = site.config['excerpt_separator']
108
+ head, _, tail = post_content.partition(separator)
109
+
110
+ "" << head << "\n\n" << tail.scan(/^\[[^\]]+\]:.+$/).join("\n")
111
+ end
112
+ end
113
+ end
@@ -10,8 +10,13 @@ module Jekyll
10
10
  #
11
11
  # Returns nothing.
12
12
  def generate(site)
13
- site.pages.dup.each do |page|
14
- paginate(site, page) if Pager.pagination_enabled?(site.config, page)
13
+ if Pager.pagination_enabled?(site)
14
+ if template = template_page(site)
15
+ paginate(site, template)
16
+ else
17
+ Jekyll.logger.warn "Pagination:", "Pagination is enabled, but I couldn't find" +
18
+ "an index.html page to use as the pagination template. Skipping pagination."
19
+ end
15
20
  end
16
21
  end
17
22
 
@@ -33,11 +38,11 @@ module Jekyll
33
38
  all_posts = site.site_payload['site']['posts']
34
39
  pages = Pager.calculate_pages(all_posts, site.config['paginate'].to_i)
35
40
  (1..pages).each do |num_page|
36
- pager = Pager.new(site.config, num_page, all_posts, pages)
41
+ pager = Pager.new(site, num_page, all_posts, pages)
37
42
  if num_page > 1
38
43
  newpage = Page.new(site, site.source, page.dir, page.name)
39
44
  newpage.pager = pager
40
- newpage.dir = File.join(page.dir, Pager.paginate_path(site.config, num_page))
45
+ newpage.dir = Pager.paginate_path(site, num_page)
41
46
  site.pages << newpage
42
47
  else
43
48
  page.pager = pager
@@ -45,6 +50,32 @@ module Jekyll
45
50
  end
46
51
  end
47
52
 
53
+ # Static: Fetch the URL of the template page. Used to determine the
54
+ # path to the first pager in the series.
55
+ #
56
+ # site - the Jekyll::Site object
57
+ #
58
+ # Returns the url of the template page
59
+ def self.first_page_url(site)
60
+ if page = Pagination.new.template_page(site)
61
+ page.url
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ # Public: Find the Jekyll::Page which will act as the pager template
68
+ #
69
+ # site - the Jekyll::Site object
70
+ #
71
+ # Returns the Jekyll::Page which will act as the pager template
72
+ def template_page(site)
73
+ site.pages.dup.select do |page|
74
+ Pager.pagination_candidate?(site.config, page)
75
+ end.sort do |one, two|
76
+ two.path.size <=> one.path.size
77
+ end.first
78
+ end
48
79
  end
49
80
  end
50
81
 
@@ -62,39 +93,78 @@ module Jekyll
62
93
  (all_posts.size.to_f / per_page.to_i).ceil
63
94
  end
64
95
 
65
- # Determine if pagination is enabled for a given file.
96
+ # Determine if pagination is enabled the site.
66
97
  #
67
- # config - The configuration Hash.
68
- # page - The Jekyll::Page with which to paginate
98
+ # site - the Jekyll::Site object
69
99
  #
70
100
  # Returns true if pagination is enabled, false otherwise.
71
- def self.pagination_enabled?(config, page)
72
- !config['paginate'].nil? &&
73
- page.name == 'index.html' &&
74
- subdirectories_identical(config['paginate_path'], page.dir)
101
+ def self.pagination_enabled?(site)
102
+ !site.config['paginate'].nil? &&
103
+ site.pages.size > 0
104
+ end
105
+
106
+ # Static: Determine if a page is a possible candidate to be a template page.
107
+ # Page's name must be `index.html` and exist in any of the directories
108
+ # between the site source and `paginate_path`.
109
+ #
110
+ # config - the site configuration hash
111
+ # page - the Jekyll::Page about which we're inquiring
112
+ #
113
+ # Returns true if the
114
+ def self.pagination_candidate?(config, page)
115
+ page_dir = File.dirname(File.expand_path(remove_leading_slash(page.path), config['source']))
116
+ paginate_path = remove_leading_slash(config['paginate_path'])
117
+ paginate_path = File.expand_path(paginate_path, config['source'])
118
+ page.name == 'index.html' &&
119
+ in_hierarchy(config['source'], page_dir, File.dirname(paginate_path))
75
120
  end
76
121
 
77
122
  # Determine if the subdirectories of the two paths are the same relative to source
78
123
  #
79
- # paginate_path - the paginate_path configuration setting
124
+ # source - the site source
80
125
  # page_dir - the directory of the Jekyll::Page
126
+ # paginate_path - the absolute paginate path (from root of FS)
81
127
  #
82
128
  # Returns whether the subdirectories are the same relative to source
83
- def self.subdirectories_identical(paginate_path, page_dir)
84
- File.dirname(paginate_path).gsub(/\A\./, '') == page_dir.gsub(/\/\z/, '')
129
+ def self.in_hierarchy(source, page_dir, paginate_path)
130
+ return false if paginate_path == File.dirname(paginate_path)
131
+ return false if paginate_path == Pathname.new(source).parent
132
+ page_dir == paginate_path ||
133
+ in_hierarchy(source, page_dir, File.dirname(paginate_path))
85
134
  end
86
135
 
87
136
  # Static: Return the pagination path of the page
88
137
  #
89
- # site_config - the site config
138
+ # site - the Jekyll::Site object
90
139
  # num_page - the pagination page number
91
140
  #
92
141
  # Returns the pagination path as a string
93
- def self.paginate_path(site_config, num_page)
94
- return nil if num_page.nil? || num_page <= 1
95
- format = site_config['paginate_path']
142
+ def self.paginate_path(site, num_page)
143
+ return nil if num_page.nil?
144
+ return Generators::Pagination.first_page_url(site) if num_page <= 1
145
+ format = site.config['paginate_path']
96
146
  format = format.sub(':num', num_page.to_s)
97
- File.basename(format)
147
+ ensure_leading_slash(format)
148
+ end
149
+
150
+ # Static: Return a String version of the input which has a leading slash.
151
+ # If the input already has a forward slash in position zero, it will be
152
+ # returned unchanged.
153
+ #
154
+ # path - a String path
155
+ #
156
+ # Returns the path with a leading slash
157
+ def self.ensure_leading_slash(path)
158
+ path[0..0] == "/" ? path : "/#{path}"
159
+ end
160
+
161
+ # Static: Return a String version of the input without a leading slash.
162
+ #
163
+ # path - a String path
164
+ #
165
+ # Returns the input without the leading slash
166
+ def self.remove_leading_slash(path)
167
+ ensure_leading_slash(path)[1..-1]
98
168
  end
99
169
 
100
170
  # Initialize a new Pager.
@@ -104,9 +174,9 @@ module Jekyll
104
174
  # all_posts - The Array of all the site's Posts.
105
175
  # num_pages - The Integer number of pages or nil if you'd like the number
106
176
  # of pages calculated.
107
- def initialize(config, page, all_posts, num_pages = nil)
177
+ def initialize(site, page, all_posts, num_pages = nil)
108
178
  @page = page
109
- @per_page = config['paginate'].to_i
179
+ @per_page = site.config['paginate'].to_i
110
180
  @total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
111
181
 
112
182
  if @page > @total_pages
@@ -119,9 +189,9 @@ module Jekyll
119
189
  @total_posts = all_posts.size
120
190
  @posts = all_posts[init..offset]
121
191
  @previous_page = @page != 1 ? @page - 1 : nil
122
- @previous_page_path = Pager.paginate_path(config, @previous_page)
192
+ @previous_page_path = Pager.paginate_path(site, @previous_page)
123
193
  @next_page = @page != @total_pages ? @page + 1 : nil
124
- @next_page_path = Pager.paginate_path(config, @next_page)
194
+ @next_page_path = Pager.paginate_path(site, @next_page)
125
195
  end
126
196
 
127
197
  # Convert this Pager's data to a Hash suitable for use by Liquid.