henrik-jekyll 0.5.0

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 (47) hide show
  1. data/History.txt +115 -0
  2. data/README.textile +545 -0
  3. data/Rakefile +91 -0
  4. data/VERSION.yml +4 -0
  5. data/bin/jekyll +140 -0
  6. data/lib/jekyll/albino.rb +120 -0
  7. data/lib/jekyll/converters/csv.rb +26 -0
  8. data/lib/jekyll/converters/mephisto.rb +79 -0
  9. data/lib/jekyll/converters/mt.rb +59 -0
  10. data/lib/jekyll/converters/textpattern.rb +50 -0
  11. data/lib/jekyll/converters/typo.rb +49 -0
  12. data/lib/jekyll/converters/wordpress.rb +54 -0
  13. data/lib/jekyll/convertible.rb +82 -0
  14. data/lib/jekyll/core_ext.rb +22 -0
  15. data/lib/jekyll/filters.rb +43 -0
  16. data/lib/jekyll/layout.rb +36 -0
  17. data/lib/jekyll/page.rb +67 -0
  18. data/lib/jekyll/post.rb +205 -0
  19. data/lib/jekyll/site.rb +238 -0
  20. data/lib/jekyll/tags/highlight.rb +56 -0
  21. data/lib/jekyll/tags/include.rb +31 -0
  22. data/lib/jekyll.rb +83 -0
  23. data/test/helper.rb +24 -0
  24. data/test/source/_includes/sig.markdown +3 -0
  25. data/test/source/_layouts/default.html +27 -0
  26. data/test/source/_layouts/simple.html +1 -0
  27. data/test/source/_posts/2008-02-02-not-published.textile +8 -0
  28. data/test/source/_posts/2008-02-02-published.textile +8 -0
  29. data/test/source/_posts/2008-10-18-foo-bar.textile +8 -0
  30. data/test/source/_posts/2008-11-21-complex.textile +8 -0
  31. data/test/source/_posts/2008-12-03-permalinked-post.textile +9 -0
  32. data/test/source/_posts/2008-12-13-include.markdown +8 -0
  33. data/test/source/_posts/2009-01-27-array-categories.textile +10 -0
  34. data/test/source/_posts/2009-01-27-categories.textile +7 -0
  35. data/test/source/_posts/2009-01-27-category.textile +7 -0
  36. data/test/source/category/_posts/2008-9-23-categories.textile +6 -0
  37. data/test/source/css/screen.css +76 -0
  38. data/test/source/foo/_posts/bar/2008-12-12-topical-post.textile +8 -0
  39. data/test/source/index.html +22 -0
  40. data/test/source/z_category/_posts/2008-9-23-categories.textile +6 -0
  41. data/test/suite.rb +9 -0
  42. data/test/test_filters.rb +41 -0
  43. data/test/test_generated_site.rb +38 -0
  44. data/test/test_post.rb +145 -0
  45. data/test/test_site.rb +57 -0
  46. data/test/test_tags.rb +35 -0
  47. metadata +163 -0
