cduruk-jekyll 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/History.txt +115 -0
  2. data/README.textile +649 -0
  3. data/Rakefile +91 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/jekyll +156 -0
  6. data/lib/jekyll.rb +84 -0
  7. data/lib/jekyll/albino.rb +122 -0
  8. data/lib/jekyll/converters/csv.rb +26 -0
  9. data/lib/jekyll/converters/mephisto.rb +79 -0
  10. data/lib/jekyll/converters/mt.rb +59 -0
  11. data/lib/jekyll/converters/textpattern.rb +50 -0
  12. data/lib/jekyll/converters/typo.rb +49 -0
  13. data/lib/jekyll/converters/wordpress.rb +54 -0
  14. data/lib/jekyll/convertible.rb +117 -0
  15. data/lib/jekyll/core_ext.rb +29 -0
  16. data/lib/jekyll/filters.rb +47 -0
  17. data/lib/jekyll/haml_helpers.rb +15 -0
  18. data/lib/jekyll/layout.rb +37 -0
  19. data/lib/jekyll/page.rb +67 -0
  20. data/lib/jekyll/post.rb +305 -0
  21. data/lib/jekyll/site.rb +300 -0
  22. data/lib/jekyll/tags/highlight.rb +68 -0
  23. data/lib/jekyll/tags/include.rb +31 -0
  24. data/test/helper.rb +24 -0
  25. data/test/source/_includes/sig.markdown +3 -0
  26. data/test/source/_layouts/default.html +27 -0
  27. data/test/source/_layouts/simple.html +1 -0
  28. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  29. data/test/source/_posts/2008-02-02-published.textile +8 -0
  30. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  31. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  32. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  33. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  34. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  35. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  36. data/test/source/_posts/2009-01-27-category.textile +7 -0
  37. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  38. data/test/source/css/screen.css +76 -0
  39. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  40. data/test/source/index.html +22 -0
  41. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  42. data/test/suite.rb +9 -0
  43. data/test/test_filters.rb +41 -0
  44. data/test/test_generated_site.rb +38 -0
  45. data/test/test_post.rb +262 -0
  46. data/test/test_site.rb +57 -0
  47. data/test/test_tags.rb +51 -0
  48. metadata +165 -0
