pol-hyde 0.1.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 (74) hide show
  1. data/.gitignore +6 -0
  2. data/History.txt +151 -0
  3. data/README.textile +56 -0
  4. data/Rakefile +91 -0
  5. data/VERSION.yml +4 -0
  6. data/bin/hyde +150 -0
  7. data/features/create_sites.feature +46 -0
  8. data/features/embed_filters.feature +60 -0
  9. data/features/pagination.feature +40 -0
  10. data/features/permalinks.feature +65 -0
  11. data/features/post_data.feature +153 -0
  12. data/features/site_configuration.feature +63 -0
  13. data/features/site_data.feature +82 -0
  14. data/features/step_definitions/jekyll_steps.rb +136 -0
  15. data/features/support/env.rb +16 -0
  16. data/hyde.gemspec +138 -0
  17. data/lib/jekyll.rb +86 -0
  18. data/lib/jekyll/albino.rb +122 -0
  19. data/lib/jekyll/converters/csv.rb +26 -0
  20. data/lib/jekyll/converters/mephisto.rb +79 -0
  21. data/lib/jekyll/converters/mt.rb +59 -0
  22. data/lib/jekyll/converters/textpattern.rb +50 -0
  23. data/lib/jekyll/converters/typo.rb +49 -0
  24. data/lib/jekyll/converters/wordpress.rb +54 -0
  25. data/lib/jekyll/convertible.rb +78 -0
  26. data/lib/jekyll/core_ext.rb +30 -0
  27. data/lib/jekyll/engines.rb +69 -0
  28. data/lib/jekyll/filters.rb +47 -0
  29. data/lib/jekyll/layout.rb +36 -0
  30. data/lib/jekyll/page.rb +112 -0
  31. data/lib/jekyll/pager.rb +45 -0
  32. data/lib/jekyll/post.rb +270 -0
  33. data/lib/jekyll/site.rb +211 -0
  34. data/lib/jekyll/tags/highlight.rb +56 -0
  35. data/lib/jekyll/tags/include.rb +31 -0
  36. data/test/helper.rb +27 -0
  37. data/test/source/_includes/sig.markdown +3 -0
  38. data/test/source/_layouts/default.html +27 -0
  39. data/test/source/_layouts/simple.html +1 -0
  40. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  41. data/test/source/_posts/2008-02-02-published.textile +7 -0
  42. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  43. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  44. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  45. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  46. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  47. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  48. data/test/source/_posts/2009-01-27-category.textile +7 -0
  49. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  50. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  51. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  52. data/test/source/_posts/2009-06-03-haml-rocks.haml +11 -0
  53. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  54. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  55. data/test/source/_posts/foo-bar.textile +9 -0
  56. data/test/source/about.html +6 -0
  57. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  58. data/test/source/contacts.html +5 -0
  59. data/test/source/css/screen.css +76 -0
  60. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  61. data/test/source/index.html +22 -0
  62. data/test/source/sitemap.xml +23 -0
  63. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  64. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  65. data/test/suite.rb +9 -0
  66. data/test/test_configuration.rb +29 -0
  67. data/test/test_filters.rb +49 -0
  68. data/test/test_generated_site.rb +40 -0
  69. data/test/test_page.rb +98 -0
  70. data/test/test_pager.rb +47 -0
  71. data/test/test_post.rb +308 -0
  72. data/test/test_site.rb +85 -0
  73. data/test/test_tags.rb +117 -0
  74. metadata +193 -0
