bridgetown-core 0.17.1 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/lib/bridgetown-core.rb +43 -28
  4. data/lib/bridgetown-core/collection.rb +5 -1
  5. data/lib/bridgetown-core/commands/apply.rb +2 -2
  6. data/lib/bridgetown-core/commands/new.rb +1 -1
  7. data/lib/bridgetown-core/concerns/layout_placeable.rb +1 -1
  8. data/lib/bridgetown-core/concerns/liquid_renderable.rb +10 -0
  9. data/lib/bridgetown-core/concerns/site/configurable.rb +21 -23
  10. data/lib/bridgetown-core/concerns/site/content.rb +44 -31
  11. data/lib/bridgetown-core/concerns/site/extensible.rb +14 -13
  12. data/lib/bridgetown-core/concerns/site/localizable.rb +6 -2
  13. data/lib/bridgetown-core/concerns/site/processable.rb +10 -9
  14. data/lib/bridgetown-core/concerns/site/renderable.rb +34 -26
  15. data/lib/bridgetown-core/concerns/site/writable.rb +7 -15
  16. data/lib/bridgetown-core/configuration.rb +5 -3
  17. data/lib/bridgetown-core/converter.rb +0 -42
  18. data/lib/bridgetown-core/converters/erb_templates.rb +75 -16
  19. data/lib/bridgetown-core/converters/liquid_templates.rb +96 -0
  20. data/lib/bridgetown-core/converters/markdown.rb +0 -3
  21. data/lib/bridgetown-core/document.rb +31 -18
  22. data/lib/bridgetown-core/drops/site_drop.rb +4 -0
  23. data/lib/bridgetown-core/drops/unified_payload_drop.rb +0 -1
  24. data/lib/bridgetown-core/drops/url_drop.rb +19 -3
  25. data/lib/bridgetown-core/excerpt.rb +1 -1
  26. data/lib/bridgetown-core/filters.rb +28 -7
  27. data/lib/bridgetown-core/generators/prototype_generator.rb +42 -25
  28. data/lib/bridgetown-core/helpers.rb +84 -0
  29. data/lib/bridgetown-core/liquid_renderer.rb +1 -1
  30. data/lib/bridgetown-core/log_writer.rb +2 -2
  31. data/lib/bridgetown-core/plugin_manager.rb +34 -1
  32. data/lib/bridgetown-core/reader.rb +1 -4
  33. data/lib/bridgetown-core/readers/post_reader.rb +28 -15
  34. data/lib/bridgetown-core/renderer.rb +42 -160
  35. data/lib/bridgetown-core/ruby_template_view.rb +22 -33
  36. data/lib/bridgetown-core/site.rb +12 -2
  37. data/lib/bridgetown-core/utils.rb +14 -0
  38. data/lib/bridgetown-core/version.rb +2 -2
  39. data/lib/bridgetown-core/watcher.rb +1 -0
  40. data/lib/site_template/src/images/.keep +1 -0
  41. metadata +7 -3
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class RubyTemplateView
5
+ class Helpers
6
+ include Bridgetown::Filters
7
+
8
+ attr_reader :view, :site
9
+
10
+ Context = Struct.new(:registers)
11
+
12
+ def initialize(view, site)
13
+ @view = view
14
+ @site = site
15
+
16
+ # duck typing for Liquid context
17
+ @context = Context.new({ site: site })
18
+ end
19
+
20
+ def webpack_path(asset_type)
21
+ Bridgetown::Utils.parse_webpack_manifest_file(site, asset_type.to_s)
22
+ end
23
+
24
+ # @param pairs [Hash] A hash of key/value pairs.
25
+ #
26
+ # @return [String] Space-separated keys where the values are truthy.
27
+ def class_map(pairs = {})
28
+ pairs.select { |_key, truthy| truthy }.keys.join(" ")
29
+ end
30
+
31
+ # This helper will generate the correct permalink URL for the file path.
32
+ #
33
+ # @param relative_path [String, Object] source file path, e.g.
34
+ # "_posts/2020-10-20-my-post.md", or object that responds to `url`
35
+ # @return [String] the permalink URL for the file
36
+ # @raise [ArgumentError] if the file cannot be found
37
+ def url_for(relative_path)
38
+ path_string = !relative_path.is_a?(String) ? relative_path.url : relative_path
39
+
40
+ return path_string if path_string.start_with?("/", "http")
41
+
42
+ site.each_site_file do |item|
43
+ if item.relative_path == path_string || item.relative_path == "/#{path_string}"
44
+ return relative_url(item)
45
+ end
46
+ end
47
+
48
+ raise ArgumentError, <<~MSG
49
+ Could not find document '#{relative_path}' in 'url_for' helper.
50
+
51
+ Make sure the document exists and the path is correct.
52
+ MSG
53
+ end
54
+ alias_method :link, :url_for
55
+
56
+ # This helper will generate the correct permalink URL for the file path.
57
+ #
58
+ # @param text [String] the content inside the anchor tag
59
+ # @param relative_path [String, Object] source file path, e.g.
60
+ # "_posts/2020-10-20-my-post.md", or object that responds to `url`
61
+ # @param options [Hash] key-value pairs of HTML attributes to add to the tag
62
+ # @return [String] the anchor tag HTML
63
+ # @raise [ArgumentError] if the file cannot be found
64
+ def link_to(text, relative_path, options = {})
65
+ segments = []
66
+ segments << "a"
67
+ segments << "href=\"#{url_for(relative_path)}\""
68
+ options.each do |attr, option|
69
+ attr = attr.to_s.tr("_", "-")
70
+ segments << "#{attr}=\"#{Utils.xml_escape(option)}\""
71
+ end
72
+ "<#{segments.join(" ")}>#{text}</a>"
73
+ end
74
+
75
+ # Forward all arguments to I18n.t method
76
+ #
77
+ # @return [String] the translated string
78
+ # @see I18n
79
+ def t(*args)
80
+ I18n.send :t, *args
81
+ end
82
+ end
83
+ end
84
+ end
@@ -26,7 +26,7 @@ module Bridgetown
26
26
  def reset
