jekyll 0.4.1 → 0.5.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 (51) hide show
  1. data/History.txt +31 -1
  2. data/README.textile +23 -457
  3. data/Rakefile +91 -0
  4. data/VERSION.yml +2 -2
  5. data/bin/jekyll +61 -57
  6. data/lib/jekyll.rb +48 -32
  7. data/lib/jekyll/albino.rb +13 -7
  8. data/lib/jekyll/converters/mephisto.rb +8 -8
  9. data/lib/jekyll/converters/mt.rb +8 -8
  10. data/lib/jekyll/converters/textpattern.rb +4 -4
  11. data/lib/jekyll/converters/typo.rb +8 -8
  12. data/lib/jekyll/converters/wordpress.rb +1 -2
  13. data/lib/jekyll/convertible.rb +33 -22
  14. data/lib/jekyll/core_ext.rb +5 -5
  15. data/lib/jekyll/filters.rb +15 -7
  16. data/lib/jekyll/layout.rb +9 -6
  17. data/lib/jekyll/page.rb +13 -10
  18. data/lib/jekyll/post.rb +108 -39
  19. data/lib/jekyll/site.rb +121 -51
  20. data/lib/jekyll/tags/highlight.rb +12 -9
  21. data/lib/jekyll/tags/include.rb +5 -5
  22. data/test/helper.rb +20 -6
  23. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  24. data/test/source/_posts/2008-02-02-published.textile +8 -0
  25. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  26. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  27. data/test/source/_posts/2009-01-27-category.textile +7 -0
  28. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  29. data/test/test_filters.rb +39 -27
  30. data/test/test_generated_site.rb +32 -16
  31. data/test/test_post.rb +258 -102
  32. data/test/test_site.rb +60 -28
  33. data/test/test_tags.rb +103 -18
  34. metadata +25 -70
  35. data/test/dest/2008/10/18/foo-bar.html +0 -28
  36. data/test/dest/2008/11/21/complex.html +0 -29
  37. data/test/dest/2008/12/13/include.html +0 -30
  38. data/test/dest/_posts/2008-10-18-foo-bar.html +0 -28
  39. data/test/dest/_posts/2008-11-21-complex.html +0 -29
  40. data/test/dest/_posts/2008-12-03-permalinked-post.html +0 -2
  41. data/test/dest/_posts/2008-12-13-include.html +0 -30
  42. data/test/dest/category/2008/09/23/categories.html +0 -27
  43. data/test/dest/category/_posts/2008-9-23-categories.html +0 -27
  44. data/test/dest/css/screen.css +0 -76
  45. data/test/dest/foo/2008/12/12/topical-post.html +0 -28
  46. data/test/dest/foo/_posts/bar/2008-12-12-topical-post.html +0 -28
  47. data/test/dest/index.html +0 -60
  48. data/test/dest/my_category/permalinked-post +0 -2
  49. data/test/dest/z_category/2008/09/23/categories.html +0 -27
  50. data/test/dest/z_category/_posts/2008-9-23-categories.html +0 -27
  51. data/test/test_jekyll.rb +0 -0
@@ -1,84 +1,147 @@
1
1
  module Jekyll
2
-
2
+
3
3
  class Site
4
- attr_accessor :source, :dest
5
- attr_accessor :layouts, :posts, :categories
6
-
4
+ attr_accessor :config, :layouts, :posts, :categories, :exclude
5
+ attr_accessor :source, :dest, :lsi, :pygments, :permalink_style
6
+
7
7
  # Initialize the site
8
- # +source+ is String path to the source directory containing
9
- # the proto-site
10
- # +dest+ is the String path to the directory where the generated
11
- # site should be written
8
+ # +config+ is a Hash containing site configurations details
12
9
  #
13
10
  # Returns <Site>
