bridgetown-core 0.19.3 → 0.20.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/bridgetown-core.gemspec +1 -1
  3. data/lib/bridgetown-core.rb +30 -11
  4. data/lib/bridgetown-core/cleaner.rb +7 -1
  5. data/lib/bridgetown-core/collection.rb +173 -77
  6. data/lib/bridgetown-core/commands/base.rb +9 -0
  7. data/lib/bridgetown-core/commands/configure.rb +4 -0
  8. data/lib/bridgetown-core/commands/console.rb +4 -0
  9. data/lib/bridgetown-core/concerns/data_accessible.rb +1 -0
  10. data/lib/bridgetown-core/concerns/site/configurable.rb +7 -3
  11. data/lib/bridgetown-core/concerns/site/content.rb +57 -15
  12. data/lib/bridgetown-core/concerns/site/processable.rb +1 -0
  13. data/lib/bridgetown-core/concerns/site/renderable.rb +26 -0
  14. data/lib/bridgetown-core/concerns/site/writable.rb +11 -1
  15. data/lib/bridgetown-core/concerns/validatable.rb +1 -0
  16. data/lib/bridgetown-core/configuration.rb +39 -19
  17. data/lib/bridgetown-core/converter.rb +14 -0
  18. data/lib/bridgetown-core/converters/identity.rb +0 -9
  19. data/lib/bridgetown-core/converters/markdown.rb +14 -4
  20. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +3 -0
  21. data/lib/bridgetown-core/current.rb +10 -0
  22. data/lib/bridgetown-core/document.rb +6 -14
  23. data/lib/bridgetown-core/drops/collection_drop.rb +1 -1
  24. data/lib/bridgetown-core/drops/page_drop.rb +4 -0
  25. data/lib/bridgetown-core/drops/resource_drop.rb +81 -0
  26. data/lib/bridgetown-core/drops/site_drop.rb +33 -8
  27. data/lib/bridgetown-core/drops/unified_payload_drop.rb +4 -0
  28. data/lib/bridgetown-core/entry_filter.rb +10 -23
  29. data/lib/bridgetown-core/errors.rb +0 -2
  30. data/lib/bridgetown-core/filters.rb +2 -1
  31. data/lib/bridgetown-core/generators/prototype_generator.rb +37 -19
  32. data/lib/bridgetown-core/layout.rb +2 -2
  33. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -0
  34. data/lib/bridgetown-core/liquid_renderer/table.rb +1 -0
  35. data/lib/bridgetown-core/model/base.rb +138 -0
  36. data/lib/bridgetown-core/model/builder_origin.rb +40 -0
  37. data/lib/bridgetown-core/model/file_origin.rb +119 -0
  38. data/lib/bridgetown-core/model/origin.rb +38 -0
  39. data/lib/bridgetown-core/page.rb +9 -1
  40. data/lib/bridgetown-core/plugin_manager.rb +0 -2
  41. data/lib/bridgetown-core/publisher.rb +7 -1
  42. data/lib/bridgetown-core/reader.rb +25 -12
  43. data/lib/bridgetown-core/readers/data_reader.rb +3 -4
  44. data/lib/bridgetown-core/readers/post_reader.rb +1 -1
  45. data/lib/bridgetown-core/regenerator.rb +8 -1
  46. data/lib/bridgetown-core/related_posts.rb +1 -1
  47. data/lib/bridgetown-core/renderer.rb +5 -12
  48. data/lib/bridgetown-core/resource/base.rb +275 -0
  49. data/lib/bridgetown-core/resource/destination.rb +49 -0
  50. data/lib/bridgetown-core/resource/permalink_processor.rb +179 -0
  51. data/lib/bridgetown-core/resource/taxonomy_term.rb +25 -0
  52. data/lib/bridgetown-core/resource/taxonomy_type.rb +47 -0
  53. data/lib/bridgetown-core/resource/transformer.rb +173 -0
  54. data/lib/bridgetown-core/ruby_template_view.rb +4 -0
  55. data/lib/bridgetown-core/site.rb +9 -1
  56. data/lib/bridgetown-core/static_file.rb +33 -10
  57. data/lib/bridgetown-core/url.rb +1 -0
  58. data/lib/bridgetown-core/utils.rb +40 -40
  59. data/lib/bridgetown-core/utils/platforms.rb +1 -0
  60. data/lib/bridgetown-core/version.rb +2 -2
  61. data/lib/site_template/webpack.config.js.erb +8 -6
  62. metadata +28 -21
  63. data/lib/bridgetown-core/page_without_a_file.rb +0 -17
  64. data/lib/bridgetown-core/readers/collection_reader.rb +0 -23
  65. data/lib/bridgetown-core/utils/exec.rb +0 -26
  66. data/lib/bridgetown-core/utils/internet.rb +0 -37
  67. data/lib/bridgetown-core/utils/win_tz.rb +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad7d5025a99240586457a8afa4aa3e9a9aba0e289484ff855648bd772b89e7a9
