mojombo-jekyll 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +13 -0
- data/Manifest.txt +28 -0
- data/README.textile +65 -0
- data/Rakefile +15 -0
- data/bin/jekyll +11 -0
- data/jekyll.gemspec +39 -0
- data/lib/jekyll.rb +30 -0
- data/lib/jekyll/convertible.rb +53 -0
- data/lib/jekyll/filters.rb +17 -0
- data/lib/jekyll/layout.rb +48 -0
- data/lib/jekyll/page.rb +64 -0
- data/lib/jekyll/post.rb +120 -0
- data/lib/jekyll/site.rb +113 -0
- data/test/helper.rb +13 -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/css/screen.css +76 -0
- data/test/source/index.html +12 -0
- data/test/suite.rb +6 -0
- data/test/test_jekyll.rb +0 -0
- data/test/test_post.rb +70 -0
- data/test/test_site.rb +30 -0
- metadata +111 -0
data/History.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
== 0.1.1 /
|
2
|
+
* Minor Additions
|
3
|
+
* Posts now support introspectional data e.g. {{ page.url }}
|
4
|
+
|
5
|
+
== 0.1.0 / 2008-11-05
|
6
|
+
* First release
|
7
|
+
* Converts posts written in Textile
|
8
|
+
* Converts regular site pages
|
9
|
+
* Simple copy of binary files
|
10
|
+
|
11
|
+
== 0.0.0 / 2008-10-19
|
12
|
+
* Birthday!
|
13
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.textile
|
4
|
+
Rakefile
|
5
|
+
bin/jekyll
|
6
|
+
jekyll.gemspec
|
7
|
+
lib/jekyll.rb
|
8
|
+
lib/jekyll/convertible.rb
|
9
|
+
lib/jekyll/filters.rb
|
10
|
+
lib/jekyll/layout.rb
|
11
|
+
lib/jekyll/page.rb
|
12
|
+
lib/jekyll/post.rb
|
13
|
+
lib/jekyll/site.rb
|
14
|
+
test/dest/2008/10/18/foo-bar.html
|
15
|
+
test/dest/2008/11/21/complex.html
|
16
|
+
test/dest/css/screen.css
|
17
|
+
test/dest/index.html
|
18
|
+
test/helper.rb
|
19
|
+
test/source/_layouts/default.html
|
20
|
+
test/source/_layouts/simple.html
|
21
|
+
test/source/_posts/2008-10-18-foo-bar.textile
|
22
|
+
test/source/_posts/2008-11-21-complex.textile
|
23
|
+
test/source/css/screen.css
|
24
|
+
test/source/index.html
|
25
|
+
test/suite.rb
|
26
|
+
test/test_jekyll.rb
|
27
|
+
test/test_post.rb
|
28
|
+
test/test_site.rb
|
data/README.textile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
h1. Jekyll
|
2
|
+
|
3
|
+
Jekyll is a simple, blog aware, static site generator. It takes a template directory (representing the raw form of a website), runs it through Textile and Liquid converters, and spits out a complete, static website suitable for serving with Apache or your favorite web server. Visit "http://tom.preston-werner.com":http://tom.preston-werner.com to see an example of a Jekyll generated blog.
|
4
|
+
|
5
|
+
To understand how this all works, open up my "TPW":http://github.com/mojombo/tpw repo in a new browser window. I'll be referencing the code there.
|
6
|
+
|
7
|
+
Take a look at "index.html":http://github.com/mojombo/tpw/tree/master/index.html. This file represents the homepage of the site. At the top of the file is a chunk of YAML that contains metadata about the file. This data tells Jekyll what layout to give the file, what the page's title should be, etc. In this case, I specify that the "default" template should be used. You can find the layout files in the "_layouts":http://github.com/mojombo/tpw/tree/master/_layouts directory. If you open "default.html":http://github.com/mojombo/tpw/tree/master/_layouts/default.html you can see that the homepage is constructed by wrapping index.html with this layout.
|
8
|
+
|
9
|
+
You'll also notice Liquid templating code in these files. "Liquid":http://www.liquidmarkup.org/ is a simple, extensible templating language that makes it easy to embed data in your templates. For my homepage I wanted to have a list of all my blog posts. Jekyll hands me a Hash containing various data about my site. A reverse chronological list of all my blog posts can be found in <code>site.posts</code>. Each post, in turn, contains various fields such as <code>title</code> and <code>date</code>.
|
10
|
+
|
11
|
+
Jekyll gets the list of blog posts by parsing the files in the "_posts":http://github.com/mojombo/tpw/tree/master/_posts directory. Each post's filename contains the publishing date and slug (what shows up in the URL) that the final HTML file should have. Open up the file corresponding to a blog post: "2008-11-17-blogging-like-a-hacker.textile":http://github.com/mojombo/tpw/tree/master/_posts/2008-11-17-blogging-like-a-hacker.textile. GitHub renders textile files by default, so to better understand the file, click on the "raw":http://github.com/mojombo/tpw/tree/master/_posts/2008-11-17-blogging-like-a-hacker.textile?raw=true view to see the original file. Here I've specified the <code>post</code> layout. If you look at that file you'll see an example of a nested layout. Layouts can contain other layouts allowing you a great deal of flexibility in how pages are assembled. In my case I use a nested layout in order to show related posts for each blog entry. The YAML also specifies the post's title which is then embedded in the post's body via Liquid.
|
12
|
+
|
13
|
+
Posts are handled in a special way by Jekyll. The date you specify in the filename is used to construct the URL in the generated site. The example post, for instance, ends up at <code>http://tom.preston-werner.com/2008/11/17/blogging-like-a-hacker.html</code>.
|
14
|
+
|
15
|
+
Files that do not reside in directories prefixed with an underscore are mirrored into a corresponding directory structure in the generated site. If a file does not have a YAML preface, it is not run through the Liquid interpreter. Binary files are copied over unmodified.
|
16
|
+
|
17
|
+
In order to convert your raw site into the finished version, you simply run:
|
18
|
+
|
19
|
+
<pre class="terminal"><code>$ jekyll /path/to/raw/site /path/to/place/generated/site</code></pre>
|
20
|
+
|
21
|
+
Jekyll is still a very young project. I've only developed the exact functionality that I've needed. As time goes on I'd like to see the project mature and support additional features. If you end up using Jekyll for your own blog, drop me a line and let me know what you'd like to see in future versions. Better yet, fork the project over at GitHub and hack in the features yourself!
|
22
|
+
|
23
|
+
h2. Example Proto-Site
|
24
|
+
|
25
|
+
My own personal site/blog is generated with Jekyll.
|
26
|
+
|
27
|
+
The proto-site repo ("http://github.com/mojombo/tpw":http://github.com/mojombo/tpw)
|
28
|
+
is converted into the actual site ("http://tom.preston-werner.com/":http://tom.preston-werner.com)
|
29
|
+
|
30
|
+
h2. Install and Run
|
31
|
+
|
32
|
+
This is beta software. You will need to download the source
|
33
|
+
and run the software from there.
|
34
|
+
|
35
|
+
$ sudo gem install RedCloth
|
36
|
+
$ sudo gem install liquid
|
37
|
+
|
38
|
+
$ git clone git://github.com/mojombo/jekyll
|
39
|
+
$ cd jekyll
|
40
|
+
$ bin/jekyll /path/to/proto/site /path/to/place/generated/site
|
41
|
+
|
42
|
+
h2. License
|
43
|
+
|
44
|
+
(The MIT License)
|
45
|
+
|
46
|
+
Copyright (c) 2008 Tom Preston-Werner
|
47
|
+
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
49
|
+
a copy of this software and associated documentation files (the
|
50
|
+
'Software'), to deal in the Software without restriction, including
|
51
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
52
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
53
|
+
permit persons to whom the Software is furnished to do so, subject to
|
54
|
+
the following conditions:
|
55
|
+
|
56
|
+
The above copyright notice and this permission notice shall be
|
57
|
+
included in all copies or substantial portions of the Software.
|
58
|
+
|
59
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
60
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
61
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
62
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
63
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
64
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
65
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
require 'lib/jekyll'
|
4
|
+
|
5
|
+
Hoe.new('jekyll', Jekyll::VERSION) do |p|
|
6
|
+
# p.rubyforge_name = 'jekyllx' # if different than lowercase project name
|
7
|
+
p.developer('Tom Preston-Werner', 'tom@mojombo.com')
|
8
|
+
p.summary = "Jekyll is a simple, blog aware, static site generator."
|
9
|
+
p.extra_deps = ['RedCloth', 'liquid']
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Open an irb session preloaded with this library"
|
13
|
+
task :console do
|
14
|
+
sh "irb -rubygems -r ./lib/jekyll.rb"
|
15
|
+
end
|
data/bin/jekyll
ADDED
data/jekyll.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{jekyll}
|
3
|
+
s.version = "0.1.1"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Tom Preston-Werner"]
|
7
|
+
s.date = %q{2008-11-22}
|
8
|
+
s.default_executable = %q{jekyll}
|
9
|
+
s.email = ["tom@mojombo.com"]
|
10
|
+
s.executables = ["jekyll"]
|
11
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
|
12
|
+
s.files = ["History.txt", "Manifest.txt", "README.textile", "Rakefile", "bin/jekyll", "jekyll.gemspec", "lib/jekyll.rb", "lib/jekyll/convertible.rb", "lib/jekyll/filters.rb", "lib/jekyll/layout.rb", "lib/jekyll/page.rb", "lib/jekyll/post.rb", "lib/jekyll/site.rb", "test/dest/2008/10/18/foo-bar.html", "test/dest/2008/11/21/complex.html", "test/dest/css/screen.css", "test/dest/index.html", "test/helper.rb", "test/source/_layouts/default.html", "test/source/_layouts/simple.html", "test/source/_posts/2008-10-18-foo-bar.textile", "test/source/_posts/2008-11-21-complex.textile", "test/source/css/screen.css", "test/source/index.html", "test/suite.rb", "test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.rdoc_options = ["--main", "README.txt"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubyforge_project = %q{jekyll}
|
17
|
+
s.rubygems_version = %q{1.3.0}
|
18
|
+
s.summary = %q{Jekyll is a simple, blog aware, static site generator.}
|
19
|
+
s.test_files = ["test/test_jekyll.rb", "test/test_post.rb", "test/test_site.rb"]
|
20
|
+
|
21
|
+
if s.respond_to? :specification_version then
|
22
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
23
|
+
s.specification_version = 2
|
24
|
+
|
25
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
26
|
+
s.add_runtime_dependency(%q<RedCloth>, [">= 0"])
|
27
|
+
s.add_runtime_dependency(%q<liquid>, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<hoe>, [">= 1.8.0"])
|
29
|
+
else
|
30
|
+
s.add_dependency(%q<RedCloth>, [">= 0"])
|
31
|
+
s.add_dependency(%q<liquid>, [">= 0"])
|
32
|
+
s.add_dependency(%q<hoe>, [">= 1.8.0"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<RedCloth>, [">= 0"])
|
36
|
+
s.add_dependency(%q<liquid>, [">= 0"])
|
37
|
+
s.add_dependency(%q<hoe>, [">= 1.8.0"])
|
38
|
+
end
|
39
|
+
end
|
data/lib/jekyll.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
2
|
+
|
3
|
+
# rubygems
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
# core
|
7
|
+
require 'fileutils'
|
8
|
+
require 'time'
|
9
|
+
|
10
|
+
# stdlib
|
11
|
+
|
12
|
+
# 3rd party
|
13
|
+
require 'liquid'
|
14
|
+
require 'redcloth'
|
15
|
+
|
16
|
+
# internal requires
|
17
|
+
require 'jekyll/site'
|
18
|
+
require 'jekyll/convertible'
|
19
|
+
require 'jekyll/layout'
|
20
|
+
require 'jekyll/page'
|
21
|
+
require 'jekyll/post'
|
22
|
+
require 'jekyll/filters'
|
23
|
+
|
24
|
+
module Jekyll
|
25
|
+
VERSION = '0.1.1'
|
26
|
+
|
27
|
+
def self.process(source, dest)
|
28
|
+
Jekyll::Site.new(source, dest).process
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Jekyll
|
2
|
+
module Convertible
|
3
|
+
# Read the YAML frontmatter
|
4
|
+
# +base+ is the String path to the dir containing the file
|
5
|
+
# +name+ is the String filename of the file
|
6
|
+
#
|
7
|
+
# Returns nothing
|
8
|
+
def read_yaml(base, name)
|
9
|
+
self.content = File.read(File.join(base, name))
|
10
|
+
|
11
|
+
if self.content =~ /^(---\n.*?)\n---\n/m
|
12
|
+
self.content = self.content[($1.size + 5)..-1]
|
13
|
+
|
14
|
+
self.data = YAML.load($1)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Transform the contents based on the file extension.
|
19
|
+
#
|
20
|
+
# Returns nothing
|
21
|
+
def transform
|
22
|
+
if self.ext == ".textile"
|
23
|
+
self.ext = ".html"
|
24
|
+
self.content = RedCloth.new(self.content).to_html
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add any necessary layouts to this post
|
29
|
+
# +layouts+ is a Hash of {"name" => "layout"}
|
30
|
+
# +site_payload+ is the site payload hash
|
31
|
+
#
|
32
|
+
# Returns nothing
|
33
|
+
def do_layout(payload, layouts, site_payload)
|
34
|
+
# construct payload
|
35
|
+
payload = payload.merge(site_payload)
|
36
|
+
|
37
|
+
# render content
|
38
|
+
self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
|
39
|
+
|
40
|
+
# output keeps track of what will finally be written
|
41
|
+
self.output = self.content
|
42
|
+
|
43
|
+
# recursively render layouts
|
44
|
+
layout = layouts[self.data["layout"]]
|
45
|
+
while layout
|
46
|
+
payload = payload.merge({"content" => self.output, "page" => self.data})
|
47
|
+
self.output = Liquid::Template.parse(layout.content).render(payload, [Jekyll::Filters])
|
48
|
+
|
49
|
+
layout = layouts[layout.data["layout"]]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class Layout
|
4
|
+
include Convertible
|
5
|
+
|
6
|
+
attr_accessor :ext
|
7
|
+
attr_accessor :data, :content
|
8
|
+
|
9
|
+
# Initialize a new Layout.
|
10
|
+
# +base+ is the String path to the <source>
|
11
|
+
# +name+ is the String filename of the post file
|
12
|
+
#
|
13
|
+
# Returns <Page>
|
14
|
+
def initialize(base, name)
|
15
|
+
@base = base
|
16
|
+
@name = name
|
17
|
+
|
18
|
+
self.data = {}
|
19
|
+
|
20
|
+
self.process(name)
|
21
|
+
self.read_yaml(base, name)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Extract information from the layout filename
|
25
|
+
# +name+ is the String filename of the layout file
|
26
|
+
#
|
27
|
+
# Returns nothing
|
28
|
+
def process(name)
|
29
|
+
self.ext = File.extname(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Add any necessary layouts to this post
|
33
|
+
# +layouts+ is a Hash of {"name" => "layout"}
|
34
|
+
# +site_payload+ is the site payload hash
|
35
|
+
#
|
36
|
+
# Returns nothing
|
37
|
+
def add_layout(layouts, site_payload)
|
38
|
+
payload = {"page" => self.data}.merge(site_payload)
|
39
|
+
self.content = Liquid::Template.parse(self.content).render(payload, [Jekyll::Filters])
|
40
|
+
|
41
|
+
layout = layouts[self.data["layout"]] || self.content
|
42
|
+
payload = {"content" => self.content, "page" => self.data}
|
43
|
+
|
44
|
+
self.content = Liquid::Template.parse(layout).render(payload, [Jekyll::Filters])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/lib/jekyll/page.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class Page
|
4
|
+
include Convertible
|
5
|
+
|
6
|
+
attr_accessor :ext
|
7
|
+
attr_accessor :data, :content, :output
|
8
|
+
|
9
|
+
# Initialize a new Page.
|
10
|
+
# +base+ is the String path to the <source>
|
11
|
+
# +dir+ is the String path between <source> and the file
|
12
|
+
# +name+ is the String filename of the post file
|
13
|
+
#
|
14
|
+
# Returns <Page>
|
15
|
+
def initialize(base, dir, name)
|
16
|
+
@base = base
|
17
|
+
@dir = dir
|
18
|
+
@name = name
|
19
|
+
|
20
|
+
self.data = {}
|
21
|
+
|
22
|
+
self.process(name)
|
23
|
+
self.read_yaml(File.join(base, dir), name)
|
24
|
+
self.transform
|
25
|
+
end
|
26
|
+
|
27
|
+
# Extract information from the post filename
|
28
|
+
# +name+ is the String filename of the post file
|
29
|
+
#
|
30
|
+
# Returns nothing
|
31
|
+
def process(name)
|
32
|
+
self.ext = File.extname(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add any necessary layouts to this post
|
36
|
+
# +layouts+ is a Hash of {"name" => "layout"}
|
37
|
+
# +site_payload+ is the site payload hash
|
38
|
+
#
|
39
|
+
# Returns nothing
|
40
|
+
def add_layout(layouts, site_payload)
|
41
|
+
payload = {"page" => self.data}
|
42
|
+
do_layout(payload, layouts, site_payload)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Write the generated page file to the destination directory.
|
46
|
+
# +dest+ is the String path to the destination dir
|
47
|
+
#
|
48
|
+
# Returns nothing
|
49
|
+
def write(dest)
|
50
|
+
FileUtils.mkdir_p(File.join(dest, @dir))
|
51
|
+
|
52
|
+
name = @name
|
53
|
+
if self.ext != ""
|
54
|
+
name = @name.split(".")[0..-2].join('.') + self.ext
|
55
|
+
end
|
56
|
+
|
57
|
+
path = File.join(dest, @dir, name)
|
58
|
+
File.open(path, 'w') do |f|
|
59
|
+
f.write(self.output)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
data/lib/jekyll/post.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Jekyll
|
2
|
+
|
3
|
+
class Post
|
4
|
+
include Comparable
|
5
|
+
include Convertible
|
6
|
+
|
7
|
+
MATCHER = /^(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
|
8
|
+
|
9
|
+
# Post name validator. Post filenames must be like:
|
10
|
+
# 2008-11-05-my-awesome-post.textile
|
11
|
+
#
|
12
|
+
# Returns <Bool>
|
13
|
+
def self.valid?(name)
|
14
|
+
name =~ MATCHER
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :date, :slug, :ext
|
18
|
+
attr_accessor :data, :content, :output
|
19
|
+
|
20
|
+
# Initialize this Post instance.
|
21
|
+
# +base+ is the String path to the dir containing the post file
|
22
|
+
# +name+ is the String filename of the post file
|
23
|
+
#
|
24
|
+
# Returns <Post>
|
25
|
+
def initialize(base, name)
|
26
|
+
@base = base
|
27
|
+
@name = name
|
28
|
+
|
29
|
+
self.process(name)
|
30
|
+
self.read_yaml(base, name)
|
31
|
+
self.transform
|
32
|
+
end
|
33
|
+
|
34
|
+
# Spaceship is based on Post#date
|
35
|
+
#
|
36
|
+
# Returns -1, 0, 1
|
37
|
+
def <=>(other)
|
38
|
+
self.date <=> other.date
|
39
|
+
end
|
40
|
+
|
41
|
+
# Extract information from the post filename
|
42
|
+
# +name+ is the String filename of the post file
|
43
|
+
#
|
44
|
+
# Returns nothing
|
45
|
+
def process(name)
|
46
|
+
m, date, slug, ext = *name.match(MATCHER)
|
47
|
+
self.date = Time.parse(date)
|
48
|
+
self.slug = slug
|
49
|
+
self.ext = ext
|
50
|
+
end
|
51
|
+
|
52
|
+
# The generated directory into which the post will be placed
|
53
|
+
# upon generation. e.g. "/2008/11/05/"
|
54
|
+
#
|
55
|
+
# Returns <String>
|
56
|
+
def dir
|
57
|
+
self.date.strftime("/%Y/%m/%d/")
|
58
|
+
end
|
59
|
+
|
60
|
+
# The generated relative url of this post
|
61
|
+
# e.g. /2008/11/05/my-awesome-post.html
|
62
|
+
#
|
63
|
+
# Returns <String>
|
64
|
+
def url
|
65
|
+
self.dir + self.slug + ".html"
|
66
|
+
end
|
67
|
+
|
68
|
+
# The UID for this post (useful in feeds)
|
69
|
+
# e.g. /2008/11/05/my-awesome-post
|
70
|
+
#
|
71
|
+
# Returns <String>
|
72
|
+
def id
|
73
|
+
self.dir + self.slug
|
74
|
+
end
|
75
|
+
|
76
|
+
# Calculate related posts.
|
77
|
+
#
|
78
|
+
# Returns [<Post>]
|
79
|
+
def related_posts(posts)
|
80
|
+
related = posts - [self]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Add any necessary layouts to this post
|
84
|
+
# +layouts+ is a Hash of {"name" => "layout"}
|
85
|
+
# +site_payload+ is the site payload hash
|
86
|
+
#
|
87
|
+
# Returns nothing
|
88
|
+
def add_layout(layouts, site_payload)
|
89
|
+
# construct post payload
|
90
|
+
related = related_posts(site_payload["site"]["posts"])
|
91
|
+
payload = {"page" => self.to_liquid.merge(self.data), "related_posts" => related}
|
92
|
+
do_layout(payload, layouts, site_payload)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Write the generated post file to the destination directory.
|
96
|
+
# +dest+ is the String path to the destination dir
|
97
|
+
#
|
98
|
+
# Returns nothing
|
99
|
+
def write(dest)
|
100
|
+
FileUtils.mkdir_p(File.join(dest, self.dir))
|
101
|
+
|
102
|
+
path = File.join(dest, self.url)
|
103
|
+
File.open(path, 'w') do |f|
|
104
|
+
f.write(self.output)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convert this post into a Hash for use in Liquid templates.
|
109
|
+
#
|
110
|
+
# Returns <Hash>
|
111
|
+
def to_liquid
|
112
|
+
{ "title" => self.data["title"] || "",
|
113
|
+
"url" => self.url,
|
114
|
+
"date" => self.date,
|
115
|
+
"id" => self.id,
|
116
|
+
"content" => self.content }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
data/lib/jekyll/site.rb
ADDED
@@ -0,0 +1,113 @@
|
|
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.read_posts
|
28
|
+
self.write_posts
|
29
|
+
self.transform_pages
|
30
|
+
end
|
31
|
+
|
32
|
+
# Read all the files in <source>/_layouts into memory for
|
33
|
+
# later use.
|
34
|
+
#
|
35
|
+
# Returns nothing
|
36
|
+
def read_layouts
|
37
|
+
base = File.join(self.source, "_layouts")
|
38
|
+
entries = Dir.entries(base)
|
39
|
+
entries = entries.reject { |e| File.directory?(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 <source>/posts and create a new Post
|
50
|
+
# object with each one.
|
51
|
+
#
|
52
|
+
# Returns nothing
|
53
|
+
def read_posts
|
54
|
+
base = File.join(self.source, "_posts")
|
55
|
+
entries = Dir.entries(base)
|
56
|
+
entries = entries.reject { |e| File.directory?(e) }
|
57
|
+
|
58
|
+
entries.each do |f|
|
59
|
+
self.posts << Post.new(base, f) if Post.valid?(f)
|
60
|
+
end
|
61
|
+
|
62
|
+
self.posts.sort!
|
63
|
+
rescue Errno::ENOENT => e
|
64
|
+
# ignore missing layout dir
|
65
|
+
end
|
66
|
+
|
67
|
+
# Write each post to <dest>/<year>/<month>/<day>/<slug>
|
68
|
+
#
|
69
|
+
# Returns nothing
|
70
|
+
def write_posts
|
71
|
+
self.posts.each do |post|
|
72
|
+
post.add_layout(self.layouts, site_payload)
|
73
|
+
post.write(self.dest)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Recursively transform and write all non-post pages to <dest>/
|
78
|
+
# +dir+ is the String path part representing the path from
|
79
|
+
# <source> to the currently processing dir (default '')
|
80
|
+
#
|
81
|
+
# Returns nothing
|
82
|
+
def transform_pages(dir = '')
|
83
|
+
base = File.join(self.source, dir)
|
84
|
+
entries = Dir.entries(base)
|
85
|
+
entries = entries.reject { |e| ['.', '_'].include?(e[0..0]) }
|
86
|
+
|
87
|
+
entries.each do |f|
|
88
|
+
if File.directory?(File.join(base, f))
|
89
|
+
transform_pages(File.join(dir, f))
|
90
|
+
else
|
91
|
+
first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
|
92
|
+
|
93
|
+
if first3 == "---"
|
94
|
+
page = Page.new(self.source, dir, f)
|
95
|
+
page.add_layout(self.layouts, site_payload)
|
96
|
+
page.write(self.dest)
|
97
|
+
else
|
98
|
+
FileUtils.mkdir_p(File.join(self.dest, dir))
|
99
|
+
FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# The Hash payload containing site-wide data
|
106
|
+
#
|
107
|
+
# Returns {"site" => {"time" => <Time>, "posts" => [<Post>]}}
|
108
|
+
def site_payload
|
109
|
+
{"site" => {"time" => Time.now, "posts" => self.posts.sort.reverse}}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
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
|
+
}
|
data/test/suite.rb
ADDED
data/test/test_jekyll.rb
ADDED
File without changes
|
data/test/test_post.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class TestPost < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_valid
|
9
|
+
assert Post.valid?("2008-10-19-foo-bar.textile")
|
10
|
+
assert !Post.valid?("blah")
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_process
|
14
|
+
p = Post.allocate
|
15
|
+
p.process("2008-10-19-foo-bar.textile")
|
16
|
+
|
17
|
+
assert_equal Time.parse("2008-10-19"), p.date
|
18
|
+
assert_equal "foo-bar", p.slug
|
19
|
+
assert_equal ".textile", p.ext
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_url
|
23
|
+
p = Post.allocate
|
24
|
+
p.process("2008-10-19-foo-bar.textile")
|
25
|
+
|
26
|
+
assert_equal "/2008/10/19/foo-bar.html", p.url
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_read_yaml
|
30
|
+
p = Post.allocate
|
31
|
+
p.read_yaml(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-10-18-foo-bar.textile")
|
32
|
+
|
33
|
+
assert_equal({"title" => "Foo Bar", "layout" => "default"}, p.data)
|
34
|
+
assert_equal "\nh1. {{ page.title }}\n\nBest *post* ever", p.content
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_transform
|
38
|
+
p = Post.allocate
|
39
|
+
p.process("2008-10-18-foo-bar.textile")
|
40
|
+
p.read_yaml(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-10-18-foo-bar.textile")
|
41
|
+
p.transform
|
42
|
+
|
43
|
+
assert_equal "<h1>{{ page.title }}</h1>\n\n\n\t<p>Best <strong>post</strong> ever</p>", p.content
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_add_layout
|
47
|
+
p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-10-18-foo-bar.textile")
|
48
|
+
layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
|
49
|
+
p.add_layout(layouts, {"site" => {"posts" => []}})
|
50
|
+
|
51
|
+
assert_equal "<<< <h1>Foo Bar</h1>\n\n\n\t<p>Best <strong>post</strong> ever</p> >>>", p.output
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_write
|
55
|
+
clear_dest
|
56
|
+
|
57
|
+
p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-10-18-foo-bar.textile")
|
58
|
+
layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
|
59
|
+
p.add_layout(layouts, {"site" => {"posts" => []}})
|
60
|
+
p.write(dest_dir)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_data
|
64
|
+
p = Post.new(File.join(File.dirname(__FILE__), *%w[source _posts]), "2008-11-21-complex.textile")
|
65
|
+
layouts = {"default" => Layout.new(File.join(File.dirname(__FILE__), *%w[source _layouts]), "simple.html")}
|
66
|
+
p.add_layout(layouts, {"site" => {"posts" => []}})
|
67
|
+
|
68
|
+
assert_equal "<<< <p>url: /2008/11/21/complex.html\ndate: Fri Nov 21 00:00:00 -0800 2008\nid: /2008/11/21/complex</p> >>>", p.output
|
69
|
+
end
|
70
|
+
end
|
data/test/test_site.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class TestSite < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
source = File.join(File.dirname(__FILE__), *%w[source])
|
6
|
+
@s = Site.new(source, dest_dir)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_site_init
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_read_layouts
|
14
|
+
@s.read_layouts
|
15
|
+
|
16
|
+
assert_equal ["default", "simple"].sort, @s.layouts.keys.sort
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_read_posts
|
20
|
+
@s.read_posts
|
21
|
+
|
22
|
+
assert_equal 2, @s.posts.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_write_posts
|
26
|
+
clear_dest
|
27
|
+
|
28
|
+
@s.process
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mojombo-jekyll
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Preston-Werner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-11-22 00:00:00 -08:00
|
13
|
+
default_executable: jekyll
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: RedCloth
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: liquid
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0"
|
32
|
+
version:
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: hoe
|
35
|
+
version_requirement:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.8.0
|
41
|
+
version:
|
42
|
+
description:
|
43
|
+
email:
|
44
|
+
- tom@mojombo.com
|
45
|
+
executables:
|
46
|
+
- jekyll
|
47
|
+
extensions: []
|
48
|
+
|
49
|
+
extra_rdoc_files:
|
50
|
+
- History.txt
|
51
|
+
- Manifest.txt
|
52
|
+
files:
|
53
|
+
- History.txt
|
54
|
+
- Manifest.txt
|
55
|
+
- README.textile
|
56
|
+
- Rakefile
|
57
|
+
- bin/jekyll
|
58
|
+
- jekyll.gemspec
|
59
|
+
- lib/jekyll.rb
|
60
|
+
- lib/jekyll/convertible.rb
|
61
|
+
- lib/jekyll/filters.rb
|
62
|
+
- lib/jekyll/layout.rb
|
63
|
+
- lib/jekyll/page.rb
|
64
|
+
- lib/jekyll/post.rb
|
65
|
+
- lib/jekyll/site.rb
|
66
|
+
- test/dest/2008/10/18/foo-bar.html
|
67
|
+
- test/dest/2008/11/21/complex.html
|
68
|
+
- test/dest/css/screen.css
|
69
|
+
- test/dest/index.html
|
70
|
+
- test/helper.rb
|
71
|
+
- test/source/_layouts/default.html
|
72
|
+
- test/source/_layouts/simple.html
|
73
|
+
- test/source/_posts/2008-10-18-foo-bar.textile
|
74
|
+
- test/source/_posts/2008-11-21-complex.textile
|
75
|
+
- test/source/css/screen.css
|
76
|
+
- test/source/index.html
|
77
|
+
- test/suite.rb
|
78
|
+
- test/test_jekyll.rb
|
79
|
+
- test/test_post.rb
|
80
|
+
- test/test_site.rb
|
81
|
+
has_rdoc: true
|
82
|
+
homepage:
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options:
|
85
|
+
- --main
|
86
|
+
- README.txt
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: "0"
|
94
|
+
version:
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: "0"
|
100
|
+
version:
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project: jekyll
|
104
|
+
rubygems_version: 1.2.0
|
105
|
+
signing_key:
|
106
|
+
specification_version: 2
|
107
|
+
summary: Jekyll is a simple, blog aware, static site generator.
|
108
|
+
test_files:
|
109
|
+
- test/test_jekyll.rb
|
110
|
+
- test/test_post.rb
|
111
|
+
- test/test_site.rb
|