bridgetown-core 0.19.2 → 0.21.0.beta3

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/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
@@ -18,7 +18,7 @@ module Bridgetown
18
18
 
19
19
  entries.each do |entry|
20
20
  path = @site.in_source_dir(entry)
21
- @path_defaults[File.dirname(path) + File::SEPARATOR] = SafeYAML.load_file(path)
21
+ @path_defaults[File.dirname(path) + File::SEPARATOR] = YAMLParser.load_file(path)
22
22
  end
23
23
 
24
24
  @path_defaults
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
+ # TODO: to be retired once the Resource engine is made official
4
5
  class PageReader
5
6
  attr_reader :site, :dir, :unfiltered_content
6
7
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
+ # TODO: to be retired once the Resource engine is made official
4
5
  class PostReader
5
6
  attr_reader :site, :unfiltered_content
6
7
 
@@ -47,7 +48,7 @@ module Bridgetown
47
48
  # Process as Document
48
49
  Document.new(path,
49
50
  site: @site,
50
- collection: @site.posts)
51
+ collection: @site.collections.posts)
51
52
  else
52
53
  # Process as Static File
53
54
  read_static_file(
@@ -66,7 +67,7 @@ module Bridgetown
66
67
  site.source,
67
68
  relative_dir,
68
69
  File.basename(full_path),
69
- @site.posts
70
+ @site.collections.posts
70
71
  )
71
72
  end
72
73
 
@@ -96,9 +97,9 @@ module Bridgetown
96
97
  return false unless processable?(item)
97
98
 
98
99
  if item.is_a?(Document)
99
- site.posts.docs << item
100
+ site.collections.posts.docs << item
100
101
  elsif item.is_a?(StaticFile)
101
- site.posts.files << item
102
+ site.collections.posts.static_files << item
102
103
  site.static_files << item
103
104
  end
104
105
 
@@ -19,7 +19,7 @@ module Bridgetown
19
19
  # Checks if a renderable object needs to be regenerated
20
20
  #
21
21
  # Returns a boolean.
22
- def regenerate?(document)
22
+ def regenerate?(document) # rubocop:todo Metrics/CyclomaticComplexity
23
23
  return true if disabled
24
24
 
25
25
  case document
@@ -27,6 +27,8 @@ module Bridgetown
27
27
  regenerate_page?(document)
28
28
  when Document
29
29
  regenerate_document?(document)
30
+ when Bridgetown::Resource::Base
31
+ regenerate_resource?(document)
30
32
  else
31
33
  source_path = document.respond_to?(:path) ? document.path : nil
32
34
  dest_path = document.destination(@site.dest) if document.respond_to?(:destination)
@@ -152,7 +154,7 @@ module Bridgetown
152
154
  begin
153
155
  Marshal.load(content)
154
156
  rescue TypeError
155
- SafeYAML.load(content)
157
+ YAMLParser.load(content)
156
158
  rescue ArgumentError => e
157
159
  Bridgetown.logger.warn("Failed to load #{metadata_file}: #{e}")
158
160
  {}
@@ -176,6 +178,11 @@ module Bridgetown
176
178
  )
177
179
  end
178
180
 
181
+ # TODO: need to manage dependencies, but for now always regenerate
182
+ def regenerate_resource?(_resource)
183
+ true
184
+ end
185
+
179
186
  def existing_file_modified?(path)
180
187
  # If one of this file dependencies have been modified,
181
188
  # set the regeneration bit for both the dependency and the file to true
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
+ # TODO: to be retired once the Resource engine is made official
4
5
  class RelatedPosts
5
6
  class << self
6
7
  attr_accessor :lsi
@@ -11,13 +12,15 @@ module Bridgetown
11
12
  def initialize(post)
12
13
  @post = post
13
14
  @site = post.site
14
- Bridgetown::External.require_with_graceful_fail("classifier-reborn") if site.lsi
15
+ if site.config.lsi
16
+ Bridgetown::Utils::RequireGems.require_with_graceful_fail("classifier-reborn")
17
+ end
15
18
  end
16
19
 
17
20
  def build
18
- return [] unless site.posts.docs.size > 1
21
+ return [] unless site.collections.posts.docs.size > 1
19
22
 
20
- if site.lsi
23
+ if site.config.lsi
21
24
  build_index
22
25
  lsi_related_posts
23
26
  else
