fagiani-jekyll 0.10.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.
Files changed (97) hide show
  1. data/History.txt +284 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +41 -0
  4. data/Rakefile +159 -0
  5. data/bin/jekyll +192 -0
  6. data/cucumber.yml +1 -0
  7. data/features/create_sites.feature +94 -0
  8. data/features/embed_filters.feature +60 -0
  9. data/features/markdown.feature +30 -0
  10. data/features/pagination.feature +27 -0
  11. data/features/permalinks.feature +65 -0
  12. data/features/post_data.feature +153 -0
  13. data/features/site_configuration.feature +126 -0
  14. data/features/site_data.feature +82 -0
  15. data/features/step_definitions/jekyll_steps.rb +145 -0
  16. data/features/support/env.rb +16 -0
  17. data/jekyll.gemspec +140 -0
  18. data/lib/jekyll.rb +125 -0
  19. data/lib/jekyll/albino.rb +120 -0
  20. data/lib/jekyll/converter.rb +50 -0
  21. data/lib/jekyll/converters/identity.rb +22 -0
  22. data/lib/jekyll/converters/markdown.rb +113 -0
  23. data/lib/jekyll/converters/textile.rb +33 -0
  24. data/lib/jekyll/convertible.rb +98 -0
  25. data/lib/jekyll/core_ext.rb +52 -0
  26. data/lib/jekyll/errors.rb +6 -0
  27. data/lib/jekyll/filters.rb +53 -0
  28. data/lib/jekyll/generator.rb +7 -0
  29. data/lib/jekyll/generators/pagination.rb +87 -0
  30. data/lib/jekyll/layout.rb +36 -0
  31. data/lib/jekyll/migrators/csv.rb +26 -0
  32. data/lib/jekyll/migrators/drupal.rb +86 -0
  33. data/lib/jekyll/migrators/marley.rb +53 -0
  34. data/lib/jekyll/migrators/mephisto.rb +79 -0
  35. data/lib/jekyll/migrators/mt.rb +77 -0
  36. data/lib/jekyll/migrators/textpattern.rb +50 -0
  37. data/lib/jekyll/migrators/typo.rb +49 -0
  38. data/lib/jekyll/migrators/wordpress.com.rb +38 -0
  39. data/lib/jekyll/migrators/wordpress.rb +56 -0
  40. data/lib/jekyll/page.rb +134 -0
  41. data/lib/jekyll/plugin.rb +76 -0
  42. data/lib/jekyll/post.rb +244 -0
  43. data/lib/jekyll/site.rb +273 -0
  44. data/lib/jekyll/static_file.rb +75 -0
  45. data/lib/jekyll/tags/highlight.rb +73 -0
  46. data/lib/jekyll/tags/include.rb +37 -0
  47. data/test/helper.rb +34 -0
  48. data/test/source/.htaccess +8 -0
  49. data/test/source/_includes/sig.markdown +3 -0
  50. data/test/source/_layouts/default.html +27 -0
  51. data/test/source/_layouts/simple.html +1 -0
  52. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  53. data/test/source/_posts/2008-02-02-published.textile +8 -0
  54. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  55. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  56. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  57. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  58. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  59. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  60. data/test/source/_posts/2009-01-27-category.textile +7 -0
  61. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  62. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  63. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  64. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  65. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  66. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  67. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  68. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  69. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  70. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  71. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  72. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  73. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  74. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  75. data/test/source/about.html +6 -0
  76. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  77. data/test/source/contacts.html +5 -0
  78. data/test/source/css/screen.css +76 -0
  79. data/test/source/deal.with.dots.html +7 -0
  80. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  81. data/test/source/index.html +22 -0
  82. data/test/source/sitemap.xml +32 -0
  83. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  84. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  85. data/test/suite.rb +9 -0
  86. data/test/test_configuration.rb +29 -0
  87. data/test/test_core_ext.rb +66 -0
  88. data/test/test_filters.rb +53 -0
  89. data/test/test_generated_site.rb +72 -0
  90. data/test/test_kramdown.rb +23 -0
  91. data/test/test_page.rb +117 -0
  92. data/test/test_pager.rb +113 -0
  93. data/test/test_post.rb +396 -0
  94. data/test/test_rdiscount.rb +18 -0
  95. data/test/test_site.rb +186 -0
  96. data/test/test_tags.rb +127 -0
  97. metadata +332 -0
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'hpricot'
3
+ require 'fileutils'
4
+
5
+ # This importer takes a wordpress.xml file,
6
+ # which can be exported from your
7
+ # wordpress.com blog (/wp-admin/export.php)
8
+
9
+ module Jekyll
10
+ module WordpressDotCom
11
+ def self.process(filename = "wordpress.xml")
12
+ FileUtils.mkdir_p "_posts"
13
+ posts = 0
14
+
15
+ doc = Hpricot::XML(File.read(filename))
16
+
17
+ (doc/:channel/:item).each do |item|
18
+ title = item.at(:title).inner_text
19
+ name = "#{Date.parse((doc/:channel/:item).first.at(:pubDate).inner_text).to_s("%Y-%m-%d")}-#{title.downcase.gsub('[^a-z0-9]', '-')}.html"
20
+
21
+ File.open("_posts/#{name}", "w") do |f|
22
+ f.puts <<-HEADER
23
+ ---
24
+ layout: post
25
+ title: #{title}
26
+ ---
27
+
28
+ HEADER
29
+ f.puts item.at('content:encoded').inner_text
30
+ end
31
+
32
+ posts += 1
33
+ end
34
+
35
+ "Imported #{posts} posts"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'sequel'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ # NOTE: This converter requires Sequel and the MySQL gems.
7
+ # The MySQL gem can be difficult to install on OS X. Once you have MySQL
8
+ # installed, running the following commands should work:
9
+ # $ sudo gem install sequel
10
+ # $ sudo gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config
11
+
12
+ module Jekyll
13
+ module WordPress
14
+
15
+ # Reads a MySQL database via Sequel and creates a post file for each
16
+ # post in wp_posts that has post_status = 'publish'.
17
+ # This restriction is made because 'draft' posts are not guaranteed to
18
+ # have valid dates.
19
+ QUERY = "select post_title, post_name, post_date, post_content, post_excerpt, ID, guid from wp_posts where post_status = 'publish' and post_type = 'post'"
20
+
21
+ def self.process(dbname, user, pass, host = 'localhost')
22
+ db = Sequel.mysql(dbname, :user => user, :password => pass, :host => host, :encoding => 'utf8')
23
+
24
+ FileUtils.mkdir_p "_posts"
25
+
26
+ db[QUERY].each do |post|
27
+ # Get required fields and construct Jekyll compatible name
28
+ title = post[:post_title]
29
+ slug = post[:post_name]
30
+ date = post[:post_date]
31
+ content = post[:post_content]
32
+ name = "%02d-%02d-%02d-%s.markdown" % [date.year, date.month, date.day,
33
+ slug]
34
+
35
+ # Get the relevant fields as a hash, delete empty fields and convert
36
+ # to YAML for the header
37
+ data = {
38
+ 'layout' => 'post',
39
+ 'title' => title.to_s,
40
+ 'excerpt' => post[:post_excerpt].to_s,
41
+ 'wordpress_id' => post[:ID],
42
+ 'wordpress_url' => post[:guid],
43
+ 'date' => date
44
+ }.delete_if { |k,v| v.nil? || v == ''}.to_yaml
45
+
46
+ # Write out the data and content to file
47
+ File.open("_posts/#{name}", "w") do |f|
48
+ f.puts data
49
+ f.puts "---"
50
+ f.puts content
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,134 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :site, :pager
7
+ attr_accessor :name, :ext, :basename, :dir
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? && html?
47
+ "/:basename/"
48
+ else
49
+ "/:basename:output_ext"
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 ||= {
61
+ "basename" => self.basename,
62
+ "output_ext" => self.output_ext,
63
+ }.inject(template) { |result, token|
64
+ result.gsub(/:#{token.first}/, token.last)
65
+ }.gsub(/\/\//, "/")
66
+ end
67
+
68
+ # Extract information from the page filename
69
+ # +name+ is the String filename of the page file
70
+ #
71
+ # Returns nothing
72
+ def process(name)
73
+ self.ext = File.extname(name)
74
+ self.basename = name[0 .. -self.ext.length-1]
75
+ end
76
+
77
+ # Add any necessary layouts to this post
78
+ # +layouts+ is a Hash of {"name" => "layout"}
79
+ # +site_payload+ is the site payload hash
80
+ #
81
+ # Returns nothing
82
+ def render(layouts, site_payload)
83
+ payload = {
84
+ "page" => self.to_liquid,
85
+ 'paginator' => pager.to_liquid
86
+ }.deep_merge(site_payload)
87
+
88
+ do_layout(payload, layouts)
89
+ end
90
+
91
+ def to_liquid
92
+ self.data.deep_merge({
93
+ "url" => File.join(@dir, self.url),
94
+ "content" => self.content })
95
+ end
96
+
97
+ # Obtain destination path.
98
+ # +dest+ is the String path to the destination dir
99
+ #
100
+ # Returns destination file path.
101
+ def destination(dest)
102
+ # The url needs to be unescaped in order to preserve the correct filename
103
+ path = File.join(dest, @dir, CGI.unescape(self.url))
104
+ path = File.join(path, "index.html") if self.url =~ /\/$/
105
+ path
106
+ end
107
+
108
+ # Write the generated page file to the destination directory.
109
+ # +dest+ is the String path to the destination dir
110
+ #
111
+ # Returns nothing
112
+ def write(dest)
113
+ path = destination(dest)
114
+ FileUtils.mkdir_p(File.dirname(path))
115
+ File.open(path, 'w') do |f|
116
+ f.write(self.output)
117
+ end
118
+ end
119
+
120
+ def inspect
121
+ "#<Jekyll:Page @name=#{self.name.inspect}>"
122
+ end
123
+
124
+ def html?
125
+ output_ext == '.html'
126
+ end
127
+
128
+ def index?
129
+ basename == 'index'
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,76 @@
1
+ module Jekyll
2
+
3
+ class Plugin
4
+ PRIORITIES = { :lowest => -100,
5
+ :low => -10,
6
+ :normal => 0,
7
+ :high => 10,
8
+ :highest => 100 }
9
+
10
+ # Install a hook so that subclasses are recorded. This method is only
11
+ # ever called by Ruby itself.
12
+ #
13
+ # base - The Class subclass.
14
+ #
15
+ # Returns nothing.
16
+ def self.inherited(base)
17
+ subclasses << base
18
+ subclasses.sort!
19
+ end
20
+
21
+ # The list of Classes that have been subclassed.
22
+ #
23
+ # Returns an Array of Class objects.
24
+ def self.subclasses
25
+ @subclasses ||= []
26
+ end
27
+
28
+ # Get or set the priority of this plugin. When called without an
29
+ # argument it returns the priority. When an argument is given, it will
30
+ # set the priority.
31
+ #
32
+ # priority - The Symbol priority (default: nil). Valid options are:
33
+ # :lowest, :low, :normal, :high, :highest
34
+ #
35
+ # Returns the Symbol priority.
36
+ def self.priority(priority = nil)
37
+ if priority && PRIORITIES.has_key?(priority)
38
+ @priority = priority
39
+ end
40
+ @priority || :normal
41
+ end
42
+
43
+ # Get or set the safety of this plugin. When called without an argument
44
+ # it returns the safety. When an argument is given, it will set the
45
+ # safety.
46
+ #
47
+ # safe - The Boolean safety (default: nil).
48
+ #
49
+ # Returns the safety Boolean.
50
+ def self.safe(safe = nil)
51
+ if safe
52
+ @safe = safe
53
+ end
54
+ @safe || false
55
+ end
56
+
57
+ # Spaceship is priority [higher -> lower]
58
+ #
59
+ # other - The class to be compared.
60
+ #
61
+ # Returns -1, 0, 1.
62
+ def self.<=>(other)
63
+ PRIORITIES[other.priority] <=> PRIORITIES[self.priority]
64
+ end
65
+
66
+ # Initialize a new plugin. This should be overridden by the subclass.
67
+ #
68
+ # config - The Hash of configuration options.
69
+ #
70
+ # Returns a new instance.
71
+ def initialize(config = {})
72
+ # no-op for default
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,244 @@
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
+ MATCHER = /^(.+\/)*(\d+-\d+-\d+)-(.*)(\.[^.]+)$/
12
+
13
+ # Post name validator. Post filenames must be like:
14
+ # 2008-11-05-my-awesome-post.textile
15
+ #
16
+ # Returns <Bool>
17
+ def self.valid?(name)
18
+ name =~ MATCHER
19
+ end
20
+
21
+ attr_accessor :site
22
+ attr_accessor :data, :content, :output, :ext
23
+ attr_accessor :date, :slug, :published, :tags, :categories
24
+
25
+ # Initialize this Post instance.
26
+ # +site+ is the Site
27
+ # +base+ is the String path to the dir containing the post file
28
+ # +name+ is the String filename of the post file
29
+ # +categories+ is an Array of Strings for the categories for this post
30
+ #
31
+ # Returns <Post>
32
+ def initialize(site, source, dir, name)
33
+ @site = site
34
+ @base = File.join(source, dir, '_posts')
35
+ @name = name
36
+
37
+ self.categories = dir.split('/').reject { |x| x.empty? }
38
+ self.process(name)
39
+ self.read_yaml(@base, name)
40
+
41
+ #If we've added a date and time to the yaml, use that instead of the filename date
42
+ #Means we'll sort correctly.
43
+ if self.data.has_key?('date')
44
+ # ensure Time via to_s and reparse
45
+ self.date = Time.parse(self.data["date"].to_s)
46
+ end
47
+
48
+ if self.data.has_key?('published') && self.data['published'] == false
49
+ self.published = false
50
+ else
51
+ self.published = true
52
+ end
53
+
54
+ self.tags = self.data.pluralized_array("tag", "tags")
55
+
56
+ if self.categories.empty?
57
+ self.categories = self.data.pluralized_array('category', 'categories')
58
+ end
59
+ end
60
+
61
+ # Spaceship is based on Post#date, slug
62
+ #
63
+ # Returns -1, 0, 1
64
+ def <=>(other)
65
+ cmp = self.date <=> other.date
66
+ if 0 == cmp
67
+ cmp = self.slug <=> other.slug
68
+ end
69
+ return cmp
70
+ end
71
+
72
+ # Extract information from the post filename
73
+ # +name+ is the String filename of the post file
74
+ #
75
+ # Returns nothing
76
+ def process(name)
77
+ m, cats, date, slug, ext = *name.match(MATCHER)
78
+ self.date = Time.parse(date)
79
+ self.slug = slug
80
+ self.ext = ext
81
+ end
82
+
83
+ # The generated directory into which the post will be placed
84
+ # upon generation. This is derived from the permalink or, if
85
+ # permalink is absent, set to the default date
86
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
87
+ #
88
+ # Returns <String>
89
+ def dir
90
+ File.dirname(url)
91
+ end
92
+
93
+ # The full path and filename of the post.
94
+ # Defined in the YAML of the post body
95
+ # (Optional)
96
+ #
97
+ # Returns <String>
98
+ def permalink
99
+ self.data && self.data['permalink']
100
+ end
101
+
102
+ def template
103
+ case self.site.permalink_style
104
+ when :pretty
105
+ "/:categories/:year/:month/:day/:title/"
106
+ when :none
107
+ "/:categories/:title.html"
108
+ when :date
109
+ "/:categories/:year/:month/:day/:title.html"
110
+ else
111
+ self.site.permalink_style.to_s
112
+ end
113
+ end
114
+
115
+ # The generated relative url of this post
116
+ # e.g. /2008/11/05/my-awesome-post.html
117
+ #
118
+ # Returns <String>
119
+ def url
120
+ return permalink if permalink
121
+
122
+ @url ||= {
123
+ "year" => date.strftime("%Y"),
124
+ "month" => date.strftime("%m"),
125
+ "day" => date.strftime("%d"),
126
+ "title" => CGI.escape(slug),
127
+ "i_day" => date.strftime("%d").to_i.to_s,
128
+ "i_month" => date.strftime("%m").to_i.to_s,
129
+ "categories" => categories.join('/'),
130
+ "output_ext" => self.output_ext
131
+ }.inject(template) { |result, token|
132
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
133
+ }.gsub(/\/\//, "/")
134
+ end
135
+
136
+ # The UID for this post (useful in feeds)
137
+ # e.g. /2008/11/05/my-awesome-post
138
+ #
139
+ # Returns <String>
140
+ def id
141
+ File.join(self.dir, self.slug)
142
+ end
143
+
144
+ # Calculate related posts.
145
+ #
146
+ # Returns [<Post>]
147
+ def related_posts(posts)
148
+ return [] unless posts.size > 1
149
+
150
+ if self.site.lsi
151
+ self.class.lsi ||= begin
152
+ puts "Running the classifier... this could take a while."
153
+ lsi = Classifier::LSI.new
154
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
155
+ puts ""
156
+ lsi
157
+ end
158
+
159
+ related = self.class.lsi.find_related(self.content, 11)
160
+ related - [self]
161
+ else
162
+ (posts - [self])[0..9]
163
+ end
164
+ end
165
+
166
+ # Add any necessary layouts to this post
167
+ # +layouts+ is a Hash of {"name" => "layout"}
168
+ # +site_payload+ is the site payload hash
169
+ #
170
+ # Returns nothing
171
+ def render(layouts, site_payload)
172
+ # construct payload
173
+ payload = {
174
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
175
+ "page" => self.to_liquid
176
+ }.deep_merge(site_payload)
177
+
178
+ do_layout(payload, layouts)
179
+ end
180
+
181
+ # Obtain destination path.
182
+ # +dest+ is the String path to the destination dir
183
+ #
184
+ # Returns destination file path.
185
+ def destination(dest)
186
+ # The url needs to be unescaped in order to preserve the correct filename
187
+ path = File.join(dest, CGI.unescape(self.url))
188
+ path = File.join(path, "index.html") if template[/\.html$/].nil?
189
+ path
190
+ end
191
+
192
+ # Write the generated post file to the destination directory.
193
+ # +dest+ is the String path to the destination dir
194
+ #
195
+ # Returns nothing
196
+ def write(dest)
197
+ path = destination(dest)
198
+ FileUtils.mkdir_p(File.dirname(path))
199
+ File.open(path, 'w') do |f|
200
+ f.write(self.output)
201
+ end
202
+ end
203
+
204
+ # Convert this post into a Hash for use in Liquid templates.
205
+ #
206
+ # Returns <Hash>
207
+ def to_liquid
208
+ self.data.deep_merge({
209
+ "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
210
+ "url" => self.url,
211
+ "date" => self.date,
212
+ "id" => self.id,
213
+ "categories" => self.categories,
214
+ "next" => self.next,
215
+ "previous" => self.previous,
216
+ "tags" => self.tags,
217
+ "content" => self.content })
218
+ end
219
+
220
+ def inspect
221
+ "<Post: #{self.id}>"
222
+ end
223
+
224
+ def next
225
+ pos = self.site.posts.index(self)
226
+
227
+ if pos && pos < self.site.posts.length-1
228
+ self.site.posts[pos+1]
229
+ else
230
+ nil
231
+ end
232
+ end
233
+
234
+ def previous
235
+ pos = self.site.posts.index(self)
236
+ if pos && pos > 0
237
+ self.site.posts[pos-1]
238
+ else
239
+ nil
240
+ end
241
+ end
242
+ end
243
+
244
+ end