spinto-jekyll 0.11.2.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 (108) hide show
  1. data/Gemfile +2 -0
  2. data/History.txt +321 -0
  3. data/LICENSE +21 -0
  4. data/README.textile +41 -0
  5. data/Rakefile +166 -0
  6. data/bin/jekyll +289 -0
  7. data/cucumber.yml +1 -0
  8. data/features/create_sites.feature +112 -0
  9. data/features/embed_filters.feature +60 -0
  10. data/features/markdown.feature +30 -0
  11. data/features/pagination.feature +27 -0
  12. data/features/permalinks.feature +65 -0
  13. data/features/post_data.feature +153 -0
  14. data/features/site_configuration.feature +145 -0
  15. data/features/site_data.feature +82 -0
  16. data/features/step_definitions/jekyll_steps.rb +145 -0
  17. data/features/support/env.rb +19 -0
  18. data/lib/jekyll.rb +136 -0
  19. data/lib/jekyll/converter.rb +50 -0
  20. data/lib/jekyll/converters/identity.rb +22 -0
  21. data/lib/jekyll/converters/markdown.rb +125 -0
  22. data/lib/jekyll/converters/textile.rb +50 -0
  23. data/lib/jekyll/convertible.rb +112 -0
  24. data/lib/jekyll/core_ext.rb +52 -0
  25. data/lib/jekyll/errors.rb +6 -0
  26. data/lib/jekyll/filters.rb +118 -0
  27. data/lib/jekyll/generator.rb +7 -0
  28. data/lib/jekyll/generators/pagination.rb +113 -0
  29. data/lib/jekyll/layout.rb +44 -0
  30. data/lib/jekyll/migrators/csv.rb +26 -0
  31. data/lib/jekyll/migrators/drupal.rb +103 -0
  32. data/lib/jekyll/migrators/enki.rb +49 -0
  33. data/lib/jekyll/migrators/joomla.rb +53 -0
  34. data/lib/jekyll/migrators/marley.rb +52 -0
  35. data/lib/jekyll/migrators/mephisto.rb +84 -0
  36. data/lib/jekyll/migrators/mt.rb +86 -0
  37. data/lib/jekyll/migrators/posterous.rb +67 -0
  38. data/lib/jekyll/migrators/rss.rb +47 -0
  39. data/lib/jekyll/migrators/textpattern.rb +58 -0
  40. data/lib/jekyll/migrators/tumblr.rb +195 -0
  41. data/lib/jekyll/migrators/typo.rb +51 -0
  42. data/lib/jekyll/migrators/wordpress.rb +294 -0
  43. data/lib/jekyll/migrators/wordpressdotcom.rb +70 -0
  44. data/lib/jekyll/page.rb +155 -0
  45. data/lib/jekyll/plugin.rb +77 -0
  46. data/lib/jekyll/post.rb +257 -0
  47. data/lib/jekyll/site.rb +337 -0
  48. data/lib/jekyll/static_file.rb +72 -0
  49. data/lib/jekyll/tags/highlight.rb +76 -0
  50. data/lib/jekyll/tags/include.rb +37 -0
  51. data/lib/jekyll/tags/post_url.rb +38 -0
  52. data/lib/spinto-jekyll.rb +3 -0
  53. data/spinto-jekyll.gemspec +155 -0
  54. data/test/helper.rb +34 -0
  55. data/test/source/.htaccess +8 -0
  56. data/test/source/_includes/sig.markdown +3 -0
  57. data/test/source/_layouts/default.html +27 -0
  58. data/test/source/_layouts/simple.html +1 -0
  59. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  60. data/test/source/_posts/2008-02-02-published.textile +8 -0
  61. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  62. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  63. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  64. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  65. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  66. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  67. data/test/source/_posts/2009-01-27-category.textile +7 -0
  68. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  69. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  70. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  71. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  72. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  73. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  74. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  75. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  76. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  77. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  78. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  79. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  80. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  81. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  82. data/test/source/_posts/2011-04-12-md-extension.md +7 -0
  83. data/test/source/_posts/2011-04-12-text-extension.text +0 -0
  84. data/test/source/about.html +6 -0
  85. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  86. data/test/source/contacts.html +5 -0
  87. data/test/source/css/screen.css +76 -0
  88. data/test/source/deal.with.dots.html +7 -0
  89. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  90. data/test/source/index.html +22 -0
  91. data/test/source/sitemap.xml +32 -0
  92. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  93. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  94. data/test/suite.rb +11 -0
  95. data/test/test_configuration.rb +29 -0
  96. data/test/test_core_ext.rb +66 -0
  97. data/test/test_filters.rb +62 -0
  98. data/test/test_generated_site.rb +72 -0
  99. data/test/test_kramdown.rb +23 -0
  100. data/test/test_page.rb +117 -0
  101. data/test/test_pager.rb +113 -0
  102. data/test/test_post.rb +456 -0
  103. data/test/test_rdiscount.rb +18 -0
  104. data/test/test_redcarpet.rb +21 -0
  105. data/test/test_redcloth.rb +86 -0
  106. data/test/test_site.rb +220 -0
  107. data/test/test_tags.rb +201 -0
  108. metadata +336 -0
