jekyll_plugin_support 0.7.2 → 0.8.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/.rubocop.yml +60 -50
- data/CHANGELOG.md +14 -0
- data/README.md +306 -132
- data/jekyll_plugin_support.gemspec +3 -2
- data/lib/jekyll_plugin_error_handling.rb +48 -0
- data/lib/jekyll_plugin_helper.rb +20 -134
- data/lib/jekyll_plugin_helper_attribution.rb +72 -0
- data/lib/jekyll_plugin_helper_class.rb +33 -0
- data/lib/jekyll_plugin_support/version.rb +1 -1
- data/lib/jekyll_plugin_support.rb +5 -202
- data/lib/jekyll_plugin_support_block.rb +77 -0
- data/lib/jekyll_plugin_support_block_noarg.rb +30 -0
- data/lib/jekyll_plugin_support_class.rb +86 -0
- data/lib/jekyll_plugin_support_tag.rb +75 -0
- data/lib/jekyll_plugin_support_tag_noarg.rb +26 -0
- data/spec/liquid_variable_parsing_spec.rb +34 -0
- data/spec/spec_helper.rb +1 -2
- data/spec/status_persistence.txt +7 -0
- metadata +28 -6
- data/lib/call_chain.rb +0 -54
- data/lib/gem_support.rb +0 -19
data/lib/jekyll_plugin_helper.rb
CHANGED
@@ -1,45 +1,14 @@
|
|
1
1
|
require 'facets/string/interpolate'
|
2
2
|
require 'key_value_parser'
|
3
3
|
require 'shellwords'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'jekyll_plugin_helper_class'
|
5
|
+
require_relative 'jekyll_plugin_helper_attribution'
|
5
6
|
|
6
|
-
|
7
|
-
class JekyllPluginHelper # rubocop:disable Metrics/ClassLength
|
7
|
+
class JekyllPluginHelper
|
8
8
|
attr_accessor :liquid_context
|
9
9
|
attr_reader :argv, :attribution, :keys_values, :logger, :markup, :no_arg_parsing, :params, :tag_name,
|
10
10
|
:argv_original, :excerpt_caller, :keys_values_original, :params_original, :jpsh_subclass_caller
|
11
11
|
|
12
|
-
# Expand an environment variable reference
|
13
|
-
def self.expand_env(str, die_if_undefined: false)
|
14
|
-
str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
|
15
|
-
envar = Regexp.last_match(1)
|
16
|
-
raise HrefError, "jekyll_href error: #{envar} is undefined".red, [] \
|
17
|
-
if !ENV.key?(envar) && die_if_undefined # Suppress stack trace
|
18
|
-
|
19
|
-
ENV.fetch(envar, nil)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.register(klass, name)
|
24
|
-
abort("Error: The #{name} plugin does not define VERSION") \
|
25
|
-
unless klass.const_defined?(:VERSION)
|
26
|
-
|
27
|
-
version = klass.const_get(:VERSION)
|
28
|
-
|
29
|
-
abort("Error: The #{name} plugin is not an instance of JekyllSupport::JekyllBlock or JekyllSupport::JekyllTag") \
|
30
|
-
unless klass.instance_of?(Class) &&
|
31
|
-
(klass.ancestors.include?(JekyllSupport::JekyllBlock) ||
|
32
|
-
klass.ancestors.include?(JekyllSupport::JekyllTag))
|
33
|
-
|
34
|
-
Liquid::Template.register_tag(name, klass)
|
35
|
-
PluginMetaLogger.instance.info { "Loaded #{name} v#{version} plugin." }
|
36
|
-
end
|
37
|
-
|
38
|
-
# strip leading and trailing quotes if present
|
39
|
-
def self.remove_quotes(string)
|
40
|
-
string.strip.gsub(/\A'|\A"|'\Z|"\Z/, '').strip if string
|
41
|
-
end
|
42
|
-
|
43
12
|
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
44
13
|
# @param tag_name [String] the name of the tag, which we already know.
|
45
14
|
# @param argument_string [String] the arguments from the tag, as a single string.
|
@@ -53,51 +22,18 @@ class JekyllPluginHelper # rubocop:disable Metrics/ClassLength
|
|
53
22
|
@tag_name = tag_name
|
54
23
|
@logger = logger
|
55
24
|
@no_arg_parsing = no_arg_parsing
|
56
|
-
|
57
|
-
|
58
|
-
@attribution = parameter_specified?('attribution') || false unless no_arg_parsing
|
59
|
-
@logger.debug { "@keys_values='#{@keys_values}'" }
|
25
|
+
@markup = markup
|
60
26
|
rescue StandardError => e
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def attribute
|
65
|
-
return unless @current_gem
|
66
|
-
|
67
|
-
<<~END_OUTPUT
|
68
|
-
<div id="jps_attribute_#{rand(999_999)}" class="jps_attribute">
|
69
|
-
<div>
|
70
|
-
<a href="#{@homepage}" target="_blank" rel="nofollow">
|
71
|
-
#{attribution_string}
|
72
|
-
</a>
|
73
|
-
</div>
|
74
|
-
</div>
|
75
|
-
END_OUTPUT
|
76
|
-
end
|
77
|
-
|
78
|
-
def default_attribution
|
79
|
-
authors = @authors&.join(', ')
|
80
|
-
result = "Generated by the \#{@name} v\#{@version} Jekyll plugin"
|
81
|
-
result << ", written by #{authors}" if authors
|
82
|
-
result << " \#{@published_date}" if @published_date
|
83
|
-
result << '.'
|
84
|
-
result
|
85
|
-
end
|
86
|
-
|
87
|
-
# Sets @current_gem if file points at a uniquely named file within a gem.
|
88
|
-
# @param file must be a fully qualified file name in a gem, for example: __FILE__
|
89
|
-
def gem_file(file)
|
90
|
-
@current_gem = GemSupport.current_spec file
|
91
|
-
@logger.debug "No gem found for '#{file} was found." unless @current_gem
|
92
|
-
annotate_globals if @attribution && @current_gem
|
27
|
+
e.shorten_backtrace
|
28
|
+
@logger.error { e.msg }
|
93
29
|
end
|
94
30
|
|
95
31
|
# @return undefined if parameter was specified, removes it from the available tokens and returns value
|
96
32
|
def parameter_specified?(name, delete_param: true)
|
97
|
-
return if @keys_values.empty?
|
33
|
+
return false if @keys_values.to_s.empty?
|
98
34
|
|
99
35
|
key = name
|
100
|
-
key = name.to_sym if @keys_values
|
36
|
+
key = name.to_sym if @keys_values&.first&.first.instance_of?(Symbol)
|
101
37
|
value = @keys_values[key]
|
102
38
|
delete_parameter(name) if delete_param
|
103
39
|
value
|
@@ -112,18 +48,21 @@ class JekyllPluginHelper # rubocop:disable Metrics/ClassLength
|
|
112
48
|
define_singleton_method(:params) { warn_fetch :params }
|
113
49
|
define_singleton_method(:parameter_specified?) { |_name| warn_parse(:parameter_specified?) }
|
114
50
|
define_singleton_method(:delete_parameter) { |_name| warn_parse(:delete_parameter) }
|
51
|
+
@attribution = false
|
115
52
|
else
|
116
53
|
parse markup
|
54
|
+
@attribution = parameter_specified?('attribution') || false
|
55
|
+
@logger.debug { "@keys_values='#{@keys_values}'" }
|
117
56
|
end
|
118
57
|
end
|
119
58
|
|
120
59
|
# Call this method to return the remaining markup after `parameter_specified?` has been invoked.
|
121
60
|
def remaining_markup
|
122
|
-
@argv
|
61
|
+
@argv&.join(' ')
|
123
62
|
end
|
124
63
|
|
125
64
|
def remaining_markup_original
|
126
|
-
@argv_original
|
65
|
+
@argv_original&.join(' ')
|
127
66
|
end
|
128
67
|
|
129
68
|
def warn_fetch(variable)
|
@@ -134,27 +73,6 @@ class JekyllPluginHelper # rubocop:disable Metrics/ClassLength
|
|
134
73
|
abort "Error: Argument parsing was suppressed, but an attempt to invoke #{meth} was made"
|
135
74
|
end
|
136
75
|
|
137
|
-
private
|
138
|
-
|
139
|
-
def attribution_string
|
140
|
-
string = if @attribution == true
|
141
|
-
default_attribution
|
142
|
-
else
|
143
|
-
@attribution
|
144
|
-
end
|
145
|
-
String.interpolate { string }
|
146
|
-
end
|
147
|
-
|
148
|
-
def annotate_globals
|
149
|
-
return unless @current_gem
|
150
|
-
|
151
|
-
@name = @current_gem.name
|
152
|
-
@authors = @current_gem.authors
|
153
|
-
@homepage = @current_gem.homepage
|
154
|
-
@published_date = @current_gem.date.to_date.to_s
|
155
|
-
@version = @current_gem.version
|
156
|
-
end
|
157
|
-
|
158
76
|
def delete_parameter(key)
|
159
77
|
return if @keys_values.empty? || @params.nil?
|
160
78
|
|
@@ -167,58 +85,26 @@ class JekyllPluginHelper # rubocop:disable Metrics/ClassLength
|
|
167
85
|
@keys_values_original.delete(key)
|
168
86
|
end
|
169
87
|
|
170
|
-
|
171
|
-
|
172
|
-
# Finds variables defined in an invoking include, or maybe somewhere else
|
173
|
-
# @return variable value or nil
|
174
|
-
def dereference_include_variable(name)
|
175
|
-
@liquid_context.scopes.each do |scope|
|
176
|
-
next if PREDEFINED_SCOPE_KEYS.include? scope.keys.first
|
177
|
-
|
178
|
-
value = scope[name]
|
179
|
-
return value if value
|
180
|
-
end
|
181
|
-
nil
|
182
|
-
end
|
183
|
-
|
184
|
-
# @return value of variable, or the empty string
|
185
|
-
def dereference_variable(name)
|
186
|
-
value = @liquid_context[name] # Finds variables named like 'include.my_variable', found in @liquid_context.scopes.first
|
187
|
-
value ||= @page[name] if @page # Finds variables named like 'page.my_variable'
|
188
|
-
value ||= dereference_include_variable(name)
|
189
|
-
value ||= ''
|
190
|
-
value
|
191
|
-
end
|
192
|
-
|
193
|
-
def lookup_variable(symbol)
|
194
|
-
string = symbol.to_s
|
195
|
-
return string unless string.start_with?('{{') && string.end_with?('}}')
|
196
|
-
|
197
|
-
dereference_variable(string.delete_prefix('{{').delete_suffix('}}'))
|
198
|
-
end
|
88
|
+
private
|
199
89
|
|
200
90
|
def page
|
201
91
|
@liquid_context.registers[:page]
|
202
92
|
end
|
203
93
|
|
204
|
-
# rubocop:disable Style/IfUnlessModifier
|
205
94
|
def parse(markup)
|
206
95
|
@argv_original = Shellwords.split(markup)
|
207
96
|
@keys_values_original = KeyValueParser
|
208
|
-
|
209
|
-
|
210
|
-
unless respond_to?(:no_arg_parsing) && no_arg_parsing
|
211
|
-
@params_original = @keys_values_original.map { |k, _v| lookup_variable(k) }
|
212
|
-
end
|
97
|
+
.new({}, { array_values: false, normalize_keys: false, separator: /=/ })
|
98
|
+
.parse(@argv_original)
|
99
|
+
@params_original = @keys_values_original unless respond_to?(:no_arg_parsing) && no_arg_parsing
|
213
100
|
|
214
101
|
@argv = Shellwords.split(self.class.expand_env(markup))
|
215
102
|
@keys_values = KeyValueParser
|
216
|
-
|
217
|
-
|
103
|
+
.new({}, { array_values: false, normalize_keys: false, separator: /=/ })
|
104
|
+
.parse(@argv)
|
218
105
|
|
219
106
|
return if respond_to?(:no_arg_parsing) && no_arg_parsing
|
220
107
|
|
221
|
-
@params = @keys_values
|
108
|
+
@params = @keys_values # TODO: @keys_values should be deleted
|
222
109
|
end
|
223
|
-
# rubocop:enable Style/IfUnlessModifier
|
224
110
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Attribution aspect of JekyllPluginHelper
|
2
|
+
class JekyllPluginHelper
|
3
|
+
# @param file must be a fully qualified file name that points to a file within a gem.
|
4
|
+
# @return Gem::Specification of gem that file points into, or nil if not called from a gem
|
5
|
+
# See https://stackoverflow.com/a/75890279/553865
|
6
|
+
def self.current_spec(file)
|
7
|
+
abort 'JekyllPluginHelper::current_spec: file is nil' if file.nil?
|
8
|
+
return nil unless File.exist?(file)
|
9
|
+
|
10
|
+
searcher = if Gem::Specification.respond_to?(:find)
|
11
|
+
Gem::Specification
|
12
|
+
elsif Gem.respond_to?(:searcher)
|
13
|
+
Gem.searcher.init_gemspecs
|
14
|
+
end
|
15
|
+
|
16
|
+
searcher&.find do |spec|
|
17
|
+
file.start_with? spec.full_gem_path
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute
|
22
|
+
return unless @current_gem
|
23
|
+
|
24
|
+
<<~END_OUTPUT
|
25
|
+
<div id="jps_attribute_#{rand(999_999)}" class="jps_attribute">
|
26
|
+
<div>
|
27
|
+
<a href="#{@homepage}" target="_blank" rel="nofollow">
|
28
|
+
#{attribution_string}
|
29
|
+
</a>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
END_OUTPUT
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_attribution
|
36
|
+
authors = @authors&.join(', ')
|
37
|
+
result = "Generated by the \#{@name} v\#{@version} Jekyll plugin"
|
38
|
+
result << ", written by #{authors}" if authors
|
39
|
+
result << " \#{@published_date}" if @published_date
|
40
|
+
result << '.'
|
41
|
+
result
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sets @current_gem if file points at a uniquely named file within a gem.
|
45
|
+
# @param file must be a fully qualified file name in a gem, for example: __FILE__
|
46
|
+
def gem_file(file)
|
47
|
+
@current_gem = JekyllPluginHelper.current_spec file
|
48
|
+
@logger.debug "No gem found for '#{file} was found." unless @current_gem
|
49
|
+
annotate_globals if @attribution && @current_gem
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def annotate_globals
|
55
|
+
return unless @current_gem
|
56
|
+
|
57
|
+
@name = @current_gem.name
|
58
|
+
@authors = @current_gem.authors
|
59
|
+
@homepage = @current_gem.homepage
|
60
|
+
@published_date = @current_gem.date.to_date.to_s
|
61
|
+
@version = @current_gem.version
|
62
|
+
end
|
63
|
+
|
64
|
+
def attribution_string
|
65
|
+
string = if @attribution == true
|
66
|
+
default_attribution
|
67
|
+
else
|
68
|
+
@attribution
|
69
|
+
end
|
70
|
+
String.interpolate { string }
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Class methods for JekyllPluginHelper
|
2
|
+
class JekyllPluginHelper
|
3
|
+
# Expand an environment variable reference
|
4
|
+
def self.expand_env(str, die_if_undefined: false)
|
5
|
+
str&.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
|
6
|
+
envar = Regexp.last_match(1)
|
7
|
+
raise JekyllPluginSupportError, "jekyll_plugin_support error: #{envar} is undefined".red, [] \
|
8
|
+
if !ENV.key?(envar) && die_if_undefined # Suppress stack trace
|
9
|
+
|
10
|
+
ENV.fetch(envar, nil)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.register(klass, name)
|
15
|
+
abort("Error: The #{name} plugin does not define VERSION") \
|
16
|
+
unless klass.const_defined?(:VERSION)
|
17
|
+
|
18
|
+
version = klass.const_get(:VERSION)
|
19
|
+
|
20
|
+
abort("Error: The #{name} plugin is not an instance of JekyllSupport::JekyllBlock or JekyllSupport::JekyllTag") \
|
21
|
+
unless klass.instance_of?(Class) &&
|
22
|
+
(klass.ancestors.include?(JekyllSupport::JekyllBlock) ||
|
23
|
+
klass.ancestors.include?(JekyllSupport::JekyllTag))
|
24
|
+
|
25
|
+
Liquid::Template.register_tag(name, klass)
|
26
|
+
PluginMetaLogger.instance.info { "Loaded #{name} v#{version} plugin." }
|
27
|
+
end
|
28
|
+
|
29
|
+
# strip leading and trailing quotes if present
|
30
|
+
def self.remove_quotes(string)
|
31
|
+
string.strip.gsub(/\A'|\A"|'\Z|"\Z/, '').strip if string
|
32
|
+
end
|
33
|
+
end
|
@@ -4,211 +4,14 @@ require 'jekyll_plugin_logger'
|
|
4
4
|
require_relative 'jekyll_plugin_helper'
|
5
5
|
require_relative 'jekyll_plugin_support/version'
|
6
6
|
|
7
|
-
# @author Copyright 2022 Michael Slinn
|
8
|
-
# @license SPDX-License-Identifier: Apache-2.0``
|
9
7
|
module NoArgParsing
|
10
8
|
attr_accessor :no_arg_parsing
|
11
9
|
|
12
10
|
@no_arg_parsing = true
|
13
11
|
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
logger.error do
|
21
|
-
error.message + "\n" + # rubocop:disable Style/StringConcatenation
|
22
|
-
error.backtrace.take(DISPLAYED_CALLS).join("\n") +
|
23
|
-
"\n...Remaining #{remaining} call sites elided.\n"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.warn_short_trace(logger, error)
|
28
|
-
remaining = error.backtrace.length - DISPLAYED_CALLS
|
29
|
-
logger.warn do
|
30
|
-
error.message + "\n" + # rubocop:disable Style/StringConcatenation
|
31
|
-
error.backtrace.take(DISPLAYED_CALLS).join("\n") +
|
32
|
-
"\n...Remaining #{remaining} call sites elided.\n"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Base class for Jekyll block tags
|
37
|
-
class JekyllBlock < Liquid::Block
|
38
|
-
attr_reader :argument_string, :helper, :line_number, :logger, :page, :site, :text
|
39
|
-
|
40
|
-
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
41
|
-
# @param tag_name [String] the name of the tag, which we usually know.
|
42
|
-
# @param argument_string [String] the arguments passed to the tag, as a single string.
|
43
|
-
# @param parse_context [Liquid::ParseContext] hash that stores Liquid options.
|
44
|
-
# By default it has two keys: :locale and :line_numbers, the first is a Liquid::I18n object, and the second,
|
45
|
-
# a boolean parameter that determines if error messages should display the line number the error occurred.
|
46
|
-
# This argument is used mostly to display localized error messages on Liquid built-in Tags and Filters.
|
47
|
-
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
48
|
-
# @return [void]
|
49
|
-
def initialize(tag_name, argument_string, parse_context)
|
50
|
-
super
|
51
|
-
@tag_name = tag_name
|
52
|
-
@argument_string = argument_string
|
53
|
-
@logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
54
|
-
@logger.debug { "#{self.class}: respond_to?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
55
|
-
@helper = JekyllPluginHelper.new tag_name, argument_string, @logger, respond_to?(:no_arg_parsing)
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return line number where tag or block was found, relative to the start of the page
|
59
|
-
def jekyll_line_number
|
60
|
-
@page['front_matter'].count("\n") + @line_number
|
61
|
-
end
|
62
|
-
|
63
|
-
# Method prescribed by the Jekyll plugin lifecycle.
|
64
|
-
# Defines @config, @envs, @mode, @page and @site
|
65
|
-
# @return [String]
|
66
|
-
def render(liquid_context)
|
67
|
-
text = super
|
68
|
-
@helper.liquid_context = liquid_context
|
69
|
-
|
70
|
-
@page = liquid_context.registers[:page] # hash
|
71
|
-
@site = liquid_context.registers[:site]
|
72
|
-
@config = @site.config
|
73
|
-
@envs = liquid_context.environments.first
|
74
|
-
|
75
|
-
@layout = @envs[:layout]
|
76
|
-
@paginator = @envs[:paginator]
|
77
|
-
@theme = @envs[:theme]
|
78
|
-
|
79
|
-
@mode = @config['env']&.key?('JEKYLL_ENV') ? @config['env']['JEKYLL_ENV'] : 'development'
|
80
|
-
|
81
|
-
render_impl text
|
82
|
-
rescue StandardError => e
|
83
|
-
@logger.error { "#{self.class} died with a #{e.full_message}" }
|
84
|
-
JekyllSupport.error_short_trace(@logger, e)
|
85
|
-
end
|
86
|
-
|
87
|
-
# Jekyll plugins should override this method, not render,
|
88
|
-
# so they can be tested more easily.
|
89
|
-
# The following variables are predefined:
|
90
|
-
# @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
|
91
|
-
# @return [String] The result to be rendered to the invoking page
|
92
|
-
def render_impl(text)
|
93
|
-
text
|
94
|
-
end
|
95
|
-
|
96
|
-
def warn_short_trace(error)
|
97
|
-
JekyllSupport.warn_short_trace(@logger, error)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
class JekyllBlockNoArgParsing < JekyllBlock
|
102
|
-
def initialize(tag_name, argument_string, parse_context)
|
103
|
-
class << self
|
104
|
-
include NoArgParsing
|
105
|
-
end
|
106
|
-
|
107
|
-
super
|
108
|
-
@logger.debug { "#{self.class}: respond_to?(:o_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
109
|
-
rescue StandardError => e
|
110
|
-
@logger.error { "#{self.class} died with a #{e.full_message}" }
|
111
|
-
JekyllSupport.error_short_trace(@logger, e)
|
112
|
-
end
|
113
|
-
|
114
|
-
def warn_short_trace(error)
|
115
|
-
JekyllSupport.warn_short_trace(@logger, error)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# Base class for Jekyll tags
|
120
|
-
class JekyllTag < Liquid::Tag
|
121
|
-
attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
|
122
|
-
|
123
|
-
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
124
|
-
# @param tag_name [String] the name of the tag, which we usually know.
|
125
|
-
# @param argument_string [String] the arguments passed to the tag, as a single string.
|
126
|
-
# @param parse_context [Liquid::ParseContext] hash that stores Liquid options.
|
127
|
-
# By default it has two keys: :locale and :line_numbers, the first is a Liquid::I18n object, and the second,
|
128
|
-
# a boolean parameter that determines if error messages should display the line number the error occurred.
|
129
|
-
# This argument is used mostly to display localized error messages on Liquid built-in Tags and Filters.
|
130
|
-
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
131
|
-
# @return [void]
|
132
|
-
def initialize(tag_name, argument_string, parse_context)
|
133
|
-
super
|
134
|
-
@tag_name = tag_name
|
135
|
-
@argument_string = argument_string
|
136
|
-
@logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
137
|
-
@logger.debug { "#{self.class}: respond_to?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
138
|
-
@helper = JekyllPluginHelper.new(tag_name, argument_string, @logger, respond_to?(:no_arg_parsing))
|
139
|
-
end
|
140
|
-
|
141
|
-
# If a Jekyll plugin needs to crash exit, and stop Jekyll, call this method.
|
142
|
-
# It does not generate a stack trace.
|
143
|
-
# This method does not return because the process is abruptly terminated.
|
144
|
-
#
|
145
|
-
# @param error StandardError or a subclass of StandardError is required
|
146
|
-
#
|
147
|
-
# Do not raise the error before calling this method, just create it via 'new', like this:
|
148
|
-
# exit_without_stack_trace StandardError.new('This is my error message')
|
149
|
-
#
|
150
|
-
# If you want to call this method from a handler method, the default index for the backtrace array must be specified.
|
151
|
-
# The default backtrace index is 1, which means the calling method.
|
152
|
-
# To specify the calling method's caller, pass in 2, like this:
|
153
|
-
# exit_without_stack_trace StandardError.new('This is my error message'), 2
|
154
|
-
def exit_without_stack_trace(error, caller_index = 1)
|
155
|
-
raise error
|
156
|
-
rescue StandardError => e
|
157
|
-
file, line_number, caller = e.backtrace[caller_index].split(':')
|
158
|
-
caller = caller.tr('`', "'")
|
159
|
-
warn "#{self.class} died with a '#{error.message}' #{caller} on line #{line_number} of #{file}".red
|
160
|
-
# Process.kill('HUP', Process.pid) # generates huge stack trace
|
161
|
-
exec "echo ''"
|
162
|
-
end
|
163
|
-
|
164
|
-
# @return line number where tag or block was found, relative to the start of the page
|
165
|
-
def jekyll_line_number
|
166
|
-
@page['front_matter'].count("\n") + @line_number
|
167
|
-
end
|
168
|
-
|
169
|
-
# Method prescribed by the Jekyll plugin lifecycle.
|
170
|
-
def render(liquid_context)
|
171
|
-
return if @helper.excerpt_caller
|
172
|
-
|
173
|
-
@helper.liquid_context = liquid_context
|
174
|
-
|
175
|
-
@envs = liquid_context.environments.first
|
176
|
-
|
177
|
-
@layout = @envs[:layout]
|
178
|
-
@paginator = @envs[:paginator]
|
179
|
-
@theme = @envs[:theme]
|
180
|
-
|
181
|
-
@page = liquid_context.registers[:page]
|
182
|
-
@site = liquid_context.registers[:site]
|
183
|
-
|
184
|
-
@config = @site.config
|
185
|
-
@mode = @config['env']&.key?('JEKYLL_ENV') ? @config['env']['JEKYLL_ENV'] : 'development'
|
186
|
-
|
187
|
-
render_impl
|
188
|
-
rescue StandardError => e
|
189
|
-
JekyllSupport.error_short_trace(@logger, e)
|
190
|
-
end
|
191
|
-
|
192
|
-
# Jekyll plugins must override this method, not render, so their plugin can be tested more easily
|
193
|
-
# The following variables are predefined:
|
194
|
-
# @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
|
195
|
-
def render_impl
|
196
|
-
abort "#{self.class}.render_impl for tag #{@tag_name} must be overridden, but it was not."
|
197
|
-
end
|
198
|
-
|
199
|
-
def warn_short_trace(error)
|
200
|
-
JekyllSupport.warn_short_trace(@logger, error)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
class JekyllTagNoArgParsing < JekyllTag
|
205
|
-
def initialize(tag_name, argument_string, parse_context)
|
206
|
-
class << self
|
207
|
-
include NoArgParsing
|
208
|
-
end
|
209
|
-
|
210
|
-
super
|
211
|
-
@logger.debug { "#{self.class}: respond_to?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
13
|
+
require_relative 'jekyll_plugin_support_class'
|
14
|
+
require_relative 'jekyll_plugin_support_block'
|
15
|
+
require_relative 'jekyll_plugin_support_block_noarg'
|
16
|
+
require_relative 'jekyll_plugin_support_tag'
|
17
|
+
require_relative 'jekyll_plugin_support_tag_noarg'
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative 'jekyll_plugin_error_handling'
|
2
|
+
|
3
|
+
module JekyllSupport
|
4
|
+
# Base class for Jekyll block tags
|
5
|
+
class JekyllBlock < Liquid::Block
|
6
|
+
attr_reader :argument_string, :helper, :line_number, :logger, :page, :site, :text
|
7
|
+
|
8
|
+
include JekyllSupportErrorHandling
|
9
|
+
extend JekyllSupportErrorHandling
|
10
|
+
|
11
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
12
|
+
# @param tag_name [String] the name of the tag, which we usually know.
|
13
|
+
# @param argument_string [String] the arguments passed to the tag, as a single string.
|
14
|
+
# @param parse_context [Liquid::ParseContext] hash that stores Liquid options.
|
15
|
+
# By default it has two keys: :locale and :line_numbers, the first is a Liquid::I18n object, and the second,
|
16
|
+
# a boolean parameter that determines if error messages should display the line number the error occurred.
|
17
|
+
# This argument is used mostly to display localized error messages on Liquid built-in Tags and Filters.
|
18
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
19
|
+
# @return [void]
|
20
|
+
def initialize(tag_name, markup, parse_context)
|
21
|
+
super
|
22
|
+
@tag_name = tag_name
|
23
|
+
@argument_string = markup.to_s # Vars in plugin parameters cannot be replaced yet
|
24
|
+
@logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
25
|
+
@logger.debug { "#{self.class}: respond_to?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
26
|
+
@helper = JekyllPluginHelper.new tag_name, markup, @logger, respond_to?(:no_arg_parsing)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Method prescribed by the Jekyll plugin lifecycle.
|
30
|
+
# Defines @config, @envs, @mode, @page and @site
|
31
|
+
# @return [String]
|
32
|
+
def render(liquid_context)
|
33
|
+
@helper.liquid_context = JekyllSupport.inject_vars @logger, liquid_context
|
34
|
+
text = super # Liquid variable values in content are looked up and substituted
|
35
|
+
|
36
|
+
@envs = liquid_context.environments.first
|
37
|
+
@page = liquid_context.registers[:page] # hash
|
38
|
+
@scopes = liquid_context.scopes
|
39
|
+
@site = liquid_context.registers[:site]
|
40
|
+
|
41
|
+
@config = @site.config
|
42
|
+
@tag_config = @config[@tag_name]
|
43
|
+
|
44
|
+
@layout = @envs[:layout]
|
45
|
+
@paginator = @envs[:paginator]
|
46
|
+
@theme = @envs[:theme]
|
47
|
+
|
48
|
+
@mode = @config['env']&.key?('JEKYLL_ENV') ? @config['env']['JEKYLL_ENV'] : 'development'
|
49
|
+
|
50
|
+
@helper.reinitialize(@markup.strip)
|
51
|
+
|
52
|
+
@attribution = @helper.parameter_specified?('attribution') || false unless @no_arg_parsing
|
53
|
+
@logger.debug { "@keys_values='#{@keys_values}'" }
|
54
|
+
|
55
|
+
markup = JekyllSupport.lookup_liquid_variables liquid_context, @argument_string
|
56
|
+
@helper.reinitialize markup
|
57
|
+
|
58
|
+
render_impl text
|
59
|
+
rescue StandardError => e
|
60
|
+
e.shorten_backtrace
|
61
|
+
msg = format_error_message e.full_message
|
62
|
+
@logger.error msg
|
63
|
+
raise e if @die_on_standard_error
|
64
|
+
|
65
|
+
"<div class='standard_error'>#{e.class}: #{msg}</div>"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Jekyll plugins should override this method, not render,
|
69
|
+
# so they can be tested more easily.
|
70
|
+
# The following variables are predefined:
|
71
|
+
# @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
|
72
|
+
# @return [String] The result to be rendered to the invoking page
|
73
|
+
def render_impl(text)
|
74
|
+
text
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'jekyll_plugin_error_handling'
|
2
|
+
|
3
|
+
module JekyllSupport
|
4
|
+
class JekyllBlockNoArgParsing < JekyllBlock
|
5
|
+
attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
|
6
|
+
|
7
|
+
include JekyllSupportErrorHandling
|
8
|
+
extend JekyllSupportErrorHandling
|
9
|
+
|
10
|
+
def initialize(tag_name, argument_string, parse_context)
|
11
|
+
class << self
|
12
|
+
include NoArgParsing
|
13
|
+
end
|
14
|
+
|
15
|
+
super
|
16
|
+
@logger.debug { "#{self.class}: respond_to?(:o_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
17
|
+
rescue StandardError => e
|
18
|
+
e.shorten_backtrace
|
19
|
+
@logger.error { e.full_message }
|
20
|
+
JekyllSupport.error_short_trace(@logger, e)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Jekyll plugins must override this method, not render, so their plugin can be tested more easily
|
24
|
+
# The following variables are predefined:
|
25
|
+
# @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
|
26
|
+
def render_impl
|
27
|
+
abort "#{self.class}.render_impl for tag #{@tag_name} must be overridden, but it was not."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|