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
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class TaxonomyTerm
6
+ attr_reader :resource
7
+
8
+ attr_reader :label
9
+
10
+ attr_reader :type
11
+
12
+ def initialize(resource:, label:, type:)
13
+ @resource = resource
14
+ @label = label
15
+ @type = type
16
+ end
17
+
18
+ def to_liquid
19
+ {
20
+ label: label,
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class TaxonomyType
6
+ # @return [Bridgetown::Site]
7
+ attr_reader :site
8
+
9
+ # @return [String] aka `category`, `tag`, `region`, etc.
10
+ attr_reader :label
11
+
12
+ # @return [String] the key used in front matter
13
+ attr_reader :key
14
+
15
+ # @return [HashWithDotAccess::Hash] any associated metadata
16
+ attr_reader :metadata
17
+
18
+ # @param site [Bridgetown::Site]
19
+ # @param label [String]
20
+ # @param key [String]
21
+ def initialize(site:, label:, key:, metadata:)
22
+ @site = site
23
+ @label = label
24
+ @key = key
25
+ @metadata = metadata
26
+ end
27
+
28
+ def terms
29
+ site.resources.map do |resource|
30
+ resource.taxonomies[label].terms
31
+ end.flatten.group_by(&:label).with_dot_access
32
+ end
33
+
34
+ def inspect
35
+ "#<#{self.class} label=#{label}>"
36
+ end
37
+
38
+ def to_liquid
39
+ {
40
+ "label" => label,
41
+ "key" => key,
42
+ "metadata" => metadata,
43
+ }
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class Transformer
6
+ # @return [Array<Hash>]
7
+ attr_reader :conversions
8
+
9
+ # @return [Bridgetown::Resource::Base]
10
+ attr_reader :resource
11
+
12
+ # @return [Bridgetown::Site]
13
+ attr_reader :site
14
+
15
+ # @return [String]
16
+ attr_reader :output_ext
17
+
18
+ def initialize(resource)
19
+ @resource = resource
20
+ @site = resource.site
21
+ execute_inline_ruby
22
+ @output_ext = output_ext_from_converters
23
+ end
24
+
25
+ # @return [String]
26
+ def final_ext
27
+ permalink_ext || output_ext
28
+ end
29
+
30
+ def process!
31
+ Bridgetown.logger.debug "Transforming:", resource.relative_path
32
+ resource.around_hook :render do
33
+ run_conversions
34
+ resource.place_in_layout? ? place_into_layouts : resource.output = resource.content.dup
35
+ end
36
+ end
37
+
38
+ def inspect
39
+ "#<#{self.class} Conversion Steps: #{conversions.length}>"
40
+ end
41
+
42
+ private
43
+
44
+ ### Utilities
45
+
46
+ def permalink_ext
47
+ resource_permalink = resource.permalink
48
+ if resource_permalink &&
49
+ !resource_permalink.end_with?("/") &&
50
+ !resource_permalink.end_with?(".*")
51
+ permalink_ext = File.extname(resource_permalink)
52
+ permalink_ext unless permalink_ext.empty?
53
+ end
54
+ end
55
+
56
+ # @return [Array<Bridgetown::Converter>]
57
+ def converters
58
+ @converters ||= site.matched_converters_for_convertible(resource)
59
+ end
60
+
61
+ # @return [String]
62
+ def output_ext_from_converters
63
+ @conversions = converters.map do |converter|
64
+ {
65
+ converter: converter,
66
+ output_ext: converter.output_ext(resource.extname),
67
+ }
68
+ end
69
+
70
+ conversions
71
+ .reverse
72
+ .find do |conversion|
73
+ conversions.length == 1 ||
74
+ !conversion[:converter].is_a?(Bridgetown::Converters::Identity)
75
+ end
76
+ .fetch(:output_ext)
77
+ end
78
+
79
+ # @return [Array<Bridgetown::Layout>]
80
+ def validated_layouts
81
+ layout = site.layouts[resource.data.layout]
82
+ warn_on_missing_layout layout, resource.data.layout
83
+
84
+ layout_list = Set.new([layout])
85
+ while layout
86
+ layout_name = layout.data.layout
87
+ layout = site.layouts[layout_name]
88
+ warn_on_missing_layout layout, layout_name
89
+
90
+ layout_list << layout
91
+ end
92
+
93
+ layout_list.to_a.compact
94
+ end
95
+
96
+ def warn_on_missing_layout(layout, layout_name)
97
+ if layout.nil? && layout_name
98
+ Bridgetown.logger.warn "Build Warning:", "Layout '#{layout_name}' " \
99
+ "requested via #{resource.relative_path} does not exist."
100
+ end
101
+ end
102
+
103
+ ### Transformation Actions
104
+
105
+ def execute_inline_ruby
106
+ return unless site.config.should_execute_inline_ruby?
107
+
108
+ Bridgetown::Utils::RubyExec.search_data_for_ruby_code(resource, self)
109
+ end
110
+
111
+ def run_conversions # rubocop:disable Metrics/AbcSize
112
+ input = resource.content.to_s
113
+
114
+ # @param content [String]
115
+ # @param converter [Bridgetown::Converter]
116
+ resource.content = converters.each_with_index.inject(input) do |content, (converter, index)|
117
+ output = if converter.method(:convert).arity == 1
118
+ converter.convert content
119
+ else
120
+ converter.convert content, resource
121
+ end
122
+ conversions[index] = {
123
+ type: :content,
124
+ converter: converter,
125
+ output: Bridgetown.env.production? ? nil : output,
126
+ output_ext: conversions[index][:output_ext],
127
+ }
128
+ output
129
+ rescue StandardError => e
130
+ Bridgetown.logger.error "Conversion error:",
131
+ "#{converter.class} encountered an error while "\
132
+ "converting `#{resource.relative_path}'"
133
+ raise e
134
+ end
135
+ end
136
+
137
+ def place_into_layouts
138
+ Bridgetown.logger.debug "Placing in Layouts:", resource.relative_path
139
+ output = resource.content.dup
140
+ validated_layouts.each do |layout|
141
+ output = run_layout_conversions layout, output
142
+ end
143
+ resource.output = output
144
+ end
145
+
146
+ def run_layout_conversions(layout, output)
147
+ layout_converters = site.matched_converters_for_convertible(layout)
148
+ layout_input = layout.content.dup
149
+
150
+ layout_converters.inject(layout_input) do |content, converter|
151
+ next(content) unless [2, -2].include?(converter.method(:convert).arity)
152
+
153
+ layout.current_document = resource
154
+ layout.current_document_output = output
155
+ layout_output = converter.convert content, layout
156
+
157
+ conversions << {
158
+ type: :layout,
159
+ layout: layout,
160
+ converter: converter,
161
+ output: Bridgetown.env.production? ? nil : layout_output,
162
+ }
163
+ layout_output
164
+ rescue StandardError => e
165
+ Bridgetown.logger.error "Conversion error:",
166
+ "#{converter.class} encountered an error while "\
167
+ "converting `#{resource.relative_path}'"
168
+ raise e
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -41,6 +41,10 @@ module Bridgetown
41
41
  end
42
42
  end
43
43
 
44
+ def collections
45
+ site.collections
46
+ end
47
+
44
48
  def site_drop
45
49
  site.site_payload.site
46
50
  end
@@ -26,6 +26,10 @@ module Bridgetown
26
26
  # @return [Array<Page>]
27
27
  attr_accessor :pages
28
28
 
29
+ # NOTE: Eventually pages will be deprecated once the Resource content engine
30
+ # is default
31
+ alias_method :generated_pages, :pages
32
+
29
33
  attr_accessor :exclude, :include, :lsi, :highlighter, :permalink_style,
30
34
  :time, :future, :unpublished, :limit_posts,
31
35
  :keep_files, :baseurl, :data, :file_read_opts,
@@ -46,7 +50,7 @@ module Bridgetown
46
50
 
47
51
  ensure_not_in_dest
48
52
 
49
- Bridgetown.sites << self
53
+ Bridgetown::Current.site = self
50
54
  Bridgetown::Hooks.trigger :site, :after_init, self
51
55
 
52
56
  reset # Processable
@@ -64,5 +68,9 @@ module Bridgetown
64
68
  end
65
69
  end
66
70
  end
71
+
72
+ def inspect
73
+ "#<Bridgetown::Site #{metadata.inspect.delete_prefix("{").delete_suffix("}")}>"
74
+ end
67
75
  end
68
76
  end
@@ -4,7 +4,7 @@ module Bridgetown
4
4
  class StaticFile
5
5
  extend Forwardable
6
6
 
7
- attr_reader :relative_path, :extname, :name, :data
7
+ attr_reader :relative_path, :extname, :name, :data, :site, :collection
8
8
 
9
9
  def_delegator :to_liquid, :to_json, :to_json
10
10
 
@@ -25,8 +25,7 @@ module Bridgetown
25
25
  # base - The String path to the <source>.
26
26
  # dir - The String path between <source> and the file.
27
27
  # name - The String filename of the file.
28
- # rubocop: disable Metrics/ParameterLists
29
- def initialize(site, base, dir, name, collection = nil)
28
+ def initialize(site, base, dir, name, collection = nil) # rubocop:disable Metrics/ParameterLists
30
29
  @site = site
31
30
  @base = base
32
31
  @dir = dir
@@ -34,9 +33,15 @@ module Bridgetown
34
33
  @collection = collection
35
34
  @relative_path = File.join(*[@dir, @name].compact)
36
35
  @extname = File.extname(@name)
37
- @data = @site.frontmatter_defaults.all(relative_path, type)
36
+ @data = @site.frontmatter_defaults.all(relative_path, type).with_dot_access
37
+ if site.uses_resource? && !data.permalink
38
+ data.permalink = if collection && !collection.builtin?
39
+ "/:collection/:path.*"
40
+ else
41
+ "/:path.*"
42
+ end
43
+ end
38
44
  end
39
- # rubocop: enable Metrics/ParameterLists
40
45
 
41
46
  # Returns source file path.
42
47
  def path
@@ -51,8 +56,11 @@ module Bridgetown
51
56
  #
52
57
  # Returns destination file path.
53
58
  def destination(dest)
54
- dest = @site.in_dest_dir(dest)
55
- @site.in_dest_dir(dest, Bridgetown::URL.unescape_path(url))
59
+ dest = site.in_dest_dir(dest)
60
+ dest_url = url
61
+ dest_url = dest_url.delete_prefix(site.baseurl) if site.uses_resource? &&
62
+ site.baseurl.present? && collection
63
+ site.in_dest_dir(dest, Bridgetown::URL.unescape_path(dest_url))
56
64
  end
57
65
 
58
66
  def destination_rel_dir
@@ -118,6 +126,15 @@ module Bridgetown
118
126
  @basename ||= File.basename(name, extname).gsub(%r!\.*\z!, "")
119
127
  end
120
128
 
129
+ def relative_path_basename_without_prefix
130
+ return_path = Pathname.new("")
131
+ Pathname.new(cleaned_relative_path).each_filename do |filename|
132
+ return_path += filename unless filename.starts_with?("_")
133
+ end
134
+
135
+ (return_path.dirname + return_path.basename(".*")).to_s
136
+ end
137
+
121
138
  def placeholders
122
139
  {
123
140
  collection: @collection.label,
@@ -154,15 +171,21 @@ module Bridgetown
154
171
  # be overriden in the collection's configuration in bridgetown.config.yml.
155
172
  def url
156
173
  @url ||= begin
157
- base = if @collection.nil? || @collection.label == "posts"
174
+ newly_processed = false
175
+ special_posts_case = @collection&.label == "posts" &&
176
+ site.config.content_engine != "resource"
177
+ base = if @collection.nil? || special_posts_case
158
178
  cleaned_relative_path
179
+ elsif site.uses_resource?
180
+ newly_processed = true
181
+ Bridgetown::Resource::PermalinkProcessor.new(self).transform
159
182
  else
160
183
  Bridgetown::URL.new(
161
184
  template: @collection.url_template,
162
185
  placeholders: placeholders
163
186
  )
164
187
  end.to_s.chomp("/")
165
- base << extname
188
+ newly_processed ? base : "#{base}#{extname}"
166
189
  end
167
190
  end
168
191
 
@@ -174,7 +197,7 @@ module Bridgetown
174
197
  # Returns the front matter defaults defined for the file's URL and/or type
175
198
  # as defined in bridgetown.config.yml.
176
199
  def defaults
177
- @defaults ||= @site.frontmatter_defaults.all url, type
200
+ @defaults ||= site.frontmatter_defaults.all url, type
178
201
  end
179
202
 
180
203
  # Returns a debug string on inspecting the static file.
@@ -129,6 +129,7 @@ module Bridgetown
129
129
  #
130
130
  # Returns the escaped path.
131
131
  def self.escape_path(path)
132
+ path = path.to_s
132
133
  return path if path.empty? || %r!^[a-zA-Z0-9./-]+$!.match?(path)
133
134
 
134
135
  # Because URI.escape doesn't escape "?", "[" and "]" by default,
@@ -4,12 +4,9 @@ module Bridgetown
4
4
  module Utils
5
5
  extend self
6
6
  autoload :Ansi, "bridgetown-core/utils/ansi"
7
- autoload :Exec, "bridgetown-core/utils/exec"
8
- autoload :Internet, "bridgetown-core/utils/internet"
9
7
  autoload :RubyExec, "bridgetown-core/utils/ruby_exec"
10
8
  autoload :Platforms, "bridgetown-core/utils/platforms"
11
9
  autoload :ThreadEvent, "bridgetown-core/utils/thread_event"
12
- autoload :WinTZ, "bridgetown-core/utils/win_tz"
13
10
 
14
11
  # Constants for use in #slugify
15
12
  SLUGIFY_MODES = %w(raw default pretty simple ascii latin).freeze
@@ -74,38 +71,32 @@ module Bridgetown
74
71
  end
75
72
  end
76
73
 
77
- # Read array from the supplied hash favouring the singular key
78
- # and then the plural key, and handling any nil entries.
74
+ # Read array from the supplied hash, merging the singular key with the
75
+ # plural key as needing, and handling any nil or duplicate entries.
79
76
  #
80
- # hash - the hash to read from
81
- # singular_key - the singular key
82
- # plural_key - the plural key
83
- #
84
- # Returns an array
85
- def pluralized_array_from_hash(hash, singular_key, plural_key)
86
- array = []
87
- value = value_from_singular_key(hash, singular_key)
88
- value ||= value_from_plural_key(hash, plural_key)
77
+ # @param hsh [Hash] the hash to read from
78
+ # @param singular_key [Symbol] the singular key
79
+ # @param plural_key [Symbol] the plural key
80
+ # @return [Array]
81
+ def pluralized_array_from_hash(hsh, singular_key, plural_key)
82
+ array = [
83
+ hsh[singular_key],
84
+ value_from_plural_key(hsh, plural_key),
85
+ ]
89
86
 
90
- array << value
91
87
  array.flatten!
92
88
  array.compact!
89
+ array.uniq!
93
90
  array
94
91
  end
95
92
 
96
- def value_from_singular_key(hash, key)
97
- hash[key] if hash.key?(key) || (hash.default_proc && hash[key])
98
- end
99
-
100
- def value_from_plural_key(hash, key)
101
- if hash.key?(key) || (hash.default_proc && hash[key])
102
- val = hash[key]
103
- case val
104
- when String
105
- val.split
106
- when Array
107
- val.compact
108
- end
93
+ def value_from_plural_key(hsh, key)
94
+ val = hsh[key]
95
+ case val
96
+ when String
97
+ val.split
98
+ when Array
99
+ val.compact
109
100
  end
110
101
  end
111
102
 
@@ -124,7 +115,7 @@ module Bridgetown
124
115
 
125
116
  # Determines whether a given file has
126
117
  #
127
- # Returns true if the YAML front matter is present.
118
+ # @return [Boolean] if the YAML front matter is present.
128
119
  # rubocop: disable Naming/PredicateName
129
120
  def has_yaml_header?(file)
130
121
  File.open(file, "rb", &:readline).match? %r!\A---\s*\r?\n!
@@ -134,7 +125,7 @@ module Bridgetown
134
125
 
135
126
  # Determine whether the given content string contains Liquid Tags or Vaiables
136
127
  #
137
- # Returns true is the string contains sequences of `{%` or `{{`
128
+ # @return [Boolean] if the string contains sequences of `{%` or `{{`
138
129
  def has_liquid_construct?(content)
139
130
  return false if content.nil? || content.empty?
140
131
 
@@ -207,7 +198,7 @@ module Bridgetown
207
198
  slug.gsub!(%r!^\-|\-$!i, "")
208
199
 
209
200
  slug.downcase! unless cased
210
- Bridgetown.logger.warn("Warning:", "Empty `slug` generated for '#{string}'.") if slug.empty?
201
+
211
202
  slug
212
203
  end
213
204
 
@@ -347,7 +338,7 @@ module Bridgetown
347
338
 
348
339
  # Return an asset path based on the Webpack manifest file
349
340
  # @param site [Bridgetown::Site] The current site object
350
- # @param asset_type [String] js or css
341
+ # @param asset_type [String] js or css, or filename in manifest
351
342
  #
352
343
  # @return [String] Returns "MISSING_WEBPACK_MANIFEST" if the manifest
353
344
  # file isnt found
@@ -363,21 +354,30 @@ module Bridgetown
363
354
  manifest = JSON.parse(File.read(manifest_file))
364
355
 
365
356
  known_assets = %w(js css)
357
+ asset_path = nil
366
358
  if known_assets.include?(asset_type)
367
359
  asset_path = manifest["main.#{asset_type}"]
368
-
369
360
  log_webpack_asset_error(asset_type) && return if asset_path.nil?
370
-
371
- asset_path = asset_path.split("/").last
372
- return [static_frontend_path(site), asset_type, asset_path].join("/")
361
+ else
362
+ asset_path = manifest.find do |item, _|
363
+ item.sub(%r{^../(frontend/|src/)?}, "") == asset_type
364
+ end&.last
373
365
  end
374
366
 
375
- Bridgetown.logger.error("Unknown Webpack asset type", asset_type)
376
- nil
367
+ if asset_path
368
+ static_frontend_path(site, ["js", asset_path])
369
+ else
370
+ Bridgetown.logger.error("Unknown Webpack asset type", asset_type)
371
+ nil
372
+ end
377
373
  end
378
374
 
379
- def static_frontend_path(site)
380
- path_parts = [site.config["baseurl"].to_s.chomp("/"), "_bridgetown/static"]
375
+ def static_frontend_path(site, additional_parts = [])
376
+ path_parts = [
377
+ site.config["baseurl"].to_s.gsub(%r(^/|/$), ""),
378
+ "_bridgetown/static",
379
+ *additional_parts,
380
+ ]
381
381
  path_parts[0] = "/#{path_parts[0]}" unless path_parts[0].empty?
382
382
  Addressable::URI.parse(path_parts.join("/")).normalize.to_s
383
383
  end