4
- data.tar.gz: 1ea0ad14dc78ca7465f7ff93bab6e178ec18b336e08641ac18bf75cf655b7246
3
+ metadata.gz: c58653595832ef2fb9e6bbea3f147a0b3be9edae554d169e974570b84565a507
4
+ data.tar.gz: 7af409e9a01570321171cf1afc562c00cacdcc6e0dce98f2b8eac4988a60e8d6
5
5
  SHA512:
6
- metadata.gz: 95b1542d9fa33741d18832a026aa43e720f99520b8367b12672e2689c7be19200e0cedd53b8070666c15e7594af43dec3c1885fa8570e6c7daf463afcae3405f
7
- data.tar.gz: '00048d6aa2ca15728544ecbf46ee05b4b813630a03a0f35154b45096a2f9720ea645e23faac13fcab18a85e395fa6782eee522cbd39248b88b0f2a80a7fcff41'
6
+ metadata.gz: b21d9c304dd99dd306b70e97e172d1ec172f0d1fa48034610cc3d9e6bc9c40b98e7fe10479226103cb2ff501c22c7c2841f20630235262cc7214661be62bcf3e
7
+ data.tar.gz: cab7afb0f441e412850ce648d6e7ca9a3996f106b0800c31ef24a0cf63af9db770498f307da275860c0572d6d6f2a5f1e56570d10349bb11cd4f17f96b3266ee
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.required_ruby_version = ">= 2.5.0"
32
32
  s.required_rubygems_version = ">= 2.7.0"
33
33
 
34
+ s.add_runtime_dependency("activemodel", "~> 6.0")
34
35
  s.add_runtime_dependency("activesupport", "~> 6.0")
35
36
  s.add_runtime_dependency("addressable", "~> 2.4")
36
37
  s.add_runtime_dependency("amazing_print", "~> 1.2")
@@ -45,7 +46,6 @@ Gem::Specification.new do |s|
45
46
  s.add_runtime_dependency("liquid", "~> 5.0")
46
47
  s.add_runtime_dependency("liquid-component", ">= 0.1")
47
48
  s.add_runtime_dependency("listen", "~> 3.0")
48
- s.add_runtime_dependency("pathutil", "~> 0.9")
49
49
  s.add_runtime_dependency("rouge", "~> 3.0")
50
50
  s.add_runtime_dependency("safe_yaml", "~> 1.0")
51
51
  s.add_runtime_dependency("terminal-table", "~> 1.8")
@@ -18,6 +18,7 @@ end
18
18
  require "rubygems"
19
19
 
20
20
  # stdlib
21
+ require "find"
21
22
  require "forwardable"
22
23
  require "fileutils"
23
24
  require "time"
@@ -31,13 +32,16 @@ require "json"
31
32
  # 3rd party
32
33
  require "active_support"
33
34
  require "active_support/core_ext/hash/keys"
35
+ require "active_support/core_ext/module/delegation"
34
36
  require "active_support/core_ext/object/blank"
37
+ require "active_support/core_ext/object/deep_dup"
38
+ require "active_support/core_ext/object/inclusion"
35
39
  require "active_support/core_ext/string/inflections"
36
40
  require "active_support/core_ext/string/inquiry"
37
41
  require "active_support/core_ext/string/starts_ends_with"
42
+ require "active_support/current_attributes"
38
43
  require "active_support/descendants_tracker"
39
44
  require "hash_with_dot_access"
40
- require "pathutil"
41
45
  require "addressable/uri"
42
46
  require "safe_yaml/load"
43
47
  require "liquid"
@@ -47,6 +51,7 @@ require "colorator"
47
51
  require "i18n"
48
52
  require "faraday"
49
53
  require "thor"
54
+ require "zeitwerk"
50
55
 
51
56
  module HashWithDotAccess
52
57
  class Hash # :nodoc:
