bridgetown-core 0.15.0 → 0.17.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +14 -0
  3. data/bridgetown-core.gemspec +4 -1
  4. data/lib/bridgetown-core.rb +8 -2
  5. data/lib/bridgetown-core/commands/concerns/actions.rb +2 -1
  6. data/lib/bridgetown-core/commands/console.rb +4 -4
  7. data/lib/bridgetown-core/concerns/data_accessible.rb +19 -0
  8. data/lib/bridgetown-core/concerns/layout_placeable.rb +17 -0
  9. data/lib/bridgetown-core/concerns/liquid_renderable.rb +20 -0
  10. data/lib/bridgetown-core/concerns/publishable.rb +10 -0
  11. data/lib/bridgetown-core/concerns/site/configurable.rb +66 -31
  12. data/lib/bridgetown-core/concerns/site/content.rb +90 -31
  13. data/lib/bridgetown-core/concerns/site/extensible.rb +15 -12
  14. data/lib/bridgetown-core/concerns/site/localizable.rb +20 -0
  15. data/lib/bridgetown-core/concerns/site/processable.rb +14 -12
  16. data/lib/bridgetown-core/concerns/site/renderable.rb +21 -2
  17. data/lib/bridgetown-core/concerns/site/writable.rb +16 -2
  18. data/lib/bridgetown-core/concerns/validatable.rb +59 -0
  19. data/lib/bridgetown-core/configuration.rb +10 -3
  20. data/lib/bridgetown-core/converter.rb +34 -0
  21. data/lib/bridgetown-core/converters/erb_templates.rb +78 -0
  22. data/lib/bridgetown-core/converters/markdown.rb +6 -23
  23. data/lib/bridgetown-core/converters/smartypants.rb +0 -10
  24. data/lib/bridgetown-core/document.rb +11 -55
  25. data/lib/bridgetown-core/drops/site_drop.rb +1 -1
  26. data/lib/bridgetown-core/errors.rb +2 -0
  27. data/lib/bridgetown-core/excerpt.rb +1 -6
  28. data/lib/bridgetown-core/filters.rb +11 -48
  29. data/lib/bridgetown-core/filters/condition_helpers.rb +56 -0
  30. data/lib/bridgetown-core/frontmatter_defaults.rb +17 -0
  31. data/lib/bridgetown-core/layout.rb +24 -1
  32. data/lib/bridgetown-core/liquid_renderer/file_system.rb +1 -1
  33. data/lib/bridgetown-core/page.rb +41 -26
  34. data/lib/bridgetown-core/plugin_manager.rb +10 -2
  35. data/lib/bridgetown-core/reader.rb +1 -0
  36. data/lib/bridgetown-core/readers/collection_reader.rb +1 -0
  37. data/lib/bridgetown-core/readers/data_reader.rb +4 -3
  38. data/lib/bridgetown-core/readers/defaults_reader.rb +27 -0
  39. data/lib/bridgetown-core/readers/layout_reader.rb +1 -0
  40. data/lib/bridgetown-core/readers/page_reader.rb +1 -0
  41. data/lib/bridgetown-core/readers/post_reader.rb +1 -0
  42. data/lib/bridgetown-core/readers/static_file_reader.rb +1 -0
  43. data/lib/bridgetown-core/regenerator.rb +1 -1
  44. data/lib/bridgetown-core/renderer.rb +40 -14
  45. data/lib/bridgetown-core/ruby_template_view.rb +113 -0
  46. data/lib/bridgetown-core/site.rb +2 -0
  47. data/lib/bridgetown-core/tags/class_map.rb +90 -0
  48. data/lib/bridgetown-core/tags/find.rb +86 -0
  49. data/lib/bridgetown-core/tags/t.rb +14 -0
  50. data/lib/bridgetown-core/tags/webpack_path.rb +19 -22
  51. data/lib/bridgetown-core/utils.rb +55 -2
  52. data/lib/bridgetown-core/utils/ruby_exec.rb +1 -1
  53. data/lib/bridgetown-core/version.rb +2 -2
  54. data/lib/site_template/src/_layouts/{default.html → default.liquid} +0 -0
  55. data/lib/site_template/src/_layouts/{home.html → home.liquid} +0 -0
  56. data/lib/site_template/src/_layouts/{page.html → page.liquid} +0 -0
  57. data/lib/site_template/src/_layouts/{post.html → post.liquid} +0 -0
  58. metadata +64 -10
  59. data/lib/bridgetown-core/concerns/convertible.rb +0 -235
