bridgetown-core 0.15.0.beta1 → 0.16.0.beta1

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +14 -0
  3. data/bridgetown-core.gemspec +2 -0
  4. data/lib/bridgetown-core.rb +6 -1
  5. data/lib/bridgetown-core/commands/concerns/actions.rb +54 -21
  6. data/lib/bridgetown-core/commands/console.rb +12 -2
  7. data/lib/bridgetown-core/commands/serve.rb +5 -0
  8. data/lib/bridgetown-core/concerns/data_accessible.rb +19 -0
  9. data/lib/bridgetown-core/concerns/layout_placeable.rb +17 -0
  10. data/lib/bridgetown-core/concerns/liquid_renderable.rb +20 -0
  11. data/lib/bridgetown-core/concerns/publishable.rb +10 -0
  12. data/lib/bridgetown-core/concerns/site/configurable.rb +62 -31
  13. data/lib/bridgetown-core/concerns/site/content.rb +88 -29
  14. data/lib/bridgetown-core/concerns/site/extensible.rb +15 -12
  15. data/lib/bridgetown-core/concerns/site/processable.rb +12 -10
  16. data/lib/bridgetown-core/concerns/site/renderable.rb +23 -4
  17. data/lib/bridgetown-core/concerns/site/writable.rb +16 -2
  18. data/lib/bridgetown-core/concerns/validatable.rb +59 -0
  19. data/lib/bridgetown-core/configuration.rb +1 -0
  20. data/lib/bridgetown-core/converter.rb +34 -0
  21. data/lib/bridgetown-core/converters/erb_templates.rb +61 -0
  22. data/lib/bridgetown-core/converters/markdown.rb +6 -23
  23. data/lib/bridgetown-core/converters/smartypants.rb +0 -10
  24. data/lib/bridgetown-core/document.rb +8 -52
  25. data/lib/bridgetown-core/drops/document_drop.rb +9 -1
  26. data/lib/bridgetown-core/drops/page_drop.rb +1 -1
  27. data/lib/bridgetown-core/errors.rb +2 -0
  28. data/lib/bridgetown-core/excerpt.rb +5 -7
  29. data/lib/bridgetown-core/filters.rb +2 -0
  30. data/lib/bridgetown-core/layout.rb +24 -1
  31. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -4
  32. data/lib/bridgetown-core/liquid_renderer/file_system.rb +1 -1
  33. data/lib/bridgetown-core/page.rb +36 -42
  34. data/lib/bridgetown-core/plugin_manager.rb +27 -13
  35. data/lib/bridgetown-core/regenerator.rb +1 -1
  36. data/lib/bridgetown-core/renderer.rb +41 -15
  37. data/lib/bridgetown-core/ruby_template_view.rb +84 -0
  38. data/lib/bridgetown-core/tags/class_map.rb +90 -0
  39. data/lib/bridgetown-core/tags/include.rb +2 -0
  40. data/lib/bridgetown-core/tags/render_content.rb +14 -2
  41. data/lib/bridgetown-core/tags/webpack_path.rb +48 -16
  42. data/lib/bridgetown-core/utils.rb +44 -0
  43. data/lib/bridgetown-core/version.rb +2 -2
  44. data/lib/site_template/bridgetown.config.yml +5 -3
  45. data/lib/site_template/package.json +1 -0
  46. data/lib/site_template/src/_components/{footer.html → footer.liquid} +0 -0
  47. data/lib/site_template/src/_components/{head.html → head.liquid} +0 -0
  48. data/lib/site_template/src/_components/{navbar.html → navbar.liquid} +0 -0
  49. data/lib/site_template/src/_layouts/default.html +1 -1
  50. data/lib/site_template/webpack.config.js +3 -3
  51. metadata +41 -6
  52. data/lib/bridgetown-core/concerns/convertible.rb +0 -238
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class PluginManager
5
5
  PLUGINS_GROUP = :bridgetown_plugins
6
+ YARN_DEPENDENCY_REGEXP = %r!(.+)@([^@]*)$!.freeze
6
7
 
7
8
  attr_reader :site
8
9
 
@@ -67,30 +68,43 @@ module Bridgetown
67
68
  # If that exact package hasn't been installed, execute yarn add
