cartera-jekyll 0.6.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 (90) hide show
  1. data/History.txt +248 -0
  2. data/LICENSE +21 -0
  3. data/README.textile +41 -0
  4. data/Rakefile +159 -0
  5. data/bin/jekyll +178 -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 +103 -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 +132 -0
  18. data/lib/jekyll.rb +106 -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 +77 -0
  23. data/lib/jekyll/converters/textile.rb +33 -0
  24. data/lib/jekyll/convertible.rb +82 -0
  25. data/lib/jekyll/core_ext.rb +52 -0
  26. data/lib/jekyll/errors.rb +6 -0
  27. data/lib/jekyll/filters.rb +47 -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/mephisto.rb +79 -0
  33. data/lib/jekyll/migrators/mt.rb +59 -0
  34. data/lib/jekyll/migrators/textpattern.rb +50 -0
  35. data/lib/jekyll/migrators/typo.rb +49 -0
  36. data/lib/jekyll/migrators/wordpress.rb +55 -0
  37. data/lib/jekyll/page.rb +133 -0
  38. data/lib/jekyll/plugin.rb +76 -0
  39. data/lib/jekyll/post.rb +241 -0
  40. data/lib/jekyll/site.rb +231 -0
  41. data/lib/jekyll/static_file.rb +76 -0
  42. data/lib/jekyll/tags/highlight.rb +73 -0
  43. data/lib/jekyll/tags/include.rb +31 -0
  44. data/test/helper.rb +33 -0
  45. data/test/source/_includes/sig.markdown +3 -0
  46. data/test/source/_layouts/default.html +27 -0
  47. data/test/source/_layouts/simple.html +1 -0
  48. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  49. data/test/source/_posts/2008-02-02-published.textile +8 -0
  50. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  51. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  52. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  53. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  54. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  55. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  56. data/test/source/_posts/2009-01-27-category.textile +7 -0
  57. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  58. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  59. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  60. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  61. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  62. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  63. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  64. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  65. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  66. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  67. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  68. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  69. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  70. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  71. data/test/source/about.html +6 -0
  72. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  73. data/test/source/contacts.html +5 -0
  74. data/test/source/css/screen.css +76 -0
  75. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  76. data/test/source/index.html +22 -0
  77. data/test/source/sitemap.xml +32 -0
  78. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  79. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  80. data/test/suite.rb +9 -0
  81. data/test/test_configuration.rb +29 -0
  82. data/test/test_core_ext.rb +66 -0
  83. data/test/test_filters.rb +49 -0
  84. data/test/test_generated_site.rb +44 -0
  85. data/test/test_page.rb +98 -0
  86. data/test/test_pager.rb +113 -0
  87. data/test/test_post.rb +396 -0
  88. data/test/test_site.rb +153 -0
  89. data/test/test_tags.rb +108 -0
  90. metadata +284 -0
