bridgetown-core 0.19.2 → 0.21.0.beta3

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/bridgetown-core.gemspec +1 -2
  3. data/lib/bridgetown-core.rb +37 -29
  4. data/lib/bridgetown-core/cleaner.rb +9 -3
  5. data/lib/bridgetown-core/collection.rb +177 -78
  6. data/lib/bridgetown-core/commands/base.rb +9 -0
  7. data/lib/bridgetown-core/commands/build.rb +0 -11
  8. data/lib/bridgetown-core/commands/concerns/git_helpers.rb +20 -0
  9. data/lib/bridgetown-core/commands/configure.rb +8 -3
  10. data/lib/bridgetown-core/commands/console.rb +4 -0
  11. data/lib/bridgetown-core/commands/doctor.rb +1 -19
  12. data/lib/bridgetown-core/commands/new.rb +2 -2
  13. data/lib/bridgetown-core/commands/plugins.rb +14 -13
  14. data/lib/bridgetown-core/commands/serve.rb +0 -14
  15. data/lib/bridgetown-core/component.rb +178 -0
  16. data/lib/bridgetown-core/concerns/data_accessible.rb +1 -0
  17. data/lib/bridgetown-core/concerns/front_matter_importer.rb +52 -0
  18. data/lib/bridgetown-core/concerns/site/configurable.rb +10 -10
  19. data/lib/bridgetown-core/concerns/site/content.rb +56 -15
  20. data/lib/bridgetown-core/concerns/site/localizable.rb +3 -5
  21. data/lib/bridgetown-core/concerns/site/processable.rb +6 -4
  22. data/lib/bridgetown-core/concerns/site/renderable.rb +26 -0
  23. data/lib/bridgetown-core/concerns/site/writable.rb +12 -2
  24. data/lib/bridgetown-core/concerns/validatable.rb +2 -5
  25. data/lib/bridgetown-core/configuration.rb +51 -30
  26. data/lib/bridgetown-core/configurations/bt-postcss.rb +1 -3
  27. data/lib/bridgetown-core/configurations/netlify.rb +1 -0
  28. data/lib/bridgetown-core/configurations/tailwindcss.rb +1 -3
  29. data/lib/bridgetown-core/converter.rb +23 -0
  30. data/lib/bridgetown-core/converters/erb_templates.rb +51 -35
  31. data/lib/bridgetown-core/converters/identity.rb +0 -9
  32. data/lib/bridgetown-core/converters/liquid_templates.rb +1 -1
  33. data/lib/bridgetown-core/converters/markdown.rb +14 -4
  34. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +5 -38
  35. data/lib/bridgetown-core/converters/ruby_templates.rb +17 -0
  36. data/lib/bridgetown-core/converters/smartypants.rb +3 -1
  37. data/lib/bridgetown-core/core_ext/psych.rb +19 -0
  38. data/lib/bridgetown-core/current.rb +10 -0
  39. data/lib/bridgetown-core/document.rb +9 -16
  40. data/lib/bridgetown-core/drops/collection_drop.rb +1 -1
  41. data/lib/bridgetown-core/drops/page_drop.rb +4 -0
  42. data/lib/bridgetown-core/drops/relations_drop.rb +23 -0
  43. data/lib/bridgetown-core/drops/resource_drop.rb +83 -0
  44. data/lib/bridgetown-core/drops/site_drop.rb +33 -8
  45. data/lib/bridgetown-core/drops/unified_payload_drop.rb +5 -0
  46. data/lib/bridgetown-core/entry_filter.rb +17 -28
  47. data/lib/bridgetown-core/errors.rb +0 -2
  48. data/lib/bridgetown-core/filters.rb +3 -26
  49. data/lib/bridgetown-core/filters/from_liquid.rb +23 -0
  50. data/lib/bridgetown-core/filters/url_filters.rb +12 -0
  51. data/lib/bridgetown-core/frontmatter_defaults.rb +1 -1
  52. data/lib/bridgetown-core/generators/prototype_generator.rb +37 -19
  53. data/lib/bridgetown-core/helpers.rb +48 -9
  54. data/lib/bridgetown-core/layout.rb +28 -13
  55. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -0
  56. data/lib/bridgetown-core/liquid_renderer/table.rb +1 -0
  57. data/lib/bridgetown-core/model/base.rb +138 -0
  58. data/lib/bridgetown-core/model/builder_origin.rb +40 -0
  59. data/lib/bridgetown-core/model/origin.rb +38 -0
  60. data/lib/bridgetown-core/model/repo_origin.rb +126 -0
  61. data/lib/bridgetown-core/page.rb +11 -2
  62. data/lib/bridgetown-core/plugin_manager.rb +1 -3
  63. data/lib/bridgetown-core/publisher.rb +8 -2
  64. data/lib/bridgetown-core/reader.rb +37 -22
  65. data/lib/bridgetown-core/readers/data_reader.rb +5 -5
  66. data/lib/bridgetown-core/readers/defaults_reader.rb +1 -1
  67. data/lib/bridgetown-core/readers/page_reader.rb +1 -0
  68. data/lib/bridgetown-core/readers/post_reader.rb +5 -4
  69. data/lib/bridgetown-core/regenerator.rb +9 -2
  70. data/lib/bridgetown-core/related_posts.rb +9 -6
  71. data/lib/bridgetown-core/renderer.rb +6 -13
  72. data/lib/bridgetown-core/resource/base.rb +313 -0
  73. data/lib/bridgetown-core/resource/destination.rb +49 -0
  74. data/lib/bridgetown-core/resource/permalink_processor.rb +179 -0
  75. data/lib/bridgetown-core/resource/relations.rb +132 -0
  76. data/lib/bridgetown-core/resource/taxonomy_term.rb +34 -0
  77. data/lib/bridgetown-core/resource/taxonomy_type.rb +56 -0
  78. data/lib/bridgetown-core/resource/transformer.rb +177 -0
  79. data/lib/bridgetown-core/ruby_template_view.rb +11 -11
  80. data/lib/bridgetown-core/site.rb +13 -6
  81. data/lib/bridgetown-core/static_file.rb +33 -10
  82. data/lib/bridgetown-core/tags/highlight.rb +2 -15
  83. data/lib/bridgetown-core/tags/include.rb +1 -1
  84. data/lib/bridgetown-core/tags/post_url.rb +2 -2
  85. data/lib/bridgetown-core/url.rb +1 -0
  86. data/lib/bridgetown-core/utils.rb +49 -43
  87. data/lib/bridgetown-core/utils/require_gems.rb +60 -0
  88. data/lib/bridgetown-core/utils/ruby_exec.rb +6 -9
  89. data/lib/bridgetown-core/utils/ruby_front_matter.rb +39 -0
  90. data/lib/bridgetown-core/version.rb +2 -2
  91. data/lib/bridgetown-core/watcher.rb +1 -1
  92. data/lib/bridgetown-core/yaml_parser.rb +22 -0
  93. data/lib/site_template/package.json.erb +2 -2
  94. data/lib/site_template/plugins/site_builder.rb +1 -1
  95. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +1 -1
  96. data/lib/site_template/webpack.config.js.erb +26 -6
  97. metadata +39 -40
  98. data/lib/bridgetown-core/external.rb +0 -58
  99. data/lib/bridgetown-core/page_without_a_file.rb +0 -17
  100. data/lib/bridgetown-core/path_manager.rb +0 -31
  101. data/lib/bridgetown-core/readers/collection_reader.rb +0 -23
  102. data/lib/bridgetown-core/readers/static_file_reader.rb +0 -25
  103. data/lib/bridgetown-core/utils/exec.rb +0 -26
  104. data/lib/bridgetown-core/utils/internet.rb +0 -37
  105. data/lib/bridgetown-core/utils/platforms.rb +0 -80
  106. data/lib/bridgetown-core/utils/thread_event.rb +0 -31
  107. data/lib/bridgetown-core/utils/win_tz.rb +0 -75