68
69
  #
69
70
  # Returns nothing.
70
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
71
71
  def self.install_yarn_dependencies(required_gems)
72
72
  return unless File.exist?("package.json")
73
73
 
74
74
  package_json = JSON.parse(File.read("package.json"))
75
75
 
76
76
  required_gems.each do |loaded_gem|
77
- next unless loaded_gem.to_spec&.metadata&.dig("yarn-add")
78
-
79
- yarn_add_dependency = loaded_gem.to_spec.metadata["yarn-add"].split("@")
80
- next unless yarn_add_dependency.length == 2
81
-
82
- # check matching version number is see if it's already installed
83
- if package_json["dependencies"]
84
- current_package = package_json["dependencies"].dig(yarn_add_dependency.first)
85
- next unless current_package.nil? || current_package != yarn_add_dependency.last
86
- end
77
+ yarn_dependency = find_yarn_dependency(loaded_gem)
78
+ next unless add_yarn_dependency?(yarn_dependency, package_json)
87
79
 
88
80
  # all right, time to install the package
89
- cmd = "yarn add #{yarn_add_dependency.join("@")}"
81
+ cmd = "yarn add #{yarn_dependency.join("@")}"
90
82
  system cmd
91
83
  end
92
84
  end
93
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
85
+
86
+ def self.find_yarn_dependency(loaded_gem)
87
+ yarn_dependency = loaded_gem.to_spec&.metadata&.dig("yarn-add")&.match(YARN_DEPENDENCY_REGEXP)
88
+ return nil if yarn_dependency&.length != 3 || yarn_dependency[2] == ""
89
+
90
+ yarn_dependency[1..2]
91
+ end
92
+
93
+ def self.add_yarn_dependency?(yarn_dependency, package_json)
94
+ return false if yarn_dependency.nil?
95
+
96
+ # check matching version number is see if it's already installed
97
+ if package_json["dependencies"]
98
+ current_version = package_json["dependencies"].dig(yarn_dependency.first)
99
+ package_requires_updating?(current_version, yarn_dependency.last)
100
+ else
101
+ true
102
+ end
103
+ end
104
+
105
+ def self.package_requires_updating?(current_version, dep_version)
106
+ current_version.nil? || current_version != dep_version && !current_version.include?("/")
107
+ end
94
108
 
95
109
  # Require all .rb files
96
110
  #
@@ -163,7 +163,7 @@ module Bridgetown
163
163
  end
164
164
 
165
165
  def regenerate_page?(document)
166
- document.asset_file? || document.data["regenerate"] ||
166
+ document.data["regenerate"] ||
167
167
  source_modified_or_dest_missing?(
168
168
  site.in_source_dir(document.relative_path), document.destination(@site.dest)
169
169
  )
@@ -53,18 +53,19 @@ module Bridgetown
53
53
 
54
54
  # Prepare payload and render the document
55
55
  #
56
- # Returns String rendered document output
56
+ # Returns nothing
57
57
  def run
58
58
  Bridgetown.logger.debug "Rendering:", document.relative_path
59
59
 
60
60
  assign_pages!
61
+ # TODO: this can be eliminated I think:
61
62
  assign_current_document!
62
63
  assign_highlighter_options!
63
64
  assign_layout_data!
64
65
 
65
66
  document.trigger_hooks(:pre_render, payload)
66
-
67
- render_document
67
+ document.output = render_document
68
+ document.trigger_hooks(:post_render)
68
69
  end
69
70
 
70
71
  # Render the document.
@@ -83,7 +84,7 @@ module Bridgetown
83
84
  end
84
85
 
85
86
  Bridgetown.logger.debug "Rendering Markup:", document.relative_path
86
- output = convert(output.to_s)
87
+ output = convert(output.to_s, document)
87
88
  document.content = output
88
89
 
89
90
  if document.place_in_layout?
@@ -140,9 +141,13 @@ module Bridgetown
140
141
  # Convert the document using the converters which match this renderer's document.
141
142
  #
142
143
  # Returns String the converted content.
143
- def convert(content)
144
+ def convert(content, document)
144
145
  converters.reduce(content) do |output, converter|
