bunto 1.0.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 (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.markdown +59 -0
  4. data/bin/bunto +51 -0
  5. data/lib/bunto.rb +179 -0
  6. data/lib/bunto/cleaner.rb +105 -0
  7. data/lib/bunto/collection.rb +205 -0
  8. data/lib/bunto/command.rb +65 -0
  9. data/lib/bunto/commands/build.rb +77 -0
  10. data/lib/bunto/commands/clean.rb +42 -0
  11. data/lib/bunto/commands/doctor.rb +114 -0
  12. data/lib/bunto/commands/help.rb +31 -0
  13. data/lib/bunto/commands/new.rb +82 -0
  14. data/lib/bunto/commands/serve.rb +204 -0
  15. data/lib/bunto/commands/serve/servlet.rb +61 -0
  16. data/lib/bunto/configuration.rb +323 -0
  17. data/lib/bunto/converter.rb +48 -0
  18. data/lib/bunto/converters/identity.rb +21 -0
  19. data/lib/bunto/converters/markdown.rb +92 -0
  20. data/lib/bunto/converters/markdown/kramdown_parser.rb +117 -0
  21. data/lib/bunto/converters/markdown/rdiscount_parser.rb +33 -0
  22. data/lib/bunto/converters/markdown/redcarpet_parser.rb +102 -0
  23. data/lib/bunto/converters/smartypants.rb +34 -0
  24. data/lib/bunto/convertible.rb +297 -0
  25. data/lib/bunto/deprecator.rb +46 -0
  26. data/lib/bunto/document.rb +444 -0
  27. data/lib/bunto/drops/bunto_drop.rb +21 -0
  28. data/lib/bunto/drops/collection_drop.rb +22 -0
  29. data/lib/bunto/drops/document_drop.rb +27 -0
  30. data/lib/bunto/drops/drop.rb +176 -0
  31. data/lib/bunto/drops/site_drop.rb +38 -0
  32. data/lib/bunto/drops/unified_payload_drop.rb +25 -0
  33. data/lib/bunto/drops/url_drop.rb +83 -0
  34. data/lib/bunto/entry_filter.rb +72 -0
  35. data/lib/bunto/errors.rb +10 -0
  36. data/lib/bunto/excerpt.rb +127 -0
  37. data/lib/bunto/external.rb +59 -0
  38. data/lib/bunto/filters.rb +367 -0
  39. data/lib/bunto/frontmatter_defaults.rb +188 -0
  40. data/lib/bunto/generator.rb +3 -0
  41. data/lib/bunto/hooks.rb +101 -0
  42. data/lib/bunto/layout.rb +49 -0
  43. data/lib/bunto/liquid_extensions.rb +22 -0
  44. data/lib/bunto/liquid_renderer.rb +39 -0
  45. data/lib/bunto/liquid_renderer/file.rb +50 -0
  46. data/lib/bunto/liquid_renderer/table.rb +94 -0
  47. data/lib/bunto/log_adapter.rb +115 -0
  48. data/lib/bunto/mime.types +800 -0
  49. data/lib/bunto/page.rb +180 -0
  50. data/lib/bunto/plugin.rb +96 -0
  51. data/lib/bunto/plugin_manager.rb +95 -0
  52. data/lib/bunto/post.rb +329 -0
  53. data/lib/bunto/publisher.rb +21 -0
  54. data/lib/bunto/reader.rb +126 -0
  55. data/lib/bunto/readers/collection_reader.rb +20 -0
  56. data/lib/bunto/readers/data_reader.rb +69 -0
  57. data/lib/bunto/readers/layout_reader.rb +53 -0
  58. data/lib/bunto/readers/page_reader.rb +21 -0
  59. data/lib/bunto/readers/post_reader.rb +62 -0
  60. data/lib/bunto/readers/static_file_reader.rb +21 -0
  61. data/lib/bunto/regenerator.rb +175 -0
  62. data/lib/bunto/related_posts.rb +56 -0
  63. data/lib/bunto/renderer.rb +191 -0
  64. data/lib/bunto/site.rb +391 -0
  65. data/lib/bunto/static_file.rb +141 -0
  66. data/lib/bunto/stevenson.rb +58 -0
  67. data/lib/bunto/tags/highlight.rb +122 -0
  68. data/lib/bunto/tags/include.rb +190 -0
  69. data/lib/bunto/tags/post_url.rb +88 -0
  70. data/lib/bunto/url.rb +136 -0
  71. data/lib/bunto/utils.rb +287 -0
  72. data/lib/bunto/utils/ansi.rb +59 -0
  73. data/lib/bunto/utils/platforms.rb +30 -0
  74. data/lib/bunto/version.rb +3 -0
  75. data/lib/site_template/.gitignore +3 -0
  76. data/lib/site_template/_config.yml +21 -0
  77. data/lib/site_template/_includes/footer.html +38 -0
  78. data/lib/site_template/_includes/head.html +12 -0
  79. data/lib/site_template/_includes/header.html +27 -0
  80. data/lib/site_template/_includes/icon-github.html +1 -0
  81. data/lib/site_template/_includes/icon-github.svg +1 -0
  82. data/lib/site_template/_includes/icon-twitter.html +1 -0
  83. data/lib/site_template/_includes/icon-twitter.svg +1 -0
  84. data/lib/site_template/_layouts/default.html +20 -0
  85. data/lib/site_template/_layouts/page.html +14 -0
  86. data/lib/site_template/_layouts/post.html +15 -0
  87. data/lib/site_template/_posts/0000-00-00-welcome-to-bunto.markdown.erb +25 -0
  88. data/lib/site_template/_sass/_base.scss +206 -0
  89. data/lib/site_template/_sass/_layout.scss +242 -0
  90. data/lib/site_template/_sass/_syntax-highlighting.scss +71 -0
  91. data/lib/site_template/about.md +15 -0
  92. data/lib/site_template/css/main.scss +53 -0
  93. data/lib/site_template/feed.xml +30 -0
  94. data/lib/site_template/index.html +23 -0
  95. metadata +252 -0
@@ -0,0 +1,56 @@
1
+ module Bunto
2
+ class RelatedPosts
3
+ class << self
4
+ attr_accessor :lsi
5
+ end
6
+
7
+ attr_reader :post, :site
8
+
9
+ def initialize(post)
10
+ @post = post
11
+ @site = post.site
12
+ Bunto::External.require_with_graceful_fail('classifier-reborn') if site.lsi
13
+ end
14
+
15
+ def build
16
+ return [] unless site.posts.docs.size > 1
17
+
18
+ if site.lsi
19
+ build_index
20
+ lsi_related_posts
21
+ else
22
+ most_recent_posts
23
+ end
24
+ end
25
+
26
+ def build_index
27
+ self.class.lsi ||= begin
28
+ lsi = ClassifierReborn::LSI.new(:auto_rebuild => false)
29
+ display("Populating LSI...")
30
+
31
+ site.posts.docs.each do |x|
32
+ lsi.add_item(x)
33
+ end
34
+
35
+ display("Rebuilding index...")
36
+ lsi.build_index
37
+ display("")
38
+ lsi
39
+ end
40
+ end
41
+
42
+ def lsi_related_posts
43
+ self.class.lsi.find_related(post, 11)
44
+ end
45
+
46
+ def most_recent_posts
47
+ @most_recent_posts ||= (site.posts.docs.reverse - [post]).first(10)
48
+ end
49
+
50
+ def display(output)
51
+ $stdout.print("\n")
52
+ $stdout.print(Bunto.logger.formatted_topic(output))
53
+ $stdout.flush
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,191 @@
1
+ # encoding: UTF-8
2
+
3
+ module Bunto
4
+ class Renderer
5
+ attr_reader :document, :site, :payload
6
+
7
+ def initialize(site, document, site_payload = nil)
8
+ @site = site
9
+ @document = document
10
+ @payload = site_payload || site.site_payload
11
+ end
12
+
13
+ # Determine which converters to use based on this document's
14
+ # extension.
15
+ #
16
+ # Returns an array of Converter instances.
17
+ def converters
18
+ @converters ||= site.converters.select { |c| c.matches(document.extname) }
19
+ end
20
+
21
+ # Determine the extname the outputted file should have
22
+ #
23
+ # Returns the output extname including the leading period.
24
+ def output_ext
25
+ @output_ext ||= (permalink_ext || converter_output_ext)
26
+ end
27
+
28
+ ######################
29
+ ## DAT RENDER THO
30
+ ######################
31
+
32
+ def run
33
+ Bunto.logger.debug "Rendering:", document.relative_path
34
+
35
+ payload["page"] = document.to_liquid
36
+
37
+ if document.respond_to? :pager
38
+ payload["paginator"] = document.pager.to_liquid
39
+ end
40
+
41
+ if document.is_a?(Document) && document.collection.label == 'posts'
42
+ payload['site']['related_posts'] = document.related_posts
43
+ end
44
+
45
+ # render and transform content (this becomes the final content of the object)
46
+ payload['highlighter_prefix'] = converters.first.highlighter_prefix
47
+ payload['highlighter_suffix'] = converters.first.highlighter_suffix
48
+
49
+ Bunto.logger.debug "Pre-Render Hooks:", document.relative_path
50
+ document.trigger_hooks(:pre_render, payload)
51
+
52
+ info = {
53
+ :filters => [Bunto::Filters],
54
+ :registers => { :site => site, :page => payload['page'] }
55
+ }
56
+
57
+ output = document.content
58
+
59
+ if document.render_with_liquid?
60
+ Bunto.logger.debug "Rendering Liquid:", document.relative_path
61
+ output = render_liquid(output, payload, info, document.path)
62
+ end
63
+
64
+ Bunto.logger.debug "Rendering Markup:", document.relative_path
65
+ output = convert(output)
66
+ document.content = output
67
+
68
+ if document.place_in_layout?
69
+ Bunto.logger.debug "Rendering Layout:", document.relative_path
70
+ place_in_layouts(
71
+ output,
72
+ payload,
73
+ info
74
+ )
75
+ else
76
+ output
77
+ end
78
+ end
79
+
80
+ # Convert the given content using the converters which match this renderer's document.
81
+ #
82
+ # content - the raw, unconverted content
83
+ #
84
+ # Returns the converted content.
85
+ def convert(content)
86
+ converters.reduce(content) do |output, converter|
87
+ begin
88
+ converter.convert output
89
+ rescue => e
90
+ Bunto.logger.error "Conversion error:", "#{converter.class} encountered an error while converting '#{document.relative_path}':"
91
+ Bunto.logger.error("", e.to_s)
92
+ raise e
93
+ end
94
+ end
95
+ end
96
+
97
+ # Render the given content with the payload and info
98
+ #
99
+ # content -
100
+ # payload -
101
+ # info -
102
+ # path - (optional) the path to the file, for use in ex
103
+ #
104
+ # Returns the content, rendered by Liquid.
105
+ def render_liquid(content, payload, info, path = nil)
106
+ site.liquid_renderer.file(path).parse(content).render!(payload, info)
107
+ rescue Tags::IncludeTagError => e
108
+ Bunto.logger.error "Liquid Exception:", "#{e.message} in #{e.path}, included in #{path || document.relative_path}"
109
+ raise e
110
+ rescue Exception => e
111
+ Bunto.logger.error "Liquid Exception:", "#{e.message} in #{path || document.relative_path}"
112
+ raise e
113
+ end
114
+
115
+ # Checks if the layout specified in the document actually exists
116
+ #
117
+ # layout - the layout to check
118
+ #
119
+ # Returns true if the layout is invalid, false if otherwise
120
+ def invalid_layout?(layout)
121
+ !document.data["layout"].nil? && layout.nil?
122
+ end
123
+
124
+ # Render layouts and place given content inside.
125
+ #
126
+ # content - the content to be placed in the layout
127
+ #
128
+ #
129
+ # Returns the content placed in the Liquid-rendered layouts
130
+ def place_in_layouts(content, payload, info)
131
+ output = content.dup
132
+ layout = site.layouts[document.data["layout"]]
133
+
134
+ Bunto.logger.warn("Build Warning:", "Layout '#{document.data["layout"]}' requested in #{document.relative_path} does not exist.") if invalid_layout? layout
135
+
136
+ used = Set.new([layout])
137
+
138
+ while layout
139
+ payload['content'] = output
140
+ payload['page'] = document.to_liquid
141
+ payload['layout'] = Utils.deep_merge_hashes(payload['layout'] || {}, layout.data)
142
+
143
+ output = render_liquid(
144
+ layout.content,
145
+ payload,
146
+ info,
147
+ File.join(site.config['layouts_dir'], layout.name)
148
+ )
149
+
150
+ # Add layout to dependency tree
151
+ site.regenerator.add_dependency(
152
+ site.in_source_dir(document.path),
153
+ site.in_source_dir(layout.path)
154
+ ) if document.write?
155
+
156
+ if layout = site.layouts[layout.data["layout"]]
157
+ if used.include?(layout)
158
+ layout = nil # avoid recursive chain
159
+ else
160
+ used << layout
161
+ end
162
+ end
163
+ end
164
+
165
+ output
166
+ end
167
+
168
+ private
169
+
170
+ def permalink_ext
171
+ if document.permalink && !document.permalink.end_with?("/")
172
+ permalink_ext = File.extname(document.permalink)
173
+ permalink_ext unless permalink_ext.empty?
174
+ end
175
+ end
176
+
177
+ def converter_output_ext
178
+ if output_exts.size == 1
179
+ output_exts.last
180
+ else
181
+ output_exts[-2]
182
+ end
183
+ end
184
+
185
+ def output_exts
186
+ @output_exts ||= converters.map do |c|
187
+ c.output_ext(document.extname)
188
+ end.compact
189
+ end
190
+ end
191
+ end
data/lib/bunto/site.rb ADDED
@@ -0,0 +1,391 @@
1
+ # encoding: UTF-8
2
+ require 'csv'
3
+
4
+ module Bunto
5
+ class Site
6
+ attr_reader :source, :dest, :config
7
+ attr_accessor :layouts, :pages, :static_files, :drafts,
8
+ :exclude, :include, :lsi, :highlighter, :permalink_style,
9
+ :time, :future, :unpublished, :safe, :plugins, :limit_posts,
10
+ :show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
11
+ :gems, :plugin_manager
12
+
13
+ attr_accessor :converters, :generators, :reader
14
+ attr_reader :regenerator, :liquid_renderer
15
+
16
+ # Public: Initialize a new Site.
17
+ #
18
+ # config - A Hash containing site configuration details.
19
+ def initialize(config)
20
+ @config = config.clone
21
+
22
+ %w(safe lsi highlighter baseurl exclude include future unpublished
23
+ show_drafts limit_posts keep_files gems).each do |opt|
24
+ self.send("#{opt}=", config[opt])
25
+ end
26
+
27
+ # Source and destination may not be changed after the site has been created.
28
+ @source = File.expand_path(config['source']).freeze
29
+ @dest = File.expand_path(config['destination']).freeze
30
+
31
+ @reader = Bunto::Reader.new(self)
32
+
33
+ # Initialize incremental regenerator
34
+ @regenerator = Regenerator.new(self)
35
+
36
+ @liquid_renderer = LiquidRenderer.new(self)
37
+
38
+ self.plugin_manager = Bunto::PluginManager.new(self)
39
+ self.plugins = plugin_manager.plugins_path
40
+
41
+ self.file_read_opts = {}
42
+ self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
43
+
44
+ self.permalink_style = config['permalink'].to_sym
45
+
46
+ Bunto.sites << self
47
+
48
+ reset
49
+ setup
50
+ end
51
+
52
+ # Public: Read, process, and write this Site to output.
53
+ #
54
+ # Returns nothing.
55
+ def process
56
+ reset
57
+ read
58
+ generate
59
+ render
60
+ cleanup
61
+ write
62
+ print_stats
63
+ end
64
+
65
+ def print_stats
66
+ if @config['profile']
67
+ puts @liquid_renderer.stats_table
68
+ end
69
+ end
70
+
71
+ # Reset Site details.
72
+ #
73
+ # Returns nothing
74
+ def reset
75
+ self.time = (config['time'] ? Utils.parse_date(config['time'].to_s, "Invalid time in _config.yml.") : Time.now)
76
+ self.layouts = {}
77
+ self.pages = []
78
+ self.static_files = []
79
+ self.data = {}
80
+ @collections = nil
81
+ @regenerator.clear_cache
82
+ @liquid_renderer.reset
83
+
84
+ if limit_posts < 0
85
+ raise ArgumentError, "limit_posts must be a non-negative number"
86
+ end
87
+
88
+ Bunto::Hooks.trigger :site, :after_reset, self
89
+ end
90
+
91
+ # Load necessary libraries, plugins, converters, and generators.
92
+ #
93
+ # Returns nothing.
94
+ def setup
95
+ ensure_not_in_dest
96
+
97
+ plugin_manager.conscientious_require
98
+
99
+ self.converters = instantiate_subclasses(Bunto::Converter)
100
+ self.generators = instantiate_subclasses(Bunto::Generator)
101
+ end
102
+
103
+ # Check that the destination dir isn't the source dir or a directory
104
+ # parent to the source dir.
105
+ def ensure_not_in_dest
106
+ dest_pathname = Pathname.new(dest)
107
+ Pathname.new(source).ascend do |path|
108
+ if path == dest_pathname
109
+ raise Errors::FatalException.new "Destination directory cannot be or contain the Source directory."
110
+ end
111
+ end
112
+ end
113
+
114
+ # The list of collections and their corresponding Bunto::Collection instances.
115
+ # If config['collections'] is set, a new instance is created for each item in the collection.
116
+ # If config['collections'] is not set, a new hash is returned.
117
+ #
118
+ # Returns a Hash containing collection name-to-instance pairs.
119
+ def collections
120
+ @collections ||= Hash[collection_names.map { |coll| [coll, Bunto::Collection.new(self, coll)] } ]
121
+ end
122
+
123
+ # The list of collection names.
124
+ #
125
+ # Returns an array of collection names from the configuration,
126
+ # or an empty array if the `collections` key is not set.
127
+ def collection_names
128
+ case config['collections']
129
+ when Hash
130
+ config['collections'].keys
131
+ when Array
132
+ config['collections']
133
+ when nil
134
+ []
135
+ else
136
+ raise ArgumentError, "Your `collections` key must be a hash or an array."
137
+ end
138
+ end
139
+
140
+ # Read Site data from disk and load it into internal data structures.
141
+ #
142
+ # Returns nothing.
143
+ def read
144
+ reader.read
145
+ limit_posts!
146
+ Bunto::Hooks.trigger :site, :post_read, self
147
+ end
148
+
149
+ # Run each of the Generators.
150
+ #
151
+ # Returns nothing.
152
+ def generate
153
+ generators.each do |generator|
154
+ generator.generate(self)
155
+ end
156
+ end
157
+
158
+ # Render the site to the destination.
159
+ #
160
+ # Returns nothing.
161
+ def render
162
+ relative_permalinks_are_deprecated
163
+
164
+ payload = site_payload
165
+
166
+ Bunto::Hooks.trigger :site, :pre_render, self, payload
167
+
168
+ collections.each do |_, collection|
169
+ collection.docs.each do |document|
170
+ if regenerator.regenerate?(document)
171
+ document.output = Bunto::Renderer.new(self, document, payload).run
172
+ document.trigger_hooks(:post_render)
173
+ end
174
+ end
175
+ end
176
+
177
+ pages.flatten.each do |page|
178
+ if regenerator.regenerate?(page)
179
+ page.output = Bunto::Renderer.new(self, page, payload).run
180
+ page.trigger_hooks(:post_render)
181
+ end
182
+ end
183
+
184
+ Bunto::Hooks.trigger :site, :post_render, self, payload
185
+ rescue Errno::ENOENT
186
+ # ignore missing layout dir
187
+ end
188
+
189
+ # Remove orphaned files and empty directories in destination.
190
+ #
191
+ # Returns nothing.
192
+ def cleanup
193
+ site_cleaner.cleanup!
194
+ end
195
+
196
+ # Write static files, pages, and posts.
197
+ #
198
+ # Returns nothing.
199
+ def write
200
+ each_site_file do |item|
201
+ item.write(dest) if regenerator.regenerate?(item)
202
+ end
203
+ regenerator.write_metadata
204
+ Bunto::Hooks.trigger :site, :post_write, self
205
+ end
206
+
207
+ def posts
208
+ collections['posts'] ||= Collection.new(self, 'posts')
209
+ end
210
+
211
+ # Construct a Hash of Posts indexed by the specified Post attribute.
212
+ #
213
+ # post_attr - The String name of the Post attribute.
214
+ #
215
+ # Examples
216
+ #
217
+ # post_attr_hash('categories')
218
+ # # => { 'tech' => [<Post A>, <Post B>],
219
+ # # 'ruby' => [<Post B>] }
220
+ #
221
+ # Returns the Hash: { attr => posts } where
222
+ # attr - One of the values for the requested attribute.
223
+ # posts - The Array of Posts with the given attr value.
224
+ def post_attr_hash(post_attr)
225
+ # Build a hash map based on the specified post attribute ( post attr =>
226
+ # array of posts ) then sort each array in reverse order.
227
+ hash = Hash.new { |h, key| h[key] = [] }
228
+ posts.docs.each { |p| p.data[post_attr].each { |t| hash[t] << p } if p.data[post_attr] }
229
+ hash.values.each { |posts| posts.sort!.reverse! }
230
+ hash
231
+ end
232
+
233
+ def tags
234
+ post_attr_hash('tags')
235
+ end
236
+
237
+ def categories
238
+ post_attr_hash('categories')
239
+ end
240
+
241
+ # Prepare site data for site payload. The method maintains backward compatibility
242
+ # if the key 'data' is already used in _config.yml.
243
+ #
244
+ # Returns the Hash to be hooked to site.data.
245
+ def site_data
246
+ config['data'] || data
247
+ end
248
+
249
+ # The Hash payload containing site-wide data.
250
+ #
251
+ # Returns the Hash: { "site" => data } where data is a Hash with keys:
252
+ # "time" - The Time as specified in the configuration or the
253
+ # current time if none was specified.
254
+ # "posts" - The Array of Posts, sorted chronologically by post date
255
+ # and then title.
256
+ # "pages" - The Array of all Pages.
257
+ # "html_pages" - The Array of HTML Pages.
258
+ # "categories" - The Hash of category values and Posts.
259
+ # See Site#post_attr_hash for type info.
260
+ # "tags" - The Hash of tag values and Posts.
261
+ # See Site#post_attr_hash for type info.
262
+ def site_payload
263
+ Drops::UnifiedPayloadDrop.new self
264
+ end
265
+
266
+ # Get the implementation class for the given Converter.
267
+ # Returns the Converter instance implementing the given Converter.
268
+ # klass - The Class of the Converter to fetch.
269
+
270
+ def find_converter_instance(klass)
271
+ converters.find { |klass_| klass_.instance_of?(klass) } || \
272
+ raise("No Converters found for #{klass}")
273
+ end
274
+
275
+ # klass - class or module containing the subclasses.
276
+ # Returns array of instances of subclasses of parameter.
277
+ # Create array of instances of the subclasses of the class or module
278
+ # passed in as argument.
279
+
280
+ def instantiate_subclasses(klass)
281
+ klass.descendants.select { |c| !safe || c.safe }.sort.map do |c|
282
+ c.new(config)
283
+ end
284
+ end
285
+
286
+ # Warns the user if permanent links are relative to the parent
287
+ # directory. As this is a deprecated function of Bunto.
288
+ #
289
+ # Returns
290
+ def relative_permalinks_are_deprecated
291
+ if config['relative_permalinks']
292
+ Bunto.logger.abort_with "Since v3.0, permalinks for pages" \
293
+ " in subfolders must be relative to the" \
294
+ " site source directory, not the parent" \
295
+ " directory. Check http://bunto.github.io/docs/upgrading/"\
296
+ " for more info."
297
+ end
298
+ end
299
+
300
+ # Get the to be written documents
301
+ #
302
+ # Returns an Array of Documents which should be written
303
+ def docs_to_write
304
+ documents.select(&:write?)
305
+ end
306
+
307
+ # Get all the documents
308
+ #
309
+ # Returns an Array of all Documents
310
+ def documents
311
+ collections.reduce(Set.new) do |docs, (_, collection)|
312
+ docs + collection.docs + collection.files
313
+ end.to_a
314
+ end
315
+
316
+ def each_site_file
317
+ %w(pages static_files docs_to_write).each do |type|
318
+ send(type).each do |item|
319
+ yield item
320
+ end
321
+ end
322
+ end
323
+
324
+ # Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
325
+ # if it doesn't already exist.
326
+ #
327
+ # Returns The FrontmatterDefaults
328
+ def frontmatter_defaults
329
+ @frontmatter_defaults ||= FrontmatterDefaults.new(self)
330
+ end
331
+
332
+ # Whether to perform a full rebuild without incremental regeneration
333
+ #
334
+ # Returns a Boolean: true for a full rebuild, false for normal build
335
+ def incremental?(override = {})
336
+ override['incremental'] || config['incremental']
337
+ end
338
+
339
+ # Returns the publisher or creates a new publisher if it doesn't
340
+ # already exist.
341
+ #
342
+ # Returns The Publisher
343
+ def publisher
344
+ @publisher ||= Publisher.new(self)
345
+ end
346
+
347
+ # Public: Prefix a given path with the source directory.
348
+ #
349
+ # paths - (optional) path elements to a file or directory within the
350
+ # source directory
351
+ #
352
+ # Returns a path which is prefixed with the source directory.
353
+ def in_source_dir(*paths)
354
+ paths.reduce(source) do |base, path|
355
+ Bunto.sanitized_path(base, path)
356
+ end
357
+ end
358
+
359
+ # Public: Prefix a given path with the destination directory.
360
+ #
361
+ # paths - (optional) path elements to a file or directory within the
362
+ # destination directory
363
+ #
364
+ # Returns a path which is prefixed with the destination directory.
365
+ def in_dest_dir(*paths)
366
+ paths.reduce(dest) do |base, path|
367
+ Bunto.sanitized_path(base, path)
368
+ end
369
+ end
370
+
371
+ private
372
+
373
+ # Limits the current posts; removes the posts which exceed the limit_posts
374
+ #
375
+ # Returns nothing
376
+ def limit_posts!
377
+ if limit_posts > 0
378
+ limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
379
+ self.posts.docs = posts.docs[-limit, limit]
380
+ end
381
+ end
382
+
383
+ # Returns the Cleaner or creates a new Cleaner if it doesn't
384
+ # already exist.
385
+ #
386
+ # Returns The Cleaner
387
+ def site_cleaner
388
+ @site_cleaner ||= Cleaner.new(self)
389
+ end
390
+ end
391
+ end