@@ -1,11 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Handles Legacy Pages
3
4
  Bridgetown::Hooks.register :pages, :post_init, reloadable: false do |page|
4
5
  if page.class != Bridgetown::PrototypePage && page.data["prototype"].is_a?(Hash)
5
6
  Bridgetown::PrototypeGenerator.add_matching_template(page)
6
7
  end
7
8
  end
8
9
 
10
+ # Handles Resources
11
+ Bridgetown::Hooks.register :resources, :post_read, reloadable: false do |resource|
12
+ if resource.data["prototype"].is_a?(Hash)
13
+ Bridgetown::PrototypeGenerator.add_matching_template(resource)
14
+ end
15
+ end
16
+
9
17
  module Bridgetown
10
18
  class PrototypeGenerator < Generator
11
19
  priority :low
@@ -13,27 +21,27 @@ module Bridgetown
13
21
  # @return [Bridgetown::Site]
14
22
  attr_reader :site
15
23
 
16
- @matching_templates = []
17
-
18
- def self.add_matching_template(template)
19
- @matching_templates << template
24
+ # @return [Array<Bridgetown::Page, Bridgetown::Resource::Base>]
25
+ def self.matching_templates
26
+ @matching_templates ||= []
20
27
  end
21
28
 
22
- class << self
23
- # @return [Array<Page>]
24
- attr_reader :matching_templates
29
+ def self.add_matching_template(template)
30
+ matching_templates << template
25
31
  end