145
- converter.convert output
146
+ if converter.method(:convert).arity == 1
147
+ converter.convert output
148
+ else
149
+ converter.convert output, document
150
+ end
146
151
  rescue StandardError => e
147
152
  Bridgetown.logger.error "Conversion error:",
148
153
  "#{converter.class} encountered an error while "\
@@ -202,17 +207,38 @@ module Bridgetown
202
207
  # Render layout content into document.output
203
208
  #
204
209
  # Returns String rendered content
210
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
205
211
  def render_layout(output, layout, liquid_context)
206
- payload["content"] = output
207
- payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
208
-
209
- render_liquid(
210
- layout.content,
211
- payload,
212
- liquid_context,
213
- layout.path
214
- )
212
+ if layout.render_with_liquid?
213
+ payload["content"] = output
214
+ payload["layout"] = Utils.deep_merge_hashes(layout.data, payload["layout"] || {})
215
+
216
+ render_liquid(
217
+ layout.content,
218
+ payload,
219
+ liquid_context,
220
+ layout.path
221
+ )
222
+ else
223
+ layout_converters ||= site.converters.select { |c| c.matches(layout.ext) }.sort
224
+
225
+ layout_content = layout.content.dup
226
+ layout_converters.reduce(layout_content) do |layout_output, converter|
227
+ next(layout_output) unless converter.method(:convert).arity == 2
228
+
229
+ layout.current_document = document
230
+ layout.current_document_output = output
231
+ converter.convert layout_output, layout
232
+ rescue StandardError => e
233
+ Bridgetown.logger.error "Conversion error:",
234
+ "#{converter.class} encountered an error while "\
235
+ "converting '#{document.relative_path}':"
236
+ Bridgetown.logger.error("", e.to_s)
237
+ raise e
238
+ end
239
+ end
215
240
  end
241
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
216
242
 
217
243
  def add_regenerator_dependencies(layout)