@@ -30,7 +33,7 @@ module Bridgetown
30
33
  lsi = ClassifierReborn::LSI.new(auto_rebuild: false)
31
34
  Bridgetown.logger.info("Populating LSI...")
32
35
 
33
- site.posts.docs.each do |x|
36
+ site.collections.posts.docs.each do |x|
34
37
  lsi.add_item(x)
35
38
  end
36
39
 
@@ -42,11 +45,11 @@ module Bridgetown
42
45
  end
43
46
 
44
47
  def lsi_related_posts
45
- self.class.lsi.find_related(post, 11)
48
+ self.class.lsi.find_related(post, 11) - [post]
46
49
  end
47
50
 
48
51
  def most_recent_posts
49
- @most_recent_posts ||= (site.posts.docs.last(11).reverse - [post]).first(10)
52
+ @most_recent_posts ||= (site.collections.posts.docs.last(11).reverse - [post]).first(10)
50
53
  end
51
54
  end
52
55
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bridgetown
4
+ # This class handles the output rendering and layout placement of pages and
5
+ # documents. For rendering of resources in particular, see Bridgetown::Resource::Transformer
4
6
  class Renderer
5
7
  attr_reader :document, :site
6
8
 
@@ -50,7 +52,7 @@ module Bridgetown
50
52
  output = document.content
51
53
  Bridgetown.logger.debug "Rendering Markup:", document.relative_path
52
54
  output = convert(output.to_s, document)
53
- document.content = output
55
+ document.content = output.html_safe
54
56
 
55
57
  if document.place_in_layout?
56
58
  Bridgetown.logger.debug "Rendering Layout:", document.relative_path
@@ -121,21 +123,11 @@ module Bridgetown
121
123
  "in #{document.relative_path} does not exist."
122
124
  end
123
125
 
124
- def converters_for_layout(layout)
125
- site.converters.select do |converter|
126
- if converter.method(:matches).arity == 1
127
- converter.matches(layout.ext)
128
- else
129
- converter.matches(layout.ext, layout)
130
- end
131
- end.sort
132
- end
133
-
134
126
  # Render layout content into document.output
135
127
  #
136
128
  # Returns String rendered content
137
129
  def render_layout(output, layout)
138
- layout_converters = converters_for_layout(layout)
130
+ layout_converters = site.matched_converters_for_convertible(layout)
139
131
 
140
132
  layout_content = layout.content.dup
141
133
  layout_converters.reduce(layout_content) do |layout_output, converter|
@@ -163,7 +155,8 @@ module Bridgetown
163
155
 
164
156
  def permalink_ext
165
157
  document_permalink = document.permalink
166
- if document_permalink && !document_permalink.end_with?("/")
158
+ if document_permalink &&
159
+ !document_permalink.end_with?("/")
167
160
  permalink_ext = File.extname(document_permalink)
168
161
  permalink_ext unless permalink_ext.empty?
