jekyll_plugin_support 0.7.2 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,86 @@
|
|
1
|
+
# Monkey patch StandardError so a new method called shorten_backtrace is added.
|
2
|
+
class StandardError
|
3
|
+
def shorten_backtrace(backtrace_element_count = 3)
|
4
|
+
# self.backtrace = backtrace[0..backtrace_element_count].map do |x|
|
5
|
+
# raise JekyllPluginSupportError, "backtrace contains a #{x.class} with value '#{x}'." unless x.instance_of? String
|
6
|
+
|
7
|
+
# x.gsub(Dir.pwd + '/', './')
|
8
|
+
# end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module JekyllSupport
|
13
|
+
DISPLAYED_CALLS = 8
|
14
|
+
|
15
|
+
def self.error_short_trace(logger, error)
|
16
|
+
error.set_backtrace error.backtrace[0..DISPLAYED_CALLS]
|
17
|
+
logger.error { error }
|
18
|
+
error
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return a new StandardError subclass containing the shorten_backtrace method
|
22
|
+
def define_error
|
23
|
+
Class.new StandardError
|
24
|
+
end
|
25
|
+
module_function :define_error
|
26
|
+
|
27
|
+
JekyllPluginSupportError = define_error
|
28
|
+
|
29
|
+
def self.dump_vars(_logger, liquid_context)
|
30
|
+
page = liquid_context.registers[:page]
|
31
|
+
vars = liquid_context.scopes.map do |scope|
|
32
|
+
scope.map { |name, value| " #{name} = #{value}" }.join("\n")
|
33
|
+
end.join("\n")
|
34
|
+
puts <<~END_MSG
|
35
|
+
#{page['name']} variables after injecting any defined in _config.yml:
|
36
|
+
#{vars}
|
37
|
+
END_MSG
|
38
|
+
end
|
39
|
+
|
40
|
+
# Add variable definitions from _config.yml to liquid_context
|
41
|
+
# Modifies liquid_context in the caller (call by reference)
|
42
|
+
# @return modified liquid_context
|
43
|
+
# See README.md#configuration-variable-definitions
|
44
|
+
# See demo/variables.html
|
45
|
+
def self.inject_vars(_logger, liquid_context)
|
46
|
+
site = liquid_context.registers[:site]
|
47
|
+
|
48
|
+
plugin_variables = site.config['liquid_vars']
|
49
|
+
return liquid_context unless plugin_variables
|
50
|
+
|
51
|
+
scope = liquid_context.scopes.last
|
52
|
+
|
53
|
+
env = site.config['env']
|
54
|
+
mode = env&.key?('JEKYLL_ENV') ? env['JEKYLL_ENV'] : 'development'
|
55
|
+
|
56
|
+
# Set default values
|
57
|
+
plugin_variables&.each do |name, value|
|
58
|
+
scope[name] = value if value.instance_of? String
|
59
|
+
end
|
60
|
+
|
61
|
+
# Override with environment-specific values
|
62
|
+
plugin_variables[mode]&.each do |name, value|
|
63
|
+
scope[name] = value if value.instance_of? String
|
64
|
+
end
|
65
|
+
|
66
|
+
liquid_context
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.lookup_liquid_variables(liquid_context, markup)
|
70
|
+
liquid_context.scopes.each do |scope|
|
71
|
+
scope.each do |name, value|
|
72
|
+
markup = markup.gsub("{{#{name}}}", value.to_s)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
markup
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.warn_short_trace(logger, error)
|
79
|
+
remaining = error.backtrace.length - DISPLAYED_CALLS
|
80
|
+
logger.warn do
|
81
|
+
error.msg + "\n" +
|
82
|
+
error.backtrace.take(DISPLAYED_CALLS).join("\n") +
|
83
|
+
"\n...Remaining #{remaining} call sites elided.\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require_relative 'jekyll_plugin_error_handling'
|
3
|
+
|
4
|
+
module JekyllSupport
|
5
|
+
# Base class for Jekyll tags
|
6
|
+
class JekyllTag < Liquid::Tag
|
7
|
+
attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
|
8
|
+
|
9
|
+
include JekyllSupportErrorHandling
|
10
|
+
extend JekyllSupportErrorHandling
|
11
|
+
|
12
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
13
|
+
# @param tag_name [String] the name of the tag, which we usually know.
|
14
|
+
# @param argument_string [String] the arguments passed to the tag, as a single string.
|
15
|
+
# @param parse_context [Liquid::ParseContext] hash that stores Liquid options.
|
16
|
+
# By default it has two keys: :locale and :line_numbers, the first is a Liquid::I18n object, and the second,
|
17
|
+
# a boolean parameter that determines if error messages should display the line number the error occurred.
|
18
|
+
# This argument is used mostly to display localized error messages on Liquid built-in Tags and Filters.
|
19
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
20
|
+
# @return [void]
|
21
|
+
def initialize(tag_name, markup, parse_context)
|
22
|
+
super
|
23
|
+
@tag_name = tag_name
|
24
|
+
raise JekyllPluginSupportError, "markup is a #{markup.class} with value '#{markup}'." unless markup.instance_of? String
|
25
|
+
|
26
|
+
@argument_string = markup
|
27
|
+
@logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
28
|
+
@logger.debug { "#{self.class}: respond_to?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
29
|
+
@helper = JekyllPluginHelper.new(tag_name, @argument_string, @logger, respond_to?(:no_arg_parsing))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Method prescribed by the Jekyll plugin lifecycle.
|
33
|
+
def render(liquid_context)
|
34
|
+
return if @helper.excerpt_caller
|
35
|
+
|
36
|
+
@helper.liquid_context = JekyllSupport.inject_vars @logger, liquid_context
|
37
|
+
|
38
|
+
@envs = liquid_context.environments.first
|
39
|
+
@page = liquid_context.registers[:page]
|
40
|
+
@scopes = liquid_context.scopes
|
41
|
+
@site = liquid_context.registers[:site]
|
42
|
+
|
43
|
+
@config = @site.config
|
44
|
+
@tag_config = @config[@tag_name]
|
45
|
+
@jps = @config['jekyll_plugin_support']
|
46
|
+
@pry_on_standard_error = @jps['pry_on_standard_error'] || false if @jps
|
47
|
+
|
48
|
+
# @envs.keys are :content, :highlighter_prefix, :highlighter_suffix, :jekyll, :layout, :page, :paginator, :site, :theme
|
49
|
+
@layout = @envs[:layout]
|
50
|
+
@paginator = @envs[:paginator]
|
51
|
+
@theme = @envs[:theme]
|
52
|
+
|
53
|
+
@mode = @config['env']&.key?('JEKYLL_ENV') ? @config['env']['JEKYLL_ENV'] : 'development'
|
54
|
+
|
55
|
+
@helper.reinitialize JekyllSupport.lookup_liquid_variables liquid_context, @argument_string
|
56
|
+
|
57
|
+
render_impl
|
58
|
+
rescue StandardError => e
|
59
|
+
e.shorten_backtrace
|
60
|
+
msg = format_error_message e.full_message
|
61
|
+
@logger.error msg
|
62
|
+
binding.pry if @pry_on_standard_error # rubocop:disable Lint/Debugger
|
63
|
+
raise e if @die_on_standard_error
|
64
|
+
|
65
|
+
"<div class='standard_error'>#{e.class}: #{msg}</div>"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Jekyll plugins must override this method, not render, so their plugin can be tested more easily
|
69
|
+
# The following variables are predefined:
|
70
|
+
# @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
|
71
|
+
def render_impl
|
72
|
+
abort "#{self.class}.render_impl for tag #{@tag_name} must be overridden, but it was not."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'jekyll_plugin_error_handling'
|
2
|
+
|
3
|
+
module JekyllSupport
|
4
|
+
class JekyllTagNoArgParsing < JekyllTag
|
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?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Jekyll plugins must override this method, not render, so their plugin can be tested more easily
|
20
|
+
# The following variables are predefined:
|
21
|
+
# @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
|
22
|
+
def render_impl
|
23
|
+
abort "#{self.class}.render_impl for tag #{@tag_name} must be overridden, but it was not."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'jekyll_plugin_logger'
|
2
|
+
require 'rspec/match_ignoring_whitespace'
|
3
|
+
require_relative '../lib/jekyll_plugin_support'
|
4
|
+
require_relative '../lib/jekyll_plugin_support_spec_support'
|
5
|
+
|
6
|
+
class LiquidVariableParsing
|
7
|
+
# @return copy of str with references to defined variables replaced by the values of the variables
|
8
|
+
def variable_replace(str, scopes)
|
9
|
+
result = str.clone
|
10
|
+
match_data_list = str.to_enum(:scan, /{{[a-z_][a-zA-Z_0-9]*}}/).map { Regexp.last_match }.reverse
|
11
|
+
match_data_list.each do |md|
|
12
|
+
from = md.begin(0)
|
13
|
+
to = md.end(0) - 1
|
14
|
+
ref = str[from..to]
|
15
|
+
name = ref[2..-3]
|
16
|
+
scopes.each do |scope|
|
17
|
+
value = scope.key?(name) ? scope[name] : ref
|
18
|
+
# puts "str=#{str}; from=#{from}; to=#{to}; name=#{name} value=#{value}"
|
19
|
+
result[from..to] = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
RSpec.describe JekyllPluginHelper do
|
26
|
+
it 'substitutes variable references for values without recursion' do
|
27
|
+
scopes = [{ 'a' => '{{', 'b' => 'asdf', 'c' => '}}' }]
|
28
|
+
str = '{{a}}{{b}}{{c}} This should be unchanged: {{d}}'
|
29
|
+
new_str = variable_replace(str, scopes)
|
30
|
+
expect(str).to start_with('{{a}}')
|
31
|
+
expect(new_str).to be('{{asdf}} This should be unchanged: {{d}}')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,9 +2,8 @@ require 'jekyll'
|
|
2
2
|
require_relative '../lib/jekyll_plugin_support'
|
3
3
|
|
4
4
|
RSpec.configure do |config|
|
5
|
-
config.
|
5
|
+
config.filter_run_when_matching focus: true
|
6
6
|
# config.order = 'random'
|
7
|
-
config.run_all_when_everything_filtered = true
|
8
7
|
|
9
8
|
# See https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures
|
10
9
|
config.example_status_persistence_file_path = 'spec/status_persistence.txt'
|
data/spec/status_persistence.txt
CHANGED
@@ -0,0 +1,7 @@
|
|
1
|
+
example_id | status | run_time |
|
2
|
+
------------------------------------------------ | ------ | --------------- |
|
3
|
+
./spec/jekyll_plugin_helper_options_spec.rb[1:1] | failed | 0.00009 seconds |
|
4
|
+
./spec/jekyll_plugin_helper_options_spec.rb[1:2] | failed | 0.00004 seconds |
|
5
|
+
./spec/jekyll_plugin_helper_options_spec.rb[1:3] | failed | 0.00002 seconds |
|
6
|
+
./spec/jekyll_plugin_helper_options_spec.rb[1:4] | failed | 0.00002 seconds |
|
7
|
+
./spec/liquid_variable_parsing_spec.rb[1:1] | failed | 0.00003 seconds |
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll_plugin_support
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Slinn
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: facets
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description:
|
70
84
|
email:
|
71
85
|
- mslinn@mslinn.com
|
@@ -80,13 +94,20 @@ files:
|
|
80
94
|
- README.md
|
81
95
|
- Rakefile
|
82
96
|
- jekyll_plugin_support.gemspec
|
83
|
-
- lib/
|
84
|
-
- lib/gem_support.rb
|
97
|
+
- lib/jekyll_plugin_error_handling.rb
|
85
98
|
- lib/jekyll_plugin_helper.rb
|
99
|
+
- lib/jekyll_plugin_helper_attribution.rb
|
100
|
+
- lib/jekyll_plugin_helper_class.rb
|
86
101
|
- lib/jekyll_plugin_support.rb
|
87
102
|
- lib/jekyll_plugin_support/version.rb
|
103
|
+
- lib/jekyll_plugin_support_block.rb
|
104
|
+
- lib/jekyll_plugin_support_block_noarg.rb
|
105
|
+
- lib/jekyll_plugin_support_class.rb
|
88
106
|
- lib/jekyll_plugin_support_spec_support.rb
|
107
|
+
- lib/jekyll_plugin_support_tag.rb
|
108
|
+
- lib/jekyll_plugin_support_tag_noarg.rb
|
89
109
|
- spec/jekyll_plugin_helper_options_spec.rb
|
110
|
+
- spec/liquid_variable_parsing_spec.rb
|
90
111
|
- spec/spec_helper.rb
|
91
112
|
- spec/status_persistence.txt
|
92
113
|
homepage: https://www.mslinn.com/jekyll_plugins/jekyll_plugin_support.html
|
@@ -116,12 +137,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
137
|
- !ruby/object:Gem::Version
|
117
138
|
version: '0'
|
118
139
|
requirements: []
|
119
|
-
rubygems_version: 3.3.
|
140
|
+
rubygems_version: 3.3.7
|
120
141
|
signing_key:
|
121
142
|
specification_version: 4
|
122
|
-
summary: Provides
|
143
|
+
summary: Provides a framework for writing and testing Jekyll plugins
|
123
144
|
test_files:
|
124
145
|
- spec/jekyll_plugin_helper_options_spec.rb
|
146
|
+
- spec/liquid_variable_parsing_spec.rb
|
125
147
|
- spec/spec_helper.rb
|
126
148
|
- spec/status_persistence.txt
|
127
149
|
...
|
data/lib/call_chain.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
# TODO: File not used, delete
|
2
|
-
|
3
|
-
# See https://stackoverflow.com/a/23363883/553865
|
4
|
-
module CallChain
|
5
|
-
ACaller = Struct.new(:filepath, :line, :method_name)
|
6
|
-
|
7
|
-
def self.caller_method(depth = 1)
|
8
|
-
parse_caller(caller(depth + 1).first).method_name
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.parse_caller(at)
|
12
|
-
return unless /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
|
13
|
-
|
14
|
-
last_match = Regexp.last_match.to_a
|
15
|
-
ACaller.new(
|
16
|
-
last_match[1],
|
17
|
-
last_match[2].to_i,
|
18
|
-
last_match[3]
|
19
|
-
)
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.excerpt_caller
|
23
|
-
call_sequence = caller
|
24
|
-
call_sequence.each do |caller_|
|
25
|
-
parsed_caller = parse_caller caller_
|
26
|
-
filepath = parsed_caller.filepath
|
27
|
-
excerpt = filepath.match? %r{jekyll[.0-9-]*/lib/jekyll/excerpt.rb\z}
|
28
|
-
puts "excerpt matched #{filepath}" if excerpt
|
29
|
-
return true if excerpt
|
30
|
-
end
|
31
|
-
false
|
32
|
-
end
|
33
|
-
|
34
|
-
# Return ACaller prior to jekyll_plugin_support
|
35
|
-
def self.jpsh_subclass_caller
|
36
|
-
state = :nothing_found
|
37
|
-
call_sequence = caller
|
38
|
-
call_sequence.each do |caller_|
|
39
|
-
parsed_caller = parse_caller caller_
|
40
|
-
filepath = parsed_caller.filepath
|
41
|
-
dirname = File.dirname filepath
|
42
|
-
jpsh = dirname.match? %r{jekyll_plugin_support[.0-9-]*/lib\z}
|
43
|
-
liquid = dirname.match? %r{liquid[.0-9-]*/lib/\z}
|
44
|
-
case state
|
45
|
-
when :nothing_found
|
46
|
-
state = :jpsh_found if jpsh
|
47
|
-
when :jpsh_found
|
48
|
-
# puts "Called from #{parsed_caller.filepath}, on line #{parsed_caller.line}, by method '#{parsed_caller.method_name}'" unless jpsh
|
49
|
-
return parsed_caller unless jpsh || liquid
|
50
|
-
end
|
51
|
-
end
|
52
|
-
nil
|
53
|
-
end
|
54
|
-
end
|
data/lib/gem_support.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# See https://stackoverflow.com/a/75890279/553865
|
2
|
-
module GemSupport
|
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
|
-
def self.current_spec(file)
|
6
|
-
abort 'GemSupport::current_spec: file is nil' if file.nil?
|
7
|
-
return nil unless File.exist?(file)
|
8
|
-
|
9
|
-
searcher = if Gem::Specification.respond_to?(:find)
|
10
|
-
Gem::Specification
|
11
|
-
elsif Gem.respond_to?(:searcher)
|
12
|
-
Gem.searcher.init_gemspecs
|
13
|
-
end
|
14
|
-
|
15
|
-
searcher&.find do |spec|
|
16
|
-
file.start_with? spec.full_gem_path
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|