218
244
  return unless document.write?
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Bridgetown
6
+ class RubyTemplateView
7
+ class Helpers
8
+ include Bridgetown::Filters
9
+ end
10
+
11
+ attr_reader :layout, :page, :site, :content
12
+
13
+ def initialize(convertible)
14
+ if convertible.is_a?(Layout)
15
+ @layout = convertible
16
+ @page = layout.current_document
17
+ @content = layout.current_document_output
18
+ else
19
+ @page = convertible
20
+ end
21
+ @site = page.site
22
+ end
23
+
24
+ def partial(_partial_name, _options = {})
25
+ raise "Must be implemented in a subclass"
26
+ end
27
+
28
+ def site_drop
29
+ site.site_payload.site
30
+ end
31
+
32
+ def liquid_render(component, options = {})
33
+ render_statement = _render_statement(component, options)
34
+
35
+ template = site.liquid_renderer.file(
36
+ "#{page.path}.#{Digest::SHA2.hexdigest(render_statement)}"
37
+ ).parse(render_statement)
38
+ template.warnings.each do |e|
39
+ Bridgetown.logger.warn "Liquid Warning:",
40
+ LiquidRenderer.format_error(e, path || document.relative_path)
41
+ end
42
+ template.render!(options.deep_stringify_keys, _liquid_context)
43
+ end
44
+
45
+ def helpers
46
+ @helpers ||= Helpers.new
47
+ end
48
+
49
+ def method_missing(method, *args, &block)
50
+ if helpers.respond_to?(method.to_sym)
51
+ helpers.send method.to_sym, *args, &block
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+ def respond_to_missing?(method, include_private = false)
58
+ helpers.respond_to?(method.to_sym, include_private) || super
59
+ end
60
+
61
+ private
62
+
63
+ def _render_statement(component, options)
64
+ render_statement = ["{% render \"#{component}\""]
65
+ unless options.empty?
66
+ render_statement << ", " + options.keys.map { |k| "#{k}: #{k}" }.join(", ")
67
+ end
68
+ render_statement << " %}"
69
+ render_statement.join
70
+ end
71
+
72
+ def _liquid_context
73
+ {
74
+ registers: {
75
+ site: site,
76
+ page: page,
77
+ cached_partials: Bridgetown::Renderer.cached_partials,
78
+ },
79
+ strict_filters: site.config["liquid"]["strict_filters"],
80
+ strict_variables: site.config["liquid"]["strict_variables"],
81
+ }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Bridgetown
6
+ module Tags
7
+ # A ClassMap class is meant to take a hash and append styles based on if the
8
+ # value is truthy or falsy
9
+ #
10
+ # @example
11
+ # center-var = true
12
+ # small-var = nil
13
+ #
14
+ # # input
15
+ # <div class="{% class_map has-centered-text: center-var, is-small: small-var %}">
16
+ # Text
17
+ # </div>
18
+ #
19
+ # # output
20
+ # <div class="has-centered-text">
21
+ # Text
22
+ # </div>
23
+ class ClassMap < Liquid::Tag
24
+ # @see https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
25
+ FALSE_VALUES = [
26
+ nil, "nil", "NIL", false, 0, "0", :"0", "f", :f, "F", :F, "false",
27
+ false, "FALSE", :FALSE,
28
+ ].to_set.freeze
29
+
30
+ # @param tag_name [String] The name to use for the tag
31
+ # @param input [String] The input to the tag
32
+ # @param tokens [Hash] A hash of config tokens for Liquid.
33
+ #
34
+ #
35
+ # @return [ClassMap] Returns a ClassMap object
36
+ def initialize(tag_name, input, tokens)
37
+ super
38
+ @input = input
39
+ end
40
+
41
+ def render(context)
42
+ class_map(@input, context)
43
+ end
44
+
45
+ private
46
+
47
+ def class_map(string, context)
48
+ ary = []
49
+
50
+ string.split(%r!,\s+!).each do |item|
51
+ kv_pair = item.split(%r!:\s+!)
52
+ klass = kv_pair[0]
53
+ variable = kv_pair[1]
54
+
55
+ # Check if a user wants the opposite of the variable
56
+ if variable[0] == "!"
57
+ check_opposite = true
58
+ variable.slice!(1..-1)
59
+ end
60
+
61
+ variable = find_variable(context, variable)
62
+
63
+ if check_opposite
64
+ ary.push(klass) if FALSE_VALUES.include?(variable)
65
+ else
66
+ ary.push(klass) unless FALSE_VALUES.include?(variable)
67
+ end
68
+ end
69
+
70
+ ary.join(" ")
71
+
72
+ # Gracefully handle if syntax is improper
73
+ rescue NoMethodError
74
+ "invalid-class-map"
75
+ end
76
+
77
+ def find_variable(context, variable)
78
+ lookup = context
79
+
80
+ variable.split(".").each do |value|
81
+ lookup = lookup[value.strip]
82
+ end
83
+
84
+ lookup || nil
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ Liquid::Template.register_tag("class_map", Bridgetown::Tags::ClassMap)
@@ -212,7 +212,9 @@ module Bridgetown
212
212
  else
213
213
  File.join(site.config["collections_dir"], page_payload["path"])
214
214
  end
