bridgetown-core 0.17.1 → 0.18.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 -13
- 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 +80 -16
- data/lib/bridgetown-core/converters/liquid_templates.rb +104 -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/layout_reader.rb +1 -1
- data/lib/bridgetown-core/readers/post_reader.rb +28 -15
- data/lib/bridgetown-core/renderer.rb +42 -162
- data/lib/bridgetown-core/ruby_template_view.rb +23 -34
- data/lib/bridgetown-core/site.rb +12 -2
- data/lib/bridgetown-core/tags/render_content.rb +2 -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,104 @@
|
|
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
|
+
return content if convertible.data[:template_engine] != "liquid"
|
27
|
+
|
28
|
+
self.class.cached_partials ||= {}
|
29
|
+
@payload = nil
|
30
|
+
|
31
|
+
@site = convertible.site
|
32
|
+
if convertible.is_a?(Bridgetown::Layout)
|
33
|
+
@document = convertible.current_document
|
34
|
+
@layout = convertible
|
35
|
+
configure_payload(layout.current_document_output)
|
36
|
+
else
|
37
|
+
@document = convertible
|
38
|
+
@layout = site.layouts[document.data["layout"]]
|
39
|
+
configure_payload
|
40
|
+
end
|
41
|
+
|
42
|
+
template = site.liquid_renderer.file(convertible.path).parse(content)
|
43
|
+
template.warnings.each do |e|
|
44
|
+
Bridgetown.logger.warn "Liquid Warning:",
|
45
|
+
LiquidRenderer.format_error(e, convertible.path)
|
46
|
+
end
|
47
|
+
template.render!(payload, liquid_context)
|
48
|
+
# rubocop: disable Lint/RescueException
|
49
|
+
rescue Exception => e
|
50
|
+
Bridgetown.logger.error "Liquid Exception:",
|
51
|
+
LiquidRenderer.format_error(e, convertible.path)
|
52
|
+
raise e
|
53
|
+
end
|
54
|
+
# rubocop: enable Lint/RescueException
|
55
|
+
# rubocop: enable Metrics/MethodLength
|
56
|
+
# rubocop: enable Metrics/AbcSize
|
57
|
+
|
58
|
+
def matches(ext, convertible)
|
59
|
+
if convertible.render_with_liquid?
|
60
|
+
convertible.data[:template_engine] = "liquid"
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
|
64
|
+
super(ext).tap do |ext_matches|
|
65
|
+
convertible.data[:template_engine] = "liquid" if ext_matches
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def output_ext(ext)
|
70
|
+
ext == ".liquid" ? ".html" : ext
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fetches the payload used in Liquid rendering.
|
74
|
+
# Falls back to site.site_payload if no payload is set.
|
75
|
+
#
|
76
|
+
# Returns a Bridgetown::Drops::UnifiedPayloadDrop
|
77
|
+
def payload
|
78
|
+
@payload ||= site.site_payload
|
79
|
+
end
|
80
|
+
|
81
|
+
# Set page content to payload and assign paginator if document has one.
|
82
|
+
#
|
83
|
+
# Returns nothing
|
84
|
+
def configure_payload(content = nil)
|
85
|
+
payload["page"] = document.to_liquid
|
86
|
+
payload["paginator"] = document.respond_to?(:paginator) ? document.paginator.to_liquid : nil
|
87
|
+
payload["layout"] = @layout ? @layout.data : {}
|
88
|
+
payload["content"] = content
|
89
|
+
end
|
90
|
+
|
91
|
+
def liquid_context
|
92
|
+
{
|
93
|
+
registers: {
|
94
|
+
site: site,
|
95
|
+
page: payload["page"],
|
96
|
+
cached_partials: self.class.cached_partials,
|
97
|
+
},
|
98
|
+
strict_filters: site.config["liquid"]["strict_filters"],
|
99
|
+
strict_variables: site.config["liquid"]["strict_variables"],
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
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")
|