27
27
  @stats = {}
28
28
  @cache = {}
29
- Renderer.cached_partials = {}
29
+ Bridgetown::Converters::LiquidTemplates.cached_partials = {}
30
30
  end
31
31
 
32
32
  def file(filename)
@@ -33,12 +33,12 @@ module Bridgetown
33
33
  true
34
34
  end
35
35
 
36
- # Log a +WARN+ message
36
+ # Log a `WARN` message
37
37
  def warn(progname = nil, &block)
38
38
  add(WARN, nil, progname.yellow, &block)
39
39
  end
40
40
 
41
- # Log an +ERROR+ message
41
+ # Log an `ERROR` message
42
42
  def error(progname = nil, &block)
43
43
  add(ERROR, nil, progname.red, &block)
44
44
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "zeitwerk"
4
+
3
5
  module Bridgetown
4
6
  class PluginManager
5
7
  PLUGINS_GROUP = :bridgetown_plugins
6
8
  YARN_DEPENDENCY_REGEXP = %r!(.+)@([^@]*)$!.freeze
7
9
 
8
- attr_reader :site
10
+ attr_reader :site, :component_loaders
9
11
 
10
12
  @source_manifests = Set.new
11
13
  @registered_plugins = Set.new
@@ -37,6 +39,7 @@ module Bridgetown
37
39
  # Returns nothing
38
40
  def initialize(site)
39
41
  @site = site
42
+ @component_loaders = {}
40
43
  end
41
44
 
42
45
  def self.require_from_bundler
@@ -158,5 +161,35 @@ module Bridgetown
158
161
  Array(site.config["plugins_dir"]).map { |d| File.expand_path(d) }
159
162
  end
160
163
  end
