jekyll_plugin_support 0.8.5 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20c575e857da7ffa50f37a587494b25232e1eb1309040946dd13d8f896250f07
4
- data.tar.gz: 4aa5e163875f4e99adca9a6bc4ca97927c95c4cc4d017f52ca0b108d9ea24670
3
+ metadata.gz: cb346f630f3f3d54d602c2c130ead054765b3f1fcd5d2a13550a82d850ce6c7f
4
+ data.tar.gz: 50a247655dbdb650eaeabb6163843b2d9f2c1ec47c69609f43b0e22df10d96c7
5
5
  SHA512:
6
- metadata.gz: f2a2123c073c9e344e097e3eecd364606037bdd0213045d7060a09d48acaac1810eb609e201126e3f4fc8e5edc0e4e6e81db43800aa4eea954490d4e21e91cdc
7
- data.tar.gz: 2d4e8daa6ea71e412065356a7029f3ff0b432fb7a06cdc98f403ba02709106f75c62e8236bae12668e6b181f4c52ccba328811fdb9619cd075597fea622052e7
6
+ metadata.gz: c3277bc67de6688e3c4af585940a26bd641d157a54be0757a34aae364454fc5bfe952cb5e7834d8dc57eda4e6957ce1c1adcf603915d3bff442e7a88e52552ca
7
+ data.tar.gz: f9e68b39c8de0fa100d720601b59e2a052a8fb3de9746ccb7a37fa28752bb43171827dd325f7b1e6bda920561716feed78a69e477d1fc022832ff43ce5d9234a
data/.rubocop.yml CHANGED
@@ -34,14 +34,14 @@ Metrics/AbcSize:
34
34
 
35
35
  Metrics/BlockLength:
36
36
  Exclude:
37
- - jekyll_all_collections.gemspec
37
+ - jekyll_plugin_support.gemspec
38
38
  Max: 30
39
39
 
40
40
  Metrics/CyclomaticComplexity:
41
41
  Max: 25
42
42
 
43
43
  Metrics/MethodLength:
44
- Max: 30
44
+ Max: 50
45
45
 
46
46
  Metrics/PerceivedComplexity:
47
47
  Max: 25
@@ -49,14 +49,17 @@ Metrics/PerceivedComplexity:
49
49
  Naming/FileName:
50
50
  Exclude:
51
51
  - Rakefile
52
+ - "*.md"
52
53
 
53
54
  RSpec/ExampleLength:
54
55
  Max: 30
55
56
 
56
- RSpec/FilePath:
57
+ RSpec/SpecFilePathFormat:
57
58
  Enabled: false
58
59
  IgnoreMethods: true
59
- SpecSuffixOnly: true
60
+
61
+ RSpec/SpecFilePathSuffix:
62
+ Enabled: false
60
63
 
61
64
  RSpec/IndexedLet:
62
65
  Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.0.0 / 2024-07-23
4
+
5
+ * Added the `redef_without_warning` method so tag and block plugins can be subclassed.
6
+ * Put most of the functionality into the `JekyllSupport` module.
7
+ * Made tag and block code more consistent
8
+
9
+
10
+ ## 0.8.6 / 2024-06-11
11
+
12
+ * Fixup version, containing what was supposed to be in v0.8.5
13
+
3
14
 
4
15
  ## 0.8.5 / 2024-03-25
5
16
 
data/README.md CHANGED
@@ -55,9 +55,9 @@ add the following line to your Jekyll plugin’s `Gemfile`.
55
55
 
56
56
  ```ruby
57
57
  group :jekyll_plugins do
58
- ...
58
+ # ...
59
59
  gem 'jekyll_plugin_support', '>= 0.8.0'
60
- ...
60
+ # ...
61
61
  end
62
62
  ```
63
63
 
@@ -65,9 +65,9 @@ Otherwise, if your custom plugin will be packaged into a gem, add the following
65
65
 
66
66
  ```ruby
67
67
  Gem::Specification.new do |spec|
68
- ...
68
+ # ...
69
69
  spec.add_dependency 'jekyll_plugin_support', '>= 0.8.0'
70
- ...
70
+ # ...
71
71
  end
72
72
  ```
73
73
 
@@ -516,7 +516,7 @@ without tokenization, and you expect that the plugin might be invoked with large
516
516
  derive your plugin from `JekyllBlockNoArgParsing` or `JekyllTagNoArgParsing`.
517
517
 
518
518
 
519
- ## Subclass Attribution
519
+ ## Attribution
520
520
 
521
521
  `JekyllTag` and `JekyllBlock` subclasses of `jekyll_plugin_support` can utilize the `attribution`
522
522
  option if they are published as a gem.
@@ -584,6 +584,21 @@ An alternative attribution string can be specified properties can be output usin
584
584
  {% my_tag attribution="Generated by the #{name} #{version} Jekyll plugin, written by #{author} #{date}" %}