26
32
 
33
+ # @param site [Bridgetown::Site]
27
34
  def generate(site)
28
35
  @site = site
29
- @configured_collection = "posts"
36
+ @configured_collection = "posts" unless site.uses_resource?
37
+ page_list = site.uses_resource? ? site.collections.pages.resources : site.pages
30
38
 
31
39
  prototype_pages = self.class.matching_templates.select do |page|
32
- site.pages.include? page
40
+ page_list.include?(page)
33
41
  end
34
42
 
35
43
  if prototype_pages.length.positive?
36
- site.pages.reject! do |page|
44
+ page_list.reject! do |page|
37
45
  prototype_pages.include? page
38
46
  end
39
47
 
@@ -42,7 +50,7 @@ module Bridgetown
42
50
  next if search_term.nil?
43
51
 
44
52
  terms_matching_pages(search_term).each do |term|
45
- generate_new_page_from_prototype(prototype_page, search_term, term).data
53
+ generate_new_page_from_prototype(prototype_page, search_term, term)
46
54
  end
47
55
  end
48
56
  end
@@ -50,7 +58,7 @@ module Bridgetown
50
58
 
51
59
  # Check incoming prototype configuration and normalize options.
52
60
  #
53
- # @param prototype_page [PrototypePage]
61
+ # @param prototype_page [Bridgetown::Page, Bridgetown::Resource::Base]
54
62
  #
55
63
  # @return [String, nil]
56
64
  def validate_search_term(prototype_page)
@@ -71,7 +79,7 @@ module Bridgetown
71
79
 
72
80
  def generate_new_page_from_prototype(prototype_page, search_term, term)
73
81
  new_page = PrototypePage.new(prototype_page, @configured_collection, search_term, term)
74
- site.pages << new_page
82
+ site.add_generated_page new_page
75
83
  new_page
76
84
  end
77
85
 
@@ -81,16 +89,26 @@ module Bridgetown
81
89
  #
82
90
  # @return [Array<String>]
83
91
  def terms_matching_pages(search_term)
92
+ pages_list = if site.uses_resource?
93
+ site.collections[@configured_collection].resources
94
+ else
95
+ site.collections[@configured_collection].docs
96
+ end
97
+
84
98
  Bridgetown::Paginate::PaginationIndexer.index_documents_by(
85
- site.collections[@configured_collection].docs, search_term
99
+ pages_list, search_term
86
100
  ).keys
