bridgetown-core 0.15.0.beta1 → 0.16.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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