jekyll_plugin_support 1.1.0 → 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 +17 -6
- data/README.md +430 -2
- data/jekyll_plugin_support.gemspec +2 -1
- data/lib/block/jekyll_plugin_support_block.rb +2 -4
- 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 +5 -5
- data/lib/helper/jekyll_plugin_helper_class.rb +2 -2
- 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 +3 -5
- 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 +17 -12
- data/lib/tag/jekyll_plugin_support_tag.rb +1 -4
- 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 +6 -6
- 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 +45 -5
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
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require_relative '../error/jekyll_custom_error'
|
2
|
-
|
3
1
|
# Monkey patch StandardError so a new method called shorten_backtrace is added.
|
4
2
|
class StandardError
|
5
3
|
def shorten_backtrace(backtrace_element_count = 3)
|
@@ -12,7 +10,7 @@ class StandardError
|
|
12
10
|
end
|
13
11
|
|
14
12
|
module JekyllSupport
|
15
|
-
DISPLAYED_CALLS = 8
|
13
|
+
DISPLAYED_CALLS = 8 unless defined?(DISPLAYED_CALLS)
|
16
14
|
|
17
15
|
def self.error_short_trace(logger, error)
|
18
16
|
error.set_backtrace error.backtrace[0..DISPLAYED_CALLS]
|
@@ -22,11 +20,11 @@ module JekyllSupport
|
|
22
20
|
|
23
21
|
# @return a new StandardError subclass containing the shorten_backtrace method
|
24
22
|
def define_error
|
25
|
-
Class.new JekyllSupport::CustomError
|
23
|
+
Class.new ::JekyllSupport::CustomError
|
26
24
|
end
|
27
25
|
module_function :define_error
|
28
26
|
|
29
|
-
JekyllPluginSupportError = define_error
|
27
|
+
JekyllPluginSupportError = define_error unless defined?(JekyllPluginSupportError)
|
30
28
|
|
31
29
|
def self.dump_vars(_logger, liquid_context)
|
32
30
|
page = liquid_context.registers[:page]
|
@@ -1,13 +1,26 @@
|
|
1
|
-
require 'colorator'
|
2
|
-
require 'jekyll'
|
3
|
-
require 'jekyll_plugin_logger'
|
4
|
-
|
5
1
|
def require_directory(dir)
|
6
2
|
Dir[File.join(dir, '*.rb')]&.sort&.each do |file|
|
7
3
|
require file unless file == __FILE__
|
8
4
|
end
|
9
5
|
end
|
10
6
|
|
7
|
+
require 'colorator'
|
8
|
+
require 'jekyll'
|
9
|
+
require 'jekyll_plugin_logger'
|
10
|
+
require 'pry'
|
11
|
+
require 'sorted_set'
|
12
|
+
|
13
|
+
# require_directory __dir__
|
14
|
+
require_directory "#{__dir__}/util"
|
15
|
+
require_directory "#{__dir__}/error"
|
16
|
+
require_directory "#{__dir__}/block"
|
17
|
+
require_directory "#{__dir__}/generator"
|
18
|
+
require_directory "#{__dir__}/helper"
|
19
|
+
require_directory "#{__dir__}/jekyll_plugin_support"
|
20
|
+
require_directory "#{__dir__}/tag"
|
21
|
+
require_directory "#{__dir__}/jekyll_all_collections"
|
22
|
+
require_directory "#{__dir__}/hooks"
|
23
|
+
|
11
24
|
module JekyllSupport
|
12
25
|
def self.redef_without_warning(const, value)
|
13
26
|
send(:remove_const, const) if const_defined?(const)
|
@@ -21,14 +34,6 @@ module NoArgParsing
|
|
21
34
|
@no_arg_parsing = true
|
22
35
|
end
|
23
36
|
|
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"
|
31
|
-
|
32
37
|
module JekyllSupport
|
33
38
|
class JekyllTag
|
34
39
|
include JekyllSupportError
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'pry'
|
2
|
-
require_relative '../error/jekyll_plugin_error_handling'
|
3
|
-
|
4
1
|
module JekyllSupport
|
5
2
|
# Base class for Jekyll tags
|
6
3
|
class JekyllTag < Liquid::Tag
|
@@ -36,7 +33,7 @@ module JekyllSupport
|
|
36
33
|
@helper = JekyllPluginHelper.new(tag_name, @argument_string, @logger, respond_to?(:no_arg_parsing))
|
37
34
|
|
38
35
|
@error_name = "#{tag_name.camelcase(:upper)}Error"
|
39
|
-
JekyllSupport::CustomError.factory @error_name
|
36
|
+
::JekyllSupport::CustomError.factory @error_name
|
40
37
|
end
|
41
38
|
|
42
39
|
# Method prescribed by the Jekyll plugin lifecycle.
|
@@ -0,0 +1,152 @@
|
|
1
|
+
unless defined?(MSlinnBinarySearchError)
|
2
|
+
class MSlinnBinarySearchError < StandardError
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
# Ruby's binary search is unsuitable because the value to be searched for changes the required ordering for String compares
|
7
|
+
class MSlinnBinarySearch
|
8
|
+
attr_reader :accessor_chain, :array # For testing only
|
9
|
+
|
10
|
+
def initialize(accessor_chain)
|
11
|
+
@array = SortedSet.new # [LruFile] Ordered highest to lowest
|
12
|
+
@accessor_chain = accessor_chain
|
13
|
+
end
|
14
|
+
|
15
|
+
# Convert the SortedSet to an Array
|
16
|
+
def enable_search
|
17
|
+
@array = @array.to_a
|
18
|
+
end
|
19
|
+
|
20
|
+
# A match is found when the Array[LruFile] has an href which starts with the given stem
|
21
|
+
# @param stem [String]
|
22
|
+
# @return first item from @array.url that matches, or nil if no match
|
23
|
+
def find(stem)
|
24
|
+
raise MSlinnBinarySearchError, 'Invalid find because stem to search for is nil.' if stem.nil?
|
25
|
+
|
26
|
+
index = find_index(stem)
|
27
|
+
return nil if index.nil?
|
28
|
+
|
29
|
+
@array[index]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param stem [String]
|
33
|
+
# @return index of first matching stem, or nil if @array is empty, or 0 if no stem specified
|
34
|
+
def find_index(stem)
|
35
|
+
return nil if @array.empty?
|
36
|
+
return 0 if stem.nil? || stem.empty?
|
37
|
+
|
38
|
+
mets = stem.reverse
|
39
|
+
return nil if @array[0].url[0...mets.size] > mets # TODO: use chain eval for item
|
40
|
+
return nil if @array[0].url[0] != mets[0]
|
41
|
+
|
42
|
+
_find_index(mets, 0, @array.length - 1)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param stem [String]
|
46
|
+
# @return [index] of matching values, or [] if @array is empty, or entire array if no stem specified
|
47
|
+
def find_indices(stem)
|
48
|
+
return [] if @array.empty?
|
49
|
+
return @array if stem.nil? || stem.empty?
|
50
|
+
|
51
|
+
first_index = _find_index(stem, 0, @array.length - 1)
|
52
|
+
last_index = first_index
|
53
|
+
last_index += 1 while @array[last_index].url.start_with? stem
|
54
|
+
[first_index..last_index]
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param item [LruFile]
|
58
|
+
# @return [int] index of matching LruFile in @array, or nil if not found
|
59
|
+
def index_of(lru_file)
|
60
|
+
raise MSlinnBinarySearchError, 'Invalid index_of lru_file (nil).' if lru_file.nil?
|
61
|
+
|
62
|
+
find_index lru_file.url
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [LruFile] item at given index in @array
|
66
|
+
def item_at(index)
|
67
|
+
if index > @array.length - 1
|
68
|
+
raise MSlinnBinarySearchError,
|
69
|
+
"Invalid item_at index (#{index}) is greater than maximum stem (#{@array.length - 1})."
|
70
|
+
end
|
71
|
+
raise MSlinnBinarySearchError, "Invalid item_at index (#{index}) is less than zero." if index.negative?
|
72
|
+
|
73
|
+
@array[index]
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param lru_file [LruFile]
|
77
|
+
def insert(lru_file)
|
78
|
+
raise MSlinnBinarySearchError, 'Invalid insert because new item is nil.' if lru_file.nil?
|
79
|
+
raise MSlinnBinarySearchError, "Invalid insert because new item has no chain (#{lru_file})" if lru_file.chain.nil?
|
80
|
+
|
81
|
+
@array.add lru_file
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: Cache this method
|
85
|
+
# @param suffix [String] to use stem search on
|
86
|
+
# @return nil if @array is empty
|
87
|
+
# @return the first item in @array if suffix is nil or an empty string
|
88
|
+
def prefix_search(suffix)
|
89
|
+
return nil if @array.empty?
|
90
|
+
return @array[0] if suffix.empty? || suffix.nil?
|
91
|
+
|
92
|
+
low = search_index { |x| x.evaluate_with suffix }
|
93
|
+
return [] if low.nil?
|
94
|
+
|
95
|
+
high = low
|
96
|
+
high += 1 while high < @array.length &&
|
97
|
+
@array[high].evaluate_with(suffix)
|
98
|
+
@array[low..high]
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param stem [String]
|
102
|
+
# @return [APage] matching APages, or [] if @array is empty, or entire array if no stem specified
|
103
|
+
def select_pages(stem)
|
104
|
+
first_index = find_index stem
|
105
|
+
return [] if first_index.nil?
|
106
|
+
|
107
|
+
last_index = first_index
|
108
|
+
while last_index < @array.length - 1
|
109
|
+
# LruFile.url is reversed, bug LruFile.page is not
|
110
|
+
break unless @array[last_index + 1].url.start_with?(stem.reverse)
|
111
|
+
|
112
|
+
last_index += 1
|
113
|
+
end
|
114
|
+
Range.new(first_index, last_index).map { |i| @array[i].page }
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# A match is found when the Array[LruFile] has an href which starts with the given stem
|
120
|
+
# @param stem [String]
|
121
|
+
# @return [int] first index in @array that matches, or nil if no match
|
122
|
+
def _find_index(mets, min_index, max_index)
|
123
|
+
raise MSlinnBinarySearchError, "_find_index min_index(#{min_index})<0" if min_index.negative?
|
124
|
+
raise MSlinnBinarySearchError, "_find_index min_index(#{min_index})>max_index(#{max_index})" if min_index > max_index
|
125
|
+
raise MSlinnBinarySearchError, "_find_index max_index(#{max_index})>=@array.length(#{@array.length})" if max_index >= @array.length
|
126
|
+
|
127
|
+
return min_index if (min_index == max_index) && @array[min_index].url.start_with?(mets)
|
128
|
+
|
129
|
+
while min_index < max_index
|
130
|
+
mid_index = (min_index + max_index) / 2
|
131
|
+
mid_value = @array[mid_index].url[0...(mets.size)] # TODO: use chain eval for item
|
132
|
+
|
133
|
+
if mid_value == mets # back up until the first match is found
|
134
|
+
index = mid_index
|
135
|
+
loop do
|
136
|
+
return 0 if index.zero?
|
137
|
+
|
138
|
+
return index unless @array[index - 1].url.start_with?(mets)
|
139
|
+
|
140
|
+
index -= 1
|
141
|
+
end
|
142
|
+
elsif mid_value > mets
|
143
|
+
max_index = mid_index - 1
|
144
|
+
return _find_index(mets, min_index, max_index)
|
145
|
+
else
|
146
|
+
min_index = mid_index + 1
|
147
|
+
return _find_index(mets, min_index, max_index)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|