164
+
165
+ # rubocop:disable Metrics/AbcSize
166
+ def setup_component_loaders
167
+ unless @component_loaders.keys.empty?
168
+ @component_loaders.each do |_path, loader|
169
+ loader.unload
170
+ end
171
+ @component_loaders = {}
172
+ end
173
+
174
+ # Because "first constant wins" in Zeitwerk, we need to load the local
175
+ # source components _before_ we load any from plugins
176
+ site.components_load_paths.reverse_each do |load_path|
177
+ next unless Dir.exist? load_path
178
+ next if Zeitwerk::Registry.loaders.find { |loader| loader.manages?(load_path) }
179
+
180
+ @component_loaders[load_path] = Zeitwerk::Loader.new
181
+ @component_loaders[load_path].push_dir(load_path)
182
+ @component_loaders[load_path].enable_reloading if load_path.start_with?(site.root_dir)
183
+ @component_loaders[load_path].ignore(File.join(load_path, "**", "*.js.rb"))
184
+ @component_loaders[load_path].setup
185
+ end
186
+ end
187
+ # rubocop:enable Metrics/AbcSize
188
+
189
+ def reload_component_loaders
190
+ @component_loaders.each do |path, loader|
191
+ loader.reload if path.start_with?(site.root_dir)
192
+ end
193
+ end
161
194
  end
162
195
  end
@@ -76,10 +76,7 @@ module Bridgetown
76
76
  def retrieve_posts(dir)
77
77
  return if outside_configured_directory?(dir)
78
78
 
79
- post_reader.read_posts(dir).tap do |entries|
80
- site.posts.docs.concat(entries.select { |entry| entry.is_a?(Document) })
81
- site.posts.files.concat(entries.select { |entry| entry.is_a?(StaticFile) })
82
- end
79
+ post_reader.read_posts(dir)
83
80
  end
84
81
 
85
82
  # Recursively traverse directories with the read_directories function.
@@ -21,13 +21,13 @@ module Bridgetown
21
21
  # Read all the files in <source>/<dir>/<magic_dir> and create a new
22
22
  # Document object with each one insofar as it matches the regexp matcher.
23
23
  #
24
- # dir - The String relative path of the directory to read.
24
+ # @param dir [String] relative path of the directory to read.
25
25
  #
26
- # Returns nothing.
26
+ # @return [Array<Document, StaticFile>]
27
27
  def read_publishable(dir, magic_dir, matcher)
28
28
  read_content(dir, magic_dir, matcher)
29
- .tap { |entries| entries.select { |entry| entry.respond_to?(:read) }.each(&:read) }
30
- .select { |entry| processable?(entry) }
29
+ .tap { |items| items.select { |item| item.respond_to?(:read) }.each(&:read) }
30
+ .select { |item| item_added_to_site?(item) }
31
31
  end
32
32
 
33
33
  # Read all the content files from <source>/<dir>/magic_dir
@@ -70,26 +70,39 @@ module Bridgetown
70
70
  )
71
71
  end
72
72
 
73
- def processable?(doc)
74
- return true if doc.is_a?(StaticFile)
73
+ def processable?(item)
74
+ return true if item.is_a?(StaticFile)
75
75
 
76
- if doc.content.nil?
77
- Bridgetown.logger.debug "Skipping:", "Content in #{doc.relative_path} is nil"
76
+ if item.content.nil?
77
+ Bridgetown.logger.debug "Skipping:", "Content in #{item.relative_path} is nil"
78
78
  false
79
- elsif !doc.content.valid_encoding?
80
- Bridgetown.logger.debug "Skipping:", "#{doc.relative_path} is not valid UTF-8"
79
+ elsif !item.content.valid_encoding?
80
+ Bridgetown.logger.debug "Skipping:", "#{item.relative_path} is not valid UTF-8"
81
81
  false
82
82
  else
83
- publishable?(doc)
83
+ publishable?(item)
84
84
  end
85
85
  end
86
86
 