14
- def initialize(source, dest)
15
- self.source = source
16
- self.dest = dest
17
- self.layouts = {}
18
- self.posts = []
19
- self.categories = Hash.new { |hash, key| hash[key] = Array.new }
11
+ def initialize(config)
12
+ self.config = config.clone
13
+
14
+ self.source = config['source']
15
+ self.dest = config['destination']
16
+ self.lsi = config['lsi']
17
+ self.pygments = config['pygments']
18
+ self.permalink_style = config['permalink'].to_sym
19
+ self.exclude = config['exclude'] || []
20
+
21
+ self.reset
22
+ self.setup
23
+ end
24
+
25
+ def reset
26
+ self.layouts = {}
27
+ self.posts = []
28
+ self.categories = Hash.new { |hash, key| hash[key] = Array.new }
29
+ end
30
+
31
+ def setup
32
+ # Check to see if LSI is enabled.
33
+ require 'classifier' if self.lsi
34
+
35
+ # Set the Markdown interpreter (and Maruku self.config, if necessary)
36
+ case self.config['markdown']
37
+ when 'rdiscount'
38
+ begin
39
+ require 'rdiscount'
40
+
41
+ def markdown(content)
42
+ RDiscount.new(content).to_html
43
+ end
44
+
45
+ rescue LoadError
46
+ puts 'You must have the rdiscount gem installed first'
47
+ end
48
+ when 'maruku'
49
+ begin
50
+ require 'maruku'
51
+
52
+ def markdown(content)
53
+ Maruku.new(content).to_html
54
+ end
55
+
56
+ if self.config['maruku']['use_divs']
57
+ require 'maruku/ext/div'
58
+ puts 'Maruku: Using extended syntax for div elements.'
59
+ end
60
+
61
+ if self.config['maruku']['use_tex']
62
+ require 'maruku/ext/math'
63
+ puts "Maruku: Using LaTeX extension. Images in `#{self.config['maruku']['png_dir']}`."
64
+
65
+ # Switch off MathML output
66
+ MaRuKu::Globals[:html_math_output_mathml] = false
67
+ MaRuKu::Globals[:html_math_engine] = 'none'
68
+
69
+ # Turn on math to PNG support with blahtex
70
+ # Resulting PNGs stored in `images/latex`
71
+ MaRuKu::Globals[:html_math_output_png] = true
72
+ MaRuKu::Globals[:html_png_engine] = self.config['maruku']['png_engine']
73
+ MaRuKu::Globals[:html_png_dir] = self.config['maruku']['png_dir']
74
+ MaRuKu::Globals[:html_png_url] = self.config['maruku']['png_url']
75
+ end
76
+ rescue LoadError
77
+ puts "The maruku gem is required for markdown support!"
78
+ end
79
+ end
80
+ end
81
+
82
+ def textile(content)
83
+ RedCloth.new(content).to_html
20
84
  end
21
-
85
+
22
86
  # Do the actual work of processing the site and generating the
23
87
  # real deal.
24
88
  #
25
89
  # Returns nothing
26
90
  def process
91
+ self.reset
27
92
  self.read_layouts
28
93
  self.transform_pages
29
94
  self.write_posts
30
95
  end
31
-
32
- # Read all the files in <source>/_layouts except backup files
33
- # (end with "~") into memory for later use.
96
+
97
+ # Read all the files in <source>/_layouts into memory for later use.
34
98
  #
35
99
  # Returns nothing
36
100
  def read_layouts
37
101
  base = File.join(self.source, "_layouts")
38
- entries = Dir.entries(base)
39
- entries = entries.reject { |e| e[-1..-1] == '~' }
40
- entries = entries.reject { |e| File.directory?(File.join(base, e)) }
41
-
102
+ entries = []
103
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
104
+
42
105
  entries.each do |f|
43
106
  name = f.split(".")[0..-2].join(".")
44
- self.layouts[name] = Layout.new(base, f)
107
+ self.layouts[name] = Layout.new(self, base, f)
45
108
  end
46
109
  rescue Errno::ENOENT => e
47
110
  # ignore missing layout dir
48
111
  end
49
-
50
- # Read all the files in <base>/_posts except backup files (end with "~")
51
- # and create a new Post object with each one.
112
+
113
+ # Read all the files in <base>/_posts and create a new Post object with each one.
52
114
  #
53
115
  # Returns nothing
54
116
  def read_posts(dir)
55
117
  base = File.join(self.source, dir, '_posts')
56
-
57
118
  entries = []
58
- Dir.chdir(base) { entries = Dir['**/*'] }
59
- entries = entries.reject { |e| e[-1..-1] == '~' }
60
- entries = entries.reject { |e| File.directory?(File.join(base, e)) }
119
+ Dir.chdir(base) { entries = filter_entries(Dir['**/*']) }
61
120
 