@@ -0,0 +1,241 @@
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
+ # Write the generated post file to the destination directory.
182
+ # +dest+ is the String path to the destination dir
183
+ #
184
+ # Returns nothing
185
+ def write(dest)
186
+ FileUtils.mkdir_p(File.join(dest, dir))
187
+
188
+ # The url needs to be unescaped in order to preserve the correct filename
189
+ path = File.join(dest, CGI.unescape(self.url))
190
+
191
+ if template[/\.html$/].nil?
192
+ FileUtils.mkdir_p(path)
193
+ path = File.join(path, "index.html")
194
+ end
195
+
196
+ File.open(path, 'w') do |f|
197
+ f.write(self.output)
198
+ end
199
+ end
200
+
201
+ # Convert this post into a Hash for use in Liquid templates.
202
+ #
203
+ # Returns <Hash>
204
+ def to_liquid
205
+ self.data.deep_merge({
206
+ "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
207
+ "url" => self.url,
208
+ "date" => self.date,
209
+ "id" => self.id,
210
+ "categories" => self.categories,
211
+ "next" => self.next,
212
+ "previous" => self.previous,
213
+ "tags" => self.tags,
214
+ "content" => self.content })
215
+ end
216
+
217
+ def inspect
218
+ "<Post: #{self.id}>"
219
+ end
220
+
221
+ def next
222
+ pos = self.site.posts.index(self)
223
+
224
+ if pos && pos < self.site.posts.length-1
225
+ self.site.posts[pos+1]
226
+ else
227
+ nil
228
+ end
229
+ end
230
+
231
+ def previous
232
+ pos = self.site.posts.index(self)
233
+ if pos && pos > 0
234
+ self.site.posts[pos-1]
235
+ else
236
+ nil
237
+ end
238
+ end
239
+ end
240
+
241
+ end
@@ -0,0 +1,231 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :config, :layouts, :posts, :pages, :static_files,
5
+ :categories, :exclude, :source, :dest, :lsi, :pygments,
6
+ :permalink_style, :tags, :time, :future, :safe, :plugins
7
+ attr_accessor :converters, :generators
8
+
9
+ # Initialize the site
10
+ # +config+ is a Hash containing site configurations details
11
+ #
12
+ # Returns <Site>
13
+ def initialize(config)
14
+ self.config = config.clone
15
+
16
+ self.safe = config['safe']
17
+ self.source = File.expand_path(config['source'])
18
+ self.dest = File.expand_path(config['destination'])
19
+ self.plugins = File.expand_path(config['plugins'])
20
+ self.lsi = config['lsi']
21
+ self.pygments = config['pygments']
22
+ self.permalink_style = config['permalink'].to_sym
23
+ self.exclude = config['exclude'] || []
24
+ self.future = config['future']
25
+
26
+ self.reset
27
+ self.setup
28
+ end
29
+
30
+ def reset
31
+ self.time = Time.parse(self.config['time'].to_s) || Time.now
32
+ self.layouts = {}
33
+ self.posts = []
34
+ self.pages = []
35
+ self.static_files = []
36
+ self.categories = Hash.new { |hash, key| hash[key] = [] }
37
+ self.tags = Hash.new { |hash, key| hash[key] = [] }
38
+ end
39
+
40
+ def setup
41
+ require 'classifier' if self.lsi
42
+
43
+ # If safe mode is off, load in any ruby files under the plugins
44
+ # directory.
45
+ unless self.safe
46
+ Dir[File.join(self.plugins, "**/*.rb")].each do |f|
47
+ require f
48
+ end
49
+ end
50
+
51
+ self.converters = Jekyll::Converter.subclasses.select do |c|
52
+ !self.safe || c.safe
53
+ end.map do |c|
54
+ c.new(self.config)
55
+ end
56
+
57
+ self.generators = Jekyll::Generator.subclasses.select do |c|
58
+ !self.safe || c.safe
59
+ end.map do |c|
60
+ c.new(self.config)
61
+ end
62
+ end
63
+
64
+ # Do the actual work of processing the site and generating the
65
+ # real deal. 5 phases; reset, read, generate, render, write. This allows
66
+ # rendering to have full site payload available.
67
+ #
68
+ # Returns nothing
69
+ def process
70
+ self.reset
71
+ self.read
72
+ self.generate
73
+ self.render
74
+ self.write
75
+ end
76
+
77
+ def read
78
+ self.read_layouts # existing implementation did this at top level only so preserved that
79
+ self.read_directories
80
+ end
81
+
82
+ # Read all the files in <source>/<dir>/_layouts and create a new Layout
83
+ # object with each one.
84
+ #
85
+ # Returns nothing
86
+ def read_layouts(dir = '')
87
+ base = File.join(self.source, dir, "_layouts")
88
+ return unless File.exists?(base)
89
+ entries = []
90
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
91
+
92
+ entries.each do |f|
93
+ name = f.split(".")[0..-2].join(".")
94
+ self.layouts[name] = Layout.new(self, base, f)
95
+ end
96
+ end
97
+
98
+ # Read all the files in <source>/<dir>/_posts and create a new Post
99
+ # object with each one.
100
+ #
101
+ # Returns nothing
102
+ def read_posts(dir)
103
+ base = File.join(self.source, dir, '_posts')
104
+ return unless File.exists?(base)
105
+ entries = Dir.chdir(base) { filter_entries(Dir['**/*']) }
106
+
107
+ # first pass processes, but does not yet render post content
108
+ entries.each do |f|
109
+ if Post.valid?(f)
110
+ post = Post.new(self, self.source, dir, f)
111
+
112
+ if post.published && (self.future || post.date <= self.time)
113
+ self.posts << post
114
+ post.categories.each { |c| self.categories[c] << post }
115
+ post.tags.each { |c| self.tags[c] << post }
116
+ end
117
+ end
118
+ end
119
+
120
+ self.posts.sort!
121
+ end
122
+
123
+ def generate
124
+ self.generators.each do |generator|
125
+ generator.generate(self)
126
+ end
127
+ end
128
+
129
+ def render
130
+ self.posts.each do |post|
131
+ post.render(self.layouts, site_payload)
132
+ end
133
+
134
+ self.pages.each do |page|
135
+ page.render(self.layouts, site_payload)
136
+ end
137
+
138
+ self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a} }
139
+ self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a} }
140
+ rescue Errno::ENOENT => e
141
+ # ignore missing layout dir
142
+ end
143
+
144
+ # Write static files, pages and posts
145
+ #
146
+ # Returns nothing
147
+ def write
148
+ self.posts.each do |post|
149
+ post.write(self.dest)
150
+ end
151
+ self.pages.each do |page|
152
+ page.write(self.dest)
153
+ end
154
+ self.static_files.each do |sf|
155
+ sf.write(self.dest)
156
+ end
157
+ end
158
+
159
+ # Reads the directories and finds posts, pages and static files that will
160
+ # become part of the valid site according to the rules in +filter_entries+.
161
+ # The +dir+ String is a relative path used to call this method
162
+ # recursively as it descends through directories
163
+ #
164
+ # Returns nothing
165
+ def read_directories(dir = '')
166
+ base = File.join(self.source, dir)
167
+ entries = filter_entries(Dir.entries(base))
168
+
169
+ self.read_posts(dir)
170
+
171
+ entries.each do |f|
172
+ f_abs = File.join(base, f)
173
+ f_rel = File.join(dir, f)
174
+ if File.directory?(f_abs)
175
+ next if self.dest.sub(/\/$/, '') == f_abs
176
+ read_directories(f_rel)
177
+ elsif !File.symlink?(f_abs)
178
+ first3 = File.open(f_abs) { |fd| fd.read(3) }
179
+ if first3 == "---"
180
+ # file appears to have a YAML header so process it as a page
181
+ pages << Page.new(self, self.source, dir, f)
182
+ else
183
+ # otherwise treat it as a static file
184
+ static_files << StaticFile.new(self, self.source, dir, f)
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ # Constructs a hash map of Posts indexed by the specified Post attribute
191
+ #
192
+ # Returns {post_attr => [<Post>]}
193
+ def post_attr_hash(post_attr)
194
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
195
+ # then sort each array in reverse order
196
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
197
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
198
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
199
+ return hash
200
+ end
201
+
202
+ # The Hash payload containing site-wide data
203
+ #
204
+ # Returns {"site" => {"time" => <Time>,
205
+ # "posts" => [<Post>],
206
+ # "pages" => [<Page>],
207
+ # "categories" => [<Post>]}
208
+ def site_payload
209
+ {"site" => self.config.merge({
210
+ "time" => self.time,
211
+ "posts" => self.posts.sort { |a,b| b <=> a },
212
+ "pages" => self.pages,
213
+ "html_pages" => self.pages.reject { |page| !page.html? },
214
+ "categories" => post_attr_hash('categories'),
215
+ "tags" => post_attr_hash('tags')})}
216
+ end
217
+
218
+ # Filter out any files/directories that are hidden or backup files (start
219
+ # with "." or "#" or end with "~"), or contain site content (start with "_"),
220
+ # or are excluded in the site configuration, unless they are web server
221
+ # files such as '.htaccess'
222
+ def filter_entries(entries)
223
+ entries = entries.reject do |e|
224
+ unless ['.htaccess'].include?(e)
225
+ ['.', '_', '#'].include?(e[0..0]) || e[-1..-1] == '~' || self.exclude.include?(e)
226
+ end
227
+ end
228
+ end
229
+
230
+ end
231
+ end