@@ -73,7 +78,6 @@ if RUBY_VERSION.start_with?("3.0")
73
78
  end
74
79
 
75
80
  module Bridgetown
76
- # internal requires
77
81
  autoload :Cleaner, "bridgetown-core/cleaner"
78
82
  autoload :Collection, "bridgetown-core/collection"
79
83
  autoload :Configuration, "bridgetown-core/configuration"
@@ -81,30 +85,38 @@ module Bridgetown
81
85
  autoload :Deprecator, "bridgetown-core/deprecator"
82
86
  autoload :Document, "bridgetown-core/document"
83
87
  autoload :EntryFilter, "bridgetown-core/entry_filter"
88
+ # TODO: we have too many errors! This is silly
84
89
  autoload :Errors, "bridgetown-core/errors"
85
90
  autoload :Excerpt, "bridgetown-core/excerpt"
91
+ # TODO: this is a poorly named, unclear class. Relocate to Utils:
86
92
  autoload :External, "bridgetown-core/external"
87
93
  autoload :FrontmatterDefaults, "bridgetown-core/frontmatter_defaults"
88
94
  autoload :Hooks, "bridgetown-core/hooks"
89
95
  autoload :Layout, "bridgetown-core/layout"
90
96
  autoload :LayoutPlaceable, "bridgetown-core/concerns/layout_placeable"
91
97
  autoload :Cache, "bridgetown-core/cache"
92
- autoload :CollectionReader, "bridgetown-core/readers/collection_reader"
98
+ autoload :Current, "bridgetown-core/current"
99
+ # TODO: remove this when legacy content engine is gone:
93
100
  autoload :DataReader, "bridgetown-core/readers/data_reader"
94
101
  autoload :DefaultsReader, "bridgetown-core/readers/defaults_reader"
95
102
  autoload :LayoutReader, "bridgetown-core/readers/layout_reader"
103
+ # TODO: remove this when legacy content engine is gone:
96
104
  autoload :PostReader, "bridgetown-core/readers/post_reader"
105
+ # TODO: we can merge this back into Reader class:
97
106
  autoload :PageReader, "bridgetown-core/readers/page_reader"
98
107
  autoload :PluginContentReader, "bridgetown-core/readers/plugin_content_reader"
108
+ # TODO: also merge this:
99
109
  autoload :StaticFileReader, "bridgetown-core/readers/static_file_reader"
100
110
  autoload :LogAdapter, "bridgetown-core/log_adapter"
101
111
  autoload :Page, "bridgetown-core/page"
102
- autoload :PageWithoutAFile, "bridgetown-core/page_without_a_file"
112
+ autoload :GeneratedPage, "bridgetown-core/page"
113
+ # TODO: figure out how to get rid of this seemingly banal class:
103
114
  autoload :PathManager, "bridgetown-core/path_manager"
104
115
  autoload :PluginManager, "bridgetown-core/plugin_manager"
105
116
  autoload :Publishable, "bridgetown-core/concerns/publishable"
106
117
  autoload :Publisher, "bridgetown-core/publisher"
107
118
  autoload :Reader, "bridgetown-core/reader"
119
+ # TODO: remove this when the incremental regenerator is gone:
108
120
  autoload :Regenerator, "bridgetown-core/regenerator"
109
121
  autoload :RelatedPosts, "bridgetown-core/related_posts"
110
122
  autoload :Renderer, "bridgetown-core/renderer"
@@ -130,6 +142,7 @@ module Bridgetown
130
142
 
131
143
  require "bridgetown-core/drops/drop"
132
144
  require "bridgetown-core/drops/document_drop"
145
+ require "bridgetown-core/drops/resource_drop"
133
146
  require_all "bridgetown-core/converters"
134
147
  require_all "bridgetown-core/converters/markdown"
135
148
  require_all "bridgetown-core/drops"
@@ -193,11 +206,7 @@ module Bridgetown
193
206
  # @return [void]
194
207
  # rubocop:disable Naming/AccessorMethodName
195
208
  def set_timezone(timezone)
196
- ENV["TZ"] = if Utils::Platforms.really_windows?
197
- Utils::WinTZ.calculate(timezone)
198
- else
199
- timezone
200
- end
209
+ ENV["TZ"] = timezone
201
210
  end
202
211
  # rubocop:enable Naming/AccessorMethodName
203
212
 
