bridgetown-core 0.19.0 → 0.21.0.beta1

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