87
- def publishable?(doc)
88
- site.publisher.publish?(doc).tap do |will_publish|
89
- if !will_publish && site.publisher.hidden_in_the_future?(doc)
90
- Bridgetown.logger.warn "Skipping:", "#{doc.relative_path} has a future date"
87
+ def publishable?(item)
88
+ site.publisher.publish?(item).tap do |will_publish|
89
+ if !will_publish && site.publisher.hidden_in_the_future?(item)
90
+ Bridgetown.logger.warn "Skipping:", "#{item.relative_path} has a future date"
91
91
  end
92
92
  end
93
93
  end
94
+
95
+ def item_added_to_site?(item)
96
+ return false unless processable?(item)
97
+
98
+ if item.is_a?(Document)
99
+ site.posts.docs << item
100
+ elsif item.is_a?(StaticFile)
101
+ site.posts.files << item
102
+ site.static_files << item
103
+ end
104
+
105
+ true
106
+ end
94
107
  end
95
108
  end
@@ -3,37 +3,10 @@
3
3
  module Bridgetown
4
4
  class Renderer
5
5
  attr_reader :document, :site
6
- attr_writer :layouts, :payload
7
6
 
8
- class << self
9
- attr_accessor :cached_partials
10
- end
11
-
12
- def initialize(site, document, site_payload = nil)
7
+ def initialize(site, document)
13
8
  @site = site
14
9
  @document = document
15
- @payload = site_payload
16
- @layouts = nil
17
- self.class.cached_partials ||= {}
18
- end
19
-
20
- # Fetches the payload used in Liquid rendering.
21
- # It can be written with #payload=(new_payload)
22
- # Falls back to site.site_payload if no payload is set.
23
- #
24
- # Returns a Bridgetown::Drops::UnifiedPayloadDrop
25
- def payload
26
- @payload ||= site.site_payload
27
- end
28
-
29
- # The list of layouts registered for this Renderer.
30
- # It can be written with #layouts=(new_layouts)
31
- # Falls back to site.layouts if no layouts are registered.
32
- #
33
- # Returns a Hash of String => Bridgetown::Layout identified
34
- # as basename without the extension name.
35
- def layouts
36
- @layouts || site.layouts
37
10
  end
38
11
 
39
12
  # Determine which converters to use based on this document's
@@ -41,7 +14,13 @@ module Bridgetown
41
14
  #
42
15
  # Returns Array of Converter instances.
43
16
  def converters
44
- @converters ||= site.converters.select { |c| c.matches(document.extname) }.sort
17
+ @converters ||= site.converters.select do |converter|
18
+ if converter.method(:matches).arity == 1
19
+ converter.matches(document.extname)
20
+ else
21
+ converter.matches(document.extname, document)
22
+ end
23
+ end.sort
45
24
  end
46
25
 
47
26
  # Determine the extname the outputted file should have
@@ -51,93 +30,42 @@ module Bridgetown
51
30
  @output_ext ||= (permalink_ext || converter_output_ext)
52
31
  end
53
32
 
54
- # Prepare payload and render the document
33
+ # Run hooks and render the document
55
34
  #
56
35
  # Returns nothing
57
36
  def run
58
37
  Bridgetown.logger.debug "Rendering:", document.relative_path
59
38
 
60
- assign_pages!
61
- # TODO: this can be eliminated I think:
62
- assign_current_document!
63
- assign_highlighter_options!
64
- assign_layout_data!
65
-
66
- document.trigger_hooks(:pre_render, payload)
39
+ document.trigger_hooks :pre_render
67
40
  document.output = render_document
68
- document.trigger_hooks(:post_render)
41
+ document.trigger_hooks :post_render
69
42
  end
70
43
 
71
44
  # Render the document.
72
45
  #
73
46
  # Returns String rendered document output
74
- # rubocop: disable Metrics/AbcSize
75
47
  def render_document
76
- liquid_context = provide_liquid_context
77
-
78
48
  execute_inline_ruby!
79
49
 
80
50
  output = document.content