@@ -0,0 +1,70 @@
1
+ # coding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'hpricot'
5
+ require 'fileutils'
6
+ require 'yaml'
7
+ require 'time'
8
+
9
+ module Jekyll
10
+ # This importer takes a wordpress.xml file, which can be exported from your
11
+ # wordpress.com blog (/wp-admin/export.php).
12
+ module WordpressDotCom
13
+ def self.process(filename = "wordpress.xml")
14
+ import_count = Hash.new(0)
15
+ doc = Hpricot::XML(File.read(filename))
16
+
17
+ (doc/:channel/:item).each do |item|
18
+ title = item.at(:title).inner_text.strip
19
+ permalink_title = item.at('wp:post_name').inner_text
20
+ # Fallback to "prettified" title if post_name is empty (can happen)
21
+ if permalink_title == ""
22
+ permalink_title = title.downcase.split.join('-')
23
+ end
24
+
25
+ date = Time.parse(item.at('wp:post_date').inner_text)
26
+ status = item.at('wp:status').inner_text
27
+
28
+ if status == "publish"
29
+ published = true
30
+ else
31
+ published = false
32
+ end
33
+
34
+ type = item.at('wp:post_type').inner_text
35
+ tags = (item/:category).map{|c| c.inner_text}.reject{|c| c == 'Uncategorized'}.uniq
36
+
37
+ metas = Hash.new
38
+ item.search("wp:postmeta").each do |meta|
39
+ key = meta.at('wp:meta_key').inner_text
40
+ value = meta.at('wp:meta_value').inner_text
41
+ metas[key] = value;
42
+ end
43
+
44
+ name = "#{date.strftime('%Y-%m-%d')}-#{permalink_title}.html"
45
+ header = {
46
+ 'layout' => type,
47
+ 'title' => title,
48
+ 'tags' => tags,
49
+ 'status' => status,
50
+ 'type' => type,
51
+ 'published' => published,
52
+ 'meta' => metas
53
+ }
54
+
55
+ FileUtils.mkdir_p "_#{type}s"
56
+ File.open("_#{type}s/#{name}", "w") do |f|
57
+ f.puts header.to_yaml
58
+ f.puts '---'
59
+ f.puts item.at('content:encoded').inner_text
60
+ end
61
+
62
+ import_count[type] += 1
63
+ end
64
+
65
+ import_count.each do |key, value|
66
+ puts "Imported #{value} #{key}s"
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,155 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_writer :dir
7
+ attr_accessor :site, :pager
8
+ attr_accessor :name, :ext, :basename
9
+ attr_accessor :data, :content, :output
10
+
11
+ # Initialize a new Page.
12
+ #
13
+ # site - The Site object.
14
+ # base - The String path to the source.
15
+ # dir - The String path between the source and the file.
16
+ # name - The String filename of the file.
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, we be '/'
30
+ #
31
+ # Returns the String destination directory.
32
+ def dir
33
+ url[-1, 1] == '/' ? url : File.dirname(url)
34
+ end
35
+
36
+ # The full path and filename of the post. Defined in the YAML of the post
37
+ # body.
38
+ #
39
+ # Returns the String permalink or nil if none has been set.
40
+ def permalink
41
+ self.data && self.data['permalink']
42
+ end
43
+
44
+ # The template of the permalink.
45
+ #
46
+ # Returns the template String.
47
+ def template
48
+ if self.site.permalink_style == :pretty && !index? && html?
49
+ "/:basename/"
50
+ else
51
+ "/:basename:output_ext"
52
+ end
53
+ end
54
+
55
+ # The generated relative url of this page. e.g. /about.html.
56
+ #
57
+ # Returns the String url.
58
+ def url
59
+ return @url if @url
60
+
61
+ url = if permalink
62
+ permalink
63
+ else
64
+ {
65
+ "basename" => self.basename,
66
+ "output_ext" => self.output_ext,
67
+ }.inject(template) { |result, token|
68
+ result.gsub(/:#{token.first}/, token.last)
69
+ }.gsub(/\/\//, "/")
70
+ end
71
+
72
+ # sanitize url
73
+ @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
74
+ @url += "/" if url =~ /\/$/
75
+ @url
76
+ end
77
+
78
+ # Extract information from the page filename.
79
+ #
80
+ # name - The String filename of the page file.
81
+ #
82
+ # Returns nothing.
83
+ def process(name)
84
+ self.ext = File.extname(name)
85
+ self.basename = name[0 .. -self.ext.length-1]
86
+ end
87
+
88
+ # Add any necessary layouts to this post
89
+ #
90
+ # layouts - The Hash of {"name" => "layout"}.
91
+ # site_payload - The site payload Hash.
92
+ #
93
+ # Returns nothing.
94
+ def render(layouts, site_payload)
95
+ payload = {
96
+ "page" => self.to_liquid,
97
+ 'paginator' => pager.to_liquid
98
+ }.deep_merge(site_payload)
99
+
100
+ do_layout(payload, layouts)
101
+ end
102
+
103
+ # Convert this Page's data to a Hash suitable for use by Liquid.
104
+ #
105
+ # Returns the Hash representation of this Page.
106
+ def to_liquid
107
+ self.data.deep_merge({
108
+ "url" => File.join(@dir, self.url),
109
+ "content" => self.content })
110
+ end
111
+
112
+ # Obtain destination path.
113
+ #
114
+ # dest - The String path to the destination dir.
115
+ #
116
+ # Returns the destination file path String.
117
+ def destination(dest)
118
+ # The url needs to be unescaped in order to preserve the correct
119
+ # filename.
120
+ path = File.join(dest, @dir, CGI.unescape(self.url))
121
+ path = File.join(path, "index.html") if self.url =~ /\/$/
122
+ path
123
+ end
124
+
125
+ # Write the generated page file to the destination directory.
126
+ #
127
+ # dest - The String path to the destination dir.
128
+ #
129
+ # Returns nothing.
130
+ def write(dest)
131
+ path = destination(dest)
132
+ FileUtils.mkdir_p(File.dirname(path))
133
+ File.open(path, 'w') do |f|
134
+ f.write(self.output)
135
+ end
136
+ end
137
+
138
+ # Returns the object as a debug String.
139
+ def inspect
140
+ "#<Jekyll:Page @name=#{self.name.inspect}>"
141
+ end
142
+
143
+ # Returns the Boolean of whether this Page is HTML or not.
144
+ def html?
145
+ output_ext == '.html'
146
+ end
147
+
148
+ # Returns the Boolean of whether this Page is an index file or not.
149
+ def index?
150
+ basename == 'index'
151
+ end
152
+
153
+ end
154
+
155
+ end
@@ -0,0 +1,77 @@
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
+ @priority ||= nil
38
+ if priority && PRIORITIES.has_key?(priority)
39
+ @priority = priority
40
+ end
41
+ @priority || :normal
42
+ end
43
+
44
+ # Get or set the safety of this plugin. When called without an argument
45
+ # it returns the safety. When an argument is given, it will set the
46
+ # safety.
47
+ #
48
+ # safe - The Boolean safety (default: nil).
49
+ #
50
+ # Returns the safety Boolean.
51
+ def self.safe(safe = nil)
52
+ if safe
53
+ @safe = safe
54
+ end
55
+ @safe || false
56
+ end
57
+
58
+ # Spaceship is priority [higher -> lower]
59
+ #
60
+ # other - The class to be compared.
61
+ #
62
+ # Returns -1, 0, 1.
63
+ def self.<=>(other)
64
+ PRIORITIES[other.priority] <=> PRIORITIES[self.priority]
65
+ end
66
+
67
+ # Initialize a new plugin. This should be overridden by the subclass.
68
+ #
69
+ # config - The Hash of configuration options.
70
+ #
71
+ # Returns a new instance.
72
+ def initialize(config = {})
73
+ # no-op for default
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,257 @@
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
+ attr_reader :name
26
+
27
+ # Initialize this Post instance.
28
+ # +site+ is the Site
29
+ # +base+ is the String path to the dir containing the post file
30
+ # +name+ is the String filename of the post file
31
+ # +categories+ is an Array of Strings for the categories for this post
32
+ #
33
+ # Returns <Post>
34
+ def initialize(site, source, dir, name)
35
+ @site = site
36
+ @base = File.join(source, dir, '_posts')
37
+ @name = name
38
+
39
+ self.categories = dir.split('/').reject { |x| x.empty? }
40
+ self.process(name)
41
+ self.read_yaml(@base, name)
42
+
43
+ #If we've added a date and time to the yaml, use that instead of the filename date
44
+ #Means we'll sort correctly.
45
+ if self.data.has_key?('date')
46
+ # ensure Time via to_s and reparse
47
+ self.date = Time.parse(self.data["date"].to_s)
48
+ end
49
+
50
+ if self.data.has_key?('published') && self.data['published'] == false
51
+ self.published = false
52
+ else
53
+ self.published = true
54
+ end
55
+
56
+ self.tags = self.data.pluralized_array("tag", "tags")
57
+
58
+ if self.categories.empty?
59
+ self.categories = self.data.pluralized_array('category', 'categories')
60
+ end
61
+ end
62
+
63
+ # Spaceship is based on Post#date, slug
64
+ #
65
+ # Returns -1, 0, 1
66
+ def <=>(other)
67
+ cmp = self.date <=> other.date
68
+ if 0 == cmp
69
+ cmp = self.slug <=> other.slug
70
+ end
71
+ return cmp
72
+ end
73
+
74
+ # Extract information from the post filename
75
+ # +name+ is the String filename of the post file
76
+ #
77
+ # Returns nothing
78
+ def process(name)
79
+ m, cats, date, slug, ext = *name.match(MATCHER)
80
+ self.date = Time.parse(date)
81
+ self.slug = slug
82
+ self.ext = ext
83
+ rescue ArgumentError
84
+ raise FatalException.new("Post #{name} does not have a valid date.")
85
+ end
86
+
87
+ # The generated directory into which the post will be placed
88
+ # upon generation. This is derived from the permalink or, if
89
+ # permalink is absent, set to the default date
90
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
91
+ #
92
+ # Returns <String>
93
+ def dir
94
+ File.dirname(url)
95
+ end
96
+
97
+ # The full path and filename of the post.
98
+ # Defined in the YAML of the post body
99
+ # (Optional)
100
+ #
101
+ # Returns <String>
102
+ def permalink
103
+ self.data && self.data['permalink']
104
+ end
105
+
106
+ def template
107
+ case self.site.permalink_style
108
+ when :pretty
109
+ "/:categories/:year/:month/:day/:title/"
110
+ when :none
111
+ "/:categories/:title.html"
112
+ when :date
113
+ "/:categories/:year/:month/:day/:title.html"
114
+ else
115
+ self.site.permalink_style.to_s
116
+ end
117
+ end
118
+
119
+ # The generated relative url of this post
120
+ # e.g. /2008/11/05/my-awesome-post.html
121
+ #
122
+ # Returns <String>
123
+ def url
124
+ return @url if @url
125
+
126
+ url = if permalink
127
+ permalink
128
+ else
129
+ {
130
+ "year" => date.strftime("%Y"),
131
+ "month" => date.strftime("%m"),
132
+ "day" => date.strftime("%d"),
133
+ "title" => CGI.escape(slug),
134
+ "i_day" => date.strftime("%d").to_i.to_s,
135
+ "i_month" => date.strftime("%m").to_i.to_s,
136
+ "categories" => categories.map { |c| URI.escape(c) }.join('/'),
137
+ "output_ext" => self.output_ext
138
+ }.inject(template) { |result, token|
139
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
140
+ }.gsub(/\/\//, "/")
141
+ end
142
+
143
+ # sanitize url
144
+ @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
145
+ @url += "/" if url =~ /\/$/
146
+ @url
147
+ end
148
+
149
+ # The UID for this post (useful in feeds)
150
+ # e.g. /2008/11/05/my-awesome-post
151
+ #
152
+ # Returns <String>
153
+ def id
154
+ File.join(self.dir, self.slug)
155
+ end
156
+
157
+ # Calculate related posts.
158
+ #
159
+ # Returns [<Post>]
160
+ def related_posts(posts)
161
+ return [] unless posts.size > 1
162
+
163
+ if self.site.lsi
164
+ self.class.lsi ||= begin
165
+ puts "Running the classifier... this could take a while."
166
+ lsi = Classifier::LSI.new
167
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
168
+ puts ""
169
+ lsi
170
+ end
171
+
172
+ related = self.class.lsi.find_related(self.content, 11)
173
+ related - [self]
174
+ else
175
+ (posts - [self])[0..9]
176
+ end
177
+ end
178
+
179
+ # Add any necessary layouts to this post
180
+ # +layouts+ is a Hash of {"name" => "layout"}
181
+ # +site_payload+ is the site payload hash
182
+ #
183
+ # Returns nothing
184
+ def render(layouts, site_payload)
185
+ # construct payload
186
+ payload = {
187
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
188
+ "page" => self.to_liquid
189
+ }.deep_merge(site_payload)
190
+
191
+ do_layout(payload, layouts)
192
+ end
193
+
194
+ # Obtain destination path.
195
+ # +dest+ is the String path to the destination dir
196
+ #
197
+ # Returns destination file path.
198
+ def destination(dest)
199
+ # The url needs to be unescaped in order to preserve the correct filename
200
+ path = File.join(dest, CGI.unescape(self.url))
201
+ path = File.join(path, "index.html") if template[/\.html$/].nil?
202
+ path
203
+ end
204
+
205
+ # Write the generated post file to the destination directory.
206
+ # +dest+ is the String path to the destination dir
207
+ #
208
+ # Returns nothing
209
+ def write(dest)
210
+ path = destination(dest)
211
+ FileUtils.mkdir_p(File.dirname(path))
212
+ File.open(path, 'w') do |f|
213
+ f.write(self.output)
214
+ end
215
+ end
216
+
217
+ # Convert this post into a Hash for use in Liquid templates.
218
+ #
219
+ # Returns <Hash>
220
+ def to_liquid
221
+ self.data.deep_merge({
222
+ "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
223
+ "url" => self.url,
224
+ "date" => self.date,
225
+ "id" => self.id,
226
+ "categories" => self.categories,
227
+ "next" => self.next,
228
+ "previous" => self.previous,
229
+ "tags" => self.tags,
230
+ "content" => self.content })
231
+ end
232
+
233
+ def inspect
234
+ "<Post: #{self.id}>"
235
+ end
236
+
237
+ def next
238
+ pos = self.site.posts.index(self)
239
+
240
+ if pos && pos < self.site.posts.length-1
241
+ self.site.posts[pos+1]
242
+ else
243
+ nil
244
+ end
245
+ end
246
+
247
+ def previous
248
+ pos = self.site.posts.index(self)
249
+ if pos && pos > 0
250
+ self.site.posts[pos-1]
251
+ else
252
+ nil
253
+ end
254
+ end
255
+ end
256
+
257
+ end