@@ -32,7 +32,7 @@ module Bridgetown
32
32
 
33
33
  # Last path in the list wins
34
34
  LiquidComponent.parse(
35
- ::File.read(found_paths.last, site.file_read_opts)
35
+ ::File.read(found_paths.last, **site.file_read_opts)
36
36
  ).content
37
37
  end
38
38
  end
@@ -2,24 +2,19 @@
2
2
 
3
3
  module Bridgetown
4
4
  class Page
5
- include Convertible
5
+ include DataAccessible
6
+ include LayoutPlaceable
7
+ include LiquidRenderable
8
+ include Publishable
9
+ include Validatable
6
10
 
7
11
  attr_writer :dir
8
- attr_accessor :site, :pager
12
+ attr_accessor :site, :paginator, :pager
9
13
  attr_accessor :name, :ext, :basename
10
14
  attr_accessor :data, :content, :output
11
15
 
12
16
  alias_method :extname, :ext
13
17
 
14
- # Attributes for Liquid templates
15
- ATTRIBUTES_FOR_LIQUID = %w(
16
- content
17
- dir
18
- name
19
- path
20
- url
21
- ).freeze
22
-
23
18
  # A set of extensions that are considered HTML or HTML-like so we
24
19
  # should not alter them, this includes .xhtml through XHTM5.
25
20
 
@@ -126,12 +121,18 @@ module Bridgetown
126
121
  # desired placeholder replacements. For details see "url.rb"
127
122
  def url_placeholders
128
123
  {
129
- path: @dir,
124
+ path: qualified_pages_path_for_url,
130
125
  basename: basename,
131
126
  output_ext: output_ext,
132
127
  }
133
128
  end
134
129
 
130
+ # Strips _pages prefix off if needed for the url/destination generation
131
+ # @return [String]
132
+ def qualified_pages_path_for_url
133
+ @dir.sub(%r!^/_pages!, "")
134
+ end
135
+
135
136
  # Extract information from the page filename.
136
137
  #
137
138
  # name - The String filename of the page file.
@@ -143,19 +144,6 @@ module Bridgetown
143
144
  self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
144
145
  end
145
146
 
146
- # Add any necessary layouts to this post
147
- #
148
- # layouts - The Hash of {"name" => "layout"}.
149
- # site_payload - The site payload Hash.
150
- #
151
- # Returns String rendered page.
152
- def render(layouts, site_payload)
153
- site_payload["page"] = to_liquid
154
- site_payload["paginator"] = pager.to_liquid
155
-
156
- do_layout(site_payload, layouts)
157
- end
158
-
159
147
  # The path to the source file
160
148
  #
161
149
  # Returns the path to the source file
@@ -168,6 +156,16 @@ module Bridgetown
168
156
  @relative_path ||= File.join(*[@dir, @name].map(&:to_s).reject(&:empty?)).delete_prefix("/")
169
157
  end
170
158
 
159
+ # FIXME: spinning up a new Renderer object just to get an extension
160
+ # seems excessive
161
+ #
162
+ # The output extension of the page.
163
+ #
164
+ # Returns the output extension
165
+ def output_ext
166
+ @output_ext ||= Bridgetown::Renderer.new(site, self).output_ext
167
+ end
168
+
171
169
  # Obtain destination path.
172
170
  #
173
171
  # dest - The String path to the destination dir.