169
162
  end
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class Base
6
+ include Comparable
7
+ include Bridgetown::Publishable
8
+ include Bridgetown::LayoutPlaceable
9
+ include Bridgetown::LiquidRenderable
10
+
11
+ # @return [HashWithDotAccess::Hash]
12
+ attr_reader :data
13
+
14
+ # @return [Destination]
15
+ attr_reader :destination
16
+
17
+ # @return [Bridgetown::Model::Base]
18
+ attr_reader :model
19
+
20
+ # @return [Bridgetown::Site]
21
+ attr_reader :site
22
+
23
+ # @return [String]
24
+ attr_accessor :content, :untransformed_content, :output
25
+
26
+ DATE_FILENAME_MATCHER = %r!^(?>.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze
27
+
28
+ # @param site [Bridgetown::Site]
29
+ # @param origin [Bridgetown::Resource::Origin]
30
+ def initialize(model:)
31
+ @model = model
32
+ @site = model.site
33
+ self.data = HashWithDotAccess::Hash.new
34
+
35
+ trigger_hooks(:post_init)
36
+ end
37
+
38
+ # Collection associated with this resource
39
+ #
40
+ # @return [Bridgetown::Collection]
41
+ def collection
42
+ model.collection
43
+ end
44
+
45
+ # The relative path of source file or file-like origin
46
+ #
47
+ # @return [Pathname]
48
+ def relative_path
49
+ model.origin.relative_path
50
+ end
51
+
52
+ # @return [Bridgetown::Resource::Transformer]
53
+ def transformer
54
+ @transformer ||= Bridgetown::Resource::Transformer.new(self)
55
+ end
56
+
57
+ # @return [Bridgetown::Resource::Relations]
58
+ def relations
59
+ @relations ||= Bridgetown::Resource::Relations.new(self)
60
+ end
61
+
62
+ # @param new_data [HashWithDotAccess::Hash]
63
+ def data=(new_data)
64
+ unless new_data.is_a?(HashWithDotAccess::Hash)
65
+ raise "#{self.class} data should be of type HashWithDotAccess::Hash"
66
+ end
67
+
68
+ @data = new_data
69
+ @data.default_proc = proc do |_, key|
70
+ site.frontmatter_defaults.find(
71
+ relative_path.to_s,
72
+ collection.label.to_sym,
73
+ key.to_s
74
+ )
75
+ end
76
+ end
77
+
78
+ # @return [Bridgetown::Resource::Base]
79
+ def read!
80
+ self.data = model.data_attributes
81
+ self.content = model.content # could be nil
82
+
83
+ unless collection.data?
84
+ self.untransformed_content = content
85
+ normalize_categories_and_tags
86
+ import_taxonomies_from_data
87
+ ensure_default_data
88
+ transformer.execute_inline_ruby!
89
+ set_date_from_string(data.date)
90
+ end
91
+
92
+ @destination = Destination.new(self) if requires_destination?
93
+
94
+ trigger_hooks(:post_read)
95
+
96
+ self
97
+ end
98
+ alias_method :read, :read! # TODO: eventually use the bang version only
99
+
100
+ def transform!
101
+ transformer.process! unless collection.data?
102
+ end
103
+
104
+ def trigger_hooks(hook_name, *args)
105
+ Bridgetown::Hooks.trigger collection.label.to_sym, hook_name, self, *args if collection
106
+ Bridgetown::Hooks.trigger :resources, hook_name, self, *args
107
+ end
108
+
109
+ def around_hook(hook_suffix)
110
+ trigger_hooks :"pre_#{hook_suffix}"
111
+ yield
112
+ trigger_hooks :"post_#{hook_suffix}"
113
+ end
114
+
115
+ # @return [String]
116
+ def relative_path_basename_without_prefix
117
+ return_path = Pathname.new("")
118
+ relative_path.each_filename do |filename|
119
+ if matches = DATE_FILENAME_MATCHER.match(filename) # rubocop:disable Lint/AssignmentInCondition
120
+ filename = matches[2] + matches[3]
121
+ end
122
+
123
+ return_path += filename unless filename.starts_with?("_")
124
+ end
125
+
126
+ (return_path.dirname + return_path.basename(".*")).to_s
127
+ end
128
+
129
+ # @return [String]
130
+ def basename_without_ext
131
+ relative_path.basename(".*").to_s
132
+ end
133
+
134
+ # @return [String]
135
+ def extname
136
+ relative_path.extname
137
+ end
138
+
139
+ # @return [String, nil]
140
+ def permalink
141
+ data&.permalink
142
+ end
143
+
144
+ # @return [String]
145
+ def path
146
+ (model.origin.respond_to?(:original_path) ? model.origin.original_path : relative_path).to_s
147
+ end
148
+
149
+ # @return [String]
150
+ def absolute_url
151
+ format_url destination&.absolute_url
152
+ end
153
+
154
+ # @return [String]
155
+ def relative_url
156
+ format_url destination&.relative_url
157
+ end
158
+
159
+ # @return [String]
160
+ def id
161
+ model.origin.id
162
+ end
163
+
164
+ def date
165
+ data["date"] ||= site.time
166
+ end
167
+
168
+ # @return [Hash<String, Hash<String => Bridgetown::Resource::TaxonomyType,
169
+ # Array<Bridgetown::Resource::TaxonomyTerm>>>]
170
+ def taxonomies
171
+ @taxonomies ||= site.taxonomy_types.values.each_with_object(
172
+ HashWithDotAccess::Hash.new
173
+ ) do |taxonomy, hsh|
174
+ hsh[taxonomy.label] = {
175
+ type: taxonomy,
176
+ terms: [],
177
+ }
178
+ end
179
+ end
180
+
181
+ def requires_destination?
182
+ collection.write? && data.config&.output != false
183
+ end
184
+
185
+ def write?
186
+ requires_destination? && site.publisher.publish?(self)
187
+ end
188
+
189
+ # Write the generated Document file to the destination directory.
190
+ #
191
+ # dest - The String path to the destination dir.
192
+ #
193
+ # Returns nothing.
194
+ def write(_dest = nil)
195
+ destination.write(output)
196
+ trigger_hooks(:post_write)
197
+ end
198
+
199
+ def to_s
200
+ output || content || ""
201
+ end
202
+
203
+ # Create a Liquid-understandable version of this resource.
204
+ #
205
+ # @return [Drops::ResourceDrop] represents this resource's data.
206
+ def to_liquid
207
+ @to_liquid ||= Drops::ResourceDrop.new(self)
208
+ end
209
+
210
+ def to_h
211
+ {
212
+ id: id,
213
+ absolute_url: absolute_url,
214
+ relative_path: relative_path,
215
+ relative_url: relative_url,
216
+ date: date,
217
+ data: data,
218
+ taxonomies: taxonomies,
219
+ untransformed_content: untransformed_content,
220
+ content: content,
221
+ output: output,
222
+ }
223
+ end
224
+
225
+ def as_json(*)
226
+ to_h
227
+ end
228
+
229
+ ruby2_keywords def to_json(*options)
230
+ as_json(*options).to_json(*options)
231
+ end
232
+
233
+ def inspect
234
+ "#<#{self.class} #{id}>"
235
+ end
236
+
237
+ # Compare this document against another document.
238
+ # Comparison is a comparison between the 2 paths of the documents.
239
+ #
240
+ # Returns -1, 0, +1 or nil depending on whether this doc's path is less than,
241
+ # equal or greater than the other doc's path. See String#<=> for more details.
242
+ def <=>(other) # rubocop:todo Metrics/AbcSize
243
+ return nil unless other.respond_to?(:data)
244
+
245
+ if data.date.respond_to?(:to_datetime) && other.data.date.respond_to?(:to_datetime)
246
+ return data.date.to_datetime <=> other.data.date.to_datetime
247
+ end
248
+
249
+ cmp = data["date"] <=> other.data["date"]
250
+ cmp = path <=> other.path if cmp.nil? || cmp.zero?
251
+ cmp
252
+ end
253
+
254
+ def next_resource
255
+ pos = collection.resources.index { |item| item.equal?(self) }
256
+ collection.resources[pos + 1] if pos && pos < collection.resources.length - 1
257
+ end
258
+ alias_method :next_doc, :next_resource
259
+
260
+ def previous_resource
261
+ pos = collection.resources.index { |item| item.equal?(self) }
262
+ collection.resources[pos - 1] if pos&.positive?
263
+ end
264
+ alias_method :previous_doc, :previous_resource
265
+
266
+ private
267
+
268
+ def ensure_default_data
269
+ slug = if matches = relative_path.to_s.match(DATE_FILENAME_MATCHER) # rubocop:disable Lint/AssignmentInCondition
270
+ set_date_from_string(matches[1]) unless data.date
271
+ matches[2]
272
+ else
273
+ basename_without_ext
274
+ end
275
+
276
+ data.slug ||= slug
277
+ data.title ||= Bridgetown::Utils.titleize_slug(slug)
278
+ end
279
+
280
+ def set_date_from_string(new_date) # rubocop:disable Naming/AccessorMethodName
281
+ return unless new_date.is_a?(String)
282
+
283
+ data.date = Bridgetown::Utils.parse_date(
284
+ new_date,
285
+ "Document '#{relative_path}' does not have a valid date in the #{model}."
286
+ )
287
+ end
288
+
289
+ def normalize_categories_and_tags
290
+ data.categories = Bridgetown::Utils.pluralized_array_from_hash(
291
+ data, :category, :categories
292
+ )
293
+ data.tags = Bridgetown::Utils.pluralized_array_from_hash(
294
+ data, :tag, :tags
295
+ )
296
+ end
297
+
298
+ def import_taxonomies_from_data
299
+ taxonomies.each do |_label, metadata|
300
+ Array(data[metadata.type.key]).each do |term|
301
+ metadata.terms << TaxonomyTerm.new(
302
+ resource: self, label: term, type: metadata.type
303
+ )
304
+ end
305
+ end
306
+ end
307
+
308
+ def format_url(url)
309
+ url.to_s.sub(%r{index\.html?$}, "").sub(%r{\.html?$}, "")
310
+ end
311
+ end
312
+ end
313
+ end