@@ -0,0 +1,82 @@
1
+ # Convertible provides methods for converting a pagelike item
2
+ # from a certain type of markup into actual content
3
+ #
4
+ # Requires
5
+ # self.site -> Jekyll::Site
6
+ module Jekyll
7
+ module Convertible
8
+ # Return the contents as a string
9
+ def to_s
10
+ self.content || ''
11
+ end
12
+
13
+ # Read the YAML frontmatter
14
+ # +base+ is the String path to the dir containing the file
15
+ # +name+ is the String filename of the file
16
+ #
17
+ # Returns nothing
18
+ def read_yaml(base, name)
19
+ self.content = File.read(File.join(base, name))
20
+
21
+ if self.content =~ /^(---\s*\n.*?)\n---\s*\n/m
22
+ self.content = self.content[($1.size + 5)..-1]
23
+
24
+ self.data = YAML.load($1)
25
+ end
26
+ end
27
+
28
+ # Transform the contents based on the file extension.
29
+ #
30
+ # Returns nothing
31
+ def transform
32
+ case self.content_type
33
+ when 'textile'
34
+ self.ext = ".html"
35
+ self.content = self.site.textile(self.content)
36
+ when 'markdown'
37
+ self.ext = ".html"
38
+ self.content = self.site.markdown(self.content)
39
+ end
40
+ end
41
+
42
+ # Determine which formatting engine to use based on this convertible's
43
+ # extension
44
+ #
45
+ # Returns one of :textile, :markdown or :unknown
46
+ def content_type
47
+ case self.ext[1..-1]
48
+ when /textile/i
49
+ return 'textile'
50
+ when /markdown/i, /mkdn/i, /md/i
51
+ return 'markdown'
52
+ end
53
+ return 'unknown'
54
+ end
55
+
56
+ # Add any necessary layouts to this convertible document
57
+ # +layouts+ is a Hash of {"name" => "layout"}
58
+ # +site_payload+ is the site payload hash
59
+ #
60
+ # Returns nothing
61
+ def do_layout(payload, layouts)
62
+ info = { :filters => [Jekyll::Filters], :registers => { :site => self.site } }
63
+
64
+ # render and transform content (this becomes the final content of the object)
65
+ payload["content_type"] = self.content_type
66
+ self.content = Liquid::Template.parse(self.content).render(payload, info)
67
+ self.transform
68
+
69
+ # output keeps track of what will finally be written
70
+ self.output = self.content
71
+
72
+ # recursively render layouts
73
+ layout = layouts[self.data["layout"]]
74
+ while layout
75
+ payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
76
+ self.output = Liquid::Template.parse(layout.content).render(payload, info)
77
+
78
+ layout = layouts[layout.data["layout"]]
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,22 @@
1
+ class Hash
2
+ # Merges self with another hash, recursively.
3
+ #
4
+ # This code was lovingly stolen from some random gem:
5
+ # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
6
+ #
7
+ # Thanks to whoever made it.
8
+ def deep_merge(hash)
9
+ target = dup
10
+
11
+ hash.keys.each do |key|
12
+ if hash[key].is_a? Hash and self[key].is_a? Hash
13
+ target[key] = target[key].deep_merge(hash[key])
14
+ next
15
+ end
16
+
17
+ target[key] = hash[key]
18
+ end
19
+
20
+ target
21
+ end
22
+ end
@@ -0,0 +1,43 @@
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
+ input.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
22
+ end
23
+
24
+ def number_of_words(input)
25
+ input.split.length
26
+ end
27
+
28
+ def array_to_sentence_string(array)
29
+ connector = "and"
30
+ case array.length
31
+ when 0
32
+ ""
33
+ when 1
34
+ array[0].to_s
35
+ when 2
36
+ "#{array[0]} #{connector} #{array[1]}"
37
+ else
38
+ "#{array[0...-1].join(', ')}, #{connector} #{array[-1]}"
39
+ end
40
+ end
41
+
42
+ end
43
+ 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,67 @@
1
+ module Jekyll
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :site
7
+ attr_accessor :ext
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.data = {}
24
+
25
+ self.process(name)
26
+ self.read_yaml(File.join(base, dir), name)
27
+ #self.transform
28
+ end
29
+
30
+ # Extract information from the page filename
31
+ # +name+ is the String filename of the page file
32
+ #
33
+ # Returns nothing
34
+ def process(name)
35
+ self.ext = File.extname(name)
36
+ end
37
+
38
+ # Add any necessary layouts to this post
39
+ # +layouts+ is a Hash of {"name" => "layout"}
40
+ # +site_payload+ is the site payload hash
41
+ #
42
+ # Returns nothing
43
+ def render(layouts, site_payload)
44
+ payload = {"page" => self.data}.deep_merge(site_payload)
45
+ do_layout(payload, layouts)
46
+ end
47
+
48
+ # Write the generated page file to the destination directory.
49
+ # +dest+ is the String path to the destination dir
50
+ #
51
+ # Returns nothing
52
+ def write(dest)
53
+ FileUtils.mkdir_p(File.join(dest, @dir))
54
+
55
+ name = @name
56
+ if self.ext != ""
57
+ name = @name.split(".")[0..-2].join('.') + self.ext
58
+ end
59
+
60
+ path = File.join(dest, @dir, name)
61
+ File.open(path, 'w') do |f|
62
+ f.write(self.output)
63
+ end
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,205 @@
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 :date, :slug, :ext, :categories, :topics, :published
23
+ attr_accessor :data, :content, :output
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
+
39
+ parts = name.split('/')
40
+ self.topics = parts.size > 1 ? parts[0..-2] : []
41
+
42
+ self.process(name)
43
+ self.read_yaml(@base, name)
44
+
45
+ if self.data.has_key?('published') && self.data['published'] == false
46
+ self.published = false
47
+ else
48
+ self.published = true
49
+ end
50
+
51
+ if self.categories.empty?
52
+ if self.data.has_key?('category')
53
+ self.categories << self.data['category']
54
+ elsif self.data.has_key?('categories')
55
+ # Look for categories in the YAML-header, either specified as
56
+ # an array or a string.
57
+ if self.data['categories'].kind_of? String
58
+ self.categories = self.data['categories'].split
59
+ else
60
+ self.categories = self.data['categories']
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ # Spaceship is based on Post#date
67
+ #
68
+ # Returns -1, 0, 1
69
+ def <=>(other)
70
+ self.date <=> other.date
71
+ end
72
+
73
+ # Extract information from the post filename
74
+ # +name+ is the String filename of the post file
75
+ #
76
+ # Returns nothing
77
+ def process(name)
78
+ m, cats, date, slug, ext = *name.match(MATCHER)
79
+ self.date = Time.parse(date)
80
+ self.slug = slug
81
+ self.ext = ext
82
+ end
83
+
84
+ # The generated directory into which the post will be placed
85
+ # upon generation. This is derived from the permalink or, if
86
+ # permalink is absent, set to the default date
87
+ # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
88
+ #
89
+ # Returns <String>
90
+ def dir
91
+ if permalink
92
+ permalink.to_s.split("/")[0..-2].join("/") + '/'
93
+ else
94
+ prefix = self.categories.empty? ? '' : '/' + self.categories.join('/')
95
+ if [:date, :pretty].include?(self.site.permalink_style)
96
+ prefix + date.strftime("/%Y/%m/%d/")
97
+ else
98
+ prefix + '/'
99
+ end
100
+ end
101
+ end
102
+
103
+ # The full path and filename of the post.
104
+ # Defined in the YAML of the post body
105
+ # (Optional)
106
+ #
107
+ # Returns <String>
108
+ def permalink
109
+ self.data && self.data['permalink']
110
+ end
111
+
112
+ # The generated relative url of this post
113
+ # e.g. /2008/11/05/my-awesome-post.html
114
+ #
115
+ # Returns <String>
116
+ def url
117
+ ext = self.site.permalink_style == :pretty ? '' : '.html'
118
+ permalink || self.id + ext
119
+ end
120
+
121
+ # The UID for this post (useful in feeds)
122
+ # e.g. /2008/11/05/my-awesome-post
123
+ #
124
+ # Returns <String>
125
+ def id
126
+ self.dir + self.slug
127
+ end
128
+
129
+ # Calculate related posts.
130
+ #
131
+ # Returns [<Post>]
132
+ def related_posts(posts)
133
+ return [] unless posts.size > 1
134
+
135
+ if self.site.lsi
136
+ self.class.lsi ||= begin
137
+ puts "Running the classifier... this could take a while."
138
+ lsi = Classifier::LSI.new
139
+ posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
140
+ puts ""
141
+ lsi
142
+ end
143
+
144
+ related = self.class.lsi.find_related(self.content, 11)
145
+ related - [self]
146
+ else
147
+ (posts - [self])[0..9]
148
+ end
149
+ end
150
+
151
+ # Add any necessary layouts to this post
152
+ # +layouts+ is a Hash of {"name" => "layout"}
153
+ # +site_payload+ is the site payload hash
154
+ #
155
+ # Returns nothing
156
+ def render(layouts, site_payload)
157
+ # construct payload
158
+ payload =
159
+ {
160
+ "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
161
+ "page" => self.to_liquid
162
+ }
163
+ payload = payload.deep_merge(site_payload)
164
+
165
+ do_layout(payload, layouts)
166
+ end
167
+
168
+ # Write the generated post file to the destination directory.
169
+ # +dest+ is the String path to the destination dir
170
+ #
171
+ # Returns nothing
172
+ def write(dest)
173
+ FileUtils.mkdir_p(File.join(dest, dir))
174
+
175
+ path = File.join(dest, self.url)
176
+
177
+ if self.site.permalink_style == :pretty
178
+ FileUtils.mkdir_p(path)
179
+ path = File.join(path, "index.html")
180
+ end
181
+
182
+ File.open(path, 'w') do |f|
183
+ f.write(self.output)
184
+ end
185
+ end
186
+
187
+ # Convert this post into a Hash for use in Liquid templates.
188
+ #
189
+ # Returns <Hash>
190
+ def to_liquid
191
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
192
+ "url" => self.url,
193
+ "date" => self.date,
194
+ "id" => self.id,
195
+ "topics" => self.topics,
196
+ "categories" => self.categories,
197
+ "content" => self.content }.deep_merge(self.data)
198
+ end
199
+
200
+ def inspect
201
+ "<Post: #{self.id}>"
202
+ end
203
+ end
204
+
205
+ end
@@ -0,0 +1,238 @@
1
+ module Jekyll
2
+
3
+ class Site
4
+ attr_accessor :config, :layouts, :posts, :categories
5
+ attr_accessor :source, :dest, :lsi, :pygments, :permalink_style
6
+
7
+ # Initialize the site
8
+ # +config+ is a Hash containing site configurations details
9
+ #
10
+ # Returns <Site>
11
+ def initialize(config)
12
+ self.config = config.clone
13
+
14
+ self.source = config['source']
15
+ self.dest = config['destination']
16
+ self.lsi = config['lsi']
17
+ self.pygments = config['pygments']
18
+ self.permalink_style = config['permalink'].to_sym
19
+
20
+ self.reset
21
+ self.setup
22
+ end
23
+
24
+ def reset
25
+ self.layouts = {}
26
+ self.posts = []
27
+ self.categories = Hash.new { |hash, key| hash[key] = Array.new }
28
+ end
29
+
30
+ def setup
31
+ # Check to see if LSI is enabled.
32
+ require 'classifier' if self.lsi
33
+
34
+ # Set the Markdown interpreter (and Maruku self.config, if necessary)
35
+ case self.config['markdown']
36
+ when 'rdiscount'
37
+ begin
38
+ require 'rdiscount'
39
+
40
+ def markdown(content)
41
+ RDiscount.new(content).to_html
42
+ end
43
+
44
+ puts 'Using rdiscount for Markdown'
45
+ rescue LoadError
46
+ puts 'You must have the rdiscount gem installed first'
47
+ end
48
+ when 'maruku'
49
+ begin
50
+ require 'maruku'
51
+
52
+ def markdown(content)
53
+ Maruku.new(content).to_html
54
+ end
55
+
56
+ if self.config['maruku']['use_divs']
57
+ require 'maruku/ext/div'
58
+ puts 'Maruku: Using extended syntax for div elements.'
59
+ end
60
+
61
+ if self.config['maruku']['use_tex']
62
+ require 'maruku/ext/math'
63
+ puts "Maruku: Using LaTeX extension. Images in `#{self.config['maruku']['png_dir']}`."
64
+
65
+ # Switch off MathML output
66
+ MaRuKu::Globals[:html_math_output_mathml] = false
67
+ MaRuKu::Globals[:html_math_engine] = 'none'
68
+
69
+ # Turn on math to PNG support with blahtex
70
+ # Resulting PNGs stored in `images/latex`
71
+ MaRuKu::Globals[:html_math_output_png] = true
72
+ MaRuKu::Globals[:html_png_engine] = self.config['maruku']['png_engine']
73
+ MaRuKu::Globals[:html_png_dir] = self.config['maruku']['png_dir']
74
+ MaRuKu::Globals[:html_png_url] = self.config['maruku']['png_url']
75
+ end
76
+ rescue LoadError
77
+ puts "The maruku gem is required for markdown support!"
78
+ end
79
+ end
80
+ end
81
+
82
+ def textile(content)
83
+ RedCloth.new(content).to_html
84
+ end
85
+
86
+ # Do the actual work of processing the site and generating the
87
+ # real deal.
88
+ #
89
+ # Returns nothing
90
+ def process
91
+ self.reset
92
+ self.read_layouts
93
+ self.transform_pages
94
+ self.write_posts
95
+ end
96
+
97
+ # Read all the files in <source>/_layouts into memory for later use.
98
+ #
99
+ # Returns nothing
100
+ def read_layouts
101
+ base = File.join(self.source, "_layouts")
102
+ entries = []
103
+ Dir.chdir(base) { entries = filter_entries(Dir['*.*']) }
104
+
105
+ entries.each do |f|
106
+ name = f.split(".")[0..-2].join(".")
107
+ self.layouts[name] = Layout.new(self, base, f)
108
+ end
109
+ rescue Errno::ENOENT => e
110
+ # ignore missing layout dir
111
+ end
112
+
113
+ # Read all the files in <base>/_posts and create a new Post object with each one.
114
+ #
115
+ # Returns nothing
116
+ def read_posts(dir)
117
+ base = File.join(self.source, dir, '_posts')
118
+ entries = []
119
+ Dir.chdir(base) { entries = filter_entries(Dir['**/*']) }
120
+
121
+ # first pass processes, but does not yet render post content
122
+ entries.each do |f|
123
+ if Post.valid?(f)
124
+ post = Post.new(self, self.source, dir, f)
125
+
126
+ if post.published
127
+ self.posts << post
128
+ post.categories.each { |c| self.categories[c] << post }
129
+ end
130
+ end
131
+ end
132
+
133
+ # second pass renders each post now that full site payload is available
134
+ self.posts.each do |post|
135
+ post.render(self.layouts, site_payload)
136
+ end
137
+
138
+ self.posts.sort!
139
+ self.categories.values.map { |cats| cats.sort! { |a, b| b <=> a} }
140
+ rescue Errno::ENOENT => e
141
+ # ignore missing layout dir
142
+ end
143
+
144
+ # Write each post to <dest>/<year>/<month>/<day>/<slug>
145
+ #
146
+ # Returns nothing
147
+ def write_posts
148
+ self.posts.each do |post|
149
+ post.write(self.dest)
150
+ end
151
+ end
152
+
153
+ # Copy all regular files from <source> to <dest>/ ignoring
154
+ # any files/directories that are hidden or backup files (start
155
+ # with "." or "#" or end with "~") or contain site content (start with "_")
156
+ # unless they are "_posts" directories or web server files such as
157
+ # '.htaccess'
158
+ # The +dir+ String is a relative path used to call this method
159
+ # recursively as it descends through directories
160
+ #
161
+ # Returns nothing
162
+ def transform_pages(dir = '')
163
+ base = File.join(self.source, dir)
164
+ entries = filter_entries(Dir.entries(base))
165
+ directories = entries.select { |e| File.directory?(File.join(base, e)) }
166
+ files = entries.reject { |e| File.directory?(File.join(base, e)) }
167
+
168
+ # we need to make sure to process _posts *first* otherwise they
169
+ # might not be available yet to other templates as {{ site.posts }}
170
+ if directories.include?('_posts')
171
+ directories.delete('_posts')
172
+ read_posts(dir)
173
+ end
174
+ [directories, files].each do |entries|
175
+ entries.each do |f|
176
+ if File.directory?(File.join(base, f))
177
+ next if self.dest.sub(/\/$/, '') == File.join(base, f)
178
+ transform_pages(File.join(dir, f))
179
+ else
180
+ first3 = File.open(File.join(self.source, dir, f)) { |fd| fd.read(3) }
181
+
182
+ if first3 == "---"
183
+ # file appears to have a YAML header so process it as a page
184
+ page = Page.new(self, self.source, dir, f)
185
+ page.render(self.layouts, site_payload)
186
+ page.write(self.dest)
187
+ else
188
+ # otherwise copy the file without transforming it
189
+ FileUtils.mkdir_p(File.join(self.dest, dir))
190
+ FileUtils.cp(File.join(self.source, dir, f), File.join(self.dest, dir, f))
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ # Constructs a hash map of Posts indexed by the specified Post attribute
198
+ #
199
+ # Returns {post_attr => [<Post>]}
200
+ def post_attr_hash(post_attr)
201
+ # Build a hash map based on the specified post attribute ( post attr => array of posts )
202
+ # then sort each array in reverse order
203
+ hash = Hash.new { |hash, key| hash[key] = Array.new }
204
+ self.posts.each { |p| p.send(post_attr.to_sym).each { |t| hash[t] << p } }
205
+ hash.values.map { |sortme| sortme.sort! { |a, b| b <=> a} }
206
+ return hash
207
+ end
208
+
209
+ # The Hash payload containing site-wide data
210
+ #
211
+ # Returns {"site" => {"time" => <Time>,
212
+ # "posts" => [<Post>],
213
+ # "categories" => [<Post>],
214
+ # "topics" => [<Post>] }}
215
+ def site_payload
216
+ {"site" => {
217
+ "time" => Time.now,
218
+ "posts" => self.posts.sort { |a,b| b <=> a },
219
+ "categories" => post_attr_hash('categories'),
220
+ "topics" => post_attr_hash('topics')
221
+ }}
222
+ end
223
+
224
+ # Filter out any files/directories that are hidden or backup files (start
225
+ # with "." or "#" or end with "~") or contain site content (start with "_")
226
+ # unless they are "_posts" directories or web server files such as
227
+ # '.htaccess'
228
+ def filter_entries(entries)
229
+ entries = entries.reject do |e|
230
+ unless ['_posts', '.htaccess'].include?(e)
231
+ # Reject backup/hidden
232
+ ['.', '_', '#'].include?(e[0..0]) or e[-1..-1] == '~'
233
+ end
234
+ end
235
+ end
236
+
237
+ end
238
+ end