bridgetown-core 0.15.0 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +14 -0
  3. data/bridgetown-core.gemspec +4 -1
  4. data/lib/bridgetown-core.rb +8 -2
  5. data/lib/bridgetown-core/commands/concerns/actions.rb +2 -1
  6. data/lib/bridgetown-core/commands/console.rb +4 -4
  7. data/lib/bridgetown-core/concerns/data_accessible.rb +19 -0
  8. data/lib/bridgetown-core/concerns/layout_placeable.rb +17 -0
  9. data/lib/bridgetown-core/concerns/liquid_renderable.rb +20 -0
  10. data/lib/bridgetown-core/concerns/publishable.rb +10 -0
  11. data/lib/bridgetown-core/concerns/site/configurable.rb +66 -31
  12. data/lib/bridgetown-core/concerns/site/content.rb +90 -31
  13. data/lib/bridgetown-core/concerns/site/extensible.rb +15 -12
  14. data/lib/bridgetown-core/concerns/site/localizable.rb +20 -0
  15. data/lib/bridgetown-core/concerns/site/processable.rb +14 -12
  16. data/lib/bridgetown-core/concerns/site/renderable.rb +21 -2
  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 +10 -3
  20. data/lib/bridgetown-core/converter.rb +34 -0
  21. data/lib/bridgetown-core/converters/erb_templates.rb +78 -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 +11 -55
  25. data/lib/bridgetown-core/drops/site_drop.rb +1 -1
  26. data/lib/bridgetown-core/errors.rb +2 -0
  27. data/lib/bridgetown-core/excerpt.rb +1 -6
  28. data/lib/bridgetown-core/filters.rb +11 -48
  29. data/lib/bridgetown-core/filters/condition_helpers.rb +56 -0
  30. data/lib/bridgetown-core/frontmatter_defaults.rb +17 -0
  31. data/lib/bridgetown-core/layout.rb +24 -1
  32. data/lib/bridgetown-core/liquid_renderer/file_system.rb +1 -1
  33. data/lib/bridgetown-core/page.rb +41 -26
  34. data/lib/bridgetown-core/plugin_manager.rb +10 -2
  35. data/lib/bridgetown-core/reader.rb +1 -0
  36. data/lib/bridgetown-core/readers/collection_reader.rb +1 -0
  37. data/lib/bridgetown-core/readers/data_reader.rb +4 -3
  38. data/lib/bridgetown-core/readers/defaults_reader.rb +27 -0
  39. data/lib/bridgetown-core/readers/layout_reader.rb +1 -0
  40. data/lib/bridgetown-core/readers/page_reader.rb +1 -0
  41. data/lib/bridgetown-core/readers/post_reader.rb +1 -0
  42. data/lib/bridgetown-core/readers/static_file_reader.rb +1 -0
  43. data/lib/bridgetown-core/regenerator.rb +1 -1
  44. data/lib/bridgetown-core/renderer.rb +40 -14
  45. data/lib/bridgetown-core/ruby_template_view.rb +113 -0
  46. data/lib/bridgetown-core/site.rb +2 -0
  47. data/lib/bridgetown-core/tags/class_map.rb +90 -0
  48. data/lib/bridgetown-core/tags/find.rb +86 -0
  49. data/lib/bridgetown-core/tags/t.rb +14 -0
  50. data/lib/bridgetown-core/tags/webpack_path.rb +19 -22
  51. data/lib/bridgetown-core/utils.rb +55 -2
  52. data/lib/bridgetown-core/utils/ruby_exec.rb +1 -1
  53. data/lib/bridgetown-core/version.rb +2 -2
  54. data/lib/site_template/src/_layouts/{default.html → default.liquid} +0 -0
  55. data/lib/site_template/src/_layouts/{home.html → home.liquid} +0 -0
  56. data/lib/site_template/src/_layouts/{page.html → page.liquid} +0 -0
  57. data/lib/site_template/src/_layouts/{post.html → post.liquid} +0 -0
  58. metadata +64 -10
  59. data/lib/bridgetown-core/concerns/convertible.rb +0 -235
@@ -32,7 +32,7 @@ module Bridgetown
32
32
 
33
33
  # Last path in the list wins
34
34
  LiquidComponent.parse(
35
- ::File.read(found_paths.last, site.file_read_opts)
35
+ ::File.read(found_paths.last, **site.file_read_opts)
36
36
  ).content
