bridgetown-core 0.20.0 → 0.21.0.beta1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bridgetown-core.rb +3 -0
  3. data/lib/bridgetown-core/collection.rb +13 -10
  4. data/lib/bridgetown-core/component.rb +178 -0
  5. data/lib/bridgetown-core/concerns/front_matter_importer.rb +52 -0
  6. data/lib/bridgetown-core/concerns/site/content.rb +2 -3
  7. data/lib/bridgetown-core/concerns/site/writable.rb +1 -1
  8. data/lib/bridgetown-core/concerns/validatable.rb +0 -4
  9. data/lib/bridgetown-core/configuration.rb +10 -9
  10. data/lib/bridgetown-core/converter.rb +9 -0
  11. data/lib/bridgetown-core/converters/erb_templates.rb +50 -34
  12. data/lib/bridgetown-core/converters/markdown.rb +1 -1
  13. data/lib/bridgetown-core/converters/ruby_templates.rb +17 -0
  14. data/lib/bridgetown-core/drops/relations_drop.rb +23 -0
  15. data/lib/bridgetown-core/drops/resource_drop.rb +3 -1
  16. data/lib/bridgetown-core/drops/unified_payload_drop.rb +1 -0
  17. data/lib/bridgetown-core/filters/from_liquid.rb +23 -0
  18. data/lib/bridgetown-core/helpers.rb +48 -9
  19. data/lib/bridgetown-core/layout.rb +27 -12
  20. data/lib/bridgetown-core/model/origin.rb +1 -1
  21. data/lib/bridgetown-core/model/{file_origin.rb → repo_origin.rb} +32 -25
  22. data/lib/bridgetown-core/reader.rb +2 -2
  23. data/lib/bridgetown-core/renderer.rb +1 -1
  24. data/lib/bridgetown-core/resource/base.rb +69 -27
  25. data/lib/bridgetown-core/resource/relations.rb +132 -0
  26. data/lib/bridgetown-core/resource/taxonomy_term.rb +10 -1
  27. data/lib/bridgetown-core/resource/taxonomy_type.rb +9 -0
  28. data/lib/bridgetown-core/resource/transformer.rb +14 -12
  29. data/lib/bridgetown-core/ruby_template_view.rb +7 -11
  30. data/lib/bridgetown-core/utils.rb +8 -1
  31. data/lib/bridgetown-core/utils/ruby_exec.rb +6 -9
  32. data/lib/bridgetown-core/utils/ruby_front_matter.rb +39 -0
  33. data/lib/bridgetown-core/version.rb +2 -2
  34. data/lib/bridgetown-core/watcher.rb +1 -1
  35. data/lib/site_template/package.json.erb +2 -2
  36. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +1 -1
  37. data/lib/site_template/webpack.config.js.erb +3 -1
  38. metadata +10 -3
@@ -69,7 +69,7 @@ module Bridgetown
69
69
  end
70
70
  else
71
71
  output = @parser.convert(content)
72
- if @parser.respond_to?(:extractions)
72
+ if convertible && @parser.respond_to?(:extractions)
73
73
  convertible.data.markdown_extractions = @parser.extractions
74
74
  end
75
75
  output
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Converters
5
+ class RubyTemplates < Converter
6
+ priority :highest
7
+ input :rb
8
+
9
+ def convert(content, convertible)
10
+ erb_view = Bridgetown::ERBView.new(convertible)
11
+ erb_view.instance_eval(
12
+ content, convertible.relative_path.to_s, line_start(convertible)
13
+ ).to_s
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Drops
5
+ class RelationsDrop < Drop
6
+ mutable false
7
+
8
+ def [](type)
9
+ return nil unless type.to_s.in?(@obj.relation_types)
10
+
11
+ @obj.resources_for_type(type)
12
+ end
13
+
14
+ def key?(type)
15
+ type.to_s.in?(@obj.relation_types)
16
+ end
17
+
18
+ def fallback_data
19
+ {}
20
+ end
21
+ end
22
+ end
23
+ end
@@ -14,6 +14,7 @@ module Bridgetown
14
14
  def_delegator :@obj, :relative_path, :path
15
15
  def_delegators :@obj,
16
16
  :id,
17
+ :data,
17
18
  :output,
