qrush-jekyll 0.3.0.1
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.
- data/History.txt +78 -0
- data/Manifest.txt +38 -0
- data/README.textile +435 -0
- data/Rakefile +29 -0
- data/bin/jekyll +127 -0
- data/jekyll.gemspec +51 -0
- data/lib/jekyll.rb +63 -0
- data/lib/jekyll/albino.rb +116 -0
- data/lib/jekyll/converters/csv.rb +26 -0
- data/lib/jekyll/converters/mephisto.rb +24 -0
- data/lib/jekyll/converters/mt.rb +59 -0
- data/lib/jekyll/converters/wordpress.rb +54 -0
- data/lib/jekyll/convertible.rb +60 -0
- data/lib/jekyll/core_ext.rb +22 -0
- data/lib/jekyll/filters.rb +39 -0
- data/lib/jekyll/layout.rb +33 -0
- data/lib/jekyll/page.rb +64 -0
- data/lib/jekyll/post.rb +170 -0
- data/lib/jekyll/site.rb +160 -0
- data/lib/jekyll/tags/highlight.rb +37 -0
- data/lib/jekyll/tags/include.rb +31 -0
- data/test/helper.rb +13 -0
- data/test/source/_includes/sig.markdown +3 -0
- data/test/source/_layouts/default.html +27 -0
- data/test/source/_layouts/simple.html +1 -0
- data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
- data/test/source/_posts/2008-11-21-complex.textile +8 -0
- data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
- data/test/source/_posts/2008-12-13-include.markdown +8 -0
- data/test/source/css/screen.css +76 -0
- data/test/source/index.html +22 -0
- data/test/suite.rb +9 -0
- data/test/test_generated_site.rb +21 -0
- data/test/test_jekyll.rb +0 -0
- data/test/test_post.rb +99 -0
- data/test/test_site.rb +30 -0
- metadata +156 -0
data/lib/jekyll/site.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class Site
|
4
|
+
attr_accessor :source, :dest
|
5
|
+
attr_accessor :layouts, :posts
|
6
|
+
|
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
|
12
|
+
#
|
13
|
+
# Returns <Site>
|
14
|
+
def initialize(source, dest)
|
15
|
+
self.source = source
|
16
|
+
self.dest = dest
|
17
|
+
self.layouts = {}
|
18
|
+
self.posts = []
|
19
|
+
end
|
20
|
+
|
21
|
+
# Do the actual work of processing the site and generating the
|
22
|
+
# real deal.
|
23
|
+
#
|
24
|
+
# Returns nothing
|
25
|
+
def process
|
26
|
+
self.read_layouts
|
27
|
+
self.transform_pages
|
28
|
+
self.write_posts
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read all the files in <source>/_layouts except backup files
|
32
|
+
# (end with "~") into memory for later use.
|
33
|
+
#
|
34
|
+
# Returns nothing
|
35
|
+
def read_layouts
|
36
|
+
base = File.join(self.source, "_layouts")
|
37
|
+
entries = Dir.entries(base)
|
38
|
+
entries = entries.reject { |e| e[-1..-1] == '~' }
|
39
|
+
entries = entries.reject { |e| File.directory?(File.join(base, e)) }
|
40
|
+
|
41
|
+
entries.each do |f|
|
42
|
+
name = f.split(".")[0..-2].join(".")
|
43
|
+
self.layouts[name] = Layout.new(base, f)
|
44
|
+
end
|
45
|
+
rescue Errno::ENOENT => e
|
46
|
+
# ignore missing layout dir
|
47
|
+
end
|
48
|
+
|
49
|
+
# Read all the files in <base>/_posts except backup files (end with "~")
|
50
|
+
# and create a new Post object with each one.
|
51
|
+
#
|
52
|
+
# Returns nothing
|
53
|
+
def read_posts(dir)
|
54
|
+
base = File.join(self.source, dir, '_posts')
|
55
|
+
|
56
|
+
entries = []
|
57
|
+
Dir.chdir(base) { entries = Dir['**/*'] }
|
58
|
+
entries = entries.reject { |e| e[-1..-1] == '~' }
|
59
|
+
entries = entries.reject { |e| File.directory?(File.join(base, e)) }
|
60
|
+
|
61
|
+
# first pass processes, but does not yet render post content
|
62
|
+
entries.each do |f|
|
63
|
+
if Post.valid?(f)
|
64
|
+
post = Post.new(self.source, dir, f)
|
65
|
+
self.posts << post
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# second pass renders each post now that full site payload is available
|
70
|
+
self.posts.each do |post|
|
71
|
+
post.render(self.layouts, site_payload)
|
72
|
+
end
|
73
|
+
|
74
|
+
self.posts.sort!
|
75
|
+
rescue Errno::ENOENT => e
|
76
|
+
# ignore missing layout dir
|
77
|
+
end
|
78
|
+
|
79
|
+
# Write each post to <dest>/<year>/<month>/<day>/<slug>
|
80
|
+
#
|
81
|
+
# Returns nothing
|
82
|
+
def write_posts
|
83
|
+
self.posts.each do |post|
|
84
|
+
post.write(self.dest)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Copy all regular files from <source> to <dest>/ ignoring
|
89
|
+
# any files/directories that are hidden or backup files (start
|
90
|
+
# with "." or end with "~") or contain site content (start with "_")
|
91
|
+
# unless they are "_posts" directories
|
92
|
+
# The +dir+ String is a relative path used to call this method
|
93
|
+
# recursively as it descends through directories
|
94
|
+
#
|
95
|
+
# Returns nothing
|
96
|
+
def transform_pages(dir = '')
|
97
|
+
base = File.join(self.source, dir)
|
98
|
+
entries = Dir.entries(base)
|
99
|
+
entries = entries.reject { |e| e[-1..-1] == '~' }
|
100
|
+
entries = entries.reject do |e|
|
101
|
+
(e != '_posts') and ['.', '_'].include?(e[0..0])
|
102
|
+
end
|
103
|
+
|
104
|
+
# we need to make sure to process _posts *first* otherwise they
|
105
|
+
# might not be available yet to other templates as {{ site.posts }}
|
106
|
+
if entries.include?('_posts')
|
107
|
+
entries.delete('_posts')
|
108
|
+
read_posts(dir)
|
109
|
+
end
|
110
|
+
|
111
|
+
entries.each do |f|
|
112
|
+
if File.directory?(File.join(base, f))
|
113
|
+
next if self.dest.sub(/\/$/, '') == File.join(base, f)
|
114
|
+
transform_pages(File.join(dir, f))
|
115
|
+
else
|
116
|
+
first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
|
117
|
+
|
118
|
+
if first3 == "---"
|
119
|
+
# file appears to have a YAML header so process it as a page
|
120
|
+
page = Page.new(self.source, dir, f)
|
121
|
+
page.render(self.layouts, site_payload)
|
122
|
+
page.write(self.dest)
|
123
|
+
else
|
124
|
+
# otherwise copy the file without transforming it
|
125
|
+
FileUtils.mkdir_p(File.join(self.dest, dir))
|
126
|
+
FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Constructs a hash map of Posts indexed by the specified Post attribute
|
133
|
+
#
|
134
|
+
# Returns {post_attr => [<Post>]}
|
135
|
+
def post_attr_hash(post_attr)
|
136
|
+
# Build a hash map based on the specified post attribute ( post attr => array of posts )
|
137
|
+
# then sort each array in reverse order
|
138
|
+
hash = Hash.new { |hash, key| hash[key] = Array.new }
|
139
|
+
self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
|
140
|
+
hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
|
141
|
+
return hash
|
142
|
+
end
|
143
|
+
|
144
|
+
# The Hash payload containing site-wide data
|
145
|
+
#
|
146
|
+
# Returns {"site" => {"time" => <Time>,
|
147
|
+
# "posts" => [<Post>],
|
148
|
+
# "categories" => [<Post>],
|
149
|
+
# "topics" => [<Post>] }}
|
150
|
+
def site_payload
|
151
|
+
{"site" => {
|
152
|
+
"time" => Time.now,
|
153
|
+
"posts" => self.posts.sort { |a,b| b <=> a },
|
154
|
+
"categories" => post_attr_hash('categories'),
|
155
|
+
"topics" => post_attr_hash('topics')
|
156
|
+
}}
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class HighlightBlock < Liquid::Block
|
4
|
+
include Liquid::StandardFilters
|
5
|
+
|
6
|
+
def initialize(tag_name, lang, tokens)
|
7
|
+
super
|
8
|
+
@lang = lang.strip
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(context)
|
12
|
+
if Jekyll.pygments
|
13
|
+
render_pygments(context, super.to_s)
|
14
|
+
else
|
15
|
+
render_codehighlighter(context, super.to_s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def render_pygments(context, code)
|
20
|
+
"<notextile>" + Albino.new(code, @lang).to_s + "</notextile>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_codehighlighter(context, code)
|
24
|
+
#The div is required because RDiscount blows ass
|
25
|
+
<<-HTML
|
26
|
+
<div>
|
27
|
+
<pre>
|
28
|
+
<code class='#{@lang}'>#{h(code).strip}</code>
|
29
|
+
</pre>
|
30
|
+
</div>
|
31
|
+
HTML
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
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(Jekyll.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)
|
data/test/helper.rb
ADDED
@@ -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>
|
@@ -0,0 +1 @@
|
|
1
|
+
<<< {{ content }} >>>
|
@@ -0,0 +1,76 @@
|
|
1
|
+
/*****************************************************************************/
|
2
|
+
/*
|
3
|
+
/* Common
|
4
|
+
/*
|
5
|
+
/*****************************************************************************/
|
6
|
+
|
7
|
+
/* Global Reset */
|
8
|
+
|
9
|
+
* {
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
}
|
13
|
+
|
14
|
+
html, body {
|
15
|
+
height: 100%;
|
16
|
+
}
|
17
|
+
|
18
|
+
body {
|
19
|
+
background-color: white;
|
20
|
+
font: 13.34px helvetica, arial, clean, sans-serif;
|
21
|
+
*font-size: small;
|
22
|
+
text-align: center;
|
23
|
+
}
|
24
|
+
|
25
|
+
h1, h2, h3, h4, h5, h6 {
|
26
|
+
font-size: 100%;
|
27
|
+
}
|
28
|
+
|
29
|
+
h1 {
|
30
|
+
margin-bottom: 1em;
|
31
|
+
}
|
32
|
+
|
33
|
+
p {
|
34
|
+
margin: 1em 0;
|
35
|
+
}
|
36
|
+
|
37
|
+
a {
|
38
|
+
color: #00a;
|
39
|
+
}
|
40
|
+
|
41
|
+
a:hover {
|
42
|
+
color: black;
|
43
|
+
}
|
44
|
+
|
45
|
+
a:visited {
|
46
|
+
color: #a0a;
|
47
|
+
}
|
48
|
+
|
49
|
+
table {
|
50
|
+
font-size: inherit;
|
51
|
+
font: 100%;
|
52
|
+
}
|
53
|
+
|
54
|
+
/*****************************************************************************/
|
55
|
+
/*
|
56
|
+
/* Site
|
57
|
+
/*
|
58
|
+
/*****************************************************************************/
|
59
|
+
|
60
|
+
.site {
|
61
|
+
font-size: 110%;
|
62
|
+
text-align: justify;
|
63
|
+
width: 40em;
|
64
|
+
margin: 3em auto 2em auto;
|
65
|
+
line-height: 1.5em;
|
66
|
+
}
|
67
|
+
|
68
|
+
.title {
|
69
|
+
color: #a00;
|
70
|
+
font-weight: bold;
|
71
|
+
margin-bottom: 2em;
|
72
|
+
}
|
73
|
+
|
74
|
+
.site .meta {
|
75
|
+
color: #aaa;
|
76
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Tom Preston-Werner
|
4
|
+
---
|
5
|
+
|
6
|
+
h1. Welcome to my site
|
7
|
+
|
8
|
+
h2. Please read our {{ site.posts | size }} Posts
|
9
|
+
|
10
|
+
<ul>
|
11
|
+
{% for post in site.posts %}
|
12
|
+
<li>{{ post.date }} <a href="{{ post.url }}">{{ post.title }}</a></li>
|
13
|
+
{% endfor %}
|
14
|
+
</ul>
|
15
|
+
|
16
|
+
{% assign first_post = site.posts.first %}
|
17
|
+
<div id="first_post">
|
18
|
+
<h1>{{ first_post.title }}</h1>
|
19
|
+
<div>
|
20
|
+
{{ first_post.content }}
|
21
|
+
</div>
|
22
|
+
</div>
|
data/test/suite.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class TestGeneratedSite < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
clear_dest
|
6
|
+
source = File.join(File.dirname(__FILE__), *%w[source])
|
7
|
+
@s = Site.new(source, dest_dir)
|
8
|
+
@s.process
|
9
|
+
@index = File.read(File.join(dest_dir, 'index.html'))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_site_posts_in_index
|
13
|
+
# confirm that {{ site.posts }} is working
|
14
|
+
assert @index.include?("#{@s.posts.size} Posts")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_post_content_in_index
|
18
|
+
# confirm that the {{ post.content }} is rendered OK
|
19
|
+
assert @index.include?('<p>This <em>is</em> cool</p>')
|
20
|
+
end
|
21
|
+
end
|