@@ -218,11 +227,11 @@ module Bridgetown
218
227
  @logger = LogAdapter.new(writer, (ENV["BRIDGETOWN_LOG_LEVEL"] || :info).to_sym)
219
228
  end
220
229
 
221
- # An array of sites. Currently only ever a single entry.
230
+ # Deprecated. Now using the Current site.
222
231
  #
223
232
  # @return [Array<Bridgetown::Site>] the Bridgetown sites created.
224
233
  def sites
225
- @sites ||= []
234
+ [Bridgetown::Current.site].compact
226
235
  end
227
236
 
228
237
  # Ensures the questionable path is prefixed with the base directory
@@ -259,3 +268,13 @@ module Bridgetown
259
268
  Bridgetown::External.require_if_present("liquid/c")
260
269
  end
261
270
  end
271
+
272
+ module Bridgetown
273
+ module Model; end
274
+ module Resource; end
275
+ end
276
+
277
+ loader = Zeitwerk::Loader.new
278
+ loader.push_dir File.join(__dir__, "bridgetown-core/model"), namespace: Bridgetown::Model
279
+ loader.push_dir File.join(__dir__, "bridgetown-core/resource"), namespace: Bridgetown::Resource
280
+ loader.setup # ready!
@@ -58,7 +58,13 @@ module Bridgetown
58
58
  # Returns a Set with the file paths
59
59
  def new_files
60
60
  @new_files ||= Set.new.tap do |files|
61
- site.each_site_file { |item| files << item.destination(site.dest) }
61
+ site.each_site_file do |item|
62
+ files << if item.method(:destination).arity == 1
63
+ item.destination(site.dest)
64
+ else
65
+ item.destination.output_path
66
+ end
67
+ end
62
68
  end
63
69
  end
64
70
 
@@ -8,82 +8,104 @@ module Bridgetown
8
8
  attr_reader :label, :metadata
9
9
  attr_writer :docs
10
10
 
11
+ attr_writer :resources
12
+
11
13
  # Create a new Collection.
12
14
  #
13
- # site - the site to which this collection belongs.
14
- # label - the name of the collection
15
- #
16
- # Returns nothing.
15
+ # @param site [Bridgetown::Site] the site to which this collection belongs
16
+ # @param label [String] the name of the collection
17
17
  def initialize(site, label)
18
18
  @site = site
19
19
  @label = sanitize_label(label)
20
20
  @metadata = extract_metadata
21
21
  end
22
22
 
23
+ def builtin?
24
+ label.in? %w(posts pages data).freeze
25
+ end
26
+
27
+ def legacy_reader?
28
+ label.in? %w(posts data).freeze
29
+ end
30
+
31
+ def data?
32
+ label == "data"
33
+ end
34
+
23
35
  # Fetch the Documents in this collection.
24
36
  # Defaults to an empty array if no documents have been read in.
25
37
  #
26
- # Returns an array of Bridgetown::Document objects.
38
+ # @return [Array<Bridgetown::Document>]
27
39
  def docs
28
40
  @docs ||= []
29
41
  end
30
42
 
31
- # Override of normal respond_to? to match method_missing's logic for
32
- # looking in @data.
33
- def respond_to_missing?(method, include_private = false)
34
- docs.respond_to?(method.to_sym, include_private) || super
43
+ # Fetch the Resources in this collection.
44
+ # Defaults to an empty array if no resources have been read in.
45
+ #
46
+ # @return [Array<Bridgetown::Resource::Base>]
47
+ def resources
48
+ @resources ||= []
35
49
  end
36
50
 
37
- # Override of method_missing to check in @data for the key.
38
- def method_missing(method, *args, &blck)
39
- if docs.respond_to?(method.to_sym)
40
- Bridgetown.logger.warn "Deprecation:",
41
- "#{label}.#{method} should be changed to #{label}.docs.#{method}."
42
- Bridgetown.logger.warn "", "Called by #{caller(0..0)}."
43
- docs.public_send(method.to_sym, *args, &blck)
44
- else
45
- super
46
- end
51
+ # Iterate over either Resources or Documents depending on how the site is
52
+ # configured
53
+ def each(&block)
54
+ site.uses_resource? ? resources.each(&block) : docs.each(&block)
47
55
  end
48
56
 
49
57
  # Fetch the static files in this collection.
50
58
  # Defaults to an empty array if no static files have been read in.
51
59
  #
