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,47 @@
1
+ module Jekyll
2
+
3
+ module Filters
4
+ def textilize(input)
5
+ RedCloth.new(input).to_html
6
+ end
7
+
8
+ def date_to_string(date)
9
+ date.strftime("%d %b %Y")
10
+ end
11
+
12
+ def date_to_long_string(date)
13
+ date.strftime("%d %B %Y")
14
+ end
15
+
16
+ def date_to_xmlschema(date)
17
+ date.xmlschema
18
+ end
19
+
20
+ def xml_escape(input)
21
+ CGI.escapeHTML(input)
22
+ end
23
+
24
+ def cgi_escape(input)
25
+ CGI::escape(input)
26
+ end
27
+
28
+ def number_of_words(input)
29
+ input.split.length
30
+ end
31
+
32
+ def array_to_sentence_string(array)
33
+ connector = "and"
34
+ case array.length
35
+ when 0
36
+ ""
37
+ when 1
38
+ array[0].to_s
39
+ when 2
40
+ "#{array[0]} #{connector} #{array[1]}"
41
+ else
42
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ module Jekyll
2
+
3
+ class Layout
4
+ include Convertible
5
+
6
+ attr_accessor :site
7
+ attr_accessor :ext
8
+ attr_accessor :data, :content
9
+
10
+ # Initialize a new Layout.
11
+ # +site+ is the Site
12
+ # +base+ is the String path to the <source>
13
+ # +name+ is the String filename of the post file
14
+ #
15
+ # Returns <Page>
16
+ def initialize(site, base, name)
17
+ @site = site
18
+ @base = base
19
+ @name = name
20
+
21
+ self.data = {}
22
+
23
+ self.process(name)
24
+ self.read_yaml(base, name)
25
+ end
26
+
27
+ # Extract information from the layout filename
28
+ # +name+ is the String filename of the layout file
29
+ #
30
+ # Returns nothing
31
+ def process(name)
32
+ self.ext = File.extname(name)
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,112 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :site
7
+ attr_accessor :name, :ext, :basename
8
+ attr_accessor :data, :content, :output
9
+
10
+ # Initialize a new Page.
11
+ # +site+ is the Site
12
+ # +base+ is the String path to the <source>
13
+ # +dir+ is the String path between <source> and the file
14
+ # +name+ is the String filename of the file
15
+ #
16
+ # Returns <Page>
17
+ def initialize(site, base, dir, name)
18
+ @site = site
19
+ @base = base
20
+ @dir = dir
21
+ @name = name
22
+
23
+ self.process(name)
24
+ self.read_yaml(File.join(base, dir), name)
25
+ end
26
+
27
+ # The generated directory into which the page will be placed
28
+ # upon generation. This is derived from the permalink or, if
29
+ # permalink is absent, set to '/'
30
+ #
31
+ # Returns <String>
32
+ def dir
33
+ url[-1, 1] == '/' ? url : File.dirname(url)
34
+ end
35
+
36
+ # The full path and filename of the post.
37
+ # Defined in the YAML of the post body
38
+ # (Optional)
39
+ #
40
+ # Returns <String>
41
+ def permalink
42
+ self.data && self.data['permalink']
43
+ end
44
+
45
+ def template
46
+ if self.site.permalink_style == :pretty && !index?
47
+ "/:name/"
48
+ else
49
+ "/:name.html"
50
+ end
51
+ end
52
+
53
+ # The generated relative url of this page
54
+ # e.g. /about.html
55
+ #
56
+ # Returns <String>
57
+ def url
58
+ return permalink if permalink
59
+
60
+ @url ||= (ext == '.html') ? template.gsub(':name', basename) : "/#{name}"
61
+ end
62
+
63
+ # Extract information from the page filename
64
+ # +name+ is the String filename of the page file
65
+ #
66
+ # Returns nothing
67
+ def process(name)
68
+ self.ext = File.extname(name)
69
+ self.basename = name.split('.')[0..-2].first
70
+ end
71
+
72
+ # Add any necessary layouts to this post
73
+ # +layouts+ is a Hash of {"name" => "layout"}
74
+ # +site_payload+ is the site payload hash
75
+ #
76
+ # Returns nothing
77
+ def render(layouts, site_payload)
78
+ payload = {"page" => self.data}.deep_merge(site_payload)
79
+ do_layout(payload, layouts)
80
+ end
81
+
82
+ # Write the generated page file to the destination directory.
83
+ # +dest_prefix+ is the String path to the destination dir
84
+ # +dest_suffix+ is a suffix path to the destination dir
85
+ #
86
+ # Returns nothing
87
+ def write(dest_prefix, dest_suffix = nil)
88
+ dest = File.join(dest_prefix, @dir)
89
+ dest = File.join(dest, dest_suffix) if dest_suffix
90
+ FileUtils.mkdir_p(dest)
91
+
92
+ # The url needs to be unescaped in order to preserve the correct filename
93
+ path = File.join(dest, CGI.unescape(self.url))
94
+ if self.ext == '.html' && self.url[/\.html$/].nil?
95
+ FileUtils.mkdir_p(path)
96
+ path = File.join(path, "index.html")
97
+ end
98
+
99
+ File.open(path, 'w') do |f|
100
+ f.write(self.output)
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def index?
107
+ basename == 'index'
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,45 @@
1
+ module Jekyll
2
+ class Pager
3
+ attr_reader :page, :per_page, :posts, :total_posts, :total_pages, :previous_page, :next_page
4
+
5
+ def self.calculate_pages(all_posts, per_page)
6
+ num_pages = all_posts.size / per_page.to_i
7
+ num_pages.abs + 1 if all_posts.size % per_page.to_i != 0
8
+ num_pages
9
+ end
10
+
11
+ def self.pagination_enabled?(config, file)
12
+ file == 'index.html' && !config['paginate'].nil?
13
+ end
14
+
15
+ def initialize(config, page, all_posts, num_pages = nil)
16
+ @page = page
17
+ @per_page = config['paginate'].to_i
18
+ @total_pages = num_pages || Pager.calculate_pages(all_posts, @per_page)
19
+
20
+ if @page > @total_pages
21
+ raise RuntimeError, "page number can't be greater than total pages: #{@page} > #{@total_pages}"
22
+ end
23
+
24
+ init = (@page - 1) * @per_page
25
+ offset = (init + @per_page - 1) >= all_posts.size ? all_posts.size : (init + @per_page - 1)
26
+
27
+ @total_posts = all_posts.size
28
+ @posts = all_posts[init..offset]
29
+ @previous_page = @page != 1 ? @page - 1 : nil
30
+ @next_page = @page != @total_pages ? @page + 1 : nil
31
+ end
32
+
33
+ def to_hash
34
+ {
35
+ 'page' => page,
36
+ 'per_page' => per_page,
37
+ 'posts' => posts,
38
+ 'total_posts' => total_posts,
39
+ 'total_pages' => total_pages,
40
+ 'previous_page' => previous_page,
41
+ 'next_page' => next_page
42
+ }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,270 @@
1
+ module Jekyll
2
+
3
+ class Post
4
+ include Comparable
5
+ include Convertible
6
+
7
+ class << self
8
+ attr_accessor :lsi
9
+ end
10
+
11
+ attr_accessor :site, :date, :slug, :ext, :published, :data, :content, :output, :tags
12
+ attr_writer :categories
13
+
14
+ def categories
15
+ @categories ||= []
16
+ end
17
+
18
+ # Initialize this Post instance.
19
+ # +site+ is the Site
20
+ # +base+ is the String path to the dir containing the post file
21
+ # +name+ is the String filename of the post file
22
+ # +categories+ is an Array of Strings for the categories for this post
23
+ #
24
+ # Returns <Post>
25
+ def initialize(site, source, dir, name)
26
+ @site = site
27
+ @base = File.join(source, dir, '_posts')
28
+ @name = name
29
+
30
+ if File.file?(File.join(@base, name))
31
+ self.read_yaml(@base, name)
32
+ else
33
+ self.data = {}
34
+ end
35
+
36
+ self.categories = dir.split('/').reject { |x| x.empty? }
37
+
38
+ if self.data.has_key?('published') && self.data['published'] == false
39
+ self.published = false
40
+ else
41
+ self.published = true
42
+ end
43
+
44
+ if self.data.has_key?("tag")
45
+ self.tags = [self.data["tag"]]
46
+ elsif self.data.has_key?("tags")
47
+ self.tags = self.data['tags']
48
+ else
49
+ self.tags = []
50
+ end
51
+
52
+ if self.categories.empty?
53
+ if self.data.has_key?('category')
54
+ self.categories << self.data['category']
55
+ elsif self.data.has_key?('categories')
56
+ # Look for categories in the YAML-header, either specified as
57
+ # an array or a string.
58
+ if self.data['categories'].kind_of? String
59
+ self.categories = self.data['categories'].split
60
+ else
61
+ self.categories = self.data['categories']
62
+ end
63
+ end
64
+ end
65
+
66
+ self.process(name) if published?
67
+ end
68
+
69
+ # Spaceship is based on Post#date, slug
70
+ #
71
+ # Returns -1, 0, 1
72
+ def <=>(other)
73
+ cmp = self.date <=> other.date
74
+ if 0 == cmp
75
+ cmp = self.slug <=> other.slug
76
+ end
77
+ return cmp
78
+ end
79
+
80
+ # Returns <Bool>
81
+ def published?
82
+ published
83
+ end
84
+
85
+ # Post validator. Every post must have e.g. a title.
86
+ #
87
+ # Returns <Bool>
88
+ def valid?
89
+ (self.ext && self.slug && self.date && self.url) ? true : false
90
+ end
91
+
92
+ # Extract information from the post filename
93
+ # +name+ is the String filename of the post file
94
+ #
95
+ # Returns nothing
96
+ def process(nn)
97
+ name = nn.dup
98
+ if name.sub!(/(\d+-\d+-\d+)-/, '')
99
+ self.date = Time.parse($1)
100
+ else
101
+ if date = self.data["date"]
102
+ self.date = date.kind_of?(Time) ? date : Time.parse(date)
103
+ end
104
+ end
105
+ matcher = /^(.+\/)*(.*)(\.[^.]+)$/
106
+ m, cats, slug, self.ext = *name.match(matcher)
107
+ if self.data && self.data["title"]
108
+ self.slug = self.data["title"].split.collect { |i| i.downcase }.join("-")
109
+ else
110
+ self.slug = slug
111
+ end
112
+ end
113
+
114
+ # The generated directory into which the post will be placed
115
+ # upon generation. This is derived from the permalink or, if
116
+ # permalink is absent, set to the default date
117
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
118
+ #
119
+ # Returns <String>
120
+ def dir
121
+ File.dirname(url)
122
+ end
123
+
124
+ # The full path and filename of the post.
125
+ # Defined in the YAML of the post body
126
+ # (Optional)
127
+ #
128
+ # Returns <String>
129
+ def permalink
130
+ self.data && self.data['permalink']
131
+ end
132
+
133
+ def template
134
+ case self.site.permalink_style
135
+ when :pretty
136
+ "/:categories/:year/:month/:day/:title/"
137
+ when :none
138
+ "/:categories/:title.html"
139
+ when :date
140
+ "/:categories/:year/:month/:day/:title.html"
141
+ else
142
+ self.site.permalink_style.to_s
143
+ end
144
+ end
145
+
146
+ # The generated relative url of this post
147
+ # e.g. /2008/11/05/my-awesome-post.html
148
+ #
149
+ # Returns <String>
150
+ def url
151
+ return permalink if permalink
152
+
153
+ @url ||= {
154
+ "year" => date.strftime("%Y"),
155
+ "month" => date.strftime("%m"),
156
+ "day" => date.strftime("%d"),
157
+ "title" => CGI.escape(slug),
158
+ "categories" => categories.sort.join('/')
159
+ }.inject(template) { |result, token|
160
+ result.gsub(/:#{token.first}/, token.last)
161
+ }.gsub(/\/\//, "/")
162
+ end
163
+
164
+ # The UID for this post (useful in feeds)
165
+ # e.g. /2008/11/05/my-awesome-post
166
+ #
167
+ # Returns <String>
168
+ def id
169
+ File.join(self.dir, self.slug)
170
+ end
171
+
172
+ # Calculate related posts.
173
+ #
174
+ # Returns [<Post>]
175
+ def related_posts(posts)
176
+ return [] unless posts.size > 1
177
+
178
+ if self.site.lsi
179
+ self.class.lsi ||= begin
180
+ puts "Running the classifier... this could take a while."
181
+ lsi = Classifier::LSI.new
182
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
183
+ puts ""
184
+ lsi
185
+ end
186
+
187
+ related = self.class.lsi.find_related(self.content, 11)
188
+ related - [self]
189
+ else
190
+ (posts - [self])[0..9]
191
+ end
192
+ end
193
+
194
+ # Add any necessary layouts to this post
195
+ # +layouts+ is a Hash of {"name" => "layout"}
196
+ # +site_payload+ is the site payload hash
197
+ #
198
+ # Returns nothing
199
+ def render(layouts, site_payload)
200
+ # construct payload
201
+ payload =
202
+ {
203
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
204
+ "page" => self.to_liquid
205
+ }
206
+ payload = payload.deep_merge(site_payload)
207
+
208
+ do_layout(payload, layouts)
209
+ end
210
+
211
+ # Write the generated post file to the destination directory.
212
+ # +dest+ is the String path to the destination dir
213
+ #
214
+ # Returns nothing
215
+ def write(dest)
216
+ FileUtils.mkdir_p(File.join(dest, dir))
217
+
218
+ # The url needs to be unescaped in order to preserve the correct filename
219
+ path = File.join(dest, CGI.unescape(self.url))
220
+
221
+ if template[/\.html$/].nil?
222
+ FileUtils.mkdir_p(path)
223
+ path = File.join(path, "index.html")
224
+ end
225
+
226
+ File.open(path, 'w') do |f|
227
+ f.write(self.output)
228
+ end
229
+ end
230
+
231
+ # Convert this post into a Hash for use in Liquid templates.
232
+ #
233
+ # Returns <Hash>
234
+ def to_liquid
235
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
236
+ "url" => self.url,
237
+ "date" => self.date,
238
+ "id" => self.id,
239
+ "categories" => self.categories,
240
+ "next" => self.next,
241
+ "previous" => self.previous,
242
+ "tags" => self.tags,
243
+ "content" => self.content }.deep_merge(self.data)
244
+ end
245
+
246
+ def inspect
247
+ "<Post: #{self.id}>"
248
+ end
249
+
250
+ def next
251
+ pos = self.site.posts.index(self)
252
+
253
+ if pos && pos < self.site.posts.length-1
254
+ self.site.posts[pos+1]
255
+ else
256
+ nil
257
+ end
258
+ end
259
+
260
+ def previous
261
+ pos = self.site.posts.index(self)
262
+ if pos && pos > 0
263
+ self.site.posts[pos-1]
264
+ else
265
+ nil
266
+ end
267
+ end
268
+ end
269
+
270
+ end