jekyll_plugin_support 1.0.3 → 3.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 +4 -4
- data/.rubocop.yml +17 -1
- data/CHANGELOG.md +33 -6
- data/README.md +787 -30
- data/jekyll_plugin_support.gemspec +13 -11
- data/lib/block/jekyll_plugin_support_block.rb +31 -15
- data/lib/block/jekyll_plugin_support_block_noarg.rb +0 -2
- data/lib/generator/jekyll_plugin_support_generator.rb +1 -7
- data/lib/helper/jekyll_plugin_helper.rb +6 -6
- data/lib/helper/jekyll_plugin_helper_class.rb +8 -3
- data/lib/hooks/a_page.rb +69 -0
- data/lib/hooks/all_collections_hooks.rb +61 -0
- data/lib/hooks/all_files.rb +48 -0
- data/lib/hooks/class_methods.rb +50 -0
- data/lib/jekyll_all_collections/all_collections_tag.rb +157 -0
- data/lib/jekyll_plugin_support/jekyll_plugin_support_class.rb +61 -28
- data/lib/jekyll_plugin_support/jekyll_plugin_support_spec_support.rb +1 -3
- data/lib/jekyll_plugin_support/version.rb +1 -1
- data/lib/jekyll_plugin_support.rb +18 -13
- data/lib/tag/jekyll_plugin_support_tag.rb +26 -15
- data/lib/tag/jekyll_plugin_support_tag_noarg.rb +0 -2
- data/lib/util/mslinn_binary_search.rb +152 -0
- data/lib/util/send_chain.rb +56 -0
- data/spec/all_collections_tag/all_collections_tag_sort_spec.rb +112 -0
- data/spec/bsearch_spec.rb +50 -0
- data/spec/custom_error_spec.rb +9 -9
- data/spec/date_sort_spec.rb +84 -0
- data/spec/jekyll_plugin_helper_options_spec.rb +7 -3
- data/spec/liquid_variable_parsing_spec.rb +8 -8
- data/spec/mslinn_binary_search_spec.rb +47 -0
- data/spec/send_chain_spec.rb +72 -0
- data/spec/send_spec.rb +28 -0
- data/spec/sorted_lru_files_spec.rb +82 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/status_persistence.txt +3 -9
- data/spec/testable_spec.rb +38 -0
- metadata +42 -5
@@ -3,12 +3,12 @@ require_relative 'lib/jekyll_plugin_support/version'
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
github = 'https://github.com/mslinn/jekyll_plugin_support'
|
5
5
|
|
6
|
-
spec.bindir
|
7
|
-
spec.authors
|
8
|
-
spec.email
|
9
|
-
spec.files
|
6
|
+
spec.bindir = 'exe'
|
7
|
+
spec.authors = ['Mike Slinn']
|
8
|
+
spec.email = ['mslinn@mslinn.com']
|
9
|
+
spec.files = Dir['.rubocop.yml', 'LICENSE.*', 'Rakefile', '{lib,spec}/**/*', '*.gemspec', '*.md']
|
10
10
|
spec.homepage = 'https://www.mslinn.com/jekyll_plugins/jekyll_plugin_support.html'
|
11
|
-
spec.license
|
11
|
+
spec.license = 'MIT'
|
12
12
|
spec.metadata = {
|
13
13
|
'allowed_push_host' => 'https://rubygems.org',
|
14
14
|
'bug_tracker_uri' => "#{github}/issues",
|
@@ -16,21 +16,23 @@ Gem::Specification.new do |spec|
|
|
16
16
|
'homepage_uri' => spec.homepage,
|
17
17
|
'source_code_uri' => github,
|
18
18
|
}
|
19
|
-
spec.name
|
19
|
+
spec.name = 'jekyll_plugin_support'
|
20
|
+
spec.platform = Gem::Platform::RUBY
|
20
21
|
spec.post_install_message = <<~END_MESSAGE
|
21
22
|
|
22
23
|
Thanks for installing #{spec.name}!
|
23
24
|
|
24
25
|
END_MESSAGE
|
25
|
-
spec.require_paths
|
26
|
+
spec.require_paths = ['lib']
|
26
27
|
spec.required_ruby_version = '>= 2.6.0'
|
27
|
-
spec.summary
|
28
|
-
spec.test_files
|
29
|
-
spec.version
|
28
|
+
spec.summary = 'Provides a framework for writing and testing Jekyll plugins'
|
29
|
+
spec.test_files = spec.files.grep %r{^(test|spec|features)/}
|
30
|
+
spec.version = JekyllPluginSupportVersion::VERSION
|
30
31
|
|
31
32
|
spec.add_dependency 'facets'
|
32
|
-
spec.add_dependency 'jekyll', '>=
|
33
|
+
spec.add_dependency 'jekyll', '>= 4.4.1'
|
33
34
|
spec.add_dependency 'jekyll_plugin_logger'
|
34
35
|
spec.add_dependency 'key-value-parser'
|
35
36
|
spec.add_dependency 'pry'
|
37
|
+
spec.add_dependency 'sorted_set'
|
36
38
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require_relative '../error/jekyll_plugin_error_handling'
|
2
|
-
|
3
1
|
module JekyllSupport
|
4
2
|
# Base class for Jekyll block tags
|
5
3
|
class JekyllBlock < Liquid::Block
|
@@ -8,22 +6,34 @@ module JekyllSupport
|
|
8
6
|
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
9
7
|
# @param tag_name [String] the name of the tag, which we usually know.
|
10
8
|
# @param argument_string [String] the arguments passed to the tag, as a single string.
|
11
|
-
# @param parse_context [Liquid::ParseContext]
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
9
|
+
# @param parse_context [Liquid::ParseContext] contains the following attributes:
|
10
|
+
# @depth might have the value 0
|
11
|
+
# @error_mode might have the value `:strict`
|
12
|
+
# @line_number duplicates @ptions[:line_number]
|
13
|
+
# @locale duplicates @ptions[:locale]
|
14
|
+
# @options is a hash with the following two keys that holds Liquid options:
|
15
|
+
# :locale is a Liquid::I18n object, used to display localized error messages on Liquid built-in tags and filters.
|
16
|
+
# :line_number is the line number containing the plugin invocation.
|
17
|
+
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
18
|
+
# @partial Boolean, unclear what this indicates
|
19
|
+
# @template_options Replicates @options
|
20
|
+
# @trim_whitespace might have the value `false`
|
21
|
+
# @warnings array
|
16
22
|
# @return [void]
|
17
23
|
def initialize(tag_name, markup, parse_context)
|
18
24
|
super
|
19
25
|
@tag_name = tag_name
|
20
|
-
|
26
|
+
raise JekyllPluginSupportError, "markup is a #{markup.class} with value '#{markup}'." unless markup.instance_of? String
|
27
|
+
|
28
|
+
# Lookup variable names with values in markup in render because site and config are not available here
|
29
|
+
@argument_string = markup # Replace variable names with values in markup in render because site and config are not available here
|
30
|
+
|
21
31
|
@logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
22
32
|
@logger.debug { "#{self.class}: respond_to?(:no_arg_parsing) #{respond_to?(:no_arg_parsing) ? 'yes' : 'no'}." }
|
23
33
|
@helper = JekyllPluginHelper.new tag_name, markup, @logger, respond_to?(:no_arg_parsing)
|
24
34
|
|
25
35
|
@error_name = "#{tag_name.camelcase(:upper)}Error"
|
26
|
-
JekyllSupport::CustomError.factory @error_name
|
36
|
+
::JekyllSupport::CustomError.factory @error_name
|
27
37
|
end
|
28
38
|
|
29
39
|
# Liquid::Block subclasses do not render if there is no content within the tag
|
@@ -36,11 +46,11 @@ module JekyllSupport
|
|
36
46
|
# Defines @config, @envs, @mode, @page and @site
|
37
47
|
# @return [String]
|
38
48
|
def render(liquid_context)
|
39
|
-
@helper.liquid_context = JekyllSupport.
|
49
|
+
@helper.liquid_context = ::JekyllSupport.inject_config_vars liquid_context # modifies liquid_context
|
40
50
|
text = super # Liquid variable values in content are looked up and substituted
|
41
51
|
|
42
52
|
@envs = liquid_context.environments.first
|
43
|
-
@page = liquid_context.registers[:page]
|
53
|
+
@page = liquid_context.registers[:page]
|
44
54
|
@scopes = liquid_context.scopes
|
45
55
|
@site = liquid_context.registers[:site]
|
46
56
|
|
@@ -51,6 +61,7 @@ module JekyllSupport
|
|
51
61
|
|
52
62
|
set_error_context
|
53
63
|
|
64
|
+
# @envs.keys are :content, :highlighter_prefix, :highlighter_suffix, :jekyll, :layout, :page, :paginator, :site, :theme
|
54
65
|
@layout = @envs[:layout]
|
55
66
|
@paginator = @envs[:paginator]
|
56
67
|
@theme = @envs[:theme]
|
@@ -58,18 +69,23 @@ module JekyllSupport
|
|
58
69
|
env = @config['env']
|
59
70
|
@mode = env&.key?('JEKYLL_ENV') ? env['JEKYLL_ENV'] : 'development'
|
60
71
|
|
61
|
-
@helper.
|
72
|
+
@argument_string = JekyllSupport.lookup_liquid_variables @logger, @helper.liquid_context, @argument_string.to_s.strip
|
73
|
+
@helper.reinitialize @argument_string.to_s.strip
|
62
74
|
|
63
75
|
@attribution = @helper.parameter_specified?('attribution') || false unless @no_arg_parsing
|
64
76
|
@logger.debug { "@keys_values='#{@keys_values}'" }
|
65
77
|
|
66
|
-
|
67
|
-
@
|
78
|
+
# @argument_string = JekyllSupport.lookup_liquid_variables @logger, liquid_context, @argument_string # Is this redundant?
|
79
|
+
# @argument_string.strip! # Is this redundant?
|
80
|
+
# @helper.reinitialize @argument_string # Is this redundant?
|
68
81
|
|
69
82
|
render_impl(text)
|
70
83
|
rescue StandardError => e
|
71
84
|
e.shorten_backtrace
|
72
|
-
|
85
|
+
file_name = e.backtrace[0]&.split(':')&.first
|
86
|
+
in_file_name = "in '#{file_name}' " if file_name
|
87
|
+
of_page = "of '#{@page['path']}'" if @page
|
88
|
+
@logger.error { "#{e.class} on line #{@line_number} #{of_page} while processing #{tag_name} #{in_file_name}- #{e.message}" }
|
73
89
|
binding.pry if @pry_on_standard_error # rubocop:disable Lint/Debugger
|
74
90
|
raise e if @die_on_standard_error
|
75
91
|
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'jekyll'
|
2
|
-
require_relative '../error/jekyll_plugin_error_handling'
|
3
|
-
|
4
1
|
module JekyllSupport
|
5
2
|
# Base class for Jekyll generators.
|
6
3
|
# PluginMetaLogger.instance.config is populated with the contents of `_config.yml` before Jekyll::Generator instances run.
|
@@ -14,7 +11,6 @@ module JekyllSupport
|
|
14
11
|
@logger ||= PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
15
12
|
|
16
13
|
@error_name = "#{self.class.name}Error"
|
17
|
-
# JekyllSupport::CustomError.factory @error_name
|
18
14
|
|
19
15
|
@site = site
|
20
16
|
@config = @site.config
|
@@ -23,8 +19,6 @@ module JekyllSupport
|
|
23
19
|
|
24
20
|
@mode = ENV['JEKYLL_ENV'] || 'development'
|
25
21
|
|
26
|
-
# set_error_context(self.class)
|
27
|
-
|
28
22
|
generate_impl
|
29
23
|
rescue StandardError => e
|
30
24
|
e.shorten_backtrace
|
@@ -62,7 +56,7 @@ module JekyllSupport
|
|
62
56
|
PluginMetaLogger.instance.info { msg }
|
63
57
|
end
|
64
58
|
|
65
|
-
def set_error_context(klass)
|
59
|
+
def set_error_context(klass) # rubocop:disable Naming/AccessorMethodName
|
66
60
|
return unless Object.const_defined? @error_name
|
67
61
|
|
68
62
|
error_class = Object.const_get @error_name
|
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'facets/string/interpolate'
|
2
2
|
require 'key_value_parser'
|
3
3
|
require 'shellwords'
|
4
|
-
require_relative 'jekyll_plugin_helper_class'
|
5
|
-
require_relative 'jekyll_plugin_helper_attribution'
|
6
4
|
|
7
5
|
module JekyllSupport
|
8
6
|
class JekyllPluginHelper
|
9
7
|
attr_accessor :liquid_context
|
10
|
-
attr_reader :
|
11
|
-
:
|
8
|
+
attr_reader :argument_string, :argv, :argv_original, :attribution, :excerpt_caller, :keys_values_original,
|
9
|
+
:keys_values, :jpsh_subclass_caller, :logger, :markup, :no_arg_parsing, :params, :params_original,
|
10
|
+
:tag_name
|
12
11
|
|
13
12
|
# See https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tags
|
14
13
|
# @param tag_name [String] the name of the tag, which we already know.
|
@@ -22,8 +21,9 @@ module JekyllSupport
|
|
22
21
|
def initialize(tag_name, markup, logger, no_arg_parsing)
|
23
22
|
@tag_name = tag_name
|
24
23
|
@logger = logger
|
25
|
-
@no_arg_parsing = no_arg_parsing
|
26
24
|
@markup = markup
|
25
|
+
@argument_string = markup
|
26
|
+
@no_arg_parsing = no_arg_parsing
|
27
27
|
rescue StandardError => e
|
28
28
|
e.shorten_backtrace
|
29
29
|
@logger.error { e.message }
|
@@ -42,7 +42,7 @@ module JekyllSupport
|
|
42
42
|
|
43
43
|
def reinitialize(markup)
|
44
44
|
# @keys_values was a Hash[Symbol, String|Boolean] but now it is Hash[String, String|Boolean]
|
45
|
-
@
|
45
|
+
@argument_string = markup
|
46
46
|
if @no_arg_parsing
|
47
47
|
define_singleton_method(:argv) { warn_fetch :argv }
|
48
48
|
define_singleton_method(:keys_values) { warn_fetch :keys_values }
|
@@ -55,7 +55,10 @@ module JekyllSupport
|
|
55
55
|
END_MSG
|
56
56
|
end
|
57
57
|
|
58
|
-
|
58
|
+
# @param klass [Class] class instance to register
|
59
|
+
# @param tag_name [String] name of plugin defined by klass to register as
|
60
|
+
# @param quiet [Boolean] suppress registration message if truthy
|
61
|
+
def self.register(klass, tag_name, quiet: false)
|
59
62
|
abort("Error: The #{tag_name} plugin does not define VERSION") \
|
60
63
|
unless klass.const_defined?(:VERSION)
|
61
64
|
|
@@ -63,10 +66,12 @@ module JekyllSupport
|
|
63
66
|
|
64
67
|
abort("Error: The #{tag_name} plugin is not an instance of JekyllSupport::JekyllBlock or JekyllSupport::JekyllTag") \
|
65
68
|
unless klass.instance_of?(Class) &&
|
66
|
-
(klass.ancestors.include?(JekyllSupport::JekyllBlock) ||
|
67
|
-
klass.ancestors.include?(JekyllSupport::JekyllTag))
|
69
|
+
(klass.ancestors.include?(::JekyllSupport::JekyllBlock) ||
|
70
|
+
klass.ancestors.include?(::JekyllSupport::JekyllTag))
|
68
71
|
|
69
72
|
Liquid::Template.register_tag(tag_name, klass)
|
73
|
+
return if quiet
|
74
|
+
|
70
75
|
msg = generate_message(klass, tag_name, version)
|
71
76
|
PluginMetaLogger.instance.info { msg }
|
72
77
|
end
|
data/lib/hooks/a_page.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module AllCollectionsHooks
|
2
|
+
class APage
|
3
|
+
attr_reader :content, :data, :date, :description, :destination, :draft, :excerpt, :ext, :extname, :href,
|
4
|
+
:label, :last_modified, :layout, :origin, :path, :relative_path, :tags, :title, :type, :url
|
5
|
+
|
6
|
+
def initialize(obj, origin)
|
7
|
+
@origin = origin
|
8
|
+
data_field_init obj
|
9
|
+
obj_field_init obj
|
10
|
+
@draft = Jekyll::Draft.draft? obj
|
11
|
+
@href = @url if @href.nil?
|
12
|
+
# @href = "/#{@href}" if @origin == 'individual_page'
|
13
|
+
@href = "#{@href}index.html" if @href.end_with? '/'
|
14
|
+
@name = File.basename(@href)
|
15
|
+
@title = if @data&.key?('title')
|
16
|
+
@data['title']
|
17
|
+
elsif obj.respond_to?(:title)
|
18
|
+
obj.title
|
19
|
+
else
|
20
|
+
"<code>#{@href}</code>"
|
21
|
+
end
|
22
|
+
rescue StandardError => e
|
23
|
+
JekyllSupport.error_short_trace(@logger, e)
|
24
|
+
# JekyllSupport.warn_short_trace(@logger, e)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
@label || @date.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def data_field_init(obj)
|
34
|
+
return unless obj.respond_to? :data
|
35
|
+
|
36
|
+
@data = obj.data
|
37
|
+
|
38
|
+
@categories = @data['categories'] if @data.key? 'categories'
|
39
|
+
@date = (@data['date'].to_date if @data&.key?('date')) || Date.today
|
40
|
+
@description = @data['description'] if @data.key? 'description'
|
41
|
+
@excerpt = @data['excerpt'] if @data.key? 'excerpt'
|
42
|
+
@ext ||= @data['ext'] if @data.key? 'ext'
|
43
|
+
@last_modified = @data['last_modified'] || @data['last_modified_at'] || @date
|
44
|
+
@last_modified_field = case @data
|
45
|
+
when @data.key?('last_modified')
|
46
|
+
'last_modified'
|
47
|
+
when @data.key?('last_modified_at')
|
48
|
+
'last_modified_at'
|
49
|
+
end
|
50
|
+
@layout = @data['layout'] if @data.key? 'layout'
|
51
|
+
@tags = @data['tags'] if @data.key? 'tags'
|
52
|
+
end
|
53
|
+
|
54
|
+
def obj_field_init(obj)
|
55
|
+
@content = obj.content if obj.respond_to? :content
|
56
|
+
|
57
|
+
# TODO: What _config.yml setting should be passed to destination()?
|
58
|
+
@destination = obj.destination('') if obj.respond_to? :destination
|
59
|
+
@ext = obj.extname
|
60
|
+
@extname = @ext # For compatibility with previous versions of all_collections
|
61
|
+
@label = obj.collection.label if obj.respond_to?(:collection) && obj.collection.respond_to?(:label)
|
62
|
+
@path = obj.path if obj.respond_to? :path
|
63
|
+
@relative_path = obj.relative_path if obj.respond_to? :relative_path
|
64
|
+
@type = obj.type if obj.respond_to? :type
|
65
|
+
@url = obj.url
|
66
|
+
@url = "#{@url}index.html" if @url.end_with? '/'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module JekyllAllCollections
|
2
|
+
module AllCollectionsHooks
|
3
|
+
class << self
|
4
|
+
attr_accessor :logger
|
5
|
+
end
|
6
|
+
@logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
|
7
|
+
|
8
|
+
# No, all_collections is not defined for this hook
|
9
|
+
# Jekyll::Hooks.register(:site, :after_init, priority: :normal) do |site|
|
10
|
+
# defined_msg = ::AllCollectionsHooks.all_collections_defined?(site)
|
11
|
+
# @logger.debug { "Jekyll::Hooks.register(:site, :after_init: #{defined_msg}" }
|
12
|
+
# end
|
13
|
+
|
14
|
+
# Creates a `Array[APage]` property called site.all_collections if it does not already exist.
|
15
|
+
# The array is available from :site, :pre_render onwards
|
16
|
+
# Each `APage` entry is one document or page.
|
17
|
+
Jekyll::Hooks.register(:site, :post_read, priority: :normal) do |site|
|
18
|
+
@site = site
|
19
|
+
unless site.class.method_defined? :all_collections
|
20
|
+
defined_msg = ::AllCollectionsHooks.all_collections_defined? site
|
21
|
+
@logger.debug { "Jekyll::Hooks.register(:site, :post_read, :normal: #{defined_msg}" }
|
22
|
+
::AllCollectionsHooks.compute(site) if !@site.class.method_defined?(:all_documents) || @site.all_documents.nil?
|
23
|
+
end
|
24
|
+
rescue StandardError => e
|
25
|
+
JekyllSupport.error_short_trace(@logger, e)
|
26
|
+
# JekyllSupport.warn_short_trace(@logger, e)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Yes, all_collections is defined for this hook
|
30
|
+
# Jekyll::Hooks.register(:site, :post_read, priority: :low) do |site|
|
31
|
+
# defined_msg = ::AllCollectionsHooks.all_collections_defined?(site)
|
32
|
+
# @logger.debug { "Jekyll::Hooks.register(:site, :post_read, :low: #{defined_msg}" }
|
33
|
+
# rescue StandardError => e
|
34
|
+
# JekyllSupport.error_short_trace(@logger, e)
|
35
|
+
# # JekyllSupport.warn_short_trace(@logger, e)
|
36
|
+
# end
|
37
|
+
|
38
|
+
# Yes, all_collections is defined for this hook
|
39
|
+
# Jekyll::Hooks.register(:site, :post_read, priority: :normal) do |site|
|
40
|
+
# defined_msg = ::AllCollectionsHooks.all_collections_defined?(site)
|
41
|
+
# @logger.debug { "Jekyll::Hooks.register(:site, :post_read, :normal: #{defined_msg}" }
|
42
|
+
# rescue StandardError => e
|
43
|
+
# JekyllSupport.error_short_trace(@logger, e)
|
44
|
+
# # JekyllSupport.warn_short_trace(@logger, e)
|
45
|
+
# end
|
46
|
+
|
47
|
+
# Yes, all_collections is defined for this hook
|
48
|
+
# Jekyll::Hooks.register(:site, :pre_render, priority: :normal) do |site, _payload|
|
49
|
+
# defined_msg = ::AllCollectionsHooks.all_collections_defined?(site)
|
50
|
+
# @logger.debug { "Jekyll::Hooks.register(:site, :pre_render: #{defined_msg}" }
|
51
|
+
# rescue StandardError => e
|
52
|
+
# JekyllSupport.error_short_trace(@logger, e)
|
53
|
+
# # JekyllSupport.warn_short_trace(@logger, e)
|
54
|
+
# end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
PluginMetaLogger.instance.logger.info do
|
59
|
+
"Loaded AllCollectionsHooks v#{JekyllPluginSupportVersion::VERSION} :site, :pre_render, :normal hook plugin."
|
60
|
+
end
|
61
|
+
Liquid::Template.register_filter(JekyllAllCollections::AllCollectionsHooks)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Insert the url of a Jekyll::Page into each LruFile instance,
|
2
|
+
# along with the Page reference
|
3
|
+
# todo: replace references to url and :url with reverse_url and :reverse_url
|
4
|
+
LruFile = Struct.new(:url, :page) do
|
5
|
+
include SendChain
|
6
|
+
|
7
|
+
def <=>(other)
|
8
|
+
url <=> other.url
|
9
|
+
end
|
10
|
+
|
11
|
+
def href
|
12
|
+
url.reverse
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Matches suffixes of an array of urls
|
17
|
+
# Converts suffixes to prefixes
|
18
|
+
class SortedLruFiles
|
19
|
+
attr_reader :msbs
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@msbs = MSlinnBinarySearch.new %i[url start_with?]
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param apages [Array[APage]]
|
26
|
+
def add_pages(apages)
|
27
|
+
apages.each { |apage| insert apage.href, apage }
|
28
|
+
@msbs.enable_search
|
29
|
+
end
|
30
|
+
|
31
|
+
def enable_search
|
32
|
+
@msbs.enable_search
|
33
|
+
end
|
34
|
+
|
35
|
+
def find(suffix)
|
36
|
+
@msbs.find suffix
|
37
|
+
end
|
38
|
+
|
39
|
+
def insert(url, file)
|
40
|
+
lru_file = LruFile.new(url.reverse, file)
|
41
|
+
lru_file.new_chain [:url, %i[start_with? placeholder]]
|
42
|
+
@msbs.insert(lru_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
def select(suffix)
|
46
|
+
@msbs.select_pages suffix
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module AllCollectionsHooks
|
2
|
+
class << self
|
3
|
+
attr_accessor :all_collections, :all_documents, :everything, :sorted_lru_files
|
4
|
+
end
|
5
|
+
|
6
|
+
# @sort_by = ->(apages, criteria) { [apages.sort(criteria)] } # todo delete this
|
7
|
+
|
8
|
+
# @return [String] indicating if :all_collections is defined or not
|
9
|
+
def self.all_collections_defined?(site)
|
10
|
+
"site.all_collections #{site.class.method_defined?(:all_collections) ? 'IS' : 'IS NOT'} defined"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create Array of AllCollectionsHooks::APage from objects
|
14
|
+
# @param objects [Array] An array of Jekyll::Document, Jekyll::Page or file names
|
15
|
+
# @param origin [String] Indicates type of objects being passed
|
16
|
+
def self.apages_from_objects(objects, origin)
|
17
|
+
pages = []
|
18
|
+
objects.each do |object|
|
19
|
+
page = APage.new(object, origin)
|
20
|
+
pages << page unless page.data['exclude_from_all'] || page.path == 'redirect.html'
|
21
|
+
end
|
22
|
+
pages
|
23
|
+
end
|
24
|
+
|
25
|
+
# Called by early, high-priority hook.
|
26
|
+
# Computes site.all_collections, site.all_documents, site.everything, and site.sorted_lru_files
|
27
|
+
def self.compute(site)
|
28
|
+
site.class.module_eval { attr_accessor :all_collections, :all_documents, :everything, :sorted_lru_files }
|
29
|
+
|
30
|
+
documents = site.collections
|
31
|
+
.values
|
32
|
+
.map { |x| x.class.method_defined?(:docs) ? x.docs : x }
|
33
|
+
.flatten
|
34
|
+
.compact
|
35
|
+
@all_collections = AllCollectionsHooks.apages_from_objects(documents, 'collection')
|
36
|
+
@all_documents = @all_collections +
|
37
|
+
AllCollectionsHooks.apages_from_objects(site.pages, 'individual_page')
|
38
|
+
@everything = @all_documents +
|
39
|
+
AllCollectionsHooks.apages_from_objects(site.static_files, 'static_file')
|
40
|
+
@sorted_lru_files = SortedLruFiles.new.add_pages @everything
|
41
|
+
|
42
|
+
site.all_collections = @all_collections
|
43
|
+
site.all_documents = @all_documents
|
44
|
+
site.everything = @everything
|
45
|
+
site.sorted_lru_files = @sorted_lru_files
|
46
|
+
rescue StandardError => e
|
47
|
+
JekyllSupport.error_short_trace(::JekyllAllCollections::AllCollectionsHooks.logger, e)
|
48
|
+
# JekyllSupport.warn_short_trace(::JekyllAllCollections::AllCollectionsHooks.logger, e)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# require 'jekyll_draft'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
# @author Copyright 2020 Michael Slinn
|
5
|
+
# @license SPDX-License-Identifier: Apache-2.0
|
6
|
+
module JekyllAllCollections
|
7
|
+
PLUGIN_NAME = 'all_collections'.freeze unless defined?(PLUGIN_NAME)
|
8
|
+
CRITERIA = %w[date destination draft label last_modified last_modified_at path relative_path title type url].freeze unless defined?(CRITERIA)
|
9
|
+
DRAFT_HTML = '<i class="jekyll_draft">Draft</i>'.freeze unless defined?(DRAFT_HTML)
|
10
|
+
|
11
|
+
class AllCollectionsTag < ::JekyllSupport::JekyllTag
|
12
|
+
include ::JekyllPluginSupportVersion
|
13
|
+
|
14
|
+
# Method prescribed by JekyllTag.
|
15
|
+
# @return [String]
|
16
|
+
def render_impl
|
17
|
+
parse_arguments # Defines instance variables like @sort_by
|
18
|
+
sort_lambda = init_sort_by @sort_by, @sort_by_param
|
19
|
+
@heading = @helper.parameter_specified?('heading') || default_head(@sort_by)
|
20
|
+
generate_output sort_lambda
|
21
|
+
rescue StandardError => e
|
22
|
+
JekyllSupport.error_short_trace @logger, e
|
23
|
+
# JekyllSupport.warn_short_trace @logger, e
|
24
|
+
end
|
25
|
+
|
26
|
+
# Descending sort keys reverse the order of comparison
|
27
|
+
# Example return values:
|
28
|
+
# "->(a, b) { [a.last_modified] <=> [b.last_modified] }"
|
29
|
+
# "->(a, b) { [b.last_modified] <=> [a.last_modified] }" (descending)
|
30
|
+
# "->(a, b) { [a.last_modified, a.date] <=> [b.last_modified, b.date] }" (descending last_modified, ascending date)
|
31
|
+
# "->(a, b) { [a.last_modified, b.date] <=> [b.last_modified, a.date] }" (ascending last_modified, descending date)
|
32
|
+
def self.create_lambda_string(criteria)
|
33
|
+
criteria_lhs_array = []
|
34
|
+
criteria_rhs_array = []
|
35
|
+
verify_sort_by_type(criteria).each do |c|
|
36
|
+
descending_sort = c.start_with? '-'
|
37
|
+
c.delete_prefix! '-'
|
38
|
+
abort("Error: '#{c}' is not a valid sort field. Valid field names are: #{CRITERIA.join ', '}") \
|
39
|
+
unless CRITERIA.include?(c)
|
40
|
+
criteria_lhs_array << (descending_sort ? "b.#{c}" : "a.#{c}")
|
41
|
+
criteria_rhs_array << (descending_sort ? "a.#{c}" : "b.#{c}")
|
42
|
+
end
|
43
|
+
"->(a, b) { [#{criteria_lhs_array.join(', ')}] <=> [#{criteria_rhs_array.join(', ')}] }"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.verify_sort_by_type(sort_by)
|
47
|
+
case sort_by
|
48
|
+
when Array
|
49
|
+
sort_by
|
50
|
+
when Enumerable
|
51
|
+
sort_by.to_a
|
52
|
+
when Date
|
53
|
+
[sort_by.to_i]
|
54
|
+
when String
|
55
|
+
[sort_by]
|
56
|
+
else
|
57
|
+
abort "Error: @sort_by was specified as '#{sort_by}'; it must either be a string or an array of strings"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def default_head(sort_by)
|
64
|
+
criteria = (sort_by.map do |x|
|
65
|
+
reverse = x.start_with? '-'
|
66
|
+
criterion = x.delete_prefix('-').capitalize
|
67
|
+
criterion += reverse ? ' (Newest to Oldest)' : ' (Oldest to Newest)'
|
68
|
+
criterion
|
69
|
+
end).join(', ')
|
70
|
+
"All Posts in All Categories Sorted By #{criteria}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def evaluate(string)
|
74
|
+
self.eval string, binding, __FILE__, __LINE__
|
75
|
+
rescue StandardError => e
|
76
|
+
warn_short_trace e.red
|
77
|
+
end
|
78
|
+
|
79
|
+
def last_modified_value(apage)
|
80
|
+
@logger.debug do
|
81
|
+
" apage.last_modified='#{apage.last_modified}'; " \
|
82
|
+
"apage.last_modified_at='#{apage.last_modified_at}'; " \
|
83
|
+
"@date_column='#{@date_column}'"
|
84
|
+
end
|
85
|
+
last_modified = if @date_column == 'last_modified' && apage.respond_to?(:last_modified)
|
86
|
+
apage.last_modified
|
87
|
+
elsif apage.respond_to? :last_modified_at
|
88
|
+
apage.last_modified_at
|
89
|
+
else
|
90
|
+
apage.date
|
91
|
+
end
|
92
|
+
last_modified ||= apage.date || Date.today
|
93
|
+
last_modified
|
94
|
+
end
|
95
|
+
|
96
|
+
def generate_output(sort_lambda)
|
97
|
+
id = @id.to_s.strip.empty? ? '' : " id='#{@id}'"
|
98
|
+
heading = @heading.strip.to_s.empty? ? '' : "<h2#{id}>#{@heading}</h2>"
|
99
|
+
data = case @data_selector
|
100
|
+
when 'all_collections'
|
101
|
+
@site.all_collections
|
102
|
+
when 'all_documents'
|
103
|
+
@site.all_documents
|
104
|
+
when 'everything'
|
105
|
+
@site.everything
|
106
|
+
else
|
107
|
+
raise AllCollectionsError, "Invalid value for @data_selector (#{data_selector})"
|
108
|
+
end
|
109
|
+
collection = data.sort(&sort_lambda)
|
110
|
+
posts = collection.map do |x|
|
111
|
+
last_modified = last_modified_value x
|
112
|
+
date = last_modified.strftime '%Y-%m-%d'
|
113
|
+
draft = x.draft ? DRAFT_HTML : ''
|
114
|
+
href = "<a href='#{x.href}'>#{x.title}</a>"
|
115
|
+
@logger.debug { " date='#{date}' #{x.title}\n" }
|
116
|
+
" <span>#{date}</span><span>#{href}#{draft}</span>"
|
117
|
+
end
|
118
|
+
<<~END_TEXT
|
119
|
+
#{heading}
|
120
|
+
<div class="posts">
|
121
|
+
#{posts.join "\n"}
|
122
|
+
</div>
|
123
|
+
END_TEXT
|
124
|
+
rescue ArgumentError => e
|
125
|
+
warn_short_trace e
|
126
|
+
end
|
127
|
+
|
128
|
+
# See https://stackoverflow.com/a/75377832/553865
|
129
|
+
def init_sort_by(sort_by, sort_by_param)
|
130
|
+
sort_lambda_string = AllCollectionsTag.create_lambda_string sort_by
|
131
|
+
|
132
|
+
@logger.debug do
|
133
|
+
"#{@page['path']} sort_by_param=#{sort_by_param} " \
|
134
|
+
"sort_lambda_string = #{sort_lambda_string}\n"
|
135
|
+
end
|
136
|
+
|
137
|
+
evaluate sort_lambda_string
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse_arguments
|
141
|
+
@data_selector = @helper.parameter_specified?('data_selector') || 'all_collections'
|
142
|
+
abort "Invalid data_selector #{@data_selector}" unless %w[all_collections all_documents everything].include? @data_selector
|
143
|
+
|
144
|
+
@date_column = @helper.parameter_specified?('date_column') || 'date'
|
145
|
+
unless %w[date last_modified].include?(@date_column)
|
146
|
+
raise AllCollectionsError "The date_column attribute must either have value 'date' or 'last_modified', " \
|
147
|
+
"but '#{@date_column}' was specified"
|
148
|
+
end
|
149
|
+
|
150
|
+
@id = @helper.parameter_specified?('id') || SecureRandom.hex(10)
|
151
|
+
@sort_by_param = @helper.parameter_specified? 'sort_by'
|
152
|
+
@sort_by = (@sort_by_param&.delete(' ')&.split(',') if @sort_by_param != false) || ['-date']
|
153
|
+
end
|
154
|
+
|
155
|
+
::JekyllSupport::JekyllPluginHelper.register(self, PLUGIN_NAME)
|
156
|
+
end
|
157
|
+
end
|