52
- # Returns an array of Bridgetown::StaticFile objects.
60
+ # @return [Array<Bridgetown::StaticFile>]
61
+ def static_files
62
+ @static_files ||= []
63
+ end
64
+
53
65
  def files
54
- @files ||= []
66
+ Bridgetown::Deprecator.deprecation_message "Collection#files is now Collection#static_files"
67
+ static_files
55
68
  end
56
69
 
57
70
  # Read the allowed documents into the collection's array of docs.
58
71
  #
59
- # Returns the sorted array of docs.
60
- def read
72
+ # @return [Bridgetown::Collection] self
73
+ def read # rubocop:todo Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
61
74
  filtered_entries.each do |file_path|
62
75
  full_path = collection_dir(file_path)
63
76
  next if File.directory?(full_path)
64
77
 
65
- if Utils.has_yaml_header? full_path
78
+ if site.uses_resource?
79
+ next if File.basename(file_path).starts_with?("_")
80
+
81
+ if label == "data" || Utils.has_yaml_header?(full_path)
82
+ read_resource(full_path)
83
+ else
84
+ read_static_file(file_path, full_path)
85
+ end
86
+ elsif Utils.has_yaml_header? full_path
66
87
  read_document(full_path)
67
88
  else
68
89
  read_static_file(file_path, full_path)
69
90
  end
70
91
  end
71
- site.static_files.concat(files)
92
+ site.static_files.concat(static_files)
72
93
  sort_docs!
94
+
95
+ self
73
96
  end
74
97
 
75
98
  # All the entries in this collection.
76
99
  #
77
- # Returns an Array of file paths to the documents in this collection
78
- # relative to the collection's directory
100
+ # @return [Array<String>] file paths to the documents in this collection
101
+ # relative to the collection's folder
79
102
  def entries
80
103
  return [] unless exists?
81
104
 
82
105
  @entries ||= begin
83
106
  collection_dir_slash = "#{collection_dir}/"
84
107
  Utils.safe_glob(collection_dir, ["**", "*"], File::FNM_DOTMATCH).map do |entry|
85
- entry[collection_dir_slash] = ""
86
- entry
108
+ entry.sub(collection_dir_slash, "")
87
109
  end
88
110
  end
89
111
  end
@@ -91,85 +113,87 @@ module Bridgetown
91
113
  # Filtered version of the entries in this collection.
92
114
  # See `Bridgetown::EntryFilter#filter` for more information.
93
115
  #
94
- # Returns a list of filtered entry paths.
116
+ # @return [Array<String>] list of filtered entry paths
95
117
  def filtered_entries
96
118
  return [] unless exists?
97
119
 
98
120
  @filtered_entries ||=
99
- Dir.chdir(directory) do
121
+ Dir.chdir(absolute_path) do
100
122
  entry_filter.filter(entries).reject do |f|
101
123
  path = collection_dir(f)
102
- File.directory?(path) || entry_filter.symlink?(f)
124
+ File.directory?(path)
103
125
  end
104
126
  end
105
127
  end
106
128
 
107
- # The directory for this Collection, relative to the site source or the directory
108
- # containing the collection.
129
+ # The folder name of this Collection, e.g. `_posts` or `_events`
109
130
  #
110
- # Returns a String containing the directory name where the collection
111
- # is stored on the filesystem.
112
- def relative_directory
113
- @relative_directory ||= "_#{label}"
131
+ # @return [String]
132
+ def folder_name
133
+ @folder_name ||= "_#{label}"
114
134
  end
135
+ alias_method :relative_directory, :folder_name
115
136
 
116
- # The relative path to the directory containing the collection.
137
+ # The relative path to the folder containing the collection.
117
138
  #
118
- # Returns a String containing the directory name where the collection
119
- # is stored relative to the source directory
139
+ # @return [String] folder where the collection is stored relative to the
140
+ # configured collections folder (usually the site source)
120
141
  def relative_path
121
- Pathname.new(container).join(relative_directory).to_s
142
+ Pathname.new(container).join(folder_name).to_s
122
143
  end
123
144
 
124
- # The full path to the directory containing the collection.
145
+ # The full path to the folder containing the collection.
125
146
  #
126
- # Returns a String containing the directory name where the collection
127
- # is stored on the filesystem.
128
- def directory
129
- @directory ||= site.in_source_dir(relative_path)
147
+ # @return [String] full path where the collection is stored on the filesystem
148
+ def absolute_path
149
+ @absolute_path ||= site.in_source_dir(relative_path)
130
150
  end