81
- if document.render_with_liquid?
82
- Bridgetown.logger.debug "Rendering Liquid:", document.relative_path
83
- output = render_liquid(output, payload, liquid_context, document.path)
84
- end
85
-
86
51
  Bridgetown.logger.debug "Rendering Markup:", document.relative_path
87
52
  output = convert(output.to_s, document)
88
53
  document.content = output
89
54
 
90
55
  if document.place_in_layout?
91
56
  Bridgetown.logger.debug "Rendering Layout:", document.relative_path
92
- output = place_in_layouts(output, payload, liquid_context)
57
+ output = place_in_layouts(output)
93
58
  end
94
59
 
95
60
  output
96
61
  end
97
62
 
98
- def provide_liquid_context
99
- {
100
- registers: {
101
- site: site,
102
- page: payload["page"],
103
- cached_partials: self.class.cached_partials,
104
- },
105
- strict_filters: liquid_options["strict_filters"],
106
- strict_variables: liquid_options["strict_variables"],
107
- }
108
- end
109
-
110
63
  def execute_inline_ruby!
111
64
  return unless site.config.should_execute_inline_ruby?
112
65
 
113
66
  Bridgetown::Utils::RubyExec.search_data_for_ruby_code(document, self)
114
67
  end
115
68
 
116
- # rubocop: enable Metrics/AbcSize
117
-
118
- # Render the given content with the payload and context
119
- #
120
- # content -
121
- # payload -
122
- # context -
123
- # path - (optional) the path to the file, for use in ex
124
- #
125
- # Returns String the content, rendered by Liquid.
126
- def render_liquid(content, payload, liquid_context, path = nil)
127
- template = site.liquid_renderer.file(path).parse(content)
128
- template.warnings.each do |e|
129
- Bridgetown.logger.warn "Liquid Warning:",
130
- LiquidRenderer.format_error(e, path || document.relative_path)
131
- end
132
- template.render!(payload, liquid_context)
133
- # rubocop: disable Lint/RescueException
134
- rescue Exception => e
135
- Bridgetown.logger.error "Liquid Exception:",
136
- LiquidRenderer.format_error(e, path || document.relative_path)
137
- raise e
138
- end
139
- # rubocop: enable Lint/RescueException
140
-
141
69
  # Convert the document using the converters which match this renderer's document.
142
70
  #
143
71
  # Returns String the converted content.
@@ -157,30 +85,18 @@ module Bridgetown
157
85
  end
158
86
  end
159
87
 
160
- # Checks if the layout specified in the document actually exists
161
- #
162
- # layout - the layout to check
163
- #
164
- # Returns Boolean true if the layout is invalid, false if otherwise
165
- def invalid_layout?(layout)
166
- !document.data["layout"].nil? && layout.nil? && !(document.is_a? Bridgetown::Excerpt)
167
- end
168
-
169
88
  # Render layouts and place document content inside.
170
89
  #
171
90
  # Returns String rendered content
172
- def place_in_layouts(content, payload, liquid_context)
91
+ def place_in_layouts(content)
173
92
  output = content.dup
174
- layout = layouts[document.data["layout"].to_s]
93
+ layout = site.layouts[document.data["layout"]]
175
94
  validate_layout(layout)
176
95
 
177
96
  used = Set.new([layout])
178
97
 
179
- # Reset the payload layout data to ensure it starts fresh for each page.
180
- payload["layout"] = nil
181
-
182
98
  while layout
183
- output = render_layout(output, layout, liquid_context)
99
+ output = render_layout(output, layout)
184
100
  add_regenerator_dependencies(layout)
185
101
 
186
102
  next unless (layout = site.layouts[layout.data["layout"]])
@@ -198,47 +114,45 @@ module Bridgetown
198
114
  # layout - the layout to check
199
115
  # Returns nothing
200
116
  def validate_layout(layout)
201
- return unless invalid_layout?(layout)
117
+ return unless document.data["layout"].present? &&
118
+ layout.nil? &&
119
+ !(document.is_a? Bridgetown::Excerpt)
202
120
 
