dysinger-jekyll 0.4.0

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 (42) hide show
  1. data/History.txt +91 -0
  2. data/README.textile +475 -0
  3. data/VERSION.yml +4 -0
  4. data/bin/jekyll +136 -0
  5. data/lib/jekyll.rb +67 -0
  6. data/lib/jekyll/albino.rb +116 -0
  7. data/lib/jekyll/converters/csv.rb +26 -0
  8. data/lib/jekyll/converters/mephisto.rb +79 -0
  9. data/lib/jekyll/converters/mt.rb +59 -0
  10. data/lib/jekyll/converters/textpattern.rb +50 -0
  11. data/lib/jekyll/converters/typo.rb +49 -0
  12. data/lib/jekyll/converters/wordpress.rb +54 -0
  13. data/lib/jekyll/convertible.rb +71 -0
  14. data/lib/jekyll/core_ext.rb +22 -0
  15. data/lib/jekyll/filters.rb +39 -0
  16. data/lib/jekyll/layout.rb +33 -0
  17. data/lib/jekyll/page.rb +64 -0
  18. data/lib/jekyll/post.rb +174 -0
  19. data/lib/jekyll/site.rb +167 -0
  20. data/lib/jekyll/tags/highlight.rb +53 -0
  21. data/lib/jekyll/tags/include.rb +31 -0
  22. data/test/helper.rb +13 -0
  23. data/test/source/_includes/sig.markdown +3 -0
  24. data/test/source/_layouts/default.html +27 -0
  25. data/test/source/_layouts/simple.html +1 -0
  26. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  27. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  28. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  29. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  30. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  31. data/test/source/css/screen.css +76 -0
  32. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  33. data/test/source/index.html +22 -0
  34. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  35. data/test/suite.rb +9 -0
  36. data/test/test_filters.rb +37 -0
  37. data/test/test_generated_site.rb +22 -0
  38. data/test/test_jekyll.rb +0 -0
  39. data/test/test_post.rb +113 -0
  40. data/test/test_site.rb +33 -0
  41. data/test/test_tags.rb +31 -0
  42. metadata +205 -0
