bridgetown-core 0.17.1 → 0.18.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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/lib/bridgetown-core.rb +43 -28
- data/lib/bridgetown-core/collection.rb +5 -1
- data/lib/bridgetown-core/commands/apply.rb +2 -2
- data/lib/bridgetown-core/commands/new.rb +1 -1
- data/lib/bridgetown-core/concerns/layout_placeable.rb +1 -1
- data/lib/bridgetown-core/concerns/liquid_renderable.rb +10 -0
- data/lib/bridgetown-core/concerns/site/configurable.rb +21 -23
- data/lib/bridgetown-core/concerns/site/content.rb +44 -31
- data/lib/bridgetown-core/concerns/site/extensible.rb +14 -13
- data/lib/bridgetown-core/concerns/site/localizable.rb +6 -2
- data/lib/bridgetown-core/concerns/site/processable.rb +10 -9
- data/lib/bridgetown-core/concerns/site/renderable.rb +34 -26
- data/lib/bridgetown-core/concerns/site/writable.rb +7 -15
- data/lib/bridgetown-core/configuration.rb +5 -3
- data/lib/bridgetown-core/converter.rb +0 -42
- data/lib/bridgetown-core/converters/erb_templates.rb +75 -16
- data/lib/bridgetown-core/converters/liquid_templates.rb +96 -0
- data/lib/bridgetown-core/converters/markdown.rb +0 -3
- data/lib/bridgetown-core/document.rb +31 -18
- data/lib/bridgetown-core/drops/site_drop.rb +4 -0
- data/lib/bridgetown-core/drops/unified_payload_drop.rb +0 -1
- data/lib/bridgetown-core/drops/url_drop.rb +19 -3
- data/lib/bridgetown-core/excerpt.rb +1 -1
- data/lib/bridgetown-core/filters.rb +28 -7
- data/lib/bridgetown-core/generators/prototype_generator.rb +42 -25
- data/lib/bridgetown-core/helpers.rb +84 -0
- data/lib/bridgetown-core/liquid_renderer.rb +1 -1
- data/lib/bridgetown-core/log_writer.rb +2 -2
- data/lib/bridgetown-core/plugin_manager.rb +34 -1
- data/lib/bridgetown-core/reader.rb +1 -4
- data/lib/bridgetown-core/readers/post_reader.rb +28 -15
- data/lib/bridgetown-core/renderer.rb +42 -160
- data/lib/bridgetown-core/ruby_template_view.rb +22 -33
- data/lib/bridgetown-core/site.rb +12 -2
- data/lib/bridgetown-core/utils.rb +14 -0
- data/lib/bridgetown-core/version.rb +2 -2
- data/lib/bridgetown-core/watcher.rb +1 -0
- data/lib/site_template/src/images/.keep +1 -0
- metadata +7 -3
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bridgetown
|
4
|
+
module Converters
|
5
|
+
class LiquidTemplates < Converter
|
6
|
+
priority :highest
|
7
|
+
input :liquid
|
8
|
+
|
9
|
+
attr_reader :site, :document, :layout
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :cached_partials
|
13
|
+
end
|
14
|
+
|
15
|
+
# rubocop: disable Metrics/AbcSize
|
16
|
+
# rubocop: disable Metrics/MethodLength
|
17
|
+
|
18
|
+
# Logic to do the Liquid content conversion.
|
19
|
+
#
|
20
|
+
# @param content [String] Content of the file (without front matter).
|
21
|
+
# @params convertible [Bridgetown::Page, Bridgetown::Document, Bridgetown::Layout]
|
22
|
+
# The instantiated object which is processing the file.
|
23
|
+
#
|
24
|
+
# @return [String] The converted content.
|
25
|
+
def convert(content, convertible)
|
26
|
+
self.class.cached_partials ||= {}
|
27
|
+
|
28
|
+
@site = convertible.site
|
29
|
+
if convertible.is_a?(Bridgetown::Layout)
|
30
|
+
@document = convertible.current_document
|
31
|
+
@layout = convertible
|
32
|
+
configure_payload(layout.current_document_output)
|
33
|
+
else
|
34
|
+
@document = convertible
|
35
|
+
@layout = site.layouts[document.data["layout"]]
|
36
|
+
configure_payload
|
37
|
+
end
|
38
|
+
|
39
|
+
template = site.liquid_renderer.file(convertible.path).parse(content)
|
40
|
+
template.warnings.each do |e|
|
41
|
+
Bridgetown.logger.warn "Liquid Warning:",
|
42
|
+
LiquidRenderer.format_error(e, convertible.path)
|
43
|
+
end
|
44
|
+
template.render!(payload, liquid_context)
|
45
|
+
# rubocop: disable Lint/RescueException
|
46
|
+
rescue Exception => e
|
47
|
+
Bridgetown.logger.error "Liquid Exception:",
|
48
|
+
LiquidRenderer.format_error(e, convertible.path)
|
49
|
+
raise e
|
50
|
+
end
|
51
|
+
# rubocop: enable Lint/RescueException
|
52
|
+
# rubocop: enable Metrics/MethodLength
|
53
|
+
# rubocop: enable Metrics/AbcSize
|
54
|
+
|
55
|
+
def matches(ext, convertible)
|
56
|
+
return true if convertible.render_with_liquid?
|
57
|
+
|
58
|
+
super(ext)
|
59
|
+
end
|
60
|
+
|
61
|
+
def output_ext(ext)
|
62
|
+
ext == ".liquid" ? ".html" : ext
|
63
|
+
end
|
64
|
+
|
65
|
+
# Fetches the payload used in Liquid rendering.
|
66
|
+
# Falls back to site.site_payload if no payload is set.
|
67
|
+
#
|
68
|
+
# Returns a Bridgetown::Drops::UnifiedPayloadDrop
|
69
|
+
def payload
|
70
|
+
@payload ||= site.site_payload
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set page content to payload and assign paginator if document has one.
|
74
|
+
#
|
75
|
+
# Returns nothing
|
76
|
+
def configure_payload(content = nil)
|
77
|
+
payload["page"] = document.to_liquid
|
78
|
+
payload["paginator"] = document.respond_to?(:paginator) ? document.paginator.to_liquid : nil
|
79
|
+
payload["layout"] = @layout ? @layout.data : {}
|
80
|
+
payload["content"] = content
|
81
|
+
end
|
82
|
+
|
83
|
+
def liquid_context
|
84
|
+
{
|
85
|
+
registers: {
|
86
|
+
site: site,
|
87
|
+
page: payload["page"],
|
88
|
+
cached_partials: self.class.cached_partials,
|
89
|
+
},
|
90
|
+
strict_filters: site.config["liquid"]["strict_filters"],
|
91
|
+
strict_variables: site.config["liquid"]["strict_variables"],
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -330,24 +330,6 @@ module Bridgetown
|
|
330
330
|
data.key?(method.to_s) || super
|
331
331
|
end
|
332
332
|
|
333
|
-
def populate_categories
|
334
|
-
categories = Array(data["categories"]) + Utils.pluralized_array_from_hash(
|
335
|
-
data, "category", "categories"
|
336
|
-
)
|
337
|
-
categories.map!(&:to_s)
|
338
|
-
categories.flatten!
|
339
|
-
categories.uniq!
|
340
|
-
|
341
|
-
merge_data!({ "categories" => categories })
|
342
|
-
end
|
343
|
-
|
344
|
-
def populate_tags
|
345
|
-
tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
|
346
|
-
tags.flatten!
|
347
|
-
|
348
|
-
merge_data!({ "tags" => tags })
|
349
|
-
end
|
350
|
-
|
351
333
|
private
|
352
334
|
|
353
335
|
def merge_categories!(other)
|
@@ -384,6 +366,7 @@ module Bridgetown
|
|
384
366
|
populate_title
|
385
367
|
populate_categories
|
386
368
|
populate_tags
|
369
|
+
determine_locale
|
387
370
|
generate_excerpt
|
388
371
|
end
|
389
372
|
|
@@ -418,6 +401,36 @@ module Bridgetown
|
|
418
401
|
data["ext"] ||= ext
|
419
402
|
end
|
420
403
|
|
404
|
+
def populate_categories
|
405
|
+
categories = Array(data["categories"]) + Utils.pluralized_array_from_hash(
|
406
|
+
data, "category", "categories"
|
407
|
+
)
|
408
|
+
categories.map!(&:to_s)
|
409
|
+
categories.flatten!
|
410
|
+
categories.uniq!
|
411
|
+
|
412
|
+
merge_data!({ "categories" => categories })
|
413
|
+
end
|
414
|
+
|
415
|
+
def populate_tags
|
416
|
+
tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
|
417
|
+
tags.flatten!
|
418
|
+
|
419
|
+
merge_data!({ "tags" => tags })
|
420
|
+
end
|
421
|
+
|
422
|
+
def determine_locale
|
423
|
+
unless data["locale"]
|
424
|
+
# if locale key isn't directly set, look for alternative front matter
|
425
|
+
# or look at the filename pattern: slug.locale.ext
|
426
|
+
alernative = data["language"] || data["lang"] ||
|
427
|
+
basename_without_ext.split(".")[1..-1].last
|
428
|
+
|
429
|
+
data["locale"] = alernative if !alernative.nil? &&
|
430
|
+
site.config[:available_locales].include?(alernative)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
421
434
|
def modify_date(date)
|
422
435
|
if !data["date"] || data["date"].to_i == site.time.to_i
|
423
436
|
merge_data!({ "date" => date }, source: "filename")
|
@@ -19,14 +19,19 @@ module Bridgetown
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def title
|
22
|
-
Utils.slugify(
|
23
|
-
Utils.slugify(@obj.basename_without_ext, mode: "pretty", cased: true)
|
22
|
+
Utils.slugify(qualified_slug_data, mode: "pretty", cased: true)
|
24
23
|
end
|
25
24
|
|
26
25
|
def slug
|
27
|
-
Utils.slugify(
|
26
|
+
Utils.slugify(qualified_slug_data)
|
28
27
|
end
|
29
28
|
|
29
|
+
def locale
|
30
|
+
locale_data = @obj.data["locale"]
|
31
|
+
@obj.site.config["available_locales"].include?(locale_data) ? locale_data : nil
|
32
|
+
end
|
33
|
+
alias_method :lang, :locale
|
34
|
+
|
30
35
|
def categories
|
31
36
|
category_set = Set.new
|
32
37
|
Array(@obj.data["categories"]).each do |category|
|
@@ -128,6 +133,17 @@ module Bridgetown
|
|
128
133
|
|
129
134
|
private
|
130
135
|
|
136
|
+
def qualified_slug_data
|
137
|
+
slug_data = @obj.data["slug"] || @obj.basename_without_ext
|
138
|
+
if @obj.data["locale"]
|
139
|
+
slug_data.split(".").tap do |segments|
|
140
|
+
segments.pop if segments.length > 1 && segments.last == @obj.data["locale"]
|
141
|
+
end.join(".")
|
142
|
+
else
|
143
|
+
slug_data
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
131
147
|
def fallback_data
|
132
148
|
@fallback_data ||= {}
|
133
149
|
end
|
@@ -79,16 +79,15 @@ module Bridgetown
|
|
79
79
|
# XML escape a string for use. Replaces any special characters with
|
80
80
|
# appropriate HTML entity replacements.
|
81
81
|
#
|
82
|
-
# input - The String to escape.
|
83
|
-
#
|
84
82
|
# Examples
|
85
83
|
#
|
86
84
|
# xml_escape('foo "bar" <baz>')
|
87
85
|
# # => "foo "bar" <baz>"
|
88
86
|
#
|
89
|
-
#
|
87
|
+
# @param input [String] The String to escape.
|
88
|
+
# @return [String] the escaped String.
|
90
89
|
def xml_escape(input)
|
91
|
-
|
90
|
+
Utils.xml_escape(input)
|
92
91
|
end
|
93
92
|
|
94
93
|
# CGI escape a string for use in a URL. Replaces any special characters
|
@@ -120,6 +119,21 @@ module Bridgetown
|
|
120
119
|
Addressable::URI.normalize_component(input)
|
121
120
|
end
|
122
121
|
|
122
|
+
# Obfuscate an email, telephone number etc.
|
123
|
+
#
|
124
|
+
# @param input[String] the String containing the contact information (email, phone etc.)
|
125
|
+
# @param prefix[String] the URL scheme to prefix (default "mailto")
|
126
|
+
# @return [String] a link unreadable for bots but will be recovered on focus or mouseover
|
127
|
+
def obfuscate_link(input, prefix = "mailto")
|
128
|
+
link = "<a href=\"#{prefix}:#{input}\">#{input}</a>"
|
129
|
+
script = "<script type=\"text/javascript\">document.currentScript.insertAdjacentHTML("
|
130
|
+
script += "beforebegin', '#{rot47(link)}'.replace(/[!-~]/g,"
|
131
|
+
script += "function(c){{var j=c.charCodeAt(0);if((j>=33)&&(j<=126)){"
|
132
|
+
script += "return String.fromCharCode(33+((j+ 14)%94));}"
|
133
|
+
script += "else{return String.fromCharCode(j);}}}));</script>"
|
134
|
+
script
|
135
|
+
end
|
136
|
+
|
123
137
|
# Replace any whitespace in the input string with a single space
|
124
138
|
#
|
125
139
|
# input - The String on which to operate.
|
@@ -316,15 +330,22 @@ module Bridgetown
|
|
316
330
|
|
317
331
|
# Convert an object into its String representation for debugging
|
318
332
|
#
|
319
|
-
# input
|
333
|
+
# @param input [Object] The Object to be converted
|
320
334
|
#
|
321
|
-
#
|
322
|
-
def inspect(input)
|
335
|
+
# @return [String] the representation of the object.
|
336
|
+
def inspect(input = nil)
|
337
|
+
return super() if input.nil?
|
338
|
+
|
323
339
|
xml_escape(input.inspect)
|
324
340
|
end
|
325
341
|
|
326
342
|
private
|
327
343
|
|
344
|
+
# Perform a rot47 rotation for obfuscation
|
345
|
+
def rot47(input)
|
346
|
+
input.tr "!-~", "P-~!-O"
|
347
|
+
end
|
348
|
+
|
328
349
|
# Sort the input Enumerable by the given property.
|
329
350
|
# If the property doesn't exist, return the sort order respective of
|
330
351
|
# which item doesn't have the property.
|
@@ -10,6 +10,9 @@ module Bridgetown
|
|
10
10
|
class PrototypeGenerator < Generator
|
11
11
|
priority :low
|
12
12
|
|
13
|
+
# @return [Bridgetown::Site]
|
14
|
+
attr_reader :site
|
15
|
+
|
13
16
|
@matching_templates = []
|
14
17
|
|
15
18
|
def self.add_matching_template(template)
|
@@ -17,6 +20,7 @@ module Bridgetown
|
|
17
20
|
end
|
18
21
|
|
19
22
|
class << self
|
23
|
+
# @return [Array<Page>]
|
20
24
|
attr_reader :matching_templates
|
21
25
|
end
|
22
26
|
|
@@ -44,7 +48,13 @@ module Bridgetown
|
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
51
|
+
# Check incoming prototype configuration and normalize options.
|
52
|
+
#
|
53
|
+
# @param prototype_page [PrototypePage]
|
54
|
+
#
|
55
|
+
# @return [String, nil]
|
47
56
|
def validate_search_term(prototype_page)
|
57
|
+
# @type [String]
|
48
58
|
search_term = prototype_page.data["prototype"]["term"]
|
49
59
|
return nil unless search_term.is_a?(String)
|
50
60
|
|
@@ -52,6 +62,8 @@ module Bridgetown
|
|
52
62
|
@configured_collection = prototype_page.data["prototype"]["collection"]
|
53
63
|
end
|
54
64
|
|
65
|
+
return nil unless site.collections[@configured_collection]
|
66
|
+
|
55
67
|
# Categories and Tags are unique in that singular and plural front matter
|
56
68
|
# can be present for each
|
57
69
|
search_term.sub(%r!^category$!, "categories").sub(%r!^tag$!, "tags")
|
@@ -59,71 +71,75 @@ module Bridgetown
|
|
59
71
|
|
60
72
|
def generate_new_page_from_prototype(prototype_page, search_term, term)
|
61
73
|
new_page = PrototypePage.new(prototype_page, @configured_collection, search_term, term)
|
62
|
-
|
74
|
+
site.pages << new_page
|
63
75
|
new_page
|
64
76
|
end
|
65
77
|
|
66
|
-
#
|
67
|
-
#
|
78
|
+
# Provide a list of all relevent indexed values for the given term.
|
79
|
+
#
|
80
|
+
# @param search_term [String]
|
81
|
+
#
|
82
|
+
# @return [Array<String>]
|
68
83
|
def terms_matching_pages(search_term)
|
69
|
-
selected_docs = @site.documents.select do |document|
|
70
|
-
document.respond_to?(:collection) && document.collection.label == @configured_collection
|
71
|
-
end
|
72
|
-
|
73
84
|
Bridgetown::Paginate::PaginationIndexer.index_documents_by(
|
74
|
-
|
85
|
+
site.collections[@configured_collection].docs, search_term
|
75
86
|
).keys
|
76
87
|
end
|
77
88
|
end
|
78
89
|
|
79
90
|
class PrototypePage < Page
|
80
|
-
|
81
|
-
|
91
|
+
# @return [Page]
|
92
|
+
attr_reader :prototyped_page
|
93
|
+
|
94
|
+
def initialize(prototyped_page, collection, search_term, term)
|
95
|
+
@prototyped_page = prototyped_page
|
96
|
+
@site = prototyped_page.site
|
82
97
|
@url = ""
|
83
98
|
@name = "index.html"
|
84
|
-
@path =
|
99
|
+
@path = prototyped_page.path
|
85
100
|
|
86
101
|
process(@name)
|
87
102
|
|
88
|
-
self.data = Bridgetown::Utils.deep_merge_hashes
|
89
|
-
self.content =
|
103
|
+
self.data = Bridgetown::Utils.deep_merge_hashes prototyped_page.data, {}
|
104
|
+
self.content = prototyped_page.content
|
90
105
|
|
91
106
|
# Perform some validation that is also performed in Bridgetown::Page
|
92
|
-
validate_data!
|
93
|
-
validate_permalink!
|
107
|
+
validate_data! prototyped_page.path
|
108
|
+
validate_permalink! prototyped_page.path
|
94
109
|
|
95
|
-
@dir = Pathname.new(
|
110
|
+
@dir = Pathname.new(prototyped_page.relative_path).dirname.to_s
|
96
111
|
@path = site.in_source_dir(@dir, @name)
|
97
112
|
|
98
|
-
process_prototype_page_data(
|
113
|
+
process_prototype_page_data(collection, search_term, term)
|
99
114
|
|
100
115
|
Bridgetown::Hooks.trigger :pages, :post_init, self
|
101
116
|
end
|
102
117
|
|
103
|
-
def process_prototype_page_data(
|
118
|
+
def process_prototype_page_data(collection, search_term, term)
|
104
119
|
# Fill in pagination details to be handled later by Bridgetown::Paginate
|
105
120
|
data["pagination"] = Bridgetown::Utils.deep_merge_hashes(
|
106
|
-
|
121
|
+
prototyped_page.data["pagination"].to_h, {
|
107
122
|
"enabled" => true,
|
108
123
|
"collection" => collection,
|
109
124
|
"where_query" => [search_term, term],
|
110
125
|
}
|
111
126
|
)
|
112
127
|
# Use the original prototype page term so we get "tag" back, not "tags":
|
113
|
-
data[
|
128
|
+
data[prototyped_page.data["prototype"]["term"]] = term
|
114
129
|
# Process title and slugs/URLs:
|
115
|
-
process_title_data_placeholder(
|
130
|
+
process_title_data_placeholder(search_term, term)
|
116
131
|
process_title_simple_placeholders(term)
|
117
132
|
slugify_term(term)
|
118
133
|
end
|
119
134
|
|
120
|
-
|
121
|
-
|
135
|
+
# rubocop:disable Metrics/AbcSize
|
136
|
+
def process_title_data_placeholder(search_term, term)
|
137
|
+
if prototyped_page.data["prototype"]["data"]
|
122
138
|
if data["title"]&.include?(":prototype-data-label")
|
123
|
-
related_data =
|
139
|
+
related_data = site.data[prototyped_page.data["prototype"]["data"]].dig(term)
|
124
140
|
if related_data
|
125
141
|
data["#{search_term}_data"] = related_data
|
126
|
-
data_label = related_data[
|
142
|
+
data_label = related_data[prototyped_page.data["prototype"]["data_label"]]
|
127
143
|
data["title"] = data["title"].gsub(
|
128
144
|
":prototype-data-label", data_label
|
129
145
|
)
|
@@ -131,6 +147,7 @@ module Bridgetown
|
|
131
147
|
end
|
132
148
|
end
|
133
149
|
end
|
150
|
+
# rubocop:enable Metrics/AbcSize
|
134
151
|
|
135
152
|
def process_title_simple_placeholders(term)
|
136
153
|
if data["title"]&.include?(":prototype-term-titleize")
|