215
+ # rubocop:disable Performance/DeleteSuffix
215
216
  resource_path.sub!(%r!/#excerpt\z!, "")
217
+ # rubocop:enable Performance/DeleteSuffix
216
218
  site.in_source_dir File.dirname(resource_path)
217
219
  end
218
220
  end
@@ -3,9 +3,12 @@
3
3
  module Bridgetown
4
4
  module Tags
5
5
  class BlockRenderTag < Liquid::Block
6
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
6
7
  def render(context)
7
8
  context.stack({}) do
8
- content = super.gsub(%r!^[ \t]+!, "") # unindent the incoming text
9
+ # unindent the incoming text
10
+ content = Bridgetown::Utils.reindent_for_markdown(super)
11
+
9
12
  regions = gather_content_regions(context)
10
13
 
11
14
  site = context.registers[:site]
@@ -17,7 +20,15 @@ module Bridgetown
17
20
  unless regions.empty?
18
21
  regions.each do |region_name, region_content|
19
22
  region_name = region_name.sub("content_with_region_", "")
20
- context[region_name] = converter.convert(region_content.gsub(%r!^[ \t]+!, ""))
23
+
24
+ if region_name.end_with? ":markdown"
25
+ region_name.sub!(%r!:markdown$!, "")
26
+ context[region_name] = converter.convert(
27
+ Bridgetown::Utils.reindent_for_markdown(region_content)
28
+ )
29
+ else
30
+ context[region_name] = region_content
31
+ end
21
32
  render_params.push "#{region_name}: #{region_name}"
22
33
  end
23
34
  end
@@ -26,6 +37,7 @@ module Bridgetown
26
37
  .render_tag(context, +"")
27
38
  end
28
39
  end
40
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
29
41
 
30
42
  private
31
43
 
@@ -2,38 +2,70 @@
2
2
 
3
3
  module Bridgetown
4
4
  module Tags
5
+ # A helper class to help find the path to webpack asset inside of a webpack
6
+ # manifest file.
5
7
  class WebpackPath < Liquid::Tag
6
8
  include Bridgetown::Filters::URLFilters
7
9
 
8
- def initialize(tag_name, asset_type, tokens)
10
+ # @param tag_name [String] Name of the tag
11
+ # @param asset_type [String] The type of asset to parse (js, css)
12
+ # @param options [Hash] An options hash
13
+ # @return WebpackPath
14
+ # @see {https://www.rdoc.info/github/Shopify/liquid/Liquid/Tag#initialize-instance_method}
15
+ def initialize(tag_name, asset_type, options)
9
16
  super
10
17
 
11
18
  # js or css
12
19
  @asset_type = asset_type.strip
13
20
  end
14
21
 
22
+ # Render the contents of a webpack manifest file
23
+ # @param context [String] Root directory that contains the manifest file
24
+ #
25
+ # @return [String] Returns "MISSING_WEBPACK_MANIFEST" if the manifest
26
+ # file isnt found
27
+ # @return [nil] Returns nil if the asset isnt found
28
+ # @return [String] Returns the path to the asset if no issues parsing
29
+ #
30
+ # @raise [WebpackAssetError] if unable to find css or js in the manifest
31
+ # file
15
32
  def render(context)
16
33
  @context = context
17
34
  site = context.registers[:site]
18
35
 
36
+ manifest_file = site.in_root_dir(".bridgetown-webpack", "manifest.json")
37
+
38
+ parse_manifest_file(manifest_file)
39
+ end
40
+
41
+ private
42
+
43
+ def parse_manifest_file(manifest_file)
44
+ return "MISSING_WEBPACK_MANIFEST" unless File.exist?(manifest_file)
45
+
46
+ manifest = JSON.parse(File.read(manifest_file))
19
47
  frontend_path = relative_url("_bridgetown/static")
20
48
 
21
- manifest_file = site.in_root_dir(".bridgetown-webpack", "manifest.json")
22
- if File.exist?(manifest_file)
23
- manifest = JSON.parse(File.read(manifest_file))
24
- if @asset_type == "js"
25
- js_path = manifest["main.js"].split("/").last
26
- [frontend_path, "js", js_path].join("/")
27
- elsif @asset_type == "css"
28
- css_path = manifest["main.css"].split("/").last
29
- [frontend_path, "css", css_path].join("/")
30
- else
31
- Bridgetown.logger.error("Unknown Webpack asset type", @asset_type)
32
- nil
33
- end
34
- else
35
- "MISSING_WEBPACK_MANIFEST"
49
+ known_assets = %w(js css)
50
+
51
+ if known_assets.include?(@asset_type)
52
+ asset_path = manifest["main.#{@asset_type}"]
53
+
54
+ log_webpack_asset_error(@asset_type) if asset_path.nil?
55
+
56
+ asset_path = asset_path.split("/").last
57
+ return [frontend_path, @asset_type, asset_path].join("/")
36
58
  end
59
+
60
+ Bridgetown.logger.error("Unknown Webpack asset type", @asset_type)
61
+ nil
62
+ end
63
+
64
+ def log_webpack_asset_error(asset_type)
65
+ error_message = "There was an error parsing your #{asset_type} files. \
66
+ Please check your #{asset_type} for any errors."
67
+
68
+ Bridgetown.logger.warn(Errors::WebpackAssetError, error_message)
37
69
  end
38
70
  end
39
71
  end