37
37
  end
38
38
  end
@@ -2,24 +2,19 @@
2
2
 
3
3
  module Bridgetown
4
4
  class Page
5
- include Convertible
5
+ include DataAccessible
6
+ include LayoutPlaceable
7
+ include LiquidRenderable
8
+ include Publishable
9
+ include Validatable
6
10
 
7
11
  attr_writer :dir
8
- attr_accessor :site, :pager
12
+ attr_accessor :site, :paginator, :pager
9
13
  attr_accessor :name, :ext, :basename
10
14
  attr_accessor :data, :content, :output
11
15
 
12
16
  alias_method :extname, :ext
13
17
 
14
- # Attributes for Liquid templates
15
- ATTRIBUTES_FOR_LIQUID = %w(
16
- content
17
- dir
18
- name
19
- path
20
- url
21
- ).freeze
22
-
23
18
  # A set of extensions that are considered HTML or HTML-like so we
24
19
  # should not alter them, this includes .xhtml through XHTM5.
25
20
 
@@ -126,12 +121,18 @@ module Bridgetown
126
121
  # desired placeholder replacements. For details see "url.rb"
127
122
  def url_placeholders
128
123
  {
129
- path: @dir,
124
+ path: qualified_pages_path_for_url,
130
125
  basename: basename,
131
126
  output_ext: output_ext,
132
127
  }
133
128
  end
134
129
 
130
+ # Strips _pages prefix off if needed for the url/destination generation
131
+ # @return [String]
132
+ def qualified_pages_path_for_url
133
+ @dir.sub(%r!^/_pages!, "")
134
+ end
135
+
135
136
  # Extract information from the page filename.
136
137
  #
137
138
  # name - The String filename of the page file.
@@ -143,19 +144,6 @@ module Bridgetown
143
144
  self.basename = name[0..-ext.length - 1].gsub(%r!\.*\z!, "")
144
145
  end
145
146
 
146
- # Add any necessary layouts to this post
147
- #
148
- # layouts - The Hash of {"name" => "layout"}.
149
- # site_payload - The site payload Hash.
150
- #
151
- # Returns String rendered page.
152
- def render(layouts, site_payload)
153
- site_payload["page"] = to_liquid
154
- site_payload["paginator"] = pager.to_liquid
155
-
156
- do_layout(site_payload, layouts)
157
- end
158
-
159
147
  # The path to the source file
160
148
  #
161
149
  # Returns the path to the source file
@@ -168,6 +156,16 @@ module Bridgetown
168
156
  @relative_path ||= File.join(*[@dir, @name].map(&:to_s).reject(&:empty?)).delete_prefix("/")
169
157
  end
170
158
 
159
+ # FIXME: spinning up a new Renderer object just to get an extension
160
+ # seems excessive
161
+ #
162
+ # The output extension of the page.
163
+ #
164
+ # Returns the output extension
165
+ def output_ext
166
+ @output_ext ||= Bridgetown::Renderer.new(site, self).output_ext
167
+ end
168
+
171
169
  # Obtain destination path.
172
170
  #
173
171
  # dest - The String path to the destination dir.
@@ -180,9 +178,22 @@ module Bridgetown
180
178
  path
181
179
  end
182
180
 
181
+ # Write the generated page file to the destination directory.
182
+ #
183
+ # dest - The String path to the destination dir.
184
+ #
185
+ # Returns nothing.
186
+ def write(dest)
187
+ path = destination(dest)
188
+ FileUtils.mkdir_p(File.dirname(path))
189
+ Bridgetown.logger.debug "Writing:", path
190
+ File.write(path, output, mode: "wb")
191
+ Bridgetown::Hooks.trigger :pages, :post_write, self
192
+ end
193
+
183
194
  # Returns the object as a debug String.
184
195
  def inspect
185
- "#<#{self.class} @relative_path=#{relative_path.inspect}>"
196
+ "#<#{self.class} #{relative_path}>"
186
197
  end
187
198
 
188
199
  # Returns the Boolean of whether this Page is HTML or not.
@@ -199,6 +210,10 @@ module Bridgetown
199
210
  Bridgetown::Hooks.trigger :pages, hook_name, self, *args
200
211
  end
201
212
 