18
19
  :content,
19
20
  :to_s,
@@ -21,7 +22,8 @@ module Bridgetown
21
22
  :relative_path,
22
23
  :relative_url,
23
24
  :date,
24
- :taxonomies
25
+ :taxonomies,
26
+ :relations
25
27
 
26
28
  private def_delegator :@obj, :data, :fallback_data
27
29
 
@@ -6,6 +6,7 @@ module Bridgetown
6
6
  mutable true
7
7
 
8
8
  attr_accessor :page, :layout, :content, :paginator
9
+ alias_method :resource, :page
9
10
 
10
11
  def bridgetown
11
12
  BridgetownDrop.global
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Filters
5
+ module FromLiquid
6
+ extend Liquid::StandardFilters
7
+
8
+ def strip_html(input)
9
+ FromLiquid.strip_html(input)
10
+ end
11
+
12
+ def strip_newlines(input)
13
+ FromLiquid.strip_newlines(input)
14
+ end
15
+
16
+ def newline_to_br(input)
17
+ FromLiquid.newline_to_br(input)
18
+ end
19
+
20
+ # FYI, truncate and truncate words are already provided by ActiveSupport! =)
21
+ end
22
+ end
23
+ end
@@ -4,8 +4,13 @@ module Bridgetown
4
4
  class RubyTemplateView
5
5
  class Helpers
6
6
  include Bridgetown::Filters
7
+ include Bridgetown::Filters::FromLiquid
7
8
 
8
- attr_reader :view, :site
9
+ # @return [Bridgetown::RubyTemplateView]
10
+ attr_reader :view
11
+
12
+ # @return [Bridgetown::Site]
13
+ attr_reader :site
9
14
 
10
15
  Context = Struct.new(:registers)
11
16
 
@@ -28,20 +33,44 @@ module Bridgetown
28
33
  pairs.select { |_key, truthy| truthy }.keys.join(" ")
29
34
  end
30
35
 
36
+ # Convert a Markdown string into HTML output.
37
+ #
38
+ # @param input [String] the Markdown to convert, if no block is passed
39
+ # @return [String]
40
+ def markdownify(input = nil, &block)
41
+ content = Bridgetown::Utils.reindent_for_markdown(
42
+ block.nil? ? input.to_s : view.capture(&block)
43
+ )
44
+ converter = site.find_converter_instance(Bridgetown::Converters::Markdown)
45
+ safe(converter.convert(content).strip)
46
+ end
47
+
31
48
  # This helper will generate the correct permalink URL for the file path.
32
49
  #
33
50
  # @param relative_path [String, Object] source file path, e.g.
34
- # "_posts/2020-10-20-my-post.md", or object that responds to `url`
51
+ # "_posts/2020-10-20-my-post.md", or object that responds to either
52
+ # `url` or `relative_url`
35
53
  # @return [String] the permalink URL for the file
36
- # @raise [ArgumentError] if the file cannot be found
37
54
  def url_for(relative_path)
38
- path_string = !relative_path.is_a?(String) ? relative_path.url : relative_path
55
+ if relative_path.respond_to?(:relative_url)
56
+ return safe(relative_path.relative_url) # new resource engine
57
+ elsif relative_path.respond_to?(:url)
58
+ return safe(relative_url(relative_path.url)) # old legacy engine
59
+ elsif relative_path.start_with?("/", "http")
60
+ return safe(relative_path)
61
+ end
39
62
 
40
- return path_string if path_string.start_with?("/", "http")
63
+ find_relative_url_for_path(relative_path)
64
+ end
65
+ alias_method :link, :url_for
41
66
 
67
+ # @param relative_path [String] source file path, e.g.
68
+ # "_posts/2020-10-20-my-post.md"
69
+ # @raise [ArgumentError] if the file cannot be found
70
+ def find_relative_url_for_path(relative_path)
42
71
  site.each_site_file do |item|
43
- if item.relative_path == path_string || item.relative_path == "/#{path_string}"
44
- return relative_url(item)
72
+ if item.relative_path == relative_path || item.relative_path == "/#{relative_path}"
73
+ return safe(item.respond_to?(:relative_url) ? item.relative_url : relative_url(item))
45
74
  end