@@ -180,9 +178,22 @@ module Bridgetown
180
178
  path
181
179
  end
182
180
 
181
+ # Write the generated page file to the destination directory.
182
+ #
183
+ # dest - The String path to the destination dir.
184
+ #
185
+ # Returns nothing.
186
+ def write(dest)
187
+ path = destination(dest)
188
+ FileUtils.mkdir_p(File.dirname(path))
189
+ Bridgetown.logger.debug "Writing:", path
190
+ File.write(path, output, mode: "wb")
191
+ Bridgetown::Hooks.trigger :pages, :post_write, self
192
+ end
193
+
183
194
  # Returns the object as a debug String.
184
195
  def inspect
185
- "#<#{self.class} @relative_path=#{relative_path.inspect}>"
196
+ "#<#{self.class} #{relative_path}>"
186
197
  end
187
198
 
188
199
  # Returns the Boolean of whether this Page is HTML or not.
@@ -199,6 +210,10 @@ module Bridgetown
199
210
  Bridgetown::Hooks.trigger :pages, hook_name, self, *args
200
211
  end
201
212
 
213
+ def type
214
+ :pages
215
+ end
216
+
202
217
  def write?
203
218
  true
204
219
  end
@@ -68,12 +68,20 @@ module Bridgetown
68
68
  # If that exact package hasn't been installed, execute yarn add
69
69
  #
70
70
  # Returns nothing.
71
- def self.install_yarn_dependencies(required_gems)
71
+ def self.install_yarn_dependencies(required_gems, single_gemname = nil)
72
72
  return unless File.exist?("package.json")
73
73
 
74
74
  package_json = JSON.parse(File.read("package.json"))
75
75
 
76
- required_gems.each do |loaded_gem|
76
+ gems_to_search = if single_gemname
77
+ required_gems.select do |loaded_gem|
78
+ loaded_gem.to_spec&.name == single_gemname.to_s
79
+ end
80
+ else
81
+ required_gems
82
+ end
83
+
84
+ gems_to_search.each do |loaded_gem|
77
85
  yarn_dependency = find_yarn_dependency(loaded_gem)
78
86
  next unless add_yarn_dependency?(yarn_dependency, package_json)
79
87
 
@@ -13,6 +13,7 @@ module Bridgetown
13
13
  # Returns nothing.
14
14
  # rubocop:disable Metrics/AbcSize
15
15
  def read
16
+ @site.defaults_reader.read
16
17
  @site.layouts = LayoutReader.new(site).read
17
18
  read_directories
18
19
  read_included_excludes
@@ -5,6 +5,7 @@ module Bridgetown
5
5
  SPECIAL_COLLECTIONS = %w(posts data).freeze
6
6
 
7
7
  attr_reader :site, :content
8
+
8
9
  def initialize(site)
9
10
  @site = site
10
11
  @content = {}
@@ -3,9 +3,10 @@
3
3
  module Bridgetown
4
4
  class DataReader
5
5
  attr_reader :site, :content
6
+
6
7
  def initialize(site)
7
8
  @site = site
8
- @content = ActiveSupport::HashWithIndifferentAccess.new
9
+ @content = {}
9
10
  @entry_filter = EntryFilter.new(site)
10
11
  end
11
12
 
@@ -19,7 +20,7 @@ module Bridgetown
19
20
  base = site.in_source_dir(dir)
20
21
  read_data_to(base, @content)
21
22
  merge_environment_specific_metadata!
22
- @content
23
+ @content = @content.with_dot_access
23
24
  end
24
25
 
25
26
  # Read and parse all .yaml, .yml, .json, .csv and .tsv
@@ -43,7 +44,7 @@ module Bridgetown
43
44
  if File.directory?(path)
44
45
  read_data_to(
45
46
  path,
46
- data[sanitize_filename(entry)] = ActiveSupport::HashWithIndifferentAccess.new
47
+ data[sanitize_filename(entry)] = {}
47
48
  )
48
49
  else