213
+ def type
214
+ :pages
215
+ end
216
+
202
217
  def write?
203
218
  true
204
219
  end
@@ -68,12 +68,20 @@ module Bridgetown
68
68
  # If that exact package hasn't been installed, execute yarn add
69
69
  #
70
70
  # Returns nothing.
71
- def self.install_yarn_dependencies(required_gems)
71
+ def self.install_yarn_dependencies(required_gems, single_gemname = nil)
72
72
  return unless File.exist?("package.json")
73
73
 
74
74
  package_json = JSON.parse(File.read("package.json"))
75
75
 
76
- required_gems.each do |loaded_gem|
76
+ gems_to_search = if single_gemname
77
+ required_gems.select do |loaded_gem|
78
+ loaded_gem.to_spec&.name == single_gemname.to_s
79
+ end
80
+ else
81
+ required_gems
82
+ end
83
+
84
+ gems_to_search.each do |loaded_gem|
77
85
  yarn_dependency = find_yarn_dependency(loaded_gem)
78
86
  next unless add_yarn_dependency?(yarn_dependency, package_json)
79
87
 
@@ -13,6 +13,7 @@ module Bridgetown
13
13
  # Returns nothing.
14
14
  # rubocop:disable Metrics/AbcSize
15
15
  def read
16
+ @site.defaults_reader.read
16
17
  @site.layouts = LayoutReader.new(site).read
17
18
  read_directories
18
19
  read_included_excludes
@@ -5,6 +5,7 @@ module Bridgetown
5
5
  SPECIAL_COLLECTIONS = %w(posts data).freeze
6
6
 
7
7
  attr_reader :site, :content
8
+
8
9
  def initialize(site)
9
10
  @site = site
10
11
  @content = {}
@@ -3,9 +3,10 @@
3
3
  module Bridgetown
4
4
  class DataReader
5
5
  attr_reader :site, :content
6
+
6
7
  def initialize(site)
7
8
  @site = site
8
- @content = ActiveSupport::HashWithIndifferentAccess.new
9
+ @content = {}
9
10
  @entry_filter = EntryFilter.new(site)
10
11
  end
11
12
 
@@ -19,7 +20,7 @@ module Bridgetown
19
20
  base = site.in_source_dir(dir)
20
21
  read_data_to(base, @content)
21
22
  merge_environment_specific_metadata!
22
- @content
23
+ @content = @content.with_dot_access
23
24
  end
24
25
 
25
26
  # Read and parse all .yaml, .yml, .json, .csv and .tsv
@@ -43,7 +44,7 @@ module Bridgetown
43
44
  if File.directory?(path)
44
45
  read_data_to(
45
46
  path,
46
- data[sanitize_filename(entry)] = ActiveSupport::HashWithIndifferentAccess.new
47
+ data[sanitize_filename(entry)] = {}
47
48
  )
48
49
  else
49
50
  key = sanitize_filename(File.basename(entry, ".*"))
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class DefaultsReader
5
+ attr_reader :site, :path_defaults
6
+
7
+ def initialize(site)
8
+ @site = site
9
+ @path_defaults = HashWithDotAccess::Hash.new
10
+ end
11
+
12
+ def read
13
+ return unless File.directory?(site.source)
14
+
15
+ entries = Dir.chdir(site.source) do
16
+ Dir["**/_defaults.{yaml,yml,json}"]
17
+ end
18
+
19
+ entries.each do |entry|
20
+ path = @site.in_source_dir(entry)
21
+ @path_defaults[File.dirname(path) + File::SEPARATOR] = SafeYAML.load_file(path)
22
+ end
23
+
24
+ @path_defaults
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class LayoutReader
5
5
  attr_reader :site
6
+
6
7
  def initialize(site)
7
8
  @site = site
8
9
  @layouts = {}
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class PageReader
5
5
  attr_reader :site, :dir, :unfiltered_content
6
+
6
7
  def initialize(site, dir)
7
8
  @site = site
8
9
  @dir = dir
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class PostReader
5
5
  attr_reader :site, :unfiltered_content
6
+
6
7
  def initialize(site)
7
8
  @site = site
8
9
  end
@@ -3,6 +3,7 @@
3
3
  module Bridgetown
4
4
  class StaticFileReader
5
5
  attr_reader :site, :dir, :unfiltered_content
6
+
6
7
  def initialize(site, dir)