@@ -0,0 +1,300 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :config, :layouts, :posts, :collated_posts, :categories, :tags
5
+ attr_accessor :source, :dest, :lsi, :pygments, :pygments_cache, :permalink_style,
6
+ :sass, :post_defaults
7
+
8
+ # Initialize the site
9
+ # +config+ is a Hash containing site configurations details
10
+ #
11
+ # Returns <Site>
12
+ def initialize(config)
13
+ self.config = config.clone
14
+
15
+ self.source = config['source']
16
+ self.dest = config['destination']
17
+ self.lsi = config['lsi']
18
+ self.pygments = config['pygments']
19
+ self.pygments_cache = config['pygments_cache']
20
+ self.permalink_style = config['permalink'].to_sym
21
+ self.post_defaults = config['post_defaults'] || {}
22
+
23
+ self.reset
24
+ self.setup
25
+ end
26
+
27
+ def reset
28
+ self.layouts = {}
29
+ self.posts = []
30
+ self.collated_posts = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = [] } } }
31
+ self.categories = Hash.new { |hash, key| hash[key] = [] }
32
+ self.tags = Hash.new { |hash, key| hash[key] = [] }
33
+ end
34
+
35
+ def setup
36
+ # Check to see if LSI is enabled.
37
+ require 'classifier' if self.lsi
38
+
39
+ if self.config['sass']
40
+ begin
41
+ require 'sass'
42
+ self.sass = true
43
+ puts 'Using Sass for CSS generation'
44
+ rescue LoadError
45
+ puts 'You must have the haml gem installed first'
46
+ end
47
+ end
48
+
49
+ if self.config['haml']
50
+ begin
51
+ require 'haml'
52
+ require 'jekyll/haml_helpers'
53
+ helpers = File.join(source, '_helpers.rb')
54
+ require helpers if File.exist?(helpers)
55
+ puts 'Enabled Haml'
56
+ rescue LoadError
57
+ puts 'You must have the haml gem installed first'
58
+ end
59
+ end
60
+
61
+ if self.pygments_cache
62
+ require 'fileutils'
63
+ FileUtils.mkdir_p(pygments_cache)
64
+ require 'digest/md5'
65
+ end
66
+
67
+ # Set the Markdown interpreter (and Maruku self.config, if necessary)
68
+ case self.config['markdown']
69
+ when 'rdiscount'
70
+ begin
71
+ require 'rdiscount'
72
+
73
+ def markdown(content)
74
+ RDiscount.new(content).to_html
75
+ end
76
+
77
+ puts 'Using rdiscount for Markdown'
78
+ rescue LoadError
79
+ puts 'You must have the rdiscount gem installed first'
80
+ end
81
+ when 'maruku'
82
+ begin
83
+ require 'maruku'
84
+
85
+ def markdown(content)
86
+ Maruku.new(content).to_html
87
+ end
88
+
89
+ if self.config['maruku']['use_divs']
90
+ require 'maruku/ext/div'
91
+ puts 'Maruku: Using extended syntax for div elements.'
92
+ end
93
+
94
+ if self.config['maruku']['use_tex']
95
+ require 'maruku/ext/math'
96
+ puts "Maruku: Using LaTeX extension. Images in `#{self.config['maruku']['png_dir']}`."
97
+
98
+ # Switch off MathML output
99
+ MaRuKu::Globals[:html_math_output_mathml] = false
100
+ MaRuKu::Globals[:html_math_engine] = 'none'
101
+
102
+ # Turn on math to PNG support with blahtex
103
+ # Resulting PNGs stored in `images/latex`
104
+ MaRuKu::Globals[:html_math_output_png] = true
105
+ MaRuKu::Globals[:html_png_engine] = self.config['maruku']['png_engine']
106
+ MaRuKu::Globals[:html_png_dir] = self.config['maruku']['png_dir']
107
+ MaRuKu::Globals[:html_png_url] = self.config['maruku']['png_url']
108
+ end
109
+ rescue LoadError
110
+ puts "The maruku gem is required for markdown support!"
111
+ end
112
+ end
113
+ end
114
+
115
+ def textile(content)
116
+ RedCloth.new(content).to_html
117
+ end
118
+
119
+ # Do the actual work of processing the site and generating the
120
+ # real deal.
121
+ #
122
+ # Returns nothing
123
+ def process
124
+ self.reset
125
+ self.read_layouts
126
+ self.transform_pages
127
+ self.transform_sass if self.sass
128
+ self.write_posts
129
+ end
130
+
131
+ # Read all the files in <source>/_layouts into memory for later use.
132
+ #
133
+ # Returns nothing
134
+ def read_layouts
135
+ base = File.join(self.source, "_layouts")
136
+ entries = []
137
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
138
+
139
+ entries.each do |f|
140
+ name = f.split(".")[0..-2].join(".")
141
+ self.layouts[name] = Layout.new(self, base, f)
142
+ end
143
+ rescue Errno::ENOENT => e
144
+ # ignore missing layout dir
145
+ end
146
+
147
+ # Read all the files in <base>/_posts and create a new Post object with each one.
148
+ #
149
+ # Returns nothing
150
+ def read_posts(dir)
151
+ base = File.join(self.source, dir, '_posts')
152
+ entries = []
153
+ Dir.chdir(base) { entries = filter_entries(Dir['**/*']) }
154
+
155
+ # first pass processes, but does not yet render post content
156
+ entries.each do |f|
157
+ if Post.valid?(f)
158
+ post = Post.new(self, self.source, dir, f)
159
+
160
+ if post.published
161
+ self.posts << post
162
+ post.categories.each { |c| self.categories[c] << post }
163
+ post.tags.each { |c| self.tags[c] << post }
164
+ end
165
+ end
166
+ end
167
+
168
+ self.posts.sort!
169
+
170
+ # second pass renders each post now that full site payload is available
171
+ self.posts.each do |post|
172
+ post.render(self.layouts, site_payload)
173
+ self.collated_posts[post.date.year][post.date.month][post.date.day].unshift(post)
174
+ end
175
+
176
+ self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a} }
177
+ self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a} }
178
+ rescue Errno::ENOENT => e
179
+ # ignore missing layout dir
180
+ end
181
+
182
+ # Write each post to <dest>/<year>/<month>/<day>/<slug>
183
+ #
184
+ # Returns nothing
185
+ def write_posts
186
+ self.posts.each do |post|
187
+ post.write(self.dest)
188
+ end
189
+ end
190
+
191
+ # Copy all regular files from <source> to <dest>/ ignoring
192
+ # any files/directories that are hidden or backup files (start
193
+ # with "." or "#" or end with "~") or contain site content (start with "_")
194
+ # unless they are "_posts" directories or web server files such as
195
+ # '.htaccess'
196
+ # The +dir+ String is a relative path used to call this method
197
+ # recursively as it descends through directories
198
+ #
199
+ # Returns nothing
200
+ def transform_pages(dir = '')
201
+ base = File.join(self.source, dir)
202
+ entries = filter_entries(Dir.entries(base))
203
+ directories = entries.select { |e| File.directory?(File.join(base, e)) }
204
+ files = entries.reject { |e| File.directory?(File.join(base, e)) }
205
+
206
+ # we need to make sure to process _posts *first* otherwise they
207
+ # might not be available yet to other templates as {{ site.posts }}
208
+ if directories.include?('_posts')
209
+ directories.delete('_posts')
210
+ read_posts(dir)
211
+ end
212
+ [directories, files].each do |entries|
213
+ entries.each do |f|
214
+ if File.directory?(File.join(base, f))
215
+ next if self.dest.sub(/\/$/, '') == File.join(base, f)
216
+ transform_pages(File.join(dir, f))
217
+ else
218
+ first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
219
+
220
+ if first3 == "---"
221
+ # file appears to have a YAML header so process it as a page
222
+ page = Page.new(self, self.source, dir, f)
223
+ page.render(self.layouts, site_payload)
224
+ page.write(self.dest)
225
+ else
226
+ # otherwise copy the file without transforming it
227
+ FileUtils.mkdir_p(File.join(self.dest, dir))
228
+ FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ # Transform all *.sass files from <dest> to css with the same name
236
+ # and delete source sass files.
237
+ # Returns nothing
238
+ def transform_sass(dir = '')
239
+ base = File.join(self.source, dir)
240
+ entries = Dir.entries(base)
241
+ entries = entries.reject { |e| ['.', '_'].include?(e[0..0]) }
242
+ directories = entries.select { |e| File.directory?(File.join(base, e)) }
243
+ directories.each { |d| transform_sass(File.join(dir, d)) }
244
+ files = entries.reject { |e| File.directory?(File.join(base, e)) }
245
+ files = files.select { |f| File.extname(File.join(base, f)) == ".sass" }
246
+ files.each do |f|
247
+ input = File.open(File.join(base, f), "r")
248
+ result = Sass::Engine.new(input.read, :style => :compact, :load_paths => base).render
249
+ FileUtils.mkdir_p(File.join(self.dest, dir))
250
+ output = File.open(File.join(self.dest, dir, f).gsub(/.sass\Z/, ".css"), "w") do |o|
251
+ o.write(result)
252
+ end
253
+ FileUtils.rm(File.join(self.dest, dir, f))
254
+ end
255
+ end
256
+
257
+ # Constructs a hash map of Posts indexed by the specified Post attribute
258
+ #
259
+ # Returns {post_attr => [<Post>]}
260
+ def post_attr_hash(post_attr)
261
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
262
+ # then sort each array in reverse order
263
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
264
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
265
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
266
+ return hash
267
+ end
268
+
269
+ # The Hash payload containing site-wide data
270
+ #
271
+ # Returns {"site" => {"time" => <Time>,
272
+ # "posts" => [<Post>],
273
+ # "categories" => [<Post>],
274
+ # "tags" => [<Post>],
275
+ # "topics" => [<Post>] }}
276
+ def site_payload
277
+ {"site" => {
278
+ "time" => Time.now,
279
+ "posts" => self.posts.sort { |a,b| b <=> a },
280
+ "categories" => post_attr_hash('categories'),
281
+ "tags" => post_attr_hash('tags'),
282
+ "topics" => post_attr_hash('topics')
283
+ }}
284
+ end
285
+
286
+ # Filter out any files/directories that are hidden or backup files (start
287
+ # with "." or "#" or end with "~") or contain site content (start with "_")
288
+ # unless they are "_posts" directories or web server files such as
289
+ # '.htaccess'
290
+ def filter_entries(entries)
291
+ entries = entries.reject do |e|
292
+ unless ['_posts', '.htaccess'].include?(e)
293
+ # Reject backup/hidden
294
+ ['.', '_', '#'].include?(e[0..0]) or e[-1..-1] == '~'
295
+ end
296
+ end
297
+ end
298
+
299
+ end
300
+ end
@@ -0,0 +1,68 @@
1
+ module Jekyll
2
+
3
+ class HighlightBlock < Liquid::Block
4
+ include Liquid::StandardFilters
5
+
6
+ # we need a language, but the linenos argument is optional.
7
+ SYNTAX = /(\w+)\s?(:?linenos)?\s?/
8
+
9
+ def initialize(tag_name, markup, tokens)
10
+ super
11
+ if markup =~ SYNTAX
12
+ @lang = $1
13
+ if defined? $2
14
+ # additional options to pass to Albino.
15
+ @options = { 'O' => 'linenos=inline' }
16
+ else
17
+ @options = {}
18
+ end
19
+ else
20
+ raise SyntaxError.new("Syntax Error in 'highlight' - Valid syntax: highlight <lang> [linenos]")
21
+ end
22
+ end
23
+
24
+ def render(context)
25
+ if context.registers[:site].pygments
26
+ render_pygments(context, super.to_s)
27
+ else
28
+ render_codehighlighter(context, super.to_s)
29
+ end
30
+ end
31
+
32
+ def render_pygments(context, code)
33
+ if cache_dir = context.registers[:site].pygments_cache
34
+ path = File.join(cache_dir, "#{@lang}-#{Digest::MD5.hexdigest(code)}.html")
35
+ if File.exist?(path)
36
+ highlighted_code = File.read(path)
37
+ else
38
+ highlighted_code = Albino.new(code, @lang).to_s(@options)
39
+ File.open(path, 'w') {|f| f.print(highlighted_code) }
40
+ end
41
+ else
42
+ highlighted_code = Albino.new(code, @lang).to_s(@options)
43
+ end
44
+
45
+ if context["content_type"] == :markdown
46
+ return "\n" + highlighted_code + "\n"
47
+ elsif context["content_type"] == :textile
48
+ return "<notextile>" + highlighted_code + "</notextile>"
49
+ else
50
+ return highlighted_code
51
+ end
52
+ end
53
+
54
+ def render_codehighlighter(context, code)
55
+ #The div is required because RDiscount blows ass
56
+ <<-HTML
57
+ <div>
58
+ <pre>
59
+ <code class='#{@lang}'>#{h(code).strip}</code>
60
+ </pre>
61
+ </div>
62
+ HTML
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ Liquid::Template.register_tag('highlight', Jekyll::HighlightBlock)
@@ -0,0 +1,31 @@
1
+ module Jekyll
2
+
3
+ class IncludeTag < Liquid::Tag
4
+ def initialize(tag_name, file, tokens)
5
+ super
6
+ @file = file.strip
7
+ end
8
+
9
+ def render(context)
10
+ if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./
11
+ return "Include file '#{@file}' contains invalid characters or sequences"
12
+ end
13
+
14
+ Dir.chdir(File.join(context.registers[:site].source, '_includes')) do
15
+ choices = Dir['**/*'].reject { |x| File.symlink?(x) }
16
+ if choices.include?(@file)
17
+ source = File.read(@file)
18
+ partial = Liquid::Template.parse(source)
19
+ context.stack do
20
+ partial.render(context)
21
+ end
22
+ else
23
+ "Included file '#{@file}' not found in _includes directory"
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ Liquid::Template.register_tag('include', Jekyll::IncludeTag)
@@ -0,0 +1,24 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. lib jekyll])
2
+
3
+ require 'test/unit'
4
+ require 'redgreen'
5
+ require 'shoulda'
6
+ require 'rr'
7
+
8
+ include Jekyll
9
+
10
+ class Test::Unit::TestCase
11
+ include RR::Adapters::TestUnit
12
+
13
+ def dest_dir(*subdirs)
14
+ File.join(File.dirname(__FILE__), 'dest', *subdirs)
15
+ end
16
+
17
+ def source_dir(*subdirs)
18
+ File.join(File.dirname(__FILE__), 'source', *subdirs)
19
+ end
20
+
21
+ def clear_dest
22
+ FileUtils.rm_rf(dest_dir)
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ --
2
+ Tom Preston-Werner
3
+ github.com/mojombo
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+
4
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
5
+ <head>
6
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
7
+ <title>{{ page.title }}</title>
8
+ <meta name="author" content="<%= @page.author %>" />
9
+
10
+ <!-- CodeRay syntax highlighting CSS -->
11
+ <link rel="stylesheet" href="/css/coderay.css" type="text/css" />
12
+
13
+ <!-- Homepage CSS -->
14
+ <link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection" />
15
+ </head>
16
+ <body>
17
+
18
+ <div class="site">
19
+ <div class="title">
20
+ Tom Preston-Werner
21
+ </div>
22
+
23
+ {{ content }}
24
+ </div>
25
+
26
+ </body>
27
+ </html>