49
50
  key = sanitize_filename(File.basename(entry, ".*"))
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class DefaultsReader
5
+ attr_reader :site, :path_defaults
6
+
7
+ def initialize(site)
8
+ @site = site
9
+ @path_defaults = HashWithDotAccess::Hash.new
10
+ end
11
+
12
+ def read
13
+ return unless File.directory?(site.source)
14
+
15
+ entries = Dir.chdir(site.source) do
16
+ Dir["**/_defaults.{yaml,yml,json}"]
17
+ end
18
+
19
+ entries.each do |entry|
20
+ path = @site.in_source_dir(entry)
21
+ @path_defaults[File.dirname(path) + File::SEPARATOR] = SafeYAML.load_file(path)
22
+ end
23
+
24
+ @path_defaults
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class LayoutReader
5
5
  attr_reader :site
6
+
6
7
  def initialize(site)
7
8
  @site = site
8
9
  @layouts = {}
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class PageReader
5
5
  attr_reader :site, :dir, :unfiltered_content
6
+
6
7
  def initialize(site, dir)
7
8
  @site = site
8
9
  @dir = dir
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class PostReader
5
5
  attr_reader :site, :unfiltered_content
6
+
6
7
  def initialize(site)
7
8
  @site = site
8
9
  end
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class StaticFileReader
5
5
  attr_reader :site, :dir, :unfiltered_content
6
+
6
7
  def initialize(site, dir)
7
8
  @site = site
8
9
  @dir = dir
@@ -163,7 +163,7 @@ module Bridgetown
163
163
  end
164
164
 
165
165
  def regenerate_page?(document)
166
- document.asset_file? || document.data["regenerate"] ||
166
+ document.data["regenerate"] ||
167
167
  source_modified_or_dest_missing?(
168
168
  site.in_source_dir(document.relative_path), document.destination(@site.dest)
169
169
  )
@@ -58,6 +58,7 @@ module Bridgetown
58
58
  Bridgetown.logger.debug "Rendering:", document.relative_path
59
59
 
60
60
  assign_pages!
61
+ # TODO: this can be eliminated I think:
61
62
  assign_current_document!
62
63
  assign_highlighter_options!
63
64
  assign_layout_data!
@@ -83,7 +84,7 @@ module Bridgetown
83
84
  end
84
85
 
85
86
  Bridgetown.logger.debug "Rendering Markup:", document.relative_path
86
- output = convert(output.to_s)
87
+ output = convert(output.to_s, document)
87
88
  document.content = output
88
89
 
89
90
  if document.place_in_layout?
@@ -140,9 +141,13 @@ module Bridgetown
140
141
  # Convert the document using the converters which match this renderer's document.
141
142
  #
142
143
  # Returns String the converted content.
143
- def convert(content)
144
+ def convert(content, document)
144
145
  converters.reduce(content) do |output, converter|
145
- converter.convert output
146
+ if converter.method(:convert).arity == 1
147
+ converter.convert output
148
+ else
149
+ converter.convert output, document
150
+ end
146
151
  rescue StandardError => e
147
152
  Bridgetown.logger.error "Conversion error:",
148
153
  "#{converter.class} encountered an error while "\
@@ -202,17 +207,38 @@ module Bridgetown
202
207
  # Render layout content into document.output
203
208
  #
204
209
  # Returns String rendered content
210
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
205
211
  def render_layout(output, layout, liquid_context)
206
- payload["content"] = output
207
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
208
-
209
- render_liquid(
210
- layout.content,
211
- payload,
212
- liquid_context,
213
- layout.path
214
- )
212
+ if layout.render_with_liquid?
213
+ payload["content"] = output
214
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
215
+
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
228
+
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
239
+ end
215
240
  end
241
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
216
242
 
217
243
  def add_regenerator_dependencies(layout)
218
244
  return unless document.write?
@@ -223,12 +249,12 @@ module Bridgetown
223
249
  )
224
250
  end
225
251
 