151
+ alias_method :directory, :absolute_path
131
152
 
132
- # The full path to the directory containing the collection, with
153
+ # The full path to the folder containing the collection, with
133
154
  # optional subpaths.
134
155
  #
135
- # *files - (optional) any other path pieces relative to the
136
- # directory to append to the path
137
- #
138
- # Returns a String containing th directory name where the collection
139
- # is stored on the filesystem.
156
+ # @param *files [Array<String>] any other path pieces relative to the
157
+ # folder to append to the path
158
+ # @return [String]
140
159
  def collection_dir(*files)
141
- return directory if files.empty?
160
+ return absolute_path if files.empty?
142
161
 
143
- site.in_source_dir(container, relative_directory, *files)
162
+ site.in_source_dir(relative_path, *files)
144
163
  end
145
164
 
146
- # Checks whether the directory "exists" for this collection.
165
+ # Checks whether the folder "exists" for this collection.
166
+ #
167
+ # @return [Boolean]
147
168
  def exists?
148
- File.directory?(directory)
169
+ File.directory?(absolute_path)
149
170
  end
150
171
 
151
172
  # The entry filter for this collection.
152
- # Creates an instance of Bridgetown::EntryFilter.
173
+ # Creates an instance of Bridgetown::EntryFilter if needed.
153
174
  #
154
- # Returns the instance of Bridgetown::EntryFilter for this collection.
175
+ # @return [Bridgetown::EntryFilter]
155
176
  def entry_filter
156
- @entry_filter ||= Bridgetown::EntryFilter.new(site, relative_directory)
177
+ @entry_filter ||= Bridgetown::EntryFilter.new(
178
+ site,
179
+ base_directory: folder_name,
180
+ include_underscores: site.uses_resource?
181
+ )
157
182
  end
158
183
 
159
184
  # An inspect string.
160
185
  #
161
- # Returns the inspect string
186
+ # @return [String]
162
187
  def inspect
163
- "#<#{self.class} @label=#{label} docs=#{docs}>"
188
+ "#<#{self.class} @label=#{label} docs=#{docs} resources=#{resources}>"
164
189
  end
165
190
 
166
191
  # Produce a sanitized label name
167
192
  # Label names may not contain anything but alphanumeric characters,
168
193
  # underscores, and hyphens.
169
194
  #
170
- # label - the possibly-unsafe label
171
- #
172
- # Returns a sanitized version of the label.
195
+ # @param label [String] the possibly-unsafe label
196
+ # @return [String] sanitized version of the label.
173
197
  def sanitize_label(label)
174
198
  label.gsub(%r![^a-z0-9_\-\.]!i, "")
175
199
  end
@@ -179,7 +203,8 @@ module Bridgetown
179
203
  # - label
180
204
  # - docs
181
205
  #
182
- # Returns a representation of this collection for use in Liquid.
206
+ # @return [Bridgetown::Drops::CollectionDrop] representation of this
207
+ # collection for use in Liquid
183
208
  def to_liquid
184
209
  Drops::CollectionDrop.new self
185
210
  end
@@ -187,14 +212,22 @@ module Bridgetown
187
212
  # Whether the collection's documents ought to be written as individual
188
213
  # files in the output.
189
214
  #
190
- # Returns true if the 'write' metadata is true, false otherwise.
215
+ # @return [Boolean] true if the 'write' metadata is true, false otherwise.
191
216
  def write?
192
217
  !!metadata.fetch("output", false)
193
218
  end
194
219
 
195
- # The URL template to render collection's documents at.
220
+ # Used by Resource's permalink processor
221
+ # @return [String]
222
+ def default_permalink
223
+ metadata.fetch("permalink") do
224
+ "/:collection/:path/"
225
+ end
226
+ end
227
+
228
+ # LEGACY: The URL template to render collection's documents at.
196
229
  #
197
- # Returns the URL template to render collection's documents at.
230
+ # @return [String]
198
231
  def url_template
199
232
  @url_template ||= metadata.fetch("permalink") do
200
233
  Utils.add_permalink_suffix("/:collection/:path", site.permalink_style)
@@ -203,13 +236,47 @@ module Bridgetown
203
236
 
204
237
  # Extract options for this collection from the site configuration.
205
238
  #
206
- # Returns the metadata for this collection
239
+ # @return [HashWithDotAccess::Hash]
207
240
  def extract_metadata
