cypher-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 (49) hide show
  1. data/History.txt +127 -0
  2. data/README.textile +41 -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 +51 -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 +306 -0
  21. data/lib/jekyll/site.rb +298 -0
  22. data/lib/jekyll/tags/highlight.rb +68 -0
  23. data/lib/jekyll/tags/include.rb +31 -0
  24. data/test/helper.rb +27 -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/_posts/2009-03-12-hash-#1.markdown +6 -0
  38. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  39. data/test/source/css/screen.css +76 -0
  40. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  41. data/test/source/index.html +22 -0
  42. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  43. data/test/suite.rb +9 -0
  44. data/test/test_filters.rb +49 -0
  45. data/test/test_generated_site.rb +38 -0
  46. data/test/test_post.rb +269 -0
  47. data/test/test_site.rb +65 -0
  48. data/test/test_tags.rb +116 -0
  49. metadata +166 -0
@@ -0,0 +1,298 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :config, :layouts, :posts, :categories, :exclude, :collated_posts, :tags
5
+ attr_accessor :source, :dest, :lsi, :pygments, :permalink_style, :pygments_cache
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.exclude = config['exclude'] || []
22
+ self.post_defaults = config['post_defaults'] || {}
23
+
24
+ self.reset
25
+ self.setup
26
+ end
27
+
28
+ def reset
29
+ self.layouts = {}
30
+ self.posts = []
31
+ self.collated_posts = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = [] } } }
32
+ self.categories = Hash.new { |hash, key| hash[key] = [] }
33
+ self.tags = Hash.new { |hash, key| hash[key] = [] }
34
+ end
35
+
36
+ def setup
37
+ # Check to see if LSI is enabled.
38
+ require 'classifier' if self.lsi
39
+
40
+ if self.config['sass']
41
+ begin
42
+ require 'sass'
43
+ self.sass = true
44
+ puts 'Using Sass for CSS generation'
45
+ rescue LoadError
46
+ puts 'You must have the haml gem installed first'
47
+ end
48
+ end
49
+
50
+ if self.config['haml']
51
+ begin
52
+ require 'haml'
53
+ require 'jekyll/haml_helpers'
54
+ helpers = File.join(source, '_helpers.rb')
55
+ require helpers if File.exist?(helpers)
56
+ puts 'Enabled Haml'
57
+ rescue LoadError
58
+ puts 'You must have the haml gem installed first'
59
+ end
60
+ end
61
+
62
+ if self.pygments_cache
63
+ require 'fileutils'
64
+ FileUtils.mkdir_p(pygments_cache)
65
+ require 'digest/md5'
66
+ end
67
+
68
+ # Set the Markdown interpreter (and Maruku self.config, if necessary)
69
+ case self.config['markdown']
70
+ when 'rdiscount'
71
+ begin
72
+ require 'rdiscount'
73
+
74
+ def markdown(content)
75
+ RDiscount.new(content).to_html
76
+ end
77
+
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
+ ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
294
+ end
295
+ end
296
+ end
297
+ end
298
+ 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,27 @@
1
+ require 'rubygems'
2
+ gem 'RedCloth', '= 4.1.0'
3
+
4
+ require File.join(File.dirname(__FILE__), *%w[.. lib jekyll])
5
+
6
+ require 'test/unit'
7
+ require 'redgreen'
8
+ require 'shoulda'
9
+ require 'rr'
10
+
11
+ include Jekyll
12
+
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
19
+
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,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>