226
- # Set page content to payload and assign pager if document has one.
252
+ # Set page content to payload and assign paginator if document has one.
227
253
  #
228
254
  # Returns nothing
229
255
  def assign_pages!
230
256
  payload["page"] = document.to_liquid
231
- payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
257
+ payload["paginator"] = document.paginator.to_liquid if document.respond_to?(:paginator)
232
258
  end
233
259
 
234
260
  # Set related posts to payload if document is a post.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "active_support/core_ext/hash/keys"
5
+
6
+ module Bridgetown
7
+ class RubyTemplateView
8
+ class Helpers
9
+ include Bridgetown::Filters
10
+
11
+ attr_reader :view, :site
12
+
13
+ Context = Struct.new(:registers)
14
+
15
+ def initialize(view, site)
16
+ @view = view
17
+ @site = site
18
+
19
+ # duck typing for Liquid context
20
+ @context = Context.new({ site: site })
21
+ end
22
+
23
+ def webpack_path(asset_type)
24
+ Bridgetown::Utils.parse_webpack_manifest_file(site, asset_type.to_s)
25
+ end
26
+
27
+ # @param pairs [Hash] A hash of key/value pairs.
28
+ #
29
+ # @return [String] Space-separated keys where the values are truthy.
30
+ def class_map(pairs = {})
31
+ pairs.select { |_key, truthy| truthy }.keys.join(" ")
32
+ end
33
+
34
+ def t(*args)
35
+ I18n.send :t, *args
36
+ end
37
+ end
38
+
39
+ attr_reader :layout, :page, :paginator, :site, :content
40
+
41
+ def initialize(convertible)
42
+ if convertible.is_a?(Layout)
43
+ @layout = convertible
44
+ @page = layout.current_document
45
+ @content = layout.current_document_output
46
+ else
47
+ @page = convertible
48
+ end
49
+ @paginator = page.paginator if page.respond_to?(:paginator)
50
+ @site = page.site
51
+ end
52
+
53
+ def partial(_partial_name, _options = {})
54
+ raise "Must be implemented in a subclass"
55
+ end
56
+
57
+ def site_drop
58
+ site.site_payload.site
59
+ end
60
+
61
+ def liquid_render(component, options = {})
62
+ render_statement = _render_statement(component, options)
63
+
64
+ template = site.liquid_renderer.file(
65
+ "#{page.path}.#{Digest::SHA2.hexdigest(render_statement)}"
66
+ ).parse(render_statement)
67
+ template.warnings.each do |e|
68
+ Bridgetown.logger.warn "Liquid Warning:",
69
+ LiquidRenderer.format_error(e, path || document.relative_path)
70
+ end
71
+ template.render!(options.deep_stringify_keys, _liquid_context)
72
+ end
73
+
74
+ def helpers
75
+ @helpers ||= Helpers.new(self, site)
76
+ end
77
+
78
+ def method_missing(method, *args, &block)
79
+ if helpers.respond_to?(method.to_sym)
80
+ helpers.send method.to_sym, *args, &block
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ def respond_to_missing?(method, include_private = false)
87
+ helpers.respond_to?(method.to_sym, include_private) || super
88
+ end
89
+
90
+ private
91
+
92
+ def _render_statement(component, options)
93
+ render_statement = ["{% render \"#{component}\""]
94
+ unless options.empty?
95
+ render_statement << ", " + options.keys.map { |k| "#{k}: #{k}" }.join(", ")
96
+ end
97
+ render_statement << " %}"
98
+ render_statement.join
99
+ end
100
+
101
+ def _liquid_context
102
+ {
103
+ registers: {
104
+ site: site,
105
+ page: page,
106
+ cached_partials: Bridgetown::Renderer.cached_partials,
107
+ },
108
+ strict_filters: site.config["liquid"]["strict_filters"],
109
+ strict_variables: site.config["liquid"]["strict_variables"],
110
+ }
111
+ end
112
+ end
113
+ end