46
75
  end
47
76
 
@@ -51,7 +80,6 @@ module Bridgetown
51
80
  Make sure the document exists and the path is correct.
52
81
  MSG
53
82
  end
54
- alias_method :link, :url_for
55
83
 
56
84
  # This helper will generate the correct permalink URL for the file path.
57
85
  #
@@ -69,7 +97,8 @@ module Bridgetown
69
97
  attr = attr.to_s.tr("_", "-")
70
98
  segments << "#{attr}=\"#{Utils.xml_escape(option)}\""
71
99
  end
72
- "<#{segments.join(" ")}>#{text}</a>"
100
+ # TODO: this might leak an XSS string into text, need to check
101
+ safe("<#{segments.join(" ")}>#{text}</a>")
73
102
  end
74
103
 
75
104
  # Forward all arguments to I18n.t method
@@ -79,6 +108,16 @@ module Bridgetown
79
108
  def t(*args)
80
109
  I18n.send :t, *args
81
110
  end
111
+
112
+ # For template contexts where ActiveSupport's output safety is loaded, we
113
+ # can ensure a string has been marked safe
114
+ #
115
+ # @param input [Object]
116
+ # @return [String]
117
+ def safe(input)
118
+ input.to_s.html_safe
119
+ end
120
+ alias_method :raw, :safe
82
121
  end
83
122
  end
84
123
  end
@@ -3,8 +3,8 @@
3
3
  module Bridgetown
4
4
  class Layout
5
5
  include DataAccessible
6
+ include FrontMatterImporter
6
7
  include LiquidRenderable
7
- include Validatable
8
8
 
9
9
  # Gets the Site object.
10
10
  attr_reader :site
@@ -27,8 +27,12 @@ module Bridgetown
27
27
  attr_accessor :data
28
28
 
29
29
  # Gets/Sets the content of this layout.
30
+ # @return [String]
30
31
  attr_accessor :content
31
32
 
33
+ # @return [Integer]
34
+ attr_accessor :front_matter_line_count
35
+
32
36
  # Gets/Sets the current document (for layout-compatible converters)
33
37
  attr_accessor :current_document
34
38
 
@@ -54,9 +58,29 @@ module Bridgetown
54
58
  @path = site.in_source_dir(base, name)
55
59
  end
56
60
  @relative_path = @path.sub(@base_dir, "")
61
+ @ext = File.extname(name)
62
+
63
+ @data = read_front_matter(@path)&.with_dot_access
64
+ rescue SyntaxError => e
65
+ Bridgetown.logger.error "Error:",
66
+ "Ruby Exception in #{e.message}"
67
+ rescue StandardError => e
68
+ handle_read_error(e)
69
+ ensure
70
+ @data ||= HashWithDotAccess::Hash.new
71
+ end
72
+
73
+ def handle_read_error(error)
74
+ if error.is_a? Psych::SyntaxError
75
+ Bridgetown.logger.warn "YAML Exception reading #{@path}: #{error.message}"
76
+ else
77
+ Bridgetown.logger.warn "Error reading file #{@path}: #{error.message}"
78
+ end
57
79
 
58
- process(name)
59
- read_yaml(base, name)
80
+ if site.config["strict_front_matter"] ||
81
+ error.is_a?(Bridgetown::Errors::FatalException)
82
+ raise error
83
+ end
60
84
  end
61
85
 
62
86
  # The inspect string for this document.
@@ -67,15 +91,6 @@ module Bridgetown
67
91
  "#<#{self.class} #{@path}>"
68
92
  end
69
93
 
70
- # Extract information from the layout filename.
71
- #
72
- # name - The String filename of the layout file.
73
- #
74
- # Returns nothing.
75
- def process(name)
76
- self.ext = File.extname(name)
77
- end
78
-
79
94
  # Provide this Layout's data to a Hash suitable for use by Liquid.
80
95
  #
81
96
  # Returns the Hash representation of this Layout.
@@ -35,4 +35,4 @@ module Bridgetown
35
35
  end
36
36
 
37
37
  require "bridgetown-core/model/builder_origin"
38
- require "bridgetown-core/model/file_origin"
38
+ require "bridgetown-core/model/repo_origin"
@@ -2,29 +2,48 @@
2
2
 
