jekyll-reloaded 0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) 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 +161 -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/jekyll.gemspec +146 -0
  19. data/lib/guard/jekyll.rb +57 -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 +125 -0
  23. data/lib/jekyll/converters/textile.rb +50 -0
  24. data/lib/jekyll/convertible.rb +116 -0
  25. data/lib/jekyll/core_ext.rb +52 -0
  26. data/lib/jekyll/errors.rb +6 -0
  27. data/lib/jekyll/filters.rb +118 -0
  28. data/lib/jekyll/generator.rb +7 -0
  29. data/lib/jekyll/generators/pagination.rb +113 -0
  30. data/lib/jekyll/layout.rb +51 -0
  31. data/lib/jekyll/live_site.rb +216 -0
  32. data/lib/jekyll/migrators/csv.rb +26 -0
  33. data/lib/jekyll/migrators/drupal.rb +103 -0
  34. data/lib/jekyll/migrators/enki.rb +49 -0
  35. data/lib/jekyll/migrators/joomla.rb +53 -0
  36. data/lib/jekyll/migrators/marley.rb +52 -0
  37. data/lib/jekyll/migrators/mephisto.rb +84 -0
  38. data/lib/jekyll/migrators/mt.rb +86 -0
  39. data/lib/jekyll/migrators/posterous.rb +67 -0
  40. data/lib/jekyll/migrators/rss.rb +47 -0
  41. data/lib/jekyll/migrators/textpattern.rb +58 -0
  42. data/lib/jekyll/migrators/tumblr.rb +195 -0
  43. data/lib/jekyll/migrators/typo.rb +51 -0
  44. data/lib/jekyll/migrators/wordpress.rb +294 -0
  45. data/lib/jekyll/migrators/wordpressdotcom.rb +70 -0
  46. data/lib/jekyll/page.rb +160 -0
  47. data/lib/jekyll/plugin.rb +77 -0
  48. data/lib/jekyll/post.rb +262 -0
  49. data/lib/jekyll/site.rb +339 -0
  50. data/lib/jekyll/static_file.rb +77 -0
  51. data/lib/jekyll/tags/highlight.rb +118 -0
  52. data/lib/jekyll/tags/include.rb +37 -0
  53. data/lib/jekyll/tags/post_url.rb +38 -0
  54. data/lib/jekyll.rb +134 -0
  55. data/test/helper.rb +34 -0
  56. data/test/source/.htaccess +8 -0
  57. data/test/source/_includes/sig.markdown +3 -0
  58. data/test/source/_layouts/default.html +27 -0
  59. data/test/source/_layouts/simple.html +1 -0
  60. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  61. data/test/source/_posts/2008-02-02-published.textile +8 -0
  62. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  63. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  64. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  65. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  66. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  67. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  68. data/test/source/_posts/2009-01-27-category.textile +7 -0
  69. data/test/source/_posts/2009-01-27-empty-categories.textile +7 -0
  70. data/test/source/_posts/2009-01-27-empty-category.textile +7 -0
  71. data/test/source/_posts/2009-03-12-hash-#1.markdown +6 -0
  72. data/test/source/_posts/2009-05-18-empty-tag.textile +6 -0
  73. data/test/source/_posts/2009-05-18-empty-tags.textile +6 -0
  74. data/test/source/_posts/2009-05-18-tag.textile +6 -0
  75. data/test/source/_posts/2009-05-18-tags.textile +9 -0
  76. data/test/source/_posts/2009-06-22-empty-yaml.textile +3 -0
  77. data/test/source/_posts/2009-06-22-no-yaml.textile +1 -0
  78. data/test/source/_posts/2010-01-08-triple-dash.markdown +5 -0
  79. data/test/source/_posts/2010-01-09-date-override.textile +7 -0
  80. data/test/source/_posts/2010-01-09-time-override.textile +7 -0
  81. data/test/source/_posts/2010-01-09-timezone-override.textile +7 -0
  82. data/test/source/_posts/2010-01-16-override-data.textile +4 -0
  83. data/test/source/_posts/2011-04-12-md-extension.md +7 -0
  84. data/test/source/_posts/2011-04-12-text-extension.text +0 -0
  85. data/test/source/about.html +6 -0
  86. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  87. data/test/source/contacts.html +5 -0
  88. data/test/source/css/screen.css +76 -0
  89. data/test/source/deal.with.dots.html +7 -0
  90. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  91. data/test/source/index.html +22 -0
  92. data/test/source/sitemap.xml +32 -0
  93. data/test/source/win/_posts/2009-05-24-yaml-linebreak.markdown +7 -0
  94. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  95. data/test/suite.rb +11 -0
  96. data/test/test_configuration.rb +29 -0
  97. data/test/test_core_ext.rb +66 -0
  98. data/test/test_filters.rb +62 -0
  99. data/test/test_generated_site.rb +72 -0
  100. data/test/test_kramdown.rb +23 -0
  101. data/test/test_page.rb +117 -0
  102. data/test/test_pager.rb +113 -0
  103. data/test/test_post.rb +450 -0
  104. data/test/test_rdiscount.rb +18 -0
  105. data/test/test_redcarpet.rb +21 -0
  106. data/test/test_redcloth.rb +86 -0
  107. data/test/test_site.rb +220 -0
  108. data/test/test_tags.rb +201 -0
  109. metadata +332 -0