208
- if site.config["collections"].is_a?(Hash)
209
- site.config["collections"][label] || {}
210
- else
211
- {}
241
+ site.config.collections[label] || HashWithDotAccess::Hash.new
242
+ end
243
+
244
+ def merge_data_resources
245
+ data_contents = {}
246
+
247
+ sanitize_filename = ->(name) do
248
+ name.gsub(%r![^\w\s-]+|(?<=^|\b\s)\s+(?=$|\s?\b)!, "")
249
+ .gsub(%r!\s+!, "_")
212
250
  end
251
+
252
+ resources.each do |data_resource|
253
+ segments = data_resource.relative_path.each_filename.to_a[1..-1]
254
+ nested = []
255
+ segments.each_with_index do |segment, index|
256
+ sanitized_segment = sanitize_filename.(File.basename(segment, ".*"))
257
+ hsh = nested.empty? ? data_contents : data_contents.dig(*nested)
258
+ hsh[sanitized_segment] = if index == segments.length - 1
259
+ data_resource.data.array || data_resource.data
260
+ else
261
+ {}
262
+ end
263
+ nested << sanitized_segment
264
+ end
265
+ end
266
+
267
+ merge_environment_specific_metadata(data_contents).with_dot_access
268
+ end
269
+
270
+ def merge_environment_specific_metadata(data_contents)
271
+ if data_contents["site_metadata"]
272
+ data_contents["site_metadata"][Bridgetown.environment]&.each_key do |k|
273
+ data_contents["site_metadata"][k] =
274
+ data_contents["site_metadata"][Bridgetown.environment][k]
275
+ end
276
+ data_contents["site_metadata"].delete(Bridgetown.environment)
277
+ end
278
+
279
+ data_contents
213
280
  end
214
281
 
215
282
  private
@@ -219,17 +286,28 @@ module Bridgetown
219
286
  end
220
287
 
221
288
  def read_document(full_path)
222
- doc = Document.new(full_path, site: site, collection: self)
223
- doc.read
289
+ doc = Document.new(full_path, site: site, collection: self).tap(&:read)
224
290
  docs << doc if site.unpublished || doc.published?
225
291
  end
226
292
 
293
+ def read_resource(full_path)
294
+ id = "file://#{label}.collection/" + Addressable::URI.escape(
295
+ Pathname(full_path).relative_path_from(Pathname(site.source)).to_s
296
+ )
297
+ resource = Bridgetown::Model::Base.find(id).to_resource.read!
298
+ resources << resource if site.unpublished || resource.published?
299
+ end
300
+
227
301
  def sort_docs!
228
302
  if metadata["sort_by"].is_a?(String)
229
303
  sort_docs_by_key!
304
+ sort_resources_by_key!
230
305
  else
231
306
  docs.sort!
307
+ resources.sort!
232
308
  end
309
+ docs.reverse! if metadata.sort_direction == "descending"
310
+ resources.reverse! if metadata.sort_direction == "descending"
233
311
  end
234
312
 
235
313
  # A custom sort function based on Schwartzian transform
@@ -252,6 +330,24 @@ module Bridgetown
252
330
  end.map!(&:last)
253
331
  end
254
332
 
333
+ def sort_resources_by_key!
334
+ meta_key = metadata["sort_by"]
335
+ # Modify `docs` array to cache document's property along with the Document instance
336
+ resources.map! { |doc| [doc.data[meta_key], doc] }.sort! do |apples, olives|
337
+ order = determine_sort_order(meta_key, apples, olives)
338
+
339
+ # Fall back to `Document#<=>` if the properties were equal or were non-sortable
340
+ # Otherwise continue with current sort-order
341
+ if order.nil? || order.zero?
342
+ apples[-1] <=> olives[-1]
343
+ else
344
+ order
345
+ end
346
+
347
+ # Finally restore the `docs` array with just the Document objects themselves
348
+ end.map!(&:last)
349
+ end
350
+
255
351
  def determine_sort_order(sort_key, apples, olives)
256
352
  apple_property, apple_document = apples
257
353
  olive_property, olive_document = olives
@@ -277,7 +373,7 @@ module Bridgetown
277
373
  File.dirname(file_path)
278
374
  ).chomp("/.")
279
375
 
280
- files << StaticFile.new(
376
+ static_files << StaticFile.new(
281
377
  site,
282
378
  site.source,
283
379
  relative_dir,