3
3
  module Bridgetown
4
4
  module Model
5
- class FileOrigin < Origin
5
+ class RepoOrigin < Origin
6
+ include Bridgetown::FrontMatterImporter
7
+ include Bridgetown::Utils::RubyFrontMatterDSL
8
+
6
9
  YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m.freeze
10
+ RUBY_FRONT_MATTER_HEADER = %r!\A[~`#\-]{3,}(?:ruby|<%|{%)\s*\n!.freeze
11
+ RUBY_FRONT_MATTER_REGEXP =
12
+ %r!#{RUBY_FRONT_MATTER_HEADER.source}(.*?\n?)^((?:%>|%})?[~`#\-]{3,}\s*$\n?)!m.freeze
13
+
14
+ # @return [String]
15
+ attr_accessor :content
16
+
17
+ # @return [Integer]
18
+ attr_accessor :front_matter_line_count
7
19
 
8
20
  class << self
9
21
  def handle_scheme?(scheme)
10
- scheme == "file"
22
+ scheme == "repo"
11
23
  end
12
24
 
13
25
  def data_file_extensions
14
- %w(.yaml .yml .json .csv .tsv).freeze
26
+ %w(.yaml .yml .json .csv .tsv .rb).freeze
15
27
  end
16
28
  end
17
29
 
18
30
  def read
19
- @data = (in_data_collection? ? read_file_data : read_frontmatter) || {}
31
+ begin
32
+ @data = (in_data_collection? ? read_file_data : read_front_matter(original_path)) || {}
33
+ rescue SyntaxError => e
34
+ Bridgetown.logger.error "Error:",
35
+ "Ruby Exception in #{e.message}"
36
+ rescue StandardError => e
37
+ handle_read_error(e)
38
+ end
39
+
40
+ @data ||= {}
20
41
  @data[:_id_] = id
21
42
  @data[:_origin_] = self
22
43
  @data[:_collection_] = collection
23
- @data[:_content_] = @content if @content
44
+ @data[:_content_] = content if content
24
45
 
25
46
  @data
26
- rescue StandardError => e
27
- handle_read_error(e)
28
47
  end
29
48
 
30
49
  def url
@@ -63,40 +82,28 @@ module Bridgetown
63
82
  collection.data?
64
83
  end
65
84
 
66
- def read_file_data
85
+ def read_file_data # rubocop:todo Metrics/MethodLength
67
86
  case original_path.extname.downcase
68
87
  when ".csv"
69
88
  {
70
- array:
89
+ rows:
71
90
  CSV.read(original_path,
72
91
  headers: true,
73
92
  encoding: Bridgetown::Current.site.config["encoding"]).map(&:to_hash),
74
93
  }
75
94
  when ".tsv"
76
95
  {
77
- array:
96
+ rows:
78
97
  CSV.read(original_path,
79
98
  col_sep: "\t",
80
99
  headers: true,
81
100
  encoding: Bridgetown::Current.site.config["encoding"]).map(&:to_hash),
82
101
  }
102
+ when ".rb"
103
+ process_ruby_data(File.read(original_path), original_path, 1)
83
104
  else
84
105
  yaml_data = SafeYAML.load_file(original_path)
85
- yaml_data.is_a?(Array) ? { array: yaml_data } : yaml_data
86
- end
87
- end
88
-
89
- def read_frontmatter
90
- @content = File.read(
91
- original_path, **Bridgetown::Utils.merged_file_read_opts(Bridgetown::Current.site, {})
92
- )
93
- content_match = @content.match(YAML_FRONT_MATTER_REGEXP)
94
- if content_match
95
- @content = content_match.post_match
96
- SafeYAML.load(content_match[1])
97
- else
98
- yaml_data = SafeYAML.load_file(original_path)
99
- yaml_data.is_a?(Array) ? { array: yaml_data } : yaml_data
106
+ yaml_data.is_a?(Array) ? { rows: yaml_data } : yaml_data
100
107
  end
101
108
  end
102
109
 
@@ -63,7 +63,7 @@ module Bridgetown
63
63
  file_path = @site.in_source_dir(base, entry)
64
64
  if File.directory?(file_path)
65
65
  dot_dirs << entry
66
- elsif Utils.has_yaml_header?(file_path)
66
+ elsif Utils.has_yaml_header?(file_path) || Utils.has_rbfm_header?(file_path)
67
67
  dot_pages << entry
68
68
  else
69
69
  dot_static_files << entry
@@ -113,7 +113,7 @@ module Bridgetown
113
113
  def retrieve_pages(dir, dot_pages)
114
114
  if site.uses_resource?
115
115
  dot_pages.each do |page_path|
116
- site.collections.pages.send(:read_resource, site.in_source_dir(dir, page_path))
116
+ site.collections.pages.read_resource(site.in_source_dir(dir, page_path))
117
117
  end
118
118
  return
119
119
  end
@@ -52,7 +52,7 @@ module Bridgetown
52
52
  output = document.content
53
53
  Bridgetown.logger.debug "Rendering Markup:", document.relative_path
54
54
  output = convert(output.to_s, document)
55
- document.content = output
55
+ document.content = output.html_safe
56
56
 
57
57
  if document.place_in_layout?
58
58
  Bridgetown.logger.debug "Rendering Layout:", document.relative_path
@@ -23,13 +23,6 @@ module Bridgetown
23
23
  # @return [String]
24
24
  attr_accessor :content, :untransformed_content, :output
25
25
 
26
- # @!attribute [r] collection
27
- # @return [Bridgetown::Collection] collection of this resource
28
-
29
- # @!attribute [r] relative_path
30
- # @return [Pathname] the relative path of source file or
31
- # file-like origin
32
-
33
26
  DATE_FILENAME_MATCHER = %r!^(?>.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze
34
27
 
35
28
  # @param site [Bridgetown::Site]
@@ -42,11 +35,15 @@ module Bridgetown
42
35
  trigger_hooks(:post_init)
43
36
  end
44
37
 
38
+ # Collection associated with this resource
39
+ #
45
40
  # @return [Bridgetown::Collection]
46
41
  def collection
47
42
  model.collection
48
43
  end
49
44
 
45
+ # The relative path of source file or file-like origin
46
+ #
50
47
  # @return [Pathname]
51
48
  def relative_path
52
49
  model.origin.relative_path
@@ -57,6 +54,11 @@ module Bridgetown
57
54
  @transformer ||= Bridgetown::Resource::Transformer.new(self)
58
55
  end
59
56
 
57
+ # @return [Bridgetown::Resource::Relations]
58
+ def relations
59
+ @relations ||= Bridgetown::Resource::Relations.new(self)
60
+ end
61
+
60
62
  # @param new_data [HashWithDotAccess::Hash]
61
63
  def data=(new_data)
62
64
  unless new_data.is_a?(HashWithDotAccess::Hash)
@@ -80,9 +82,11 @@ module Bridgetown
80
82
 
81
83
  unless collection.data?
82
84
  self.untransformed_content = content
83
- determine_slug_and_date
84
85
  normalize_categories_and_tags
85
86
  import_taxonomies_from_data
87
+ ensure_default_data
88
+ transformer.execute_inline_ruby!
89
+ set_date_from_string(data.date)
86
90
  end
87
91
 
88
92
  @destination = Destination.new(self) if requires_destination?
@@ -94,7 +98,7 @@ module Bridgetown
94
98
  alias_method :read, :read! # TODO: eventually use the bang version only
95
99
 
96
100
  def transform!
97
- transformer.process! unless collection.data?
101
+ transformer.process! if output_allowed?
98
102
  end
99
103
 
100
104
  def trigger_hooks(hook_name, *args)
@@ -108,6 +112,7 @@ module Bridgetown
108
112
  trigger_hooks :"post_#{hook_suffix}"
109
113
  end
110
114
 
115
+ # @return [String]
111
116
  def relative_path_basename_without_prefix
112
117
  return_path = Pathname.new("")
113
118
  relative_path.each_filename do |filename|
@@ -121,6 +126,7 @@ module Bridgetown
121
126
  (return_path.dirname + return_path.basename(".*")).to_s
122
127
  end
123
128
 
129
+ # @return [String]
124
130
  def basename_without_ext
125
131
  relative_path.basename(".*").to_s
126
132
  end
@@ -135,21 +141,28 @@ module Bridgetown
135
141
  data&.permalink
136
142
  end
137
143
 
144
+ # @return [String]
138
145
  def path
139
146
  (model.origin.respond_to?(:original_path) ? model.origin.original_path : relative_path).to_s
140
147
  end
141
148
 
149
+ # @return [String]
142
150
  def absolute_url
143
151
  format_url destination&.absolute_url
144
152
  end
145
153
 
154
+ # @return [String]
146
155
  def relative_url
147
156
  format_url destination&.relative_url
148
157
  end
149
- alias_method :id, :relative_url
158
+
159
+ # @return [String]
160
+ def id
161
+ model.origin.id
162
+ end
150
163
 
151
164
  def date
152
- data["date"] ||= site.time # TODO: this doesn't reflect documented behavior
165
+ data["date"] ||= site.time
153
166
  end
154
167
 
155
168
  # @return [Hash<String, Hash<String => Bridgetown::Resource::TaxonomyType,
@@ -165,8 +178,12 @@ module Bridgetown
165
178
  end
166
179
  end
167
180
 
181
+ def output_allowed?
182
+ !collection.data? && data.config&.output != false
183
+ end
184
+
168
185
  def requires_destination?
169
- collection.write? && data.config&.output != false
186
+ collection.write? && output_allowed?
170
187
  end
171
188
 
172
189
  def write?
@@ -189,13 +206,36 @@ module Bridgetown
189
206
 
190
207
  # Create a Liquid-understandable version of this resource.
191
208
  #
192
- # @return [Drops::DocumentDrop] represents this resource's data.
209
+ # @return [Drops::ResourceDrop] represents this resource's data.
193
210
  def to_liquid
194
211
  @to_liquid ||= Drops::ResourceDrop.new(self)
195
212
  end
196
213
 
214
+ def to_h
215
+ {
216
+ id: id,
217
+ absolute_url: absolute_url,
218
+ relative_path: relative_path,
219
+ relative_url: relative_url,
220
+ date: date,
221
+ data: data,
222
+ taxonomies: taxonomies,
223
+ untransformed_content: untransformed_content,
224
+ content: content,
225
+ output: output,
226
+ }
227
+ end
228
+
229
+ def as_json(*)
230
+ to_h
231
+ end
232
+
233
+ ruby2_keywords def to_json(*options)
234
+ as_json(*options).to_json(*options)
235
+ end
236
+
197
237
  def inspect
198
- "#<#{self.class} [#{collection.label}] #{relative_path}>"
238
+ "#<#{self.class} #{id}>"
199
239
  end
200
240
 
201
241
  # Compare this document against another document.
@@ -229,23 +269,25 @@ module Bridgetown
229
269
 
230
270
  private
231
271
 
232
- def determine_slug_and_date
233
- return unless relative_path.to_s =~ DATE_FILENAME_MATCHER
234
-
235
- new_date, slug = Regexp.last_match.captures
236
- modify_date(new_date)
272
+ def ensure_default_data
273
+ slug = if matches = relative_path.to_s.match(DATE_FILENAME_MATCHER) # rubocop:disable Lint/AssignmentInCondition
274
+ set_date_from_string(matches[1]) unless data.date
275
+ matches[2]
276
+ else
277
+ basename_without_ext
278
+ end
237
279
 
238
- slug.gsub!(%r!\.*\z!, "")
239
280
  data.slug ||= slug
281
+ data.title ||= Bridgetown::Utils.titleize_slug(slug)
240
282
  end
241
283
 
242
- def modify_date(new_date)
243
- if !data.date || data.date.to_i == site.time.to_i
244
- data.date = Utils.parse_date(
245
- new_date,
246
- "Document '#{relative_path}' does not have a valid date in the #{model}."
247
- )
248
- end
284
+ def set_date_from_string(new_date) # rubocop:disable Naming/AccessorMethodName
285
+ return unless new_date.is_a?(String)
286
+
287
+ data.date = Bridgetown::Utils.parse_date(
288
+ new_date,
289
+ "Document '#{relative_path}' does not have a valid date in the #{model}."
290
+ )
249
291
  end
250
292
 
251
293
  def normalize_categories_and_tags