@@ -0,0 +1,262 @@
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 :content, :output, :ext
23
+ attr_accessor :date, :slug, :published, :tags, :categories
24
+
25
+ attr_reader :name, :data
26
+
27
+ # Initialize a new Post.
28
+ #
29
+ # site - The Site object.
30
+ # source - The String path to the source.
31
+ # dir - The String path between the source and the file.
32
+ # name - The String filename of the file.
33
+ def initialize(site, source, dir, name)
34
+ @site = site
35
+ @base = File.join(source, dir, '_posts')
36
+ @name = name
37
+ @categories = dir.split('/').reject { |x| x.empty? }
38
+
39
+ self.process(name)
40
+ self.read_yaml(@base, name)
41
+ end
42
+
43
+ # The source filename for this post.
44
+ def filename
45
+ File.join(@base, name)
46
+ end
47
+
48
+ # Save post data and extract various post properties from it.
49
+ def data=(data)
50
+ @data = data
51
+ return if data.nil?
52
+
53
+ # data can override the date from the filename
54
+ if data.has_key?('date')
55
+ self.date = Time.parse(data['date'].to_s)
56
+ end
57
+
58
+ self.published = data['published'] != false
59
+
60
+ self.tags = data.pluralized_array('tag', 'tags')
61
+
62
+ if self.categories.empty?
63
+ # TODO: merge with categories from path?
64
+ self.categories = data.pluralized_array('category', 'categories')
65
+ end
66
+
67
+ @data
68
+ end
69
+
70
+ # Spaceship is based on Post#date, slug
71
+ #
72
+ # Returns -1, 0, 1
73
+ def <=>(other)
74
+ cmp = self.date <=> other.date
75
+ if 0 == cmp
76
+ cmp = self.slug <=> other.slug
77
+ end
78
+ return cmp
79
+ end
80
+
81
+ # Extract information from the post filename
82
+ # +name+ is the String filename of the post file
83
+ #
84
+ # Returns nothing
85
+ def process(name)
86
+ _, date, self.slug, self.ext = *name.match(MATCHER)
87
+ self.date = Time.parse(date)
88
+ rescue ArgumentError
89
+ raise FatalException.new("Post #{name} does not have a valid date.")
90
+ end
91
+
92
+ # The generated directory into which the post will be placed
93
+ # upon generation. This is derived from the permalink or, if
94
+ # permalink is absent, set to the default date
95
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
96
+ #
97
+ # Returns <String>
98
+ def dir
99
+ File.dirname(url)
100
+ end
101
+
102
+ # The full path and filename of the post.
103
+ # Defined in the YAML of the post body
104
+ # (Optional)
105
+ #
106
+ # Returns <String>
107
+ def permalink
108
+ self.data && self.data['permalink']
109
+ end
110
+
111
+ def template
112
+ case self.site.permalink_style
113
+ when :pretty
114
+ "/:categories/:year/:month/:day/:title/"
115
+ when :none
116
+ "/:categories/:title.html"
117
+ when :date
118
+ "/:categories/:year/:month/:day/:title.html"
119
+ else
120
+ self.site.permalink_style.to_s
121
+ end
122
+ end
123
+
124
+ # The generated relative url of this post
125
+ # e.g. /2008/11/05/my-awesome-post.html
126
+ #
127
+ # Returns <String>
128
+ def url
129
+ return @url if @url
130
+
131
+ url = if permalink
132
+ permalink
133
+ else
134
+ {
135
+ "year" => date.strftime("%Y"),
136
+ "month" => date.strftime("%m"),
137
+ "day" => date.strftime("%d"),
138
+ "title" => CGI.escape(slug),
139
+ "i_day" => date.strftime("%d").to_i.to_s,
140
+ "i_month" => date.strftime("%m").to_i.to_s,
141
+ "categories" => categories.map { |c| URI.escape(c) }.join('/'),
142
+ "output_ext" => self.output_ext
143
+ }.inject(template) { |result, token|
144
+ result.gsub(/:#{Regexp.escape token.first}/, token.last)
145
+ }.gsub(/\/\//, "/")
146
+ end
147
+
148
+ # sanitize url
149
+ @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
150
+ @url += "/" if url =~ /\/$/
151
+ @url
152
+ end
153
+
154
+ # The UID for this post (useful in feeds)
155
+ # e.g. /2008/11/05/my-awesome-post
156
+ #
157
+ # Returns <String>
158
+ def id
159
+ File.join(self.dir, self.slug)
160
+ end
161
+
162
+ # Calculate related posts.
163
+ #
164
+ # Returns [<Post>]
165
+ def related_posts(posts)
166
+ return [] unless posts.size > 1
167
+
168
+ if self.site.lsi
169
+ self.class.lsi ||= begin
170
+ puts "Running the classifier... this could take a while."
171
+ lsi = Classifier::LSI.new
172
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
173
+ puts ""
174
+ lsi
175
+ end
176
+
177
+ related = self.class.lsi.find_related(self.content, 11)
178
+ related - [self]
179
+ else
180
+ (posts - [self])[0..9]
181
+ end
182
+ end
183
+
184
+ # Add any necessary layouts to this post
185
+ # +layouts+ is a Hash of {"name" => "layout"}
186
+ # +site_payload+ is the site payload hash
187
+ #
188
+ # Returns nothing
189
+ def render(layouts, site_payload)
190
+ # construct payload
191
+ payload = {
192
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
193
+ "page" => self.to_liquid
194
+ }.deep_merge(site_payload)
195
+
196
+ do_layout(payload, layouts)
197
+ end
198
+
199
+ # Obtain destination path.
200
+ # +dest+ is the String path to the destination dir
201
+ #
202
+ # Returns destination file path.
203
+ def destination(dest)
204
+ # The url needs to be unescaped in order to preserve the correct filename
205
+ path = File.join(dest, CGI.unescape(self.url))
206
+ path = File.join(path, "index.html") if template[/\.html$/].nil?
207
+ path
208
+ end
209
+
210
+ # Write the generated post file to the destination directory.
211
+ # +dest+ is the String path to the destination dir
212
+ #
213
+ # Returns nothing
214
+ def write(dest)
215
+ path = destination(dest)
216
+ FileUtils.mkdir_p(File.dirname(path))
217
+ File.open(path, 'w') do |f|
218
+ f.write(self.output)
219
+ end
220
+ end
221
+
222
+ # Convert this post into a Hash for use in Liquid templates.
223
+ #
224
+ # Returns <Hash>
225
+ def to_liquid
226
+ self.data.deep_merge({
227
+ "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
228
+ "url" => self.url,
229
+ "date" => self.date,
230
+ "id" => self.id,
231
+ "categories" => self.categories,
232
+ "next" => self.next,
233
+ "previous" => self.previous,
234
+ "tags" => self.tags,
235
+ "content" => self.content })
236
+ end
237
+
238
+ def inspect
239
+ "<Post: #{self.id}>"
240
+ end
241
+
242
+ def next
243
+ pos = self.site.posts.index(self)
244
+
245
+ if pos && pos < self.site.posts.length-1
246
+ self.site.posts[pos+1]
247
+ else
248
+ nil
249
+ end
250
+ end
251
+
252
+ def previous
253
+ pos = self.site.posts.index(self)
254
+ if pos && pos > 0
255
+ self.site.posts[pos-1]
256
+ else
257
+ nil
258
+ end
259
+ end
260
+ end
261
+
262
+ end
@@ -0,0 +1,339 @@
1
+ require 'set'
2
+
3
+ module Jekyll
4
+
5
+ class Site
6
+ attr_accessor :config, :layouts, :posts, :pages, :static_files,
7
+ :categories, :exclude, :include, :source, :dest, :lsi, :pygments,
8
+ :permalink_style, :tags, :time, :future, :safe, :plugins, :limit_posts
9
+
10
+ attr_accessor :converters, :generators
11
+
12
+ # Public: Initialize a new Site.
13
+ #
14
+ # config - A Hash containing site configuration details.
15
+ def initialize(config)
16
+ self.config = config.clone
17
+
18
+ self.safe = config['safe']
19
+ self.source = File.expand_path(config['source'])
20
+ self.dest = File.expand_path(config['destination'])
21
+ self.plugins = Array(config['plugins']).map { |d| File.expand_path(d) }
22
+ self.lsi = config['lsi']
23
+ self.pygments = config['pygments']
24
+ self.permalink_style = config['permalink'].to_sym
25
+ self.exclude = config['exclude'] || []
26
+ self.include = config['include'] || []
27
+ self.future = config['future']
28
+ self.limit_posts = config['limit_posts'] || nil
29
+
30
+ self.reset
31
+ self.setup
32
+ end
33
+
34
+ # Public: Read, process, and write this Site to output.
35
+ #
36
+ # Returns nothing.
37
+ def process
38
+ self.reset
39
+ self.read
40
+ self.generate
41
+ self.render
42
+ self.cleanup
43
+ self.write
44
+ end
45
+
46
+ # Reset Site details.
47
+ #
48
+ # Returns nothing
49
+ def reset
50
+ self.time = if self.config['time']
51
+ Time.parse(self.config['time'].to_s)
52
+ else
53
+ Time.now
54
+ end
55
+ self.layouts = {}
56
+ self.posts = []
57
+ self.pages = []
58
+ self.static_files = []
59
+ self.categories = Hash.new { |hash, key| hash[key] = [] }
60
+ self.tags = Hash.new { |hash, key| hash[key] = [] }
61
+
62
+ if !self.limit_posts.nil? && self.limit_posts < 1
63
+ raise ArgumentError, "Limit posts must be nil or >= 1"
64
+ end
65
+ end
66
+
67
+ # Load necessary libraries, plugins, converters, and generators.
68
+ #
69
+ # Returns nothing.
70
+ def setup
71
+ require 'classifier' if self.lsi
72
+
73
+ # If safe mode is off, load in any Ruby files under the plugins
74
+ # directory.
75
+ unless self.safe
76
+ self.plugins.each do |plugins|
77
+ Dir[File.join(plugins, "**/*.rb")].each do |f|
78
+ require f
79
+ end
80
+ end
81
+ end
82
+
83
+ self.converters = Jekyll::Converter.subclasses.select do |c|
84
+ !self.safe || c.safe
85
+ end.map do |c|
86
+ c.new(self.config)
87
+ end
88
+
89
+ self.generators = Jekyll::Generator.subclasses.select do |c|
90
+ !self.safe || c.safe
91
+ end.map do |c|
92
+ c.new(self.config)
93
+ end
94
+ end
95
+
96
+ # Read Site data from disk and load it into internal data structures.
97
+ #
98
+ # Returns nothing.
99
+ def read
100
+ self.read_layouts
101
+ self.read_directories
102
+ end
103
+
104
+ # Read all the files in <source>/<dir>/_layouts and create a new Layout
105
+ # object with each one.
106
+ #
107
+ # Returns nothing.
108
+ def read_layouts(dir = '')
109
+ base = File.join(self.source, dir, "_layouts")
110
+ return unless File.exists?(base)
111
+ entries = []
112
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
113
+
114
+ entries.each do |f|
115
+ name = f.split(".")[0..-2].join(".")
116
+ self.layouts[name] = Layout.new(self, base, f)
117
+ end
118
+ end
119
+
120
+ # Recursively traverse directories to find posts, pages and static files
121
+ # that will become part of the site according to the rules in
122
+ # filter_entries.
123
+ #
124
+ # dir - The String relative path of the directory to read.
125
+ #
126
+ # Returns nothing.
127
+ def read_directories(dir = '')
128
+ base = File.join(self.source, dir)
129
+ entries = Dir.chdir(base) { filter_entries(Dir.entries('.')) }
130
+
131
+ self.read_posts(dir)
132
+
133
+ entries.each do |f|
134
+ f_abs = File.join(base, f)
135
+ f_rel = File.join(dir, f)
136
+ if File.directory?(f_abs)
137
+ next if self.dest.sub(/\/$/, '') == f_abs
138
+ read_directories(f_rel)
139
+ elsif !File.symlink?(f_abs)
140
+ first3 = File.open(f_abs) { |fd| fd.read(3) }
141
+ if first3 == "---"
142
+ # file appears to have a YAML header so process it as a page
143
+ pages << Page.new(self, self.source, dir, f)
144
+ else
145
+ # otherwise treat it as a static file
146
+ static_files << StaticFile.new(self, self.source, dir, f)
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ # Read all the files in <source>/<dir>/_posts and create a new Post
153
+ # object with each one.
154
+ #
155
+ # dir - The String relative path of the directory to read.
156
+ #
157
+ # Returns nothing.
158
+ def read_posts(dir)
159
+ base = File.join(self.source, dir, '_posts')
160
+ return unless File.exists?(base)
161
+ entries = Dir.chdir(base) { filter_entries(Dir['**/*']) }
162
+
163
+ # first pass processes, but does not yet render post content
164
+ entries.each do |f|
165
+ if Post.valid?(f)
166
+ post = Post.new(self, self.source, dir, f)
167
+
168
+ if post.published && (self.future || post.date <= self.time)
169
+ self.posts << post
170
+ post.categories.each { |c| self.categories[c] << post }
171
+ post.tags.each { |c| self.tags[c] << post }
172
+ end
173
+ end
174
+ end
175
+
176
+ self.posts.sort!
177
+
178
+ # limit the posts if :limit_posts option is set
179
+ if limit_posts
180
+ limit = self.posts.length < limit_posts ? self.posts.length : limit_posts
181
+ self.posts = self.posts[-limit, limit]
182
+ end
183
+ end
184
+
185
+ # Run each of the Generators.
186
+ #
187
+ # Returns nothing.
188
+ def generate
189
+ self.generators.each do |generator|
190
+ generator.generate(self)
191
+ end
192
+ end
193
+
194
+ # Render the site to the destination.
195
+ #
196
+ # Returns nothing.
197
+ def render
198
+ payload = site_payload
199
+
200
+ self.posts.each do |post|
201
+ post.render(self.layouts, payload)
202
+ end
203
+
204
+ self.pages.each do |page|
205
+ page.render(self.layouts, payload)
206
+ end
207
+
208
+ self.categories.values.map { |ps| ps.sort! { |a, b| b <=> a } }
209
+ self.tags.values.map { |ps| ps.sort! { |a, b| b <=> a } }
210
+ rescue Errno::ENOENT
211
+ # ignore missing layout dir
212
+ end
213
+
214
+ # Remove orphaned files and empty directories in destination.
215
+ #
216
+ # Returns nothing.
217
+ def cleanup
218
+ # all files and directories in destination, including hidden ones
219
+ dest_files = Set.new
220
+ Dir.glob(File.join(self.dest, "**", "*"), File::FNM_DOTMATCH) do |file|
221
+ dest_files << file unless file =~ /\/\.{1,2}$/
222
+ end
223
+
224
+ # files to be written
225
+ files = Set.new
226
+ self.posts.each do |post|
227
+ files << post.destination(self.dest)
228
+ end
229
+ self.pages.each do |page|
230
+ files << page.destination(self.dest)
231
+ end
232
+ self.static_files.each do |sf|
233
+ files << sf.destination(self.dest)
234
+ end
235
+
236
+ # adding files' parent directories
237
+ dirs = Set.new
238
+ files.each { |file| dirs << File.dirname(file) }
239
+ files.merge(dirs)
240
+
241
+ obsolete_files = dest_files - files
242
+
243
+ FileUtils.rm_rf(obsolete_files.to_a)
244
+ end
245
+
246
+ # Write static files, pages, and posts.
247
+ #
248
+ # Returns nothing.
249
+ def write
250
+ self.posts.each do |post|
251
+ post.write(self.dest)
252
+ end
253
+ self.pages.each do |page|
254
+ page.write(self.dest)
255
+ end
256
+ self.static_files.each do |sf|
257
+ sf.write(self.dest)
258
+ end
259
+ end
260
+
261
+ # Constructs a Hash of Posts indexed by the specified Post attribute.
262
+ #
263
+ # post_attr - The String name of the Post attribute.
264
+ #
265
+ # Examples
266
+ #
267
+ # post_attr_hash('categories')
268
+ # # => { 'tech' => [<Post A>, <Post B>],
269
+ # # 'ruby' => [<Post B>] }
270
+ #
271
+ # Returns the Hash: { attr => posts } where
272
+ # attr - One of the values for the requested attribute.
273
+ # posts - The Array of Posts with the given attr value.
274
+ def post_attr_hash(post_attr)
275
+ # Build a hash map based on the specified post attribute ( post attr =>
276
+ # array of posts ) then sort each array in reverse order.
277
+ hash = Hash.new { |hsh, key| hsh[key] = Array.new }
278
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
279
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a } }
280
+ hash
281
+ end
282
+
283
+ # The Hash payload containing site-wide data.
284
+ #
285
+ # Returns the Hash: { "site" => data } where data is a Hash with keys:
286
+ # "time" - The Time as specified in the configuration or the
287
+ # current time if none was specified.
288
+ # "posts" - The Array of Posts, sorted chronologically by post date
289
+ # and then title.
290
+ # "pages" - The Array of all Pages.
291
+ # "html_pages" - The Array of HTML Pages.
292
+ # "categories" - The Hash of category values and Posts.
293
+ # See Site#post_attr_hash for type info.
294
+ # "tags" - The Hash of tag values and Posts.
295
+ # See Site#post_attr_hash for type info.
296
+ def site_payload
297
+ {"site" => self.config.merge({
298
+ "time" => self.time,
299
+ "posts" => self.posts.sort { |a, b| b <=> a },
300
+ "pages" => self.pages,
301
+ "html_pages" => self.pages.reject { |page| !page.html? },
302
+ "categories" => post_attr_hash('categories'),
303
+ "tags" => post_attr_hash('tags')})}
304
+ end
305
+
306
+ # Filter out any files/directories that are hidden or backup files (start
307
+ # with "." or "#" or end with "~"), or contain site content (start with "_"),
308
+ # or are excluded in the site configuration, unless they are web server
309
+ # files such as '.htaccess'.
310
+ #
311
+ # entries - The Array of file/directory entries to filter.
312
+ #
313
+ # Returns the Array of filtered entries.
314
+ def filter_entries(entries)
315
+ entries = entries.reject do |e|
316
+ unless self.include.include?(e)
317
+ ['.', '_', '#'].include?(e[0..0]) ||
318
+ e[-1..-1] == '~' ||
319
+ self.exclude.include?(e) ||
320
+ File.symlink?(e)
321
+ end
322
+ end
323
+ end
324
+
325
+ # Get the implementation class for the given Converter.
326
+ #
327
+ # klass - The Class of the Converter to fetch.
328
+ #
329
+ # Returns the Converter instance implementing the given Converter.
330
+ def getConverterImpl(klass)
331
+ matches = self.converters.select { |c| c.class == klass }
332
+ if impl = matches.first
333
+ impl
334
+ else
335
+ raise "Converter implementation not found for #{klass}"
336
+ end
337
+ end
338
+ end
339
+ end
@@ -0,0 +1,77 @@
1
+ module Jekyll
2
+
3
+ class StaticFile
4
+ # The cache of last modification times [path] -> mtime.
5
+ @@mtimes = Hash.new
6
+
7
+ # Initialize a new StaticFile.
8
+ #
9
+ # site - The Site.
10
+ # base - The String path to the <source>.
11
+ # dir - The String path between <source> and the file.
12
+ # name - The String filename of the file.
13
+ def initialize(site, base, dir, name)
14
+ @site = site
15
+ @base = base
16
+ @dir = dir
17
+ @name = name
18
+ end
19
+
20
+ # Returns source file path.
21
+ def path
22
+ File.join(@base, @dir, @name)
23
+ end
24
+ alias filename path
25
+
26
+ def inspect
27
+ "<StaticFile: #{self.path}>"
28
+ end
29
+
30
+ # Obtain destination path.
31
+ #
32
+ # dest - The String path to the destination dir.
33
+ #
34
+ # Returns destination file path.
35
+ def destination(dest)
36
+ File.join(dest, @dir, @name)
37
+ end
38
+
39
+ # Returns last modification time for this file.
40
+ def mtime
41
+ File.stat(path).mtime.to_i
42
+ end
43
+
44
+ # Is source path modified?
45
+ #
46
+ # Returns true if modified since last write.
47
+ def modified?
48
+ @@mtimes[path] != mtime
49
+ end
50
+
51
+ # Write the static file to the destination directory (if modified).
52
+ #
53
+ # dest - The String path to the destination dir.
54
+ #
55
+ # Returns false if the file was not modified since last time (no-op).
56
+ def write(dest)
57
+ dest_path = destination(dest)
58
+
59
+ return false if File.exist?(dest_path) and !modified?
60
+ @@mtimes[path] = mtime
61
+
62
+ FileUtils.mkdir_p(File.dirname(dest_path))
63
+ FileUtils.cp(path, dest_path)
64
+
65
+ true
66
+ end
67
+
68
+ # Reset the mtimes cache (for testing purposes).
69
+ #
70
+ # Returns nothing.
71
+ def self.reset_cache
72
+ @@mtimes = Hash.new
73
+ nil
74
+ end
75
+ end
76
+
77
+ end