62
121
  # first pass processes, but does not yet render post content
63
122
  entries.each do |f|
64
123
  if Post.valid?(f)
65
- post = Post.new(self.source, dir, f)
66
- self.posts << post
67
- post.categories.each { |c| self.categories[c] << post }
124
+ post = Post.new(self, self.source, dir, f)
125
+
126
+ if post.published
127
+ self.posts << post
128
+ post.categories.each { |c| self.categories[c] << post }
129
+ end
68
130
  end
69
131
  end
70
-
132
+
133
+ self.posts.sort!
134
+
71
135
  # second pass renders each post now that full site payload is available
72
136
  self.posts.each do |post|
73
137
  post.render(self.layouts, site_payload)
74
138
  end
75
-
76
- self.posts.sort!
139
+
77
140
  self.categories.values.map { |cats| cats.sort! { |a, b| b <=> a} }
78
141
  rescue Errno::ENOENT => e
79
142
  # ignore missing layout dir
80
143
  end
81
-
144
+
82
145
  # Write each post to <dest>/<year>/<month>/<day>/<slug>
83
146
  #
84
147
  # Returns nothing
@@ -87,10 +150,10 @@ module Jekyll
87
150
  post.write(self.dest)
88
151
  end
89
152
  end
90
-
153
+
91
154
  # Copy all regular files from <source> to <dest>/ ignoring
92
155
  # any files/directories that are hidden or backup files (start
93
- # with "." or end with "~") or contain site content (start with "_")
156
+ # with "." or "#" or end with "~") or contain site content (start with "_")
94
157
  # unless they are "_posts" directories or web server files such as
95
158
  # '.htaccess'
96
159
  # The +dir+ String is a relative path used to call this method
@@ -99,18 +162,14 @@ module Jekyll
99
162
  # Returns nothing
100
163
  def transform_pages(dir = '')
101
164
  base = File.join(self.source, dir)
102
- entries = Dir.entries(base)
103
- entries = entries.reject { |e| e[-1..-1] == '~' }
104
- entries = entries.reject do |e|
105
- (e != '_posts') and ['.', '_'].include?(e[0..0]) unless ['.htaccess'].include?(e)
106
- end
165
+ entries = filter_entries(Dir.entries(base))
107
166
  directories = entries.select { |e| File.directory?(File.join(base, e)) }
108
167
  files = entries.reject { |e| File.directory?(File.join(base, e)) }
109
168
 
110
- # we need to make sure to process _posts *first* otherwise they
169
+ # we need to make sure to process _posts *first* otherwise they
111
170
  # might not be available yet to other templates as {{ site.posts }}
112
- if entries.include?('_posts')
113
- entries.delete('_posts')
171
+ if directories.include?('_posts')
172
+ directories.delete('_posts')
114
173
  read_posts(dir)
115
174
  end
116
175
  [directories, files].each do |entries|
@@ -120,10 +179,10 @@ module Jekyll
120
179
  transform_pages(File.join(dir, f))
121
180
  else
122
181
  first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
123
-
182
+
124
183
  if first3 == "---"
125
184
  # file appears to have a YAML header so process it as a page
126
- page = Page.new(self.source, dir, f)
185
+ page = Page.new(self, self.source, dir, f)
127
186
  page.render(self.layouts, site_payload)
128
187
  page.write(self.dest)
129
188
  else
@@ -156,12 +215,23 @@ module Jekyll
156
215
  # "topics" => [<Post>] }}
157
216
  def site_payload
158
217
  {"site" => {
159
- "time" => Time.now,
218
+ "time" => Time.now,
160
219
  "posts" => self.posts.sort { |a,b| b <=> a },
161
220
  "categories" => post_attr_hash('categories'),
162
221
  "topics" => post_attr_hash('topics')
163
222
  }}
164
223
  end
165
- end
166
224
 
225
+ # Filter out any files/directories that are hidden or backup files (start
226
+ # with "." or "#" or end with "~") or contain site content (start with "_")
227
+ # unless they are "_posts" directories or web server files such as
228
+ # '.htaccess'
229
+ def filter_entries(entries)
230
+ entries = entries.reject do |e|
231
+ unless ['_posts', '.htaccess'].include?(e)
232
+ ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
233
+ end
234
+ end
235
+ end
236
+ end
167
237
  end