585
585
  ```
586
586
 
587
+ ## Subclassing
588
+
589
+ Jekyll plugins created using `jekyll_plugin_support` are implemented as Ruby classes.
590
+ If you would like to create a version of an existing Jekyll plugin, you will need to subclass the plugin.
591
+ In order to do that, you will need to override the plugin name and version, which are defined as constants.
592
+
593
+ `Jekyll_plugin_support` provides a method that allows
594
+ a constant to be redefined, called `redef_without_warning`.
595
+ Use it in a subclass like this:
596
+
597
+ ```ruby
598
+ redef_without_warning :PLUGIN_NAME, 'my_plugin'.freeze
599
+ redef_without_warning :VERSION, '0.1.0'.freeze
600
+ ```
601
+
587
602
 
588
603
  ## Development
589
604
 
@@ -1,13 +1,10 @@
1
- require_relative 'jekyll_plugin_error_handling'
1
+ require_relative '../error/jekyll_plugin_error_handling'
2
2
 
3
3
  module JekyllSupport
4
4
  # Base class for Jekyll block tags
5
5
  class JekyllBlock < Liquid::Block
6
6
  attr_reader :argument_string, :helper, :line_number, :logger, :page, :site, :text
7
7
 
8
- include JekyllSupportErrorHandling
9
- extend JekyllSupportErrorHandling
10
-
11
8
  # See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
12
9
  # @param tag_name [String] the name of the tag, which we usually know.
13
10
  # @param argument_string [String] the arguments passed to the tag, as a single string.
@@ -26,7 +23,7 @@ module JekyllSupport
26
23
  @helper = JekyllPluginHelper.new tag_name, markup, @logger, respond_to?(:no_arg_parsing)
27
24
 
28
25
  @error_name = "#{tag_name.camelcase(:upper)}Error"
29
- Jekyll::CustomError.factory @error_name
26
+ JekyllSupport::CustomError.factory @error_name
30
27
  end
31
28
 
32
29
  # Liquid::Block subclasses do not render if there is no content within the tag
@@ -49,6 +46,8 @@ module JekyllSupport
49
46
 
50
47
  @config = @site.config
51
48
  @tag_config = @config[@tag_name]
49
+ @jps = @config['jekyll_plugin_support']
50
+ @pry_on_standard_error = @jps['pry_on_standard_error'] || false if @jps
52
51
 
53
52
  set_error_context
54
53
 
@@ -56,7 +55,8 @@ module JekyllSupport
56
55
  @paginator = @envs[:paginator]
57
56
  @theme = @envs[:theme]
58
57
 
59
- @mode = @config['env']&.key?('JEKYLL_ENV') ? @config['env']['JEKYLL_ENV'] : 'development'
58
+ env = @config['env']
59
+ @mode = env&.key?('JEKYLL_ENV') ? env['JEKYLL_ENV'] : 'development'
60
60
 
61
61
  @helper.reinitialize @markup.strip
62
62
 
@@ -69,13 +69,13 @@ module JekyllSupport
69
69
  render_impl(text)
70
70
  rescue StandardError => e
71
71
  e.shorten_backtrace
72
- @logger.error { "#{e.class} on line #{@line_number} of #{e.backtrace[0].split(':').first} by #{tag_name} - #{e.message}" }
72
+ @logger.error { "#{e.class} on line #{@line_number} of #{e.backtrace[0].split(':').first} by #{@tag_name} - #{e.message}" }
73
73
  binding.pry if @pry_on_standard_error # rubocop:disable Lint/Debugger
74
74
  raise e if @die_on_standard_error
75
75
 
76
76
  <<~END_MSG
77
77
  <div class='standard_error'>
78
- #{e.class} on line #{@line_number} of #{e.backtrace[0].split(':').first} by #{tag_name}: #{e.message}
78
+ #{e.class} on line #{@line_number} of #{e.backtrace[0].split(':').first} by #{@tag_name}: #{e.message}
79
79
  </div>
80
80
  END_MSG
81
81
  end
@@ -1,12 +1,9 @@
1
- require_relative 'jekyll_plugin_error_handling'
1
+ require_relative '../error/jekyll_plugin_error_handling'
2
2
 
3
3
  module JekyllSupport
4
4
  class JekyllBlockNoArgParsing < JekyllBlock
5
5
  attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
6
6
 
7
- include JekyllSupportErrorHandling
8
- extend JekyllSupportErrorHandling
9
-
10
7
  def initialize(tag_name, argument_string, parse_context)
11
8
  class << self
12
9
  include NoArgParsing
@@ -20,6 +17,12 @@ module JekyllSupport
20
17
  JekyllSupport.error_short_trace(@logger, e)
21
18
  end
22
19
 
20
+ # Liquid::Block subclasses do not render if there is no content within the tag
21
+ # This override fixes that
22
+ def blank?
23
+ false
24
+ end
25
+
23
26
  # Jekyll plugins must override this method, not render, so their plugin can be tested more easily
24
27
  # The following variables are predefined:
25
28
  # @argument_string, @config, @envs, @helper, @layout, @logger, @mode, @page, @paginator, @site, @tag_name and @theme
@@ -1,7 +1,7 @@
1
1
  require 'facets/string/camelcase'
2
2
  require 'facets/string/snakecase'
3
3
 
4
- module Jekyll
4
+ module JekyllSupport
5
5
  # Use like this:
6
6
  # CustomError.new(:MyError, 'blah', 'asdf')
7
7
  class CustomError < StandardError
@@ -1,4 +1,4 @@
1
- module JekyllSupportErrorHandling
1
+ module JekyllSupport
2
2
  attr_reader :logger, :page
3
3
 
4
4
  # If a Jekyll plugin needs to crash exit, and stop Jekyll, call this method.
@@ -0,0 +1,75 @@
1
+ require 'jekyll'
2
+ require_relative '../error/jekyll_plugin_error_handling'
3
+
4
+ module JekyllSupport
5
+ # Base class for Jekyll generators.
6
+ # PluginMetaLogger.instance.config is populated with the contents of `_config.yml` before Jekyll::Generator instances run.
7
+ class JekyllGenerator < Jekyll::Generator
8
+ attr_reader :helper, :line_number, :logger, :site
9
+
10
+ # Method prescribed by the Jekyll plugin lifecycle.
11
+ # Defines @config, @envs, @mode and @site
12
+ # @return [void]
13
+ def generate(site)
14
+ @logger ||= PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
15
+
16
+ @error_name = "#{self.class.name}Error"
17
+ # JekyllSupport::CustomError.factory @error_name
18
+
19
+ @site = site
20
+ @config = @site.config
21
+ @envs = site.config['env']
22
+ @theme = @site.theme
23
+
24
+ @mode = ENV['JEKYLL_ENV'] || 'development'
25
+
26
+ # set_error_context(self.class)
27
+
28
+ generate_impl
29
+ rescue StandardError => e
30
+ e.shorten_backtrace
31
+ @logger.error { "#{e.class} on line #{@line_number} of #{e.backtrace[0].split(':').first} by #{self.class.name} - #{e.message}" }
32
+ binding.pry if @pry_on_standard_error # rubocop:disable Lint/Debugger
33
+ raise e if @die_on_standard_error
34
+
35
+ <<~END_MSG
36
+ <div class='standard_error'>
37
+ #{e.class} on line #{@line_number} of #{e.backtrace[0].split(':').first} by #{self.class.name}: #{e.message}
38
+ </div>
39
+ END_MSG
40
+ end
41
+
42
+ # Jekyll plugins should override this method, not `generate`, so they can be tested more easily.
43
+ # The following variables are predefined:
44
+ # @config, @envs, @helper, @logger, @mode, @paginator, @site and @theme
45
+ # @return [void]
46
+ def generate_impl; end
47
+
48
+ def self.register(klass)
49
+ abort("Error: The #{klass.name} plugin does not define VERSION") \
50
+ unless klass.const_defined?(:VERSION)
51
+
52
+ version = klass.const_get(:VERSION)
53
+ error_name_stub = klass.name.include?('::') ? klass.name.split('::')[1] : klass.name
54
+ error_ruby_class_name = "#{error_name_stub.camelcase(:upper)}Error"
55
+ error_css_class_name = error_ruby_class_name.split('::').last.snakecase
56
+ msg = <<~END_MSG
57
+ Loaded generator plugin #{klass.name} v#{version}. It has:
58
+ Error class: #{@error_name}
59
+ CSS class for error messages: #{error_css_class_name}
60
+ END_MSG
61
+
62
+ PluginMetaLogger.instance.info { msg }
63
+ end
64
+
65
+ def set_error_context(klass)
66
+ return unless Object.const_defined? @error_name
67
+
68
+ error_class = Object.const_get @error_name
69
+ error_class.class_variable_set(:@@argument_string, @argument_string)
70
+ error_class.class_variable_set(:@@line_number, @line_number)
71
+ error_class.class_variable_set(:@@path, @page['path'])
72
+ error_class.class_variable_set(:@@tag_name, klass.name)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,112 @@
1
+ require 'facets/string/interpolate'
2
+ require 'key_value_parser'
3
+ require 'shellwords'
4
+ require_relative 'jekyll_plugin_helper_class'
5
+ require_relative 'jekyll_plugin_helper_attribution'
6
+
7
+ module JekyllSupport
8
+ class JekyllPluginHelper
9
+ attr_accessor :liquid_context
10
+ attr_reader :argv, :attribution, :keys_values, :logger, :markup, :no_arg_parsing, :params, :tag_name,
11
+ :argv_original, :excerpt_caller, :keys_values_original, :params_original, :jpsh_subclass_caller
12
+
13
+ # See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
14
+ # @param tag_name [String] the name of the tag, which we already know.
15
+ # @param argument_string [String] the arguments from the tag, as a single string.
16
+ # @param parse_context [Liquid::ParseContext] hash that stores Liquid options.
17
+ # By default it has two keys: :locale and :line_numbers, the first is a Liquid::I18n object, and the second,
18
+ # a boolean parameter that determines if error messages should display the line number the error occurred.
19
+ # This argument is used mostly to display localized error messages on Liquid built-in Tags and Filters.
20
+ # See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
21
+ # @return [void]
22
+ def initialize(tag_name, markup, logger, no_arg_parsing)
23
+ @tag_name = tag_name
24
+ @logger = logger
25
+ @no_arg_parsing = no_arg_parsing
26
+ @markup = markup
27
+ rescue StandardError => e
28
+ e.shorten_backtrace
29
+ @logger.error { e.message }
30
+ end
31
+
32
+ # @return undefined if parameter was specified, removes it from the available tokens and returns value
33
+ def parameter_specified?(name, delete_param: true)
34
+ return false if @keys_values.to_s.empty?
35
+
36
+ key = name
37
+ key = name.to_sym if @keys_values&.first&.first.instance_of?(Symbol)
38
+ value = @keys_values[key]
39
+ delete_parameter(name) if delete_param
40
+ value
41
+ end
42
+
43
+ def reinitialize(markup)
44
+ # @keys_values was a Hash[Symbol, String|Boolean] but now it is Hash[String, String|Boolean]
45
+ @markup = markup
46
+ if @no_arg_parsing
47
+ define_singleton_method(:argv) { warn_fetch :argv }
48
+ define_singleton_method(:keys_values) { warn_fetch :keys_values }
49
+ define_singleton_method(:params) { warn_fetch :params }
50
+ define_singleton_method(:parameter_specified?) { |_name| warn_parse(:parameter_specified?) }
51
+ define_singleton_method(:delete_parameter) { |_name| warn_parse(:delete_parameter) }
52
+ @attribution = false
53
+ else
54
+ parse markup
55
+ @attribution = parameter_specified?('attribution') || false
56
+ @logger.debug { "@keys_values='#{@keys_values}'" }
57
+ end
58
+ end
59
+
60
+ # Call this method to return the remaining markup after `parameter_specified?` has been invoked.
61
+ def remaining_markup
62
+ @argv&.join(' ')
63
+ end
64
+
65
+ def remaining_markup_original
66
+ @argv_original&.join(' ')
67
+ end
68
+
69
+ def warn_fetch(variable)
70
+ abort "Error: Argument parsing was suppressed, but an attempt to obtain the value of #{variable} was made"
71
+ end
72
+
73
+ def warn_parse(meth)
74
+ abort "Error: Argument parsing was suppressed, but an attempt to invoke #{meth} was made"
75
+ end
76
+
77
+ def delete_parameter(key)
78
+ return if @keys_values.empty? || @params.nil?
79
+
80
+ @params.delete(key)
81
+ @argv.delete_if { |x| x == key or x.start_with?("#{key}=") }
82
+ @keys_values.delete(key)
83
+
84
+ @params_original.delete(key)
85
+ @argv_original.delete_if { |x| x == key or x.start_with?("#{key}=") }
86
+ @keys_values_original.delete(key)
87
+ end
88
+
89
+ private
90
+
91
+ def page
92
+ @liquid_context.registers[:page]
93
+ end
94
+
95
+ def parse(markup)
96
+ @argv_original = Shellwords.split(markup)
97
+ @keys_values_original = KeyValueParser
98
+ .new({}, { array_values: false, normalize_keys: false, separator: /=/ })
99
+ .parse(@argv_original)
100
+ @params_original = @keys_values_original unless respond_to?(:no_arg_parsing) && no_arg_parsing
101
+
102
+ @argv = Shellwords.split(self.class.expand_env(markup, logger))
103
+ @keys_values = KeyValueParser
104
+ .new({}, { array_values: false, normalize_keys: false, separator: /=/ })
105
+ .parse(@argv)
106
+
107
+ return if respond_to?(:no_arg_parsing) && no_arg_parsing
108
+
109
+ @params = @keys_values # TODO: @keys_values should be deleted
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,74 @@
1
+ module JekyllSupport
2
+ # Attribution aspect of JekyllPluginHelper
3
+ class JekyllPluginHelper
4
+ # @param file must be a fully qualified file name that points to a file within a gem.
5
+ # @return Gem::Specification of gem that file points into, or nil if not called from a gem
6
+ # See https://stackoverflow.com/a/75890279/553865
7
+ def self.current_spec(file)
8
+ abort 'JekyllPluginHelper::current_spec: file is nil' if file.nil?
9
+ return nil unless File.exist?(file)
10
+
11
+ searcher = if Gem::Specification.respond_to?(:find)
12
+ Gem::Specification
13
+ elsif Gem.respond_to?(:searcher)
14
+ Gem.searcher.init_gemspecs
15
+ end
16
+
17
+ searcher&.find do |spec|
18
+ file.start_with? spec.full_gem_path
19
+ end
20
+ end
21
+
22
+ def attribute
23
+ return unless @current_gem
24
+
25
+ <<~END_OUTPUT
26
+ <div id="jps_attribute_#{rand(999_999)}" class="jps_attribute">
27
+ <div>
28
+ <a href="#{@homepage}" target="_blank" rel="nofollow">
29
+ #{attribution_string}
30
+ </a>
31
+ </div>
32
+ </div>
33
+ END_OUTPUT
34
+ end
35
+
36
+ def default_attribution
37
+ authors = @authors&.join(', ')
38
+ result = "Generated by the \#{@name} v\#{@version} Jekyll plugin"
39
+ result << ", written by #{authors}" if authors
40
+ result << " \#{@published_date}" if @published_date
41
+ result << '.'
42
+ result
43
+ end
44
+
45
+ # Sets @current_gem if file points at a uniquely named file within a gem.
46
+ # @param file must be a fully qualified file name in a gem, for example: __FILE__
47
+ def gem_file(file)
48
+ @current_gem = JekyllPluginHelper.current_spec file
49
+ @logger.debug "No gem found for '#{file} was found." unless @current_gem
50
+ annotate_globals if @attribution && @current_gem
51
+ end
52
+
53
+ private
54
+
55
+ def annotate_globals
56
+ return unless @current_gem
57
+
58
+ @name = @current_gem.name
59
+ @authors = @current_gem.authors
60
+ @homepage = @current_gem.homepage
61
+ @published_date = @current_gem.date.to_date.to_s
62
+ @version = @current_gem.version
63
+ end
64
+
65
+ def attribution_string
66
+ string = if @attribution == true
67
+ default_attribution
68
+ else
69
+ @attribution
70
+ end
71
+ String.interpolate { string }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,83 @@
1
+ require 'facets/string/camelcase'
2
+ require 'facets/string/snakecase'
3
+ require 'yaml'
4
+
5
+ module JekyllSupport
6
+ # Class methods for JekyllPluginHelper
7
+ class JekyllPluginHelper
8
+ # Expand an environment variable reference
9
+ def self.expand_env(str, logger = nil, die_if_undefined: false)
10
+ str&.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}/) do
11
+ envar = Regexp.last_match(1)
12
+ unless ENV.key? envar
13
+ msg = "jekyll_plugin_support error: environment variable #{envar} is undefined"
14
+ raise JekyllPluginSupportError, msg.red, [] if die_if_undefined
15
+
16
+ if logger
17
+ logger.warn msg
18
+ else
19
+ puts msg.red
20
+ end
21
+ end
22
+ ENV.fetch(envar, nil)
23
+ end
24
+ end
25
+
26
+ def self.generate_message(klass, tag_name, version)
27
+ error_name_stub = klass.name.include?('::') ? klass.name.split('::')[1] : klass.name
28
+ error_ruby_class_name = "#{error_name_stub.camelcase(:upper)}Error"
29
+ config_die_key = "die_on_#{error_ruby_class_name.snakecase}"
30
+ error_css_class_name = error_ruby_class_name.split('::').last.snakecase
31
+ config = YAML.load_file('_config.yml')
32
+ tag_config = config[tag_name]
33
+ tag_config_msg = if tag_config.nil?
34
+ <<~END_MSG
35
+ _config.yml does not contain configuration information for this plugin.
36
+ You could add a section containing default values by specifying a section for the tag name,
37
+ and an entry whose name starts with `die_on_`, followed by a snake_case version of the error name.
38
+
39
+ #{tag_name}:
40
+ #{config_die_key}: false
41
+ END_MSG
42
+ else
43
+ <<~END_MSG
44
+ _config.yml contains the following configuration for this plugin:
45
+ #{tag_config}
46
+ END_MSG
47
+ end
48
+
49
+ <<~END_MSG
50
+ Loaded plugin #{tag_name} v#{version}. It has:
51
+ Error class: #{error_ruby_class_name}
52
+ CSS class for error messages: #{error_css_class_name}
53
+
54
+ #{tag_config_msg}
55
+ END_MSG
56
+ end
57
+
58
+ def self.register(klass, tag_name)
59
+ abort("Error: The #{tag_name} plugin does not define VERSION") \
60
+ unless klass.const_defined?(:VERSION)
61
+
62
+ version = klass.const_get(:VERSION)
63
+
64
+ abort("Error: The #{tag_name} plugin is not an instance of JekyllSupport::JekyllBlock or JekyllSupport::JekyllTag") \
65
+ unless klass.instance_of?(Class) &&
66
+ (klass.ancestors.include?(JekyllSupport::JekyllBlock) ||
67
+ klass.ancestors.include?(JekyllSupport::JekyllTag))
68
+
69
+ Liquid::Template.register_tag(tag_name, klass)
70
+ msg = generate_message(klass, tag_name, version)
71
+ PluginMetaLogger.instance.info { msg }
72
+ end
73
+
74
+ def self.remove_html_tags(string)
75
+ string.gsub(/<[^>]*>/, '').strip
76
+ end
77
+
78
+ # strip leading and trailing quotes if present
79
+ def self.remove_quotes(string)
80
+ string.strip.gsub(/\A'|\A"|'\Z|"\Z/, '').strip if string
81
+ end
82
+ end
83
+ end
@@ -1,4 +1,4 @@
1
- require_relative 'jekyll_custom_error'
1
+ require_relative '../error/jekyll_custom_error'
2
2
 
3
3
  # Monkey patch StandardError so a new method called shorten_backtrace is added.
4
4
  class StandardError
@@ -22,7 +22,7 @@ module JekyllSupport
22
22
 
23
23
  # @return a new StandardError subclass containing the shorten_backtrace method
24
24
  def define_error
25
- Class.new Jekyll::CustomError
25
+ Class.new JekyllSupport::CustomError
26
26
  end
27
27
  module_function :define_error
28
28
 
@@ -1,3 +1,3 @@
1
1
  module JekyllPluginSupportVersion
2
- VERSION = '0.8.5'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -1,8 +1,19 @@
1
1
  require 'colorator'
2
2
  require 'jekyll'
3
3
  require 'jekyll_plugin_logger'
4
- require_relative 'jekyll_plugin_helper'
5
- require_relative 'jekyll_plugin_support/version'
4
+
5
+ def require_directory(dir)
6
+ Dir[File.join(dir, '*.rb')].sort.each do |file|
7
+ require file unless file == __FILE__
8
+ end
9
+ end
10
+
11
+ module JekyllSupport
12
+ def self.redef_without_warning(const, value)
13
+ send(:remove_const, const) if const_defined?(const)
14
+ const_set const, value
15
+ end
16
+ end
6
17
 
7
18
  module NoArgParsing
8
19
  attr_accessor :no_arg_parsing
@@ -10,9 +21,10 @@ module NoArgParsing
10
21
  @no_arg_parsing = true
11
22
  end
12
23
 
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'
18
- require_relative 'jekyll_custom_error'
24
+ require_directory __dir__
25
+ require_directory "#{__dir__}/block"
26
+ require_directory "#{__dir__}/error"
27
+ require_directory "#{__dir__}/generator"
28
+ require_directory "#{__dir__}/helper"
29
+ require_directory "#{__dir__}/jekyll_plugin_support"
30
+ require_directory "#{__dir__}/tag"
@@ -1,14 +1,11 @@
1
1
  require 'pry'
2
- require_relative 'jekyll_plugin_error_handling'
2
+ require_relative '../error/jekyll_plugin_error_handling'
3
3
 
4
4
  module JekyllSupport
5
5
  # Base class for Jekyll tags
6
6
  class JekyllTag < Liquid::Tag
7
7
  attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
8
8
 
9
- include JekyllSupportErrorHandling
10
- extend JekyllSupportErrorHandling
11
-
12
9
  # See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
13
10
  # @param tag_name [String] the name of the tag, which we usually know.
14
11
  # @param argument_string [String] the arguments passed to the tag, as a single string.
@@ -29,7 +26,7 @@ module JekyllSupport
29
26
  @helper = JekyllPluginHelper.new(tag_name, @argument_string, @logger, respond_to?(:no_arg_parsing))
30
27
 
31
28
  @error_name = "#{tag_name.camelcase(:upper)}Error"
32
- Jekyll::CustomError.factory @error_name
29
+ JekyllSupport::CustomError.factory @error_name
33
30
  end
34
31
 
35
32
  # Method prescribed by the Jekyll plugin lifecycle.
@@ -55,10 +52,11 @@ module JekyllSupport
55
52
  @paginator = @envs[:paginator]
56
53
  @theme = @envs[:theme]
57
54
 
58
- @mode = @config['env']&.key?('JEKYLL_ENV') ? @config['env']['JEKYLL_ENV'] : 'development'
55
+ env = @config['env']
56
+ @mode = env&.key?('JEKYLL_ENV') ? env['JEKYLL_ENV'] : 'development'
59
57
 
60
58
  markup = JekyllSupport.lookup_liquid_variables liquid_context, @argument_string
61
- @helper.reinitialize markup
59
+ @helper.reinitialize markup.strip
62
60
 
63
61
  render_impl
64
62
  rescue StandardError => e
@@ -1,12 +1,9 @@
1
- require_relative 'jekyll_plugin_error_handling'
1
+ require_relative '../error/jekyll_plugin_error_handling'
2
2
 
3
3
  module JekyllSupport
4
4
  class JekyllTagNoArgParsing < JekyllTag
5
5
  attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
6
6
 
7
- include JekyllSupportErrorHandling
8
- extend JekyllSupportErrorHandling
9
-
10
7
  def initialize(tag_name, argument_string, parse_context)
11
8
  class << self
12
9
  include NoArgParsing
@@ -17,7 +17,7 @@ class CustomErrorSpec
17
17
  raise AnError, 'Oops'
18
18
  rescue AnError => e
19
19
  puts "Caught AnError: #{e.message}"
20
- rescue Jekyll::CustomError => e
20
+ rescue JekyllSupport::CustomError => e
21
21
  puts "Caught CustomError: #{e.message}"
22
22
  end
23
23
 
@@ -31,7 +31,7 @@ class CustomErrorSpec
31
31
  end
32
32
  end
33
33
 
34
- RSpec.describe Jekyll::CustomError do
34
+ RSpec.describe JekyllSupport::CustomError do
35
35
  it 'can create custom errors' do
36
36
  expect { raise AnError, 'Oops' }.to raise_error(AnError)
37
37
  end
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.8.5
4
+ version: 1.0.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: 2024-03-25 00:00:00.000000000 Z
11
+ date: 2024-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: facets
@@ -94,19 +94,20 @@ files:
94
94
  - README.md
95
95
  - Rakefile
96
96
  - jekyll_plugin_support.gemspec
97
- - lib/jekyll_custom_error.rb
98
- - lib/jekyll_plugin_error_handling.rb
99
- - lib/jekyll_plugin_helper.rb
100
- - lib/jekyll_plugin_helper_attribution.rb
101
- - lib/jekyll_plugin_helper_class.rb
97
+ - lib/block/jekyll_plugin_support_block.rb
98
+ - lib/block/jekyll_plugin_support_block_noarg.rb
99
+ - lib/error/jekyll_custom_error.rb
100
+ - lib/error/jekyll_plugin_error_handling.rb
101
+ - lib/generator/jekyll_plugin_support_generator.rb
102
+ - lib/helper/jekyll_plugin_helper.rb
103
+ - lib/helper/jekyll_plugin_helper_attribution.rb
104
+ - lib/helper/jekyll_plugin_helper_class.rb
102
105
  - lib/jekyll_plugin_support.rb
106
+ - lib/jekyll_plugin_support/jekyll_plugin_support_class.rb
107
+ - lib/jekyll_plugin_support/jekyll_plugin_support_spec_support.rb
103
108
  - lib/jekyll_plugin_support/version.rb
104
- - lib/jekyll_plugin_support_block.rb
105
- - lib/jekyll_plugin_support_block_noarg.rb
106
- - lib/jekyll_plugin_support_class.rb
107
- - lib/jekyll_plugin_support_spec_support.rb
108
- - lib/jekyll_plugin_support_tag.rb
109
- - lib/jekyll_plugin_support_tag_noarg.rb
109
+ - lib/tag/jekyll_plugin_support_tag.rb
110
+ - lib/tag/jekyll_plugin_support_tag_noarg.rb
110
111
  - spec/custom_error_spec.rb
111
112
  - spec/jekyll_plugin_helper_options_spec.rb
112
113
  - spec/liquid_variable_parsing_spec.rb
@@ -139,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
140
  - !ruby/object:Gem::Version
140
141
  version: '0'
141
142
  requirements: []
142
- rubygems_version: 3.5.6
143
+ rubygems_version: 3.5.16
143
144
  signing_key:
144
145
  specification_version: 4
145
146
  summary: Provides a framework for writing and testing Jekyll plugins
@@ -1,110 +0,0 @@
1
- require 'facets/string/interpolate'
2
- require 'key_value_parser'
3
- require 'shellwords'
4
- require_relative 'jekyll_plugin_helper_class'
5
- require_relative 'jekyll_plugin_helper_attribution'
6
-
7
- class JekyllPluginHelper
8
- attr_accessor :liquid_context
9
- attr_reader :argv, :attribution, :keys_values, :logger, :markup, :no_arg_parsing, :params, :tag_name,
10
- :argv_original, :excerpt_caller, :keys_values_original, :params_original, :jpsh_subclass_caller
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 already know.
14
- # @param argument_string [String] the arguments from 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, logger, no_arg_parsing)
22
- @tag_name = tag_name
23
- @logger = logger
24
- @no_arg_parsing = no_arg_parsing
25
- @markup = markup
26
- rescue StandardError => e
27
- e.shorten_backtrace
28
- @logger.error { e.message }
29
- end
30
-
31
- # @return undefined if parameter was specified, removes it from the available tokens and returns value
32
- def parameter_specified?(name, delete_param: true)
33
- return false if @keys_values.to_s.empty?
34
-
35
- key = name
36
- key = name.to_sym if @keys_values&.first&.first.instance_of?(Symbol)
37
- value = @keys_values[key]
38
- delete_parameter(name) if delete_param
39
- value
40
- end
41
-
42
- def reinitialize(markup)
43
- # @keys_values was a Hash[Symbol, String|Boolean] but now it is Hash[String, String|Boolean]
44
- @markup = markup
45
- if @no_arg_parsing
46
- define_singleton_method(:argv) { warn_fetch :argv }
47
- define_singleton_method(:keys_values) { warn_fetch :keys_values }
48
- define_singleton_method(:params) { warn_fetch :params }
49
- define_singleton_method(:parameter_specified?) { |_name| warn_parse(:parameter_specified?) }
50
- define_singleton_method(:delete_parameter) { |_name| warn_parse(:delete_parameter) }
51
- @attribution = false
52
- else
53
- parse markup
54
- @attribution = parameter_specified?('attribution') || false
55
- @logger.debug { "@keys_values='#{@keys_values}'" }
56
- end
57
- end
58
-
59
- # Call this method to return the remaining markup after `parameter_specified?` has been invoked.
60
- def remaining_markup
61
- @argv&.join(' ')
62
- end
63
-
64
- def remaining_markup_original
65
- @argv_original&.join(' ')
66
- end
67
-
68
- def warn_fetch(variable)
69
- abort "Error: Argument parsing was suppressed, but an attempt to obtain the value of #{variable} was made"
70
- end
71
-
72
- def warn_parse(meth)
73
- abort "Error: Argument parsing was suppressed, but an attempt to invoke #{meth} was made"
74
- end
75
-
76
- def delete_parameter(key)
77
- return if @keys_values.empty? || @params.nil?
78
-
79
- @params.delete(key)
80
- @argv.delete_if { |x| x == key or x.start_with?("#{key}=") }
81
- @keys_values.delete(key)
82
-
83
- @params_original.delete(key)
84
- @argv_original.delete_if { |x| x == key or x.start_with?("#{key}=") }
85
- @keys_values_original.delete(key)
86
- end
87
-
88
- private
89
-
90
- def page
91
- @liquid_context.registers[:page]
92
- end
93
-
94
- def parse(markup)
95
- @argv_original = Shellwords.split(markup)
96
- @keys_values_original = KeyValueParser
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
100
-
101
- @argv = Shellwords.split(self.class.expand_env(markup, logger))
102
- @keys_values = KeyValueParser
103
- .new({}, { array_values: false, normalize_keys: false, separator: /=/ })
104
- .parse(@argv)
105
-
106
- return if respond_to?(:no_arg_parsing) && no_arg_parsing
107
-
108
- @params = @keys_values # TODO: @keys_values should be deleted
109
- end
110
- end
@@ -1,72 +0,0 @@
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
@@ -1,81 +0,0 @@
1
- require 'facets/string/camelcase'
2
- require 'facets/string/snakecase'
3
- require 'yaml'
4
-
5
- # Class methods for JekyllPluginHelper
6
- class JekyllPluginHelper
7
- # Expand an environment variable reference
8
- def self.expand_env(str, logger = nil, die_if_undefined: false)
9
- str&.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}/) do
10
- envar = Regexp.last_match(1)
11
- unless ENV.key? envar
12
- msg = "jekyll_plugin_support error: environment variable #{envar} is undefined"
13
- raise JekyllPluginSupportError, msg.red, [] if die_if_undefined
14
-
15
- if logger
16
- logger.warn msg
17
- else
18
- puts msg.red
19
- end
20
- end
21
- ENV.fetch(envar, nil)
22
- end
23
- end
24
-
25
- def self.generate_message(klass, tag_name, version)
26
- error_name_stub = klass.name.include?('::') ? klass.name.split('::')[1] : klass.name
27
- error_ruby_class_name = "#{error_name_stub.camelcase(:upper)}Error"
28
- config_die_key = "die_on_#{error_ruby_class_name.snakecase}"
29
- error_css_class_name = error_ruby_class_name.split('::').last.snakecase
30
- config = YAML.load_file('_config.yml')
31
- tag_config = config[tag_name]
32
- tag_config_msg = if tag_config.nil?
33
- <<~END_MSG
34
- _config.yml does not contain configuration information for this plugin.
35
- You could add a section containing default values by specifying a section for the tag name,
36
- and an entry whose name starts with `die_on_`, followed by a snake_case version of the error name.
37
-
38
- #{tag_name}:
39
- #{config_die_key}: false
40
- END_MSG
41
- else
42
- <<~END_MSG
43
- _config.yml contains the following configuration for this plugin:
44
- #{tag_config}
45
- END_MSG
46
- end
47
-
48
- <<~END_MSG
49
- Loaded plugin #{tag_name} v#{version}. It has:
50
- Error class: #{error_ruby_class_name}
51
- CSS class for error messages: #{error_css_class_name}
52
-
53
- #{tag_config_msg}
54
- END_MSG
55
- end
56
-
57
- def self.register(klass, tag_name)
58
- abort("Error: The #{tag_name} plugin does not define VERSION") \
59
- unless klass.const_defined?(:VERSION)
60
-
61
- version = klass.const_get(:VERSION)
62
-
63
- abort("Error: The #{tag_name} plugin is not an instance of JekyllSupport::JekyllBlock or JekyllSupport::JekyllTag") \
64
- unless klass.instance_of?(Class) &&
65
- (klass.ancestors.include?(JekyllSupport::JekyllBlock) ||
66
- klass.ancestors.include?(JekyllSupport::JekyllTag))
67
-
68
- Liquid::Template.register_tag(tag_name, klass)
69
- msg = generate_message(klass, tag_name, version)
70
- PluginMetaLogger.instance.info { msg }
71
- end
72
-
73
- def self.remove_html_tags(string)
74
- string.gsub(/<[^>]*>/, '').strip
75
- end
76
-
77
- # strip leading and trailing quotes if present
78
- def self.remove_quotes(string)
79
- string.strip.gsub(/\A'|\A"|'\Z|"\Z/, '').strip if string
80
- end
81
- end