@@ -0,0 +1,211 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :config, :layouts, :posts, :categories, :exclude,
5
+ :source, :dest, :lsi, :pygments, :permalink_style, :tags
6
+
7
+ # Initialize the site
8
+ # +config+ is a Hash containing site configurations details
9
+ #
10
+ # Returns <Site>
11
+ def initialize(config)
12
+ self.config = config.clone
13
+
14
+ self.source = config['source']
15
+ self.dest = config['destination']
16
+ self.lsi = config['lsi']
17
+ self.pygments = config['pygments']
18
+ self.permalink_style = config['permalink'].to_sym
19
+ self.exclude = config['exclude'] || []
20
+
21
+ self.reset
22
+ self.setup
23
+ end
24
+
25
+ def reset
26
+ self.layouts = {}
27
+ self.posts = []
28
+ self.categories = Hash.new { |hash, key| hash[key] = [] }
29
+ self.tags = Hash.new { |hash, key| hash[key] = [] }
30
+ end
31
+
32
+ def setup
33
+ # Check to see if LSI is enabled.
34
+ require 'classifier' if self.lsi
35
+ Engines.setup(self.config)
36
+ end
37
+
38
+ # Do the actual work of processing the site and generating the
39
+ # real deal.
40
+ #
41
+ # Returns nothing
42
+ def process
43
+ self.reset
44
+ self.read_layouts
45
+ self.transform_pages
46
+ self.write_posts
47
+ end
48
+
49
+ # Read all the files in <source>/_layouts into memory for later use.
50
+ #
51
+ # Returns nothing
52
+ def read_layouts
53
+ base = File.join(self.source, "_layouts")
54
+ entries = []
55
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
56
+
57
+ entries.each do |f|
58
+ name = f.split(".")[0..-2].join(".")
59
+ self.layouts[name] = Layout.new(self, base, f)
60
+ end
61
+ rescue Errno::ENOENT => e
62
+ # ignore missing layout dir
63
+ end
64
+
65
+ # Read all the files in <base>/_posts and create a new Post object with each one.
66
+ #
67
+ # Returns nothing
68
+ def read_posts(dir)
69
+ base = File.join(self.source, dir, '_posts')
70
+ entries = []
71
+ Dir.chdir(base) { entries = filter_entries(Dir['**/*']) }
72
+
73
+ # first pass processes, but does not yet render post content
74
+ entries.each do |f|
75
+ post = Post.new(self, self.source, dir, f)
76
+ if post.valid? && post.published?
77
+ self.posts << post
78
+ post.categories.each { |c| self.categories[c] << post }
79
+ post.tags.each { |c| self.tags[c] << post }
80
+ end
81
+ end
82
+
83
+ self.posts.sort!
84
+
85
+ # second pass renders each post now that full site payload is available
86
+ self.posts.each do |post|
87
+ post.render(self.layouts, site_payload)
88
+ end
89
+
90
+ self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a} }
91
+ self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a} }
92
+ rescue Errno::ENOENT => e
93
+ # ignore missing layout dir
94
+ end
95
+
96
+ # Write each post to <dest>/<year>/<month>/<day>/<slug>
97
+ #
98
+ # Returns nothing
99
+ def write_posts
100
+ self.posts.each do |post|
101
+ post.write(self.dest)
102
+ end
103
+ end
104
+
105
+ # Copy all regular files from <source> to <dest>/ ignoring
106
+ # any files/directories that are hidden or backup files (start
107
+ # with "." or "#" or end with "~") or contain site content (start with "_")
108
+ # unless they are "_posts" directories or web server files such as
109
+ # '.htaccess'
110
+ # The +dir+ String is a relative path used to call this method
111
+ # recursively as it descends through directories
112
+ #
113
+ # Returns nothing
114
+ def transform_pages(dir = '')
115
+ base = File.join(self.source, dir)
116
+ entries = filter_entries(Dir.entries(base))
117
+ directories = entries.select { |e| File.directory?(File.join(base, e)) }
118
+ files = entries.reject { |e| File.directory?(File.join(base, e)) || File.symlink?(File.join(base, e)) }
119
+
120
+ # we need to make sure to process _posts *first* otherwise they
121
+ # might not be available yet to other templates as {{ site.posts }}
122
+ if directories.include?('_posts')
123
+ directories.delete('_posts')
124
+ read_posts(dir)
125
+ end
126
+
127
+ [directories, files].each do |entries|
128
+ entries.each do |f|
129
+ if File.directory?(File.join(base, f))
130
+ next if self.dest.sub(/\/$/, '') == File.join(base, f)
131
+ transform_pages(File.join(dir, f))
132
+ elsif Pager.pagination_enabled?(self.config, f)
133
+ paginate_posts(f, dir)
134
+ else
135
+ first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
136
+ if first3 == "---"
137
+ # file appears to have a YAML header so process it as a page
138
+ page = Page.new(self, self.source, dir, f)
139
+ page.render(self.layouts, site_payload)
140
+ page.write(self.dest)
141
+ else
142
+ # otherwise copy the file without transforming it
143
+ FileUtils.mkdir_p(File.join(self.dest, dir))
144
+ FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # Constructs a hash map of Posts indexed by the specified Post attribute
152
+ #
153
+ # Returns {post_attr => [<Post>]}
154
+ def post_attr_hash(post_attr)
155
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
156
+ # then sort each array in reverse order
157
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
158
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
159
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
160
+ return hash
161
+ end
162
+
163
+ # The Hash payload containing site-wide data
164
+ #
165
+ # Returns {"site" => {"time" => <Time>,
166
+ # "posts" => [<Post>],
167
+ # "categories" => [<Post>]}
168
+ def site_payload
169
+ {"site" => self.config.merge({
170
+ "time" => Time.now,
171
+ "posts" => self.posts.sort { |a,b| b <=> a },
172
+ "categories" => post_attr_hash('categories'),
173
+ "tags" => post_attr_hash('tags')})}
174
+ end
175
+
176
+ # Filter out any files/directories that are hidden or backup files (start
177
+ # with "." or "#" or end with "~") or contain site content (start with "_")
178
+ # unless they are "_posts" directories or web server files such as
179
+ # '.htaccess'
180
+ def filter_entries(entries)
181
+ entries = entries.reject do |e|
182
+ unless ['_posts', '.htaccess'].include?(e)
183
+ ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
184
+ end
185
+ end
186
+ end
187
+
188
+ # Paginates the blog's posts. Renders the index.html file into paginated directories, ie: page2, page3...
189
+ # and adds more wite-wide data
190
+ #
191
+ # {"paginator" => { "page" => <Number>,
192
+ # "per_page" => <Number>,
193
+ # "posts" => [<Post>],
194
+ # "total_posts" => <Number>,
195
+ # "total_pages" => <Number>,
196
+ # "previous_page" => <Number>,
197
+ # "next_page" => <Number> }}
198
+ def paginate_posts(file, dir)
199
+ all_posts = self.posts.sort { |a,b| b <=> a }
200
+ pages = Pager.calculate_pages(all_posts, self.config['paginate'].to_i)
201
+ pages += 1
202
+ (1..pages).each do |num_page|
203
+ pager = Pager.new(self.config, num_page, all_posts, pages)
204
+ page = Page.new(self, self.source, dir, file)
205
+ page.render(self.layouts, site_payload.merge({'paginator' => pager.to_hash}))
206
+ suffix = "page#{num_page}" if num_page > 1
207
+ page.write(self.dest, suffix)
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,56 @@
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 context["content_type"] == "markdown"
34
+ return "\n" + Albino.new(code, @lang).to_s(@options) + "\n"
35
+ elsif context["content_type"] == "textile"
36
+ return "<notextile>" + Albino.new(code, @lang).to_s(@options) + "</notextile>"
37
+ else
38
+ return Albino.new(code, @lang).to_s(@options)
39
+ end
40
+ end
41
+
42
+ def render_codehighlighter(context, code)
43
+ #The div is required because RDiscount blows ass
44
+ <<-HTML
45
+ <div>
46
+ <pre>
47
+ <code class='#{@lang}'>#{h(code).strip}</code>
48
+ </pre>
49
+ </div>
50
+ HTML
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ 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.2.1'
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>
@@ -0,0 +1 @@
1
+ <<< {{ content }} >>>
@@ -0,0 +1,8 @@
1
+ ---
2
+ layout: default
3
+ title: Not published!
4
+ published: false
5
+ category: publish_test
6
+ ---
7
+
8
+ This should *not* be published!
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Published
4
+ category: publish_test
5
+ ---
6
+
7
+ This should be published.
@@ -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,10 @@
1
+ ---
2
+ layout: default
3
+ title: Array categories in YAML
4
+ categories:
5
+ - foo
6
+ - bar
7
+ - baz
8
+ ---
9
+
10
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Categories in YAML
4
+ categories: foo bar baz
5
+ ---
6
+
7
+ Best *post* ever
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: default
3
+ title: Category in YAML
4
+ category: foo
5
+ ---
6
+
7
+ Best *post* ever