@@ -1,10 +1,11 @@
1
1
  module Jekyll
2
-
2
+
3
3
  class HighlightBlock < Liquid::Block
4
4
  include Liquid::StandardFilters
5
+
5
6
  # we need a language, but the linenos argument is optional.
6
7
  SYNTAX = /(\w+)\s?(:?linenos)?\s?/
7
-
8
+
8
9
  def initialize(tag_name, markup, tokens)
9
10
  super
10
11
  if markup =~ SYNTAX
@@ -19,23 +20,25 @@ module Jekyll
19
20
  raise SyntaxError.new("Syntax Error in 'highlight' - Valid syntax: highlight <lang> [linenos]")
20
21
  end
21
22
  end
22
-
23
+
23
24
  def render(context)
24
- if Jekyll.pygments
25
+ if context.registers[:site].pygments
25
26
  render_pygments(context, super.to_s)
26
27
  else
27
28
  render_codehighlighter(context, super.to_s)
28
29
  end
29
30
  end
30
-
31
+
31
32
  def render_pygments(context, code)
32
- if Jekyll.content_type == :markdown
33
+ if context["content_type"] == "markdown"
33
34
  return "\n" + Albino.new(code, @lang).to_s(@options) + "\n"
35
+ elsif context["content_type"] == "textile"
36
+ return "<notextile>" + Albino.new(code, @lang).to_s(@options) + "</notextile>"
34
37
  else
35
- "<notextile>" + Albino.new(code, @lang).to_s(@options) + "</notextile>"
38
+ return Albino.new(code, @lang).to_s(@options)
36
39
  end
37
40
  end
38
-
41
+
39
42
  def render_codehighlighter(context, code)
40
43
  #The div is required because RDiscount blows ass
41
44
  <<-HTML
@@ -47,7 +50,7 @@ module Jekyll
47
50
  HTML
48
51
  end
49
52
  end
50
-
53
+
51
54
  end
52
55
 
53
56
  Liquid::Template.register_tag('highlight', Jekyll::HighlightBlock)
@@ -1,17 +1,17 @@
1
1
  module Jekyll
2
-
2
+
3
3
  class IncludeTag < Liquid::Tag
4
4
  def initialize(tag_name, file, tokens)
5
5
  super
6
6
  @file = file.strip
7
7
  end
8
-
8
+
9
9
  def render(context)
10
10
  if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./
11
11
  return "Include file '#{@file}' contains invalid characters or sequences"
12
12
  end
13
-
14
- Dir.chdir(File.join(Jekyll.source, '_includes')) do
13
+
14
+ Dir.chdir(File.join(context.registers[:site].source, '_includes')) do
15
15
  choices = Dir['**/*'].reject { |x| File.symlink?(x) }
16
16
  if choices.include?(@file)
17
17
  source = File.read(@file)
@@ -25,7 +25,7 @@ module Jekyll
25
25
  end
26
26
  end
27
27
  end
28
-
28
+
29
29
  end
30
30
 
31
31
  Liquid::Template.register_tag('include', Jekyll::IncludeTag)
@@ -1,13 +1,27 @@
1
+ require 'rubygems'
2
+ gem 'RedCloth', '= 4.1.0'
3
+
1
4
  require File.join(File.dirname(__FILE__), *%w[.. lib jekyll])
2
5
 
3
6
  require 'test/unit'
7
+ require 'redgreen'
8
+ require 'shoulda'
9
+ require 'rr'
4
10
 
5
11
  include Jekyll
6
12
 
7
- def dest_dir
8
- File.join(File.dirname(__FILE__), *%w[dest])
9
- end
13
+ class Test::Unit::TestCase
14
+ include RR::Adapters::TestUnit
15
+
16
+ def dest_dir(*subdirs)
17
+ File.join(File.dirname(__FILE__), 'dest', *subdirs)
18
+ end
10
19
 
