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.
@@ -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.filter_run :focus
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'
@@ -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.7.2
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-08-14 00:00:00.000000000 Z
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/call_chain.rb
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.3
140
+ rubygems_version: 3.3.7
120
141
  signing_key:
121
142
  specification_version: 4
122
- summary: Provides support for writing Jekyll plugins.
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