bridgetown-core 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +42 -0
  3. data/bridgetown-core.gemspec +46 -0
  4. data/lib/bridgetown-core.rb +202 -0
  5. data/lib/bridgetown-core/cache.rb +190 -0
  6. data/lib/bridgetown-core/cleaner.rb +111 -0
  7. data/lib/bridgetown-core/collection.rb +279 -0
  8. data/lib/bridgetown-core/command.rb +106 -0
  9. data/lib/bridgetown-core/commands/build.rb +96 -0
  10. data/lib/bridgetown-core/commands/clean.rb +43 -0
  11. data/lib/bridgetown-core/commands/console.rb +56 -0
  12. data/lib/bridgetown-core/commands/doctor.rb +172 -0
  13. data/lib/bridgetown-core/commands/help.rb +34 -0
  14. data/lib/bridgetown-core/commands/new.rb +148 -0
  15. data/lib/bridgetown-core/commands/serve.rb +273 -0
  16. data/lib/bridgetown-core/commands/serve/servlet.rb +68 -0
  17. data/lib/bridgetown-core/configuration.rb +323 -0
  18. data/lib/bridgetown-core/converter.rb +54 -0
  19. data/lib/bridgetown-core/converters/identity.rb +39 -0
  20. data/lib/bridgetown-core/converters/markdown.rb +108 -0
  21. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +132 -0
  22. data/lib/bridgetown-core/converters/smartypants.rb +69 -0
  23. data/lib/bridgetown-core/convertible.rb +237 -0
  24. data/lib/bridgetown-core/deprecator.rb +50 -0
  25. data/lib/bridgetown-core/document.rb +475 -0
  26. data/lib/bridgetown-core/drops/bridgetown_drop.rb +32 -0
  27. data/lib/bridgetown-core/drops/collection_drop.rb +20 -0
  28. data/lib/bridgetown-core/drops/document_drop.rb +69 -0
  29. data/lib/bridgetown-core/drops/drop.rb +215 -0
  30. data/lib/bridgetown-core/drops/excerpt_drop.rb +19 -0
  31. data/lib/bridgetown-core/drops/page_drop.rb +14 -0
  32. data/lib/bridgetown-core/drops/site_drop.rb +62 -0
  33. data/lib/bridgetown-core/drops/static_file_drop.rb +14 -0
  34. data/lib/bridgetown-core/drops/unified_payload_drop.rb +26 -0
  35. data/lib/bridgetown-core/drops/url_drop.rb +132 -0
  36. data/lib/bridgetown-core/entry_filter.rb +108 -0
  37. data/lib/bridgetown-core/errors.rb +20 -0
  38. data/lib/bridgetown-core/excerpt.rb +202 -0
  39. data/lib/bridgetown-core/external.rb +62 -0
  40. data/lib/bridgetown-core/filters.rb +467 -0
  41. data/lib/bridgetown-core/filters/date_filters.rb +110 -0
  42. data/lib/bridgetown-core/filters/grouping_filters.rb +64 -0
  43. data/lib/bridgetown-core/filters/url_filters.rb +79 -0
  44. data/lib/bridgetown-core/frontmatter_defaults.rb +238 -0
  45. data/lib/bridgetown-core/generator.rb +5 -0
  46. data/lib/bridgetown-core/hooks.rb +103 -0
  47. data/lib/bridgetown-core/layout.rb +57 -0
  48. data/lib/bridgetown-core/liquid_extensions.rb +22 -0
  49. data/lib/bridgetown-core/liquid_renderer.rb +71 -0
  50. data/lib/bridgetown-core/liquid_renderer/file.rb +67 -0
  51. data/lib/bridgetown-core/liquid_renderer/table.rb +75 -0
  52. data/lib/bridgetown-core/log_adapter.rb +151 -0
  53. data/lib/bridgetown-core/log_writer.rb +60 -0
  54. data/lib/bridgetown-core/mime.types +867 -0
  55. data/lib/bridgetown-core/page.rb +214 -0
  56. data/lib/bridgetown-core/page_without_a_file.rb +14 -0
  57. data/lib/bridgetown-core/path_manager.rb +31 -0
  58. data/lib/bridgetown-core/plugin.rb +80 -0
  59. data/lib/bridgetown-core/plugin_manager.rb +60 -0
  60. data/lib/bridgetown-core/publisher.rb +23 -0
  61. data/lib/bridgetown-core/reader.rb +185 -0
  62. data/lib/bridgetown-core/readers/collection_reader.rb +22 -0
  63. data/lib/bridgetown-core/readers/data_reader.rb +75 -0
  64. data/lib/bridgetown-core/readers/layout_reader.rb +48 -0
  65. data/lib/bridgetown-core/readers/page_reader.rb +24 -0
  66. data/lib/bridgetown-core/readers/post_reader.rb +74 -0
  67. data/lib/bridgetown-core/readers/static_file_reader.rb +24 -0
  68. data/lib/bridgetown-core/regenerator.rb +195 -0
  69. data/lib/bridgetown-core/related_posts.rb +52 -0
  70. data/lib/bridgetown-core/renderer.rb +261 -0
  71. data/lib/bridgetown-core/site.rb +469 -0
  72. data/lib/bridgetown-core/static_file.rb +205 -0
  73. data/lib/bridgetown-core/tags/component.rb +34 -0
  74. data/lib/bridgetown-core/tags/highlight.rb +111 -0
  75. data/lib/bridgetown-core/tags/include.rb +220 -0
  76. data/lib/bridgetown-core/tags/link.rb +41 -0
  77. data/lib/bridgetown-core/tags/post_url.rb +107 -0
  78. data/lib/bridgetown-core/url.rb +164 -0
  79. data/lib/bridgetown-core/utils.rb +367 -0
  80. data/lib/bridgetown-core/utils/ansi.rb +57 -0
  81. data/lib/bridgetown-core/utils/exec.rb +26 -0
  82. data/lib/bridgetown-core/utils/internet.rb +37 -0
  83. data/lib/bridgetown-core/utils/platforms.rb +80 -0
  84. data/lib/bridgetown-core/utils/thread_event.rb +31 -0
  85. data/lib/bridgetown-core/utils/win_tz.rb +75 -0
  86. data/lib/bridgetown-core/version.rb +5 -0
  87. data/lib/bridgetown-core/watcher.rb +139 -0
  88. data/lib/site_template/.gitignore +6 -0
  89. data/lib/site_template/bridgetown.config.yml +21 -0
  90. data/lib/site_template/frontend/javascript/index.js +3 -0
  91. data/lib/site_template/frontend/styles/index.scss +17 -0
  92. data/lib/site_template/package.json +23 -0
  93. data/lib/site_template/src/404.html +9 -0
  94. data/lib/site_template/src/_data/site_metadata.yml +11 -0
  95. data/lib/site_template/src/_includes/footer.html +3 -0
  96. data/lib/site_template/src/_includes/head.html +9 -0
  97. data/lib/site_template/src/_includes/navbar.html +4 -0
  98. data/lib/site_template/src/_layouts/default.html +15 -0
  99. data/lib/site_template/src/_layouts/home.html +7 -0
  100. data/lib/site_template/src/_layouts/page.html +7 -0
  101. data/lib/site_template/src/_layouts/post.html +7 -0
  102. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +26 -0
  103. data/lib/site_template/src/about.md +11 -0
  104. data/lib/site_template/src/index.md +7 -0
  105. data/lib/site_template/webpack.config.js +60 -0
  106. data/rake/release.rake +30 -0
  107. metadata +106 -1
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class RelatedPosts
5
+ class << self
6
+ attr_accessor :lsi
7
+ end
8
+
9
+ attr_reader :post, :site
10
+
11
+ def initialize(post)
12
+ @post = post
13
+ @site = post.site
14
+ Bridgetown::External.require_with_graceful_fail("classifier-reborn") if site.lsi
15
+ end
16
+
17
+ def build
18
+ return [] unless site.posts.docs.size > 1
19
+
20
+ if site.lsi
21
+ build_index
22
+ lsi_related_posts
23
+ else
24
+ most_recent_posts
25
+ end
26
+ end
27
+
28
+ def build_index
29
+ self.class.lsi ||= begin
30
+ lsi = ClassifierReborn::LSI.new(:auto_rebuild => false)
31
+ Bridgetown.logger.info("Populating LSI...")
32
+
33
+ site.posts.docs.each do |x|
34
+ lsi.add_item(x)
35
+ end
36
+
37
+ Bridgetown.logger.info("Rebuilding index...")
38
+ lsi.build_index
39
+ Bridgetown.logger.info("")
40
+ lsi
41
+ end
42
+ end
43
+
44
+ def lsi_related_posts
45
+ self.class.lsi.find_related(post, 11)
46
+ end
47
+
48
+ def most_recent_posts
49
+ @most_recent_posts ||= (site.posts.docs.last(11).reverse - [post]).first(10)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class Renderer
5
+ attr_reader :document, :site
6
+ attr_writer :layouts, :payload
7
+
8
+ def initialize(site, document, site_payload = nil)
9
+ @site = site
10
+ @document = document
11
+ @payload = site_payload
12
+ @layouts = nil
13
+ end
14
+
15
+ # Fetches the payload used in Liquid rendering.
16
+ # It can be written with #payload=(new_payload)
17
+ # Falls back to site.site_payload if no payload is set.
18
+ #
19
+ # Returns a Bridgetown::Drops::UnifiedPayloadDrop
20
+ def payload
21
+ @payload ||= site.site_payload
22
+ end
23
+
24
+ # The list of layouts registered for this Renderer.
25
+ # It can be written with #layouts=(new_layouts)
26
+ # Falls back to site.layouts if no layouts are registered.
27
+ #
28
+ # Returns a Hash of String => Bridgetown::Layout identified
29
+ # as basename without the extension name.
30
+ def layouts
31
+ @layouts || site.layouts
32
+ end
33
+
34
+ # Determine which converters to use based on this document's
35
+ # extension.
36
+ #
37
+ # Returns Array of Converter instances.
38
+ def converters
39
+ @converters ||= site.converters.select { |c| c.matches(document.extname) }.sort
40
+ end
41
+
42
+ # Determine the extname the outputted file should have
43
+ #
44
+ # Returns String the output extname including the leading period.
45
+ def output_ext
46
+ @output_ext ||= (permalink_ext || converter_output_ext)
47
+ end
48
+
49
+ # Prepare payload and render the document
50
+ #
51
+ # Returns String rendered document output
52
+ def run
53
+ Bridgetown.logger.debug "Rendering:", document.relative_path
54
+
55
+ assign_pages!
56
+ assign_current_document!
57
+ assign_highlighter_options!
58
+ assign_layout_data!
59
+
60
+ Bridgetown.logger.debug "Pre-Render Hooks:", document.relative_path
61
+ document.trigger_hooks(:pre_render, payload)
62
+
63
+ render_document
64
+ end
65
+
66
+ # Render the document.
67
+ #
68
+ # Returns String rendered document output
69
+ # rubocop: disable Metrics/AbcSize
70
+ def render_document
71
+ info = {
72
+ :registers => { :site => site, :page => payload["page"] },
73
+ :strict_filters => liquid_options["strict_filters"],
74
+ :strict_variables => liquid_options["strict_variables"],
75
+ }
76
+
77
+ output = document.content
78
+ if document.render_with_liquid?
79
+ Bridgetown.logger.debug "Rendering Liquid:", document.relative_path
80
+ output = render_liquid(output, payload, info, document.path)
81
+ end
82
+
83
+ Bridgetown.logger.debug "Rendering Markup:", document.relative_path
84
+ output = convert(output.to_s)
85
+ document.content = output
86
+
87
+ if document.place_in_layout?
88
+ Bridgetown.logger.debug "Rendering Layout:", document.relative_path
89
+ output = place_in_layouts(output, payload, info)
90
+ end
91
+
92
+ output
93
+ end
94
+ # rubocop: enable Metrics/AbcSize
95
+
96
+ # Convert the document using the converters which match this renderer's document.
97
+ #
98
+ # Returns String the converted content.
99
+ def convert(content)
100
+ converters.reduce(content) do |output, converter|
101
+ begin
102
+ converter.convert output
103
+ rescue StandardError => e
104
+ Bridgetown.logger.error "Conversion error:",
105
+ "#{converter.class} encountered an error while "\
106
+ "converting '#{document.relative_path}':"
107
+ Bridgetown.logger.error("", e.to_s)
108
+ raise e
109
+ end
110
+ end
111
+ end
112
+
113
+ # Render the given content with the payload and info
114
+ #
115
+ # content -
116
+ # payload -
117
+ # info -
118
+ # path - (optional) the path to the file, for use in ex
119
+ #
120
+ # Returns String the content, rendered by Liquid.
121
+ def render_liquid(content, payload, info, path = nil)
122
+ template = site.liquid_renderer.file(path).parse(content)
123
+ template.warnings.each do |e|
124
+ Bridgetown.logger.warn "Liquid Warning:",
125
+ LiquidRenderer.format_error(e, path || document.relative_path)
126
+ end
127
+ template.render!(payload, info)
128
+ # rubocop: disable Lint/RescueException
129
+ rescue Exception => e
130
+ Bridgetown.logger.error "Liquid Exception:",
131
+ LiquidRenderer.format_error(e, path || document.relative_path)
132
+ raise e
133
+ end
134
+ # rubocop: enable Lint/RescueException
135
+
136
+ # Checks if the layout specified in the document actually exists
137
+ #
138
+ # layout - the layout to check
139
+ #
140
+ # Returns Boolean true if the layout is invalid, false if otherwise
141
+ def invalid_layout?(layout)
142
+ !document.data["layout"].nil? && layout.nil? && !(document.is_a? Bridgetown::Excerpt)
143
+ end
144
+
145
+ # Render layouts and place document content inside.
146
+ #
147
+ # Returns String rendered content
148
+ def place_in_layouts(content, payload, info)
149
+ output = content.dup
150
+ layout = layouts[document.data["layout"].to_s]
151
+ validate_layout(layout)
152
+
153
+ used = Set.new([layout])
154
+
155
+ # Reset the payload layout data to ensure it starts fresh for each page.
156
+ payload["layout"] = nil
157
+
158
+ while layout
159
+ output = render_layout(output, layout, info)
160
+ add_regenerator_dependencies(layout)
161
+
162
+ next unless (layout = site.layouts[layout.data["layout"]])
163
+ break if used.include?(layout)
164
+
165
+ used << layout
166
+ end
167
+ output
168
+ end
169
+
170
+ private
171
+
172
+ # Checks if the layout specified in the document actually exists
173
+ #
174
+ # layout - the layout to check
175
+ # Returns nothing
176
+ def validate_layout(layout)
177
+ return unless invalid_layout?(layout)
178
+
179
+ Bridgetown.logger.warn "Build Warning:", "Layout '#{document.data["layout"]}' requested " \
180
+ "in #{document.relative_path} does not exist."
181
+ end
182
+
183
+ # Render layout content into document.output
184
+ #
185
+ # Returns String rendered content
186
+ def render_layout(output, layout, info)
187
+ payload["content"] = output
188
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
189
+
190
+ render_liquid(
191
+ layout.content,
192
+ payload,
193
+ info,
194
+ layout.path
195
+ )
196
+ end
197
+
198
+ def add_regenerator_dependencies(layout)
199
+ return unless document.write?
200
+
201
+ site.regenerator.add_dependency(
202
+ site.in_source_dir(document.path),
203
+ layout.path
204
+ )
205
+ end
206
+
207
+ # Set page content to payload and assign pager if document has one.
208
+ #
209
+ # Returns nothing
210
+ def assign_pages!
211
+ payload["page"] = document.to_liquid
212
+ payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
213
+ end
214
+
215
+ # Set related posts to payload if document is a post.
216
+ #
217
+ # Returns nothing
218
+ def assign_current_document!
219
+ payload["site"].current_document = document
220
+ end
221
+
222
+ # Set highlighter prefix and suffix
223
+ #
224
+ # Returns nothing
225
+ def assign_highlighter_options!
226
+ payload["highlighter_prefix"] = converters.first.highlighter_prefix
227
+ payload["highlighter_suffix"] = converters.first.highlighter_suffix
228
+ end
229
+
230
+ def assign_layout_data!
231
+ layout = layouts[document.data["layout"]]
232
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {}) if layout
233
+ end
234
+
235
+ def permalink_ext
236
+ document_permalink = document.permalink
237
+ if document_permalink && !document_permalink.end_with?("/")
238
+ permalink_ext = File.extname(document_permalink)
239
+ permalink_ext unless permalink_ext.empty?
240
+ end
241
+ end
242
+
243
+ def converter_output_ext
244
+ if output_exts.size == 1
245
+ output_exts.last
246
+ else
247
+ output_exts[-2]
248
+ end
249
+ end
250
+
251
+ def output_exts
252
+ @output_exts ||= converters.map do |c|
253
+ c.output_ext(document.extname)
254
+ end.compact
255
+ end
256
+
257
+ def liquid_options
258
+ @liquid_options ||= site.config["liquid"]
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,469 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class Site
5
+ attr_reader :root_dir, :source, :dest, :cache_dir, :config
6
+ attr_accessor :layouts, :pages, :static_files,
7
+ :exclude, :include, :lsi, :highlighter, :permalink_style,
8
+ :time, :future, :unpublished, :plugins, :limit_posts,
9
+ :keep_files, :baseurl, :data, :file_read_opts,
10
+ :plugin_manager
11
+
12
+ attr_accessor :converters, :generators, :reader
13
+ attr_reader :regenerator, :liquid_renderer, :includes_load_paths
14
+
15
+ # Public: Initialize a new Site.
16
+ #
17
+ # config - A Hash containing site configuration details.
18
+ def initialize(config)
19
+ # Source and destination may not be changed after the site has been created.
20
+ @root_dir = File.expand_path(config["root_dir"]).freeze
21
+ @source = File.expand_path(config["source"]).freeze
22
+ @dest = File.expand_path(config["destination"]).freeze
23
+
24
+ self.config = config
25
+
26
+ @cache_dir = in_root_dir(config["cache_dir"])
27
+ @reader = Reader.new(self)
28
+ @regenerator = Regenerator.new(self)
29
+ @liquid_renderer = LiquidRenderer.new(self)
30
+
31
+ Bridgetown.sites << self
32
+
33
+ reset
34
+ setup
35
+
36
+ Bridgetown::Hooks.trigger :site, :after_init, self
37
+ end
38
+
39
+ # Public: Set the site's configuration. This handles side-effects caused by
40
+ # changing values in the configuration.
41
+ #
42
+ # config - a Bridgetown::Configuration, containing the new configuration.
43
+ #
44
+ # Returns the new configuration.
45
+ def config=(config)
46
+ @config = config.clone
47
+
48
+ %w(lsi highlighter baseurl exclude include future unpublished
49
+ limit_posts keep_files).each do |opt|
50
+ send("#{opt}=", config[opt])
51
+ end
52
+
53
+ configure_cache
54
+ configure_plugins
55
+ configure_include_paths
56
+ configure_file_read_opts
57
+
58
+ self.permalink_style = config["permalink"].to_sym
59
+
60
+ @config
61
+ end
62
+
63
+ # Public: Read, process, and write this Site to output.
64
+ #
65
+ # Returns nothing.
66
+ def process
67
+ reset
68
+ read
69
+ generate
70
+ render
71
+ cleanup
72
+ write
73
+ print_stats if config["profile"]
74
+ end
75
+
76
+ def print_stats
77
+ Bridgetown.logger.info @liquid_renderer.stats_table
78
+ end
79
+
80
+ # rubocop:disable Metrics/MethodLength
81
+ #
82
+ # Reset Site details.
83
+ #
84
+ # Returns nothing
85
+ def reset
86
+ self.time = if config["time"]
87
+ Utils.parse_date(config["time"].to_s, "Invalid time in bridgetown.config.yml.")
88
+ else
89
+ Time.now
90
+ end
91
+ self.layouts = {}
92
+ self.pages = []
93
+ self.static_files = []
94
+ self.data = {}
95
+ @post_attr_hash = {}
96
+ @site_data = nil
97
+ @collections = nil
98
+ @documents = nil
99
+ @docs_to_write = nil
100
+ @regenerator.clear_cache
101
+ @liquid_renderer.reset
102
+ @site_cleaner = nil
103
+ frontmatter_defaults.reset
104
+
105
+ raise ArgumentError, "limit_posts must be a non-negative number" if limit_posts.negative?
106
+
107
+ Bridgetown::Cache.clear_if_config_changed config
108
+ Bridgetown::Hooks.trigger :site, :after_reset, self
109
+ end
110
+ # rubocop:enable Metrics/MethodLength
111
+
112
+ # Load necessary libraries, plugins, converters, and generators.
113
+ #
114
+ # Returns nothing.
115
+ def setup
116
+ ensure_not_in_dest
117
+
118
+ plugin_manager.conscientious_require
119
+
120
+ self.converters = instantiate_subclasses(Bridgetown::Converter)
121
+ self.generators = instantiate_subclasses(Bridgetown::Generator)
122
+ end
123
+
124
+ # Check that the destination dir isn't the source dir or a directory
125
+ # parent to the source dir.
126
+ def ensure_not_in_dest
127
+ dest_pathname = Pathname.new(dest)
128
+ Pathname.new(source).ascend do |path|
129
+ if path == dest_pathname
130
+ raise Errors::FatalException,
131
+ "Destination directory cannot be or contain the Source directory."
132
+ end
133
+ end
134
+ end
135
+
136
+ # The list of collections and their corresponding Bridgetown::Collection instances.
137
+ # If config['collections'] is set, a new instance is created
138
+ # for each item in the collection, a new hash is returned otherwise.
139
+ #
140
+ # Returns a Hash containing collection name-to-instance pairs.
141
+ def collections
142
+ @collections ||= collection_names.each_with_object({}) do |name, hsh|
143
+ hsh[name] = Bridgetown::Collection.new(self, name)
144
+ end
145
+ end
146
+
147
+ # The list of collection names.
148
+ #
149
+ # Returns an array of collection names from the configuration,
150
+ # or an empty array if the `collections` key is not set.
151
+ def collection_names
152
+ case config["collections"]
153
+ when Hash
154
+ config["collections"].keys
155
+ when Array
156
+ config["collections"]
157
+ when nil
158
+ []
159
+ else
160
+ raise ArgumentError, "Your `collections` key must be a hash or an array."
161
+ end
162
+ end
163
+
164
+ # Read Site data from disk and load it into internal data structures.
165
+ #
166
+ # Returns nothing.
167
+ def read
168
+ reader.read
169
+ limit_posts!
170
+ Bridgetown::Hooks.trigger :site, :post_read, self
171
+ end
172
+
173
+ # Run each of the Generators.
174
+ #
175
+ # Returns nothing.
176
+ def generate
177
+ generators.each do |generator|
178
+ start = Time.now
179
+ generator.generate(self)
180
+ Bridgetown.logger.debug "Generating:",
181
+ "#{generator.class} finished in #{Time.now - start} seconds."
182
+ end
183
+ end
184
+
185
+ # Render the site to the destination.
186
+ #
187
+ # Returns nothing.
188
+ def render
189
+ payload = site_payload
190
+
191
+ Bridgetown::Hooks.trigger :site, :pre_render, self, payload
192
+
193
+ render_docs(payload)
194
+ render_pages(payload)
195
+
196
+ Bridgetown::Hooks.trigger :site, :post_render, self, payload
197
+ end
198
+
199
+ # Remove orphaned files and empty directories in destination.
200
+ #
201
+ # Returns nothing.
202
+ def cleanup
203
+ site_cleaner.cleanup!
204
+ end
205
+
206
+ # Write static files, pages, and posts.
207
+ #
208
+ # Returns nothing.
209
+ def write
210
+ each_site_file do |item|
211
+ item.write(dest) if regenerator.regenerate?(item)
212
+ end
213
+ regenerator.write_metadata
214
+ Bridgetown::Hooks.trigger :site, :post_write, self
215
+ end
216
+
217
+ def posts
218
+ collections["posts"] ||= Collection.new(self, "posts")
219
+ end
220
+
221
+ # Construct a Hash of Posts indexed by the specified Post attribute.
222
+ #
223
+ # post_attr - The String name of the Post attribute.
224
+ #
225
+ # Examples
226
+ #
227
+ # post_attr_hash('categories')
228
+ # # => { 'tech' => [<Post A>, <Post B>],
229
+ # # 'ruby' => [<Post B>] }
230
+ #
231
+ # Returns the Hash: { attr => posts } where
232
+ # attr - One of the values for the requested attribute.
233
+ # posts - The Array of Posts with the given attr value.
234
+ def post_attr_hash(post_attr)
235
+ # Build a hash map based on the specified post attribute ( post attr =>
236
+ # array of posts ) then sort each array in reverse order.
237
+ @post_attr_hash[post_attr] ||= begin
238
+ hash = Hash.new { |h, key| h[key] = [] }
239
+ posts.docs.each do |p|
240
+ p.data[post_attr]&.each { |t| hash[t] << p }
241
+ end
242
+ hash.each_value { |posts| posts.sort!.reverse! }
243
+ hash
244
+ end
245
+ end
246
+
247
+ def tags
248
+ post_attr_hash("tags")
249
+ end
250
+
251
+ def categories
252
+ post_attr_hash("categories")
253
+ end
254
+
255
+ # Prepare site data for site payload. The method maintains backward compatibility
256
+ # if the key 'data' is already used in bridgetown.config.yml.
257
+ #
258
+ # Returns the Hash to be hooked to site.data.
259
+ def site_data
260
+ @site_data ||= (config["data"] || data)
261
+ end
262
+
263
+ # The Hash payload containing site-wide data.
264
+ #
265
+ # Returns the Hash: { "site" => data } where data is a Hash with keys:
266
+ # "time" - The Time as specified in the configuration or the
267
+ # current time if none was specified.
268
+ # "posts" - The Array of Posts, sorted chronologically by post date
269
+ # and then title.
270
+ # "pages" - The Array of all Pages.
271
+ # "html_pages" - The Array of HTML Pages.
272
+ # "categories" - The Hash of category values and Posts.
273
+ # See Site#post_attr_hash for type info.
274
+ # "tags" - The Hash of tag values and Posts.
275
+ # See Site#post_attr_hash for type info.
276
+ def site_payload
277
+ Drops::UnifiedPayloadDrop.new self
278
+ end
279
+ alias_method :to_liquid, :site_payload
280
+
281
+ # Get the implementation class for the given Converter.
282
+ # Returns the Converter instance implementing the given Converter.
283
+ # klass - The Class of the Converter to fetch.
284
+ def find_converter_instance(klass)
285
+ @find_converter_instance ||= {}
286
+ @find_converter_instance[klass] ||= begin
287
+ converters.find { |converter| converter.instance_of?(klass) } || \
288
+ raise("No Converters found for #{klass}")
289
+ end
290
+ end
291
+
292
+ # klass - class or module containing the subclasses.
293
+ # Returns array of instances of subclasses of parameter.
294
+ # Create array of instances of the subclasses of the class or module
295
+ # passed in as argument.
296
+
297
+ def instantiate_subclasses(klass)
298
+ klass.descendants.sort.map do |c|
299
+ c.new(config)
300
+ end
301
+ end
302
+
303
+ # Get the to be written documents
304
+ #
305
+ # Returns an Array of Documents which should be written
306
+ def docs_to_write
307
+ documents.select(&:write?)
308
+ end
309
+
310
+ # Get all the documents
311
+ #
312
+ # Returns an Array of all Documents
313
+ def documents
314
+ collections.each_with_object(Set.new) do |(_, collection), set|
315
+ set.merge(collection.docs).merge(collection.files)
316
+ end.to_a
317
+ end
318
+
319
+ def each_site_file
320
+ %w(pages static_files docs_to_write).each do |type|
321
+ send(type).each do |item|
322
+ yield item
323
+ end
324
+ end
325
+ end
326
+
327
+ # Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
328
+ # if it doesn't already exist.
329
+ #
330
+ # Returns The FrontmatterDefaults
331
+ def frontmatter_defaults
332
+ @frontmatter_defaults ||= FrontmatterDefaults.new(self)
333
+ end
334
+
335
+ # Whether to perform a full rebuild without incremental regeneration
336
+ #
337
+ # Returns a Boolean: true for a full rebuild, false for normal build
338
+ def incremental?(override = {})
339
+ override["incremental"] || config["incremental"]
340
+ end
341
+
342
+ # Returns the publisher or creates a new publisher if it doesn't
343
+ # already exist.
344
+ #
345
+ # Returns The Publisher
346
+ def publisher
347
+ @publisher ||= Publisher.new(self)
348
+ end
349
+
350
+ # Public: Prefix a given path with the root directory.
351
+ #
352
+ # paths - (optional) path elements to a file or directory within the
353
+ # root directory
354
+ #
355
+ # Returns a path which is prefixed with the root_dir directory.
356
+ def in_root_dir(*paths)
357
+ paths.reduce(root_dir) do |base, path|
358
+ Bridgetown.sanitized_path(base, path)
359
+ end
360
+ end
361
+
362
+ # Public: Prefix a given path with the source directory.
363
+ #
364
+ # paths - (optional) path elements to a file or directory within the
365
+ # source directory
366
+ #
367
+ # Returns a path which is prefixed with the source directory.
368
+ def in_source_dir(*paths)
369
+ paths.reduce(source) do |base, path|
370
+ Bridgetown.sanitized_path(base, path)
371
+ end
372
+ end
373
+
374
+ # Public: Prefix a given path with the destination directory.
375
+ #
376
+ # paths - (optional) path elements to a file or directory within the
377
+ # destination directory
378
+ #
379
+ # Returns a path which is prefixed with the destination directory.
380
+ def in_dest_dir(*paths)
381
+ paths.reduce(dest) do |base, path|
382
+ Bridgetown.sanitized_path(base, path)
383
+ end
384
+ end
385
+
386
+ # Public: Prefix a given path with the cache directory.
387
+ #
388
+ # paths - (optional) path elements to a file or directory within the
389
+ # cache directory
390
+ #
391
+ # Returns a path which is prefixed with the cache directory.
392
+ def in_cache_dir(*paths)
393
+ paths.reduce(cache_dir) do |base, path|
394
+ Bridgetown.sanitized_path(base, path)
395
+ end
396
+ end
397
+
398
+ # Public: The full path to the directory that houses all the collections registered
399
+ # with the current site.
400
+ #
401
+ # Returns the source directory or the absolute path to the custom collections_dir
402
+ def collections_path
403
+ dir_str = config["collections_dir"]
404
+ @collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str)
405
+ end
406
+
407
+ private
408
+
409
+ # Limits the current posts; removes the posts which exceed the limit_posts
410
+ #
411
+ # Returns nothing
412
+ def limit_posts!
413
+ if limit_posts.positive?
414
+ limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts
415
+ posts.docs = posts.docs[-limit, limit]
416
+ end
417
+ end
418
+
419
+ # Returns the Cleaner or creates a new Cleaner if it doesn't
420
+ # already exist.
421
+ #
422
+ # Returns The Cleaner
423
+ def site_cleaner
424
+ @site_cleaner ||= Cleaner.new(self)
425
+ end
426
+
427
+ # Disable Marshaling cache to disk in Safe Mode
428
+ def configure_cache
429
+ Bridgetown::Cache.cache_dir = in_root_dir(config["cache_dir"], "Bridgetown/Cache")
430
+ Bridgetown::Cache.disable_disk_cache! if config["disable_disk_cache"]
431
+ end
432
+
433
+ def configure_plugins
434
+ self.plugin_manager = Bridgetown::PluginManager.new(self)
435
+ self.plugins = plugin_manager.plugins_path
436
+ end
437
+
438
+ def configure_include_paths
439
+ @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
440
+ end
441
+
442
+ def configure_file_read_opts
443
+ self.file_read_opts = {}
444
+ file_read_opts[:encoding] = config["encoding"] if config["encoding"]
445
+ self.file_read_opts = Bridgetown::Utils.merged_file_read_opts(self, {})
446
+ end
447
+
448
+ def render_docs(payload)
449
+ collections.each_value do |collection|
450
+ collection.docs.each do |document|
451
+ render_regenerated(document, payload)
452
+ end
453
+ end
454
+ end
455
+
456
+ def render_pages(payload)
457
+ pages.each do |page|
458
+ render_regenerated(page, payload)
459
+ end
460
+ end
461
+
462
+ def render_regenerated(document, payload)
463
+ return unless regenerator.regenerate?(document)
464
+
465
+ document.output = Bridgetown::Renderer.new(self, document, payload).run
466
+ document.trigger_hooks(:post_render)
467
+ end
468
+ end
469
+ end