87
101
  end
88
102
  end
89
103
 
90
- class PrototypePage < Page
91
- # @return [Page]
104
+ class PrototypePage < GeneratedPage
105
+ # @return [Bridgetown::Page, Bridgetown::Resource::Base]
92
106
  attr_reader :prototyped_page
93
107
 
108
+ # @param prototyped_page [Bridgetown::Page, Bridgetown::Resource::Base]
109
+ # @param collection [Bridgetown::Collection]
110
+ # @param search_term [String]
111
+ # @param term [String]
94
112
  def initialize(prototyped_page, collection, search_term, term)
95
113
  @prototyped_page = prototyped_page
96
114
  @site = prototyped_page.site
@@ -107,7 +125,7 @@ module Bridgetown
107
125
  validate_data! prototyped_page.path
108
126
  validate_permalink! prototyped_page.path
109
127
 
110
- @dir = Pathname.new(prototyped_page.relative_path).dirname.to_s
128
+ @dir = Pathname.new(prototyped_page.relative_path).dirname.to_s.sub(%r{^_pages}, "")
111
129
  @path = site.in_source_dir(@dir, @name)
112
130
 
113
131
  process_prototype_page_data(collection, search_term, term)
@@ -132,7 +150,7 @@ module Bridgetown
132
150
  slugify_term(term)
133
151
  end
134
152
 
135
- # rubocop:disable Metrics/AbcSize
153
+ # rubocop:todo Metrics/AbcSize
136
154
  def process_title_data_placeholder(search_term, term)
137
155
  if prototyped_page.data["prototype"]["data"]
138
156
  if data["title"]&.include?(":prototype-data-label")
@@ -164,7 +182,7 @@ module Bridgetown
164
182
  end
165
183
 
166
184
  def slugify_term(term)
167
- term_slug = Bridgetown::Utils.slugify(term)
185
+ term_slug = Bridgetown::Utils.slugify(term, mode: site.config.slugify_mode)
168
186
  @url = if permalink.is_a?(String)
169
187
  data["permalink"] = data["permalink"].sub(":term", term_slug)
170
188
  else
@@ -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
@@ -21,12 +21,18 @@ module Bridgetown
21
21
  # Gets/Sets the extension of this layout.
22
22
  attr_accessor :ext
23
23
 
24
+ alias_method :extname, :ext
25
+
24
26
  # Gets/Sets the Hash that holds the metadata for this layout.
25
27
  attr_accessor :data
26
28
 
27
29
  # Gets/Sets the content of this layout.
30
+ # @return [String]
28
31
  attr_accessor :content
29
32
 
33
+ # @return [Integer]
34
+ attr_accessor :front_matter_line_count
35
+
30
36
  # Gets/Sets the current document (for layout-compatible converters)
31
37
  attr_accessor :current_document
32
38
 
@@ -52,11 +58,29 @@ module Bridgetown
52
58
  @path = site.in_source_dir(base, name)
53
59
  end
54
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
55
72
 
56
- self.data = {}
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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bridgetown
4
4
  class LiquidRenderer
5
+ # TODO: deprecate or move to a separate repo/plugin
5
6
  class File
6
7
  def initialize(renderer, filename)
7
8
  @renderer = renderer
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bridgetown
4
4
  class LiquidRenderer
5
+ # TODO: deprecate or move to a separate repo/plugin
5
6
  class Table
6
7
  GAUGES = [:count, :bytes, :time].freeze