@@ -0,0 +1,167 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :source, :dest
5
+ attr_accessor :layouts, :posts, :categories
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
+ self.categories = Hash.new { |hash, key| hash[key] = Array.new }
20
+ end
21
+
22
+ # Do the actual work of processing the site and generating the
23
+ # real deal.
24
+ #
25
+ # Returns nothing
26
+ def process
27
+ self.read_layouts
28
+ self.transform_pages
29
+ self.write_posts
30
+ end
31
+
32
+ # Read all the files in <source>/_layouts except backup files
33
+ # (end with "~") into memory for 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| e[-1..-1] == '~' }
40
+ entries = entries.reject { |e| File.directory?(File.join(base, e)) }
41
+
42
+ entries.each do |f|
43
+ name = f.split(".")[0..-2].join(".")
44
+ self.layouts[name] = Layout.new(base, f)
45
+ end
46
+ rescue Errno::ENOENT => e
47
+ # ignore missing layout dir
48
+ 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.
52
+ #
53
+ # Returns nothing
54
+ def read_posts(dir)
55
+ base = File.join(self.source, dir, '_posts')
56
+
57
+ 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)) }
61
+
62
+ # first pass processes, but does not yet render post content
63
+ entries.each do |f|
64
+ 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 }
68
+ end
69
+ end
70
+
71
+ # second pass renders each post now that full site payload is available
72
+ self.posts.each do |post|
73
+ post.render(self.layouts, site_payload)
74
+ end
75
+
76
+ self.posts.sort!
77
+ self.categories.values.map { |cats| cats.sort! { |a, b| b <=> a} }
78
+ rescue Errno::ENOENT => e
79
+ # ignore missing layout dir
80
+ end
81
+
82
+ # Write each post to <dest>/<year>/<month>/<day>/<slug>
83
+ #
84
+ # Returns nothing
85
+ def write_posts
86
+ self.posts.each do |post|
87
+ post.write(self.dest)
88
+ end
89
+ end
90
+
91
+ # Copy all regular files from <source> to <dest>/ ignoring
92
+ # any files/directories that are hidden or backup files (start
93
+ # with "." or end with "~") or contain site content (start with "_")
94
+ # unless they are "_posts" directories or web server files such as
95
+ # '.htaccess'
96
+ # The +dir+ String is a relative path used to call this method
97
+ # recursively as it descends through directories
98
+ #
99
+ # Returns nothing
100
+ def transform_pages(dir = '')
101
+ 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
107
+ directories = entries.select { |e| File.directory?(File.join(base, e)) }
108
+ files = entries.reject { |e| File.directory?(File.join(base, e)) }
109
+
110
+ # we need to make sure to process _posts *first* otherwise they
111
+ # might not be available yet to other templates as {{ site.posts }}
112
+ if entries.include?('_posts')
113
+ entries.delete('_posts')
114
+ read_posts(dir)
115
+ end
116
+ [directories, files].each do |entries|
117
+ entries.each do |f|
118
+ if File.directory?(File.join(base, f))
119
+ next if self.dest.sub(/\/$/, '') == File.join(base, f)
120
+ transform_pages(File.join(dir, f))
121
+ else
122
+ first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
123
+
124
+ if first3 == "---"
125
+ # file appears to have a YAML header so process it as a page
126
+ page = Page.new(self.source, dir, f)
127
+ page.render(self.layouts, site_payload)
128
+ page.write(self.dest)
129
+ else
130
+ # otherwise copy the file without transforming it
131
+ FileUtils.mkdir_p(File.join(self.dest, dir))
132
+ FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ # Constructs a hash map of Posts indexed by the specified Post attribute
140
+ #
141
+ # Returns {post_attr => [<Post>]}
142
+ def post_attr_hash(post_attr)
143
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
144
+ # then sort each array in reverse order
145
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
146
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
147
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
148
+ return hash
149
+ end
150
+
151
+ # The Hash payload containing site-wide data
152
+ #
153
+ # Returns {"site" => {"time" => <Time>,
154
+ # "posts" => [<Post>],
155
+ # "categories" => [<Post>],
156
+ # "topics" => [<Post>] }}
157
+ def site_payload
158
+ {"site" => {
159
+ "time" => Time.now,
160
+ "posts" => self.posts.sort { |a,b| b <=> a },
161
+ "categories" => post_attr_hash('categories'),
162
+ "topics" => post_attr_hash('topics')
163
+ }}
164
+ end
165
+ end
166
+
167
+ end
@@ -0,0 +1,53 @@
1
+ module Jekyll
2
+
3
+ class HighlightBlock < Liquid::Block
4
+ include Liquid::StandardFilters
5
+ # we need a language, but the linenos argument is optional.
6
+ SYNTAX = /(\w+)\s?(:?linenos)?\s?/
7
+
8
+ def initialize(tag_name, markup, tokens)
9
+ super
10
+ if markup =~ SYNTAX
11
+ @lang = $1
12
+ if defined? $2
13
+ # additional options to pass to Albino.
14
+ @options = { 'O' => 'linenos=inline' }
15
+ else
16
+ @options = {}
17
+ end
18
+ else
19
+ raise SyntaxError.new("Syntax Error in 'highlight' - Valid syntax: highlight <lang> [linenos]")
20
+ end
21
+ end
22
+
23
+ def render(context)
24
+ if Jekyll.pygments
25
+ render_pygments(context, super.to_s)
26
+ else
27
+ render_codehighlighter(context, super.to_s)
28
+ end
29
+ end
30
+
31
+ def render_pygments(context, code)
32
+ if Jekyll.content_type == :markdown
33
+ return "\n" + Albino.new(code, @lang).to_s(@options) + "\n"
34
+ else
35
+ "<notextile>" + Albino.new(code, @lang).to_s(@options) + "</notextile>"
36
+ end
37
+ end
38
+
39
+ def render_codehighlighter(context, code)
40
+ #The div is required because RDiscount blows ass
41
+ <<-HTML
42
+ <div>
43
+ <pre>
44
+ <code class='#{@lang}'>#{h(code).strip}</code>
45
+ </pre>
46
+ </div>
47
+ HTML
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ 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)
@@ -0,0 +1,13 @@
1
+ require File.join(File.dirname(__FILE__), *%w[.. lib jekyll])
2
+
3
+ require 'test/unit'
4
+
5
+ include Jekyll
6
+
7
+ def dest_dir
8
+ File.join(File.dirname(__FILE__), *%w[dest])
9
+ end
10
+
11
+ def clear_dest
12
+ FileUtils.rm_rf(dest_dir)
13
+ 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>
@@ -0,0 +1 @@
1
+ <<< {{ content }} >>>
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Foo Bar
4
+ ---
5
+
6
+ h1. {{ page.title }}
7
+
8
+ Best *post* ever
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Complex
4
+ ---
5
+
6
+ url: {{ page.url }}
7
+ date: {{ page.date }}
8
+ id: {{ page.id }}
@@ -0,0 +1,9 @@
1
+ ---
2
+ title: Post with Permalink
3
+ permalink: my_category/permalinked-post
4
+ ---
5
+
6
+ h1. {{ page.title }}
7
+
8
+
9
+ <p>Best <strong>post</strong> ever</p>
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Include
4
+ ---
5
+
6
+ {% include sig.markdown %}
7
+
8
+ This _is_ cool
@@ -0,0 +1,6 @@
1
+ ---
2
+ layout: default
3
+ title: Categories
4
+ ---
5
+
6
+ Categories _should_ work
@@ -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,8 @@
1
+ ---
2
+ layout: default
3
+ title: Topical Post
4
+ ---
5
+
6
+ h1. {{ page.title }}
7
+
8
+ This post has a topic.
@@ -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>