7
8
  @site = site
8
9
  @dir = dir
@@ -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
  )
@@ -58,6 +58,7 @@ module Bridgetown
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!
@@ -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?
@@ -223,12 +249,12 @@ module Bridgetown
223
249
  )
224
250
  end
225
251
 
226
- # Set page content to payload and assign pager if document has one.
252
+ # Set page content to payload and assign paginator if document has one.
227
253
  #
228
254
  # Returns nothing
229
255
  def assign_pages!
230
256
  payload["page"] = document.to_liquid
231
- payload["paginator"] = (document.pager.to_liquid if document.respond_to?(:pager))
257
+ payload["paginator"] = document.paginator.to_liquid if document.respond_to?(:paginator)
232
258
  end
233
259
 
234
260
  # Set related posts to payload if document is a post.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "active_support/core_ext/hash/keys"
5
+
6
+ module Bridgetown
7
+ class RubyTemplateView
8
+ class Helpers
9
+ include Bridgetown::Filters
10
+
11
+ attr_reader :view, :site
12
+
13
+ Context = Struct.new(:registers)
14
+
15
+ def initialize(view, site)
16
+ @view = view
17
+ @site = site
18
+
19
+ # duck typing for Liquid context
20
+ @context = Context.new({ site: site })
21
+ end
22
+
23
+ def webpack_path(asset_type)
24
+ Bridgetown::Utils.parse_webpack_manifest_file(site, asset_type.to_s)
25
+ end
26
+
27
+ # @param pairs [Hash] A hash of key/value pairs.
28
+ #
29
+ # @return [String] Space-separated keys where the values are truthy.
30
+ def class_map(pairs = {})
31
+ pairs.select { |_key, truthy| truthy }.keys.join(" ")
32
+ end
33
+
34
+ def t(*args)
35
+ I18n.send :t, *args
36
+ end
37
+ end
38
+
39
+ attr_reader :layout, :page, :paginator, :site, :content
40
+
41
+ def initialize(convertible)
42
+ if convertible.is_a?(Layout)
43
+ @layout = convertible
44
+ @page = layout.current_document
45
+ @content = layout.current_document_output
46
+ else
47
+ @page = convertible
48
+ end
49
+ @paginator = page.paginator if page.respond_to?(:paginator)
50
+ @site = page.site
51
+ end
52
+
53
+ def partial(_partial_name, _options = {})
54
+ raise "Must be implemented in a subclass"
55
+ end
56
+
57
+ def site_drop
58
+ site.site_payload.site
59
+ end
60
+
61
+ def liquid_render(component, options = {})
62
+ render_statement = _render_statement(component, options)
63
+
64
+ template = site.liquid_renderer.file(
65
+ "#{page.path}.#{Digest::SHA2.hexdigest(render_statement)}"
66
+ ).parse(render_statement)
67
+ template.warnings.each do |e|
68
+ Bridgetown.logger.warn "Liquid Warning:",
69
+ LiquidRenderer.format_error(e, path || document.relative_path)
70
+ end
71
+ template.render!(options.deep_stringify_keys, _liquid_context)
72
+ end
73
+
74
+ def helpers
75
+ @helpers ||= Helpers.new(self, site)
76
+ end
77
+
78
+ def method_missing(method, *args, &block)
79
+ if helpers.respond_to?(method.to_sym)
80
+ helpers.send method.to_sym, *args, &block
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ def respond_to_missing?(method, include_private = false)
87
+ helpers.respond_to?(method.to_sym, include_private) || super
88
+ end
89
+
90
+ private
91
+
92
+ def _render_statement(component, options)
93
+ render_statement = ["{% render \"#{component}\""]
94
+ unless options.empty?
95
+ render_statement << ", " + options.keys.map { |k| "#{k}: #{k}" }.join(", ")
96
+ end
97
+ render_statement << " %}"
98
+ render_statement.join
99
+ end
100
+
101
+ def _liquid_context
102
+ {
103
+ registers: {
104
+ site: site,
105
+ page: page,
106
+ cached_partials: Bridgetown::Renderer.cached_partials,
107
+ },
108
+ strict_filters: site.config["liquid"]["strict_filters"],
109
+ strict_variables: site.config["liquid"]["strict_variables"],
110
+ }
111
+ end
112
+ end
113
+ end