11
- def clear_dest
12
- FileUtils.rm_rf(dest_dir)
13
- end
20
+ def source_dir(*subdirs)
21
+ File.join(File.dirname(__FILE__), 'source', *subdirs)
22
+ end
23
+
24
+ def clear_dest
25
+ FileUtils.rm_rf(dest_dir)
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Not published!
4
+ published: false
5
+ category: publish_test
6
+ ---
7
+
8
+ This should *not* be published!
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Publish
4
+ category: publish_test
5
+ ---
6
+
7
+ This should be published.
8
+
@@ -0,0 +1,10 @@
1
+ ---
2
+ layout: default
3
+ title: Array categories in YAML
4
+ categories:
5
+ - foo
6
+ - bar
7
+ - baz
8
+ ---
9
+
10
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Categories in YAML
4
+ categories: foo bar baz
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Category in YAML
4
+ category: foo
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,6 @@
1
+ ---
2
+ layout: default
3
+ title: Hash #1
4
+ ---
5
+
6
+ Hashes are nice
@@ -1,37 +1,49 @@
1
1
  require File.dirname(__FILE__) + '/helper'
2
2
 
3
3
  class TestFilters < Test::Unit::TestCase
4
-
5
4
  class JekyllFilter
6
5
  include Jekyll::Filters
7
6
  end
8
-
9
- def setup
10
- @filter = JekyllFilter.new
11
- end
12
7
 
13
- def test_array_to_sentence_string_with_no_args
14
- assert_equal "", @filter.array_to_sentence_string([])
15
- end
8
+ context "filters" do
9
+ setup do
10
+ @filter = JekyllFilter.new
11
+ end
16
12
 
17
- def test_array_to_sentence_string_with_one_arg
18
- assert_equal "1", @filter.array_to_sentence_string([1])
19
- assert_equal "chunky", @filter.array_to_sentence_string(["chunky"])
20
- end
21
-
22
- def test_array_to_sentence_string_with_two_args
23
- assert_equal "1 and 2", @filter.array_to_sentence_string([1, 2])
24
- assert_equal "chunky and bacon", @filter.array_to_sentence_string(["chunky", "bacon"])
25
- end
26
-
27
- def test_array_to_sentence_string_with_multiple_args
28
- assert_equal "1, 2, 3, and 4", @filter.array_to_sentence_string([1, 2, 3, 4])
29
- assert_equal "chunky, bacon, bits, and pieces", @filter.array_to_sentence_string(["chunky", "bacon", "bits", "pieces"])
30
- end
31
-
32
- def test_xml_escape_with_ampersands
33
- assert_equal "AT&amp;T", @filter.xml_escape("AT&T")
34
- assert_equal "&lt;code&gt;command &amp;lt;filename&amp;gt;&lt;/code&gt;", @filter.xml_escape("<code>command &lt;filename&gt;</code>")
13
+ should "textilize with simple string" do
14
+ assert_equal "<p>something <strong>really</strong> simple</p>", @filter.textilize("something *really* simple")
15
+ end
16
+
17
+ should "convert array to sentence string with no args" do
18
+ assert_equal "", @filter.array_to_sentence_string([])
19
+ end
20
+
21
+ should "convert array to sentence string with one arg" do
22
+ assert_equal "1", @filter.array_to_sentence_string([1])
23
+ assert_equal "chunky", @filter.array_to_sentence_string(["chunky"])
24
+ end
25
+
26
+ should "convert array to sentence string with two args" do
27
+ assert_equal "1 and 2", @filter.array_to_sentence_string([1, 2])
28
+ assert_equal "chunky and bacon", @filter.array_to_sentence_string(["chunky", "bacon"])
29
+ end
30
+
31
+ should "convert array to sentence string with multiple args" do
32
+ assert_equal "1, 2, 3, and 4", @filter.array_to_sentence_string([1, 2, 3, 4])
33
+ assert_equal "chunky, bacon, bits, and pieces", @filter.array_to_sentence_string(["chunky", "bacon", "bits", "pieces"])
34
+ end
35
+
36
+ should "escape xml with ampersands" do
37
+ assert_equal "AT&amp;T", @filter.xml_escape("AT&T")
38
+ assert_equal "&lt;code&gt;command &amp;lt;filename&amp;gt;&lt;/code&gt;", @filter.xml_escape("<code>command &lt;filename&gt;</code>")
39
+ end
40
+
41
+ should "escape space as plus" do
42
+ assert_equal "my+things", @filter.cgi_escape("my things")
43
+ end
44
+
45
+ should "escape special characters" do
46
+ assert_equal "hey%21", @filter.cgi_escape("hey!")
47
+ end
35
48
  end
36
-
37
49
  end