7
8
 
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model"
4
+
5
+ module Bridgetown
6
+ module Model
7
+ class Base
8
+ include ActiveModel::Model
9
+ extend ActiveModel::Callbacks # also extends with DescendantsTracker
10
+ define_model_callbacks :load, :save, :destroy
11
+
12
+ def self.loads_id?(id)
13
+ name == ActiveSupport::Inflector.classify(
14
+ URI.parse(id).host.chomp(".collection")
15
+ )
16
+ end
17
+
18
+ def self.find(id)
19
+ unless Bridgetown::Current.site
20
+ raise "A Bridgetown site must be initialized and added to Current"
21
+ end
22
+
23
+ model_klass = klass_for_id(id)
24
+ model_klass.new(read_data_for_id(id))
25
+ end
26
+
27
+ def self.klass_for_id(id)
28
+ descendants.find do |klass|
29
+ klass.loads_id?(id)
30
+ end || self
31
+ end
32
+
33
+ def self.read_data_for_id(id)
34
+ origin_for_id(id).read
35
+ end
36
+
37
+ def self.origin_for_id(id)
38
+ scheme = URI.parse(id).scheme
39
+ origin_klass = Origin.descendants.find do |klass|
40
+ klass.handle_scheme?(scheme)
41
+ end
42
+
43
+ raise "No origin could be found for #{id}" unless origin_klass
44
+
45
+ origin_klass.new(id)
46
+ end
47
+
48
+ class << self
49
+ def build(collection_name, path, data)
50
+ data = Bridgetown::Model::BuilderOrigin.new("builder://#{path}").read do
51
+ data[:_collection_] = Bridgetown::Current.site.collections[collection_name]
52
+ data
53
+ end
54
+ new(data)
55
+ end
56
+ end
57
+
58
+ def initialize(attributes = {})
59
+ run_callbacks :load do
60
+ super
61
+ end
62
+ end
63
+
64
+ def id
65
+ attributes[:id] || attributes[:_id_]
66
+ end
67
+
68
+ # @return [Bridgetown::Model::Origin]
69
+ def origin
70
+ attributes[:_origin_]
71
+ end
72
+
73
+ def persisted?
74
+ id && origin.exists?
75
+ end
76
+
77
+ # @return [Bridgetown::Resource::Base]
78
+ def to_resource
79
+ Bridgetown::Resource::Base.new(model: self)
80
+ end
81
+
82
+ def as_resource_in_collection
83
+ collection.resources << to_resource.read!
84
+ collection.resources.last
85
+ end
86
+
87
+ # override if need be
88
+ # @return [Bridgetown::Site]
89
+ def site
90
+ Bridgetown::Current.site
91
+ end
92
+
93
+ # @return [Bridgetown::Collection]
94
+ def collection
95
+ attributes[:_collection_]
96
+ end
97
+
98
+ # @return [String]
99
+ def content
100
+ attributes[:_content_]
101
+ end
102
+
103
+ def attributes
104
+ @attributes ||= HashWithDotAccess::Hash.new
105
+ end
106
+
107
+ # Strip out keys like _origin_, _collection_, etc.
108
+ # @return [HashWithDotAccess::Hash]
109
+ def data_attributes
110
+ attributes.reject { |k| k.starts_with?("_") && k.ends_with?("_") }
111
+ end
112
+
113
+ def respond_to_missing?(method_name, include_private = false)
114
+ attributes.key?(method_name) || method_name.to_s.end_with?("=") || super
115
+ end
116
+
117
+ def method_missing(method_name, *args) # rubocop:disable Style/MethodMissingSuper
118
+ return attributes[method_name] if attributes.key?(method_name)
119
+
120
+ key = method_name.to_s
121
+ if key.end_with?("=")
122
+ key.chop!
123
+ # attribute_will_change!(key)
124
+ attributes[key] = args.first
125
+ return attributes[key]
126
+ end
127
+
128
+ Bridgetown.logger.warn "key `#{method_name}' not found in attributes for" \
129
+ " #{attributes[:id].presence || ("new " + self.class.to_s)}"
130
+ nil
131
+ end
132
+
133
+ def inspect
134
+ "#<#{self.class} #{data_attributes.inspect.delete_prefix("{").delete_suffix("}")}>"
135
+ end
136
+ end
137
+ end
138
+ end