203
121
  Bridgetown.logger.warn "Build Warning:", "Layout '#{document.data["layout"]}' requested " \
204
122
  "in #{document.relative_path} does not exist."
205
123
  end
206
124
 
125
+ def converters_for_layout(layout)
126
+ site.converters.select do |converter|
127
+ if converter.method(:matches).arity == 1
128
+ converter.matches(layout.ext)
129
+ else
130
+ converter.matches(layout.ext, layout)
131
+ end
132
+ end.sort
133
+ end
134
+
207
135
  # Render layout content into document.output
208
136
  #
209
137
  # Returns String rendered content
210
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
211
- def render_layout(output, layout, liquid_context)
212
- if layout.render_with_liquid?
213
- payload["content"] = output
214
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
138
+ def render_layout(output, layout)
139
+ layout_converters = converters_for_layout(layout)
215
140
 
216
- render_liquid(
217
- layout.content,
218
- payload,
219
- liquid_context,
220
- layout.path
221
- )
222
- else
223
- layout_converters ||= site.converters.select { |c| c.matches(layout.ext) }.sort
224
-
225
- layout_content = layout.content.dup
226
- layout_converters.reduce(layout_content) do |layout_output, converter|
227
- next(layout_output) unless converter.method(:convert).arity == 2
141
+ layout_content = layout.content.dup
142
+ layout_converters.reduce(layout_content) do |layout_output, converter|
143
+ next(layout_output) unless converter.method(:convert).arity == 2
228
144
 
229
- layout.current_document = document
230
- layout.current_document_output = output
231
- converter.convert layout_output, layout
232
- rescue StandardError => e
233
- Bridgetown.logger.error "Conversion error:",
234
- "#{converter.class} encountered an error while "\
235
- "converting '#{document.relative_path}':"
236
- Bridgetown.logger.error("", e.to_s)
237
- raise e
238
- end
145
+ layout.current_document = document
146
+ layout.current_document_output = output
147
+ converter.convert layout_output, layout
239
148
  end
149
+ rescue StandardError => e
150
+ Bridgetown.logger.error "Conversion error:",
151
+ "#{converter.class} encountered an error while "\
152
+ "converting '#{document.relative_path}':"
153
+ Bridgetown.logger.error("", e.to_s)
154
+ raise e
240
155
  end
241
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
242
156
 
243
157
  def add_regenerator_dependencies(layout)
244
158
  return unless document.write?
@@ -249,34 +163,6 @@ module Bridgetown
249
163
  )
250
164
  end
251
165
 
252
- # Set page content to payload and assign paginator if document has one.
253
- #
254
- # Returns nothing
255
- def assign_pages!
256
- payload["page"] = document.to_liquid
257
- payload["paginator"] = document.paginator.to_liquid if document.respond_to?(:paginator)
258
- end
259
-
260
- # Set related posts to payload if document is a post.
261
- #
262
- # Returns nothing
263
- def assign_current_document!
264
- payload["site"].current_document = document
265
- end
266
-
267
- # Set highlighter prefix and suffix
268
- #
269
- # Returns nothing
270
- def assign_highlighter_options!
271
- payload["highlighter_prefix"] = converters.first.highlighter_prefix
272
- payload["highlighter_suffix"] = converters.first.highlighter_suffix
273
- end
274
-
275
- def assign_layout_data!
276
- layout = layouts[document.data["layout"]]
277
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {}) if layout
278
- end
279
-
280
166
  def permalink_ext
281
167
  document_permalink = document.permalink
282
168
  if document_permalink && !document_permalink.end_with?("/")
@@ -298,9 +184,5 @@ module Bridgetown
298
184
  c.output_ext(document.extname)
299
185
  end.compact
300
186
  end
301
-
302
- def liquid_options
303
- @liquid_options ||= site.config["liquid"]
304
- end
305
187
  end
306
188
  end