jekyll_plugin_support 1.1.0 → 3.1.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +38 -3
  3. data/CHANGELOG.md +30 -6
  4. data/README.md +474 -4
  5. data/jekyll_plugin_support.gemspec +3 -1
  6. data/lib/block/jekyll_plugin_support_block.rb +5 -6
  7. data/lib/block/jekyll_plugin_support_block_noarg.rb +1 -3
  8. data/lib/error/jekyll_custom_error.rb +6 -5
  9. data/lib/generator/jekyll_plugin_support_generator.rb +1 -7
  10. data/lib/helper/jekyll_plugin_helper.rb +5 -5
  11. data/lib/helper/jekyll_plugin_helper_class.rb +2 -2
  12. data/lib/hooks/a_page.rb +203 -0
  13. data/lib/hooks/all_collections_hooks.rb +61 -0
  14. data/lib/hooks/all_files.rb +48 -0
  15. data/lib/hooks/class_methods.rb +38 -0
  16. data/lib/jekyll_all_collections/all_collections_tag.rb +174 -0
  17. data/lib/jekyll_plugin_support/jekyll_plugin_support_class.rb +6 -7
  18. data/lib/jekyll_plugin_support/jekyll_plugin_support_spec_support.rb +1 -3
  19. data/lib/jekyll_plugin_support/version.rb +1 -1
  20. data/lib/jekyll_plugin_support.rb +27 -12
  21. data/lib/tag/jekyll_plugin_support_tag.rb +1 -4
  22. data/lib/tag/jekyll_plugin_support_tag_noarg.rb +0 -2
  23. data/lib/util/mslinn_binary_search.rb +152 -0
  24. data/lib/util/send_chain.rb +56 -0
  25. data/spec/all_collections_tag/all_collections_tag_sort_spec.rb +184 -0
  26. data/spec/bsearch_spec.rb +50 -0
  27. data/spec/custom_error_spec.rb +12 -10
  28. data/spec/date_sort_spec.rb +84 -0
  29. data/spec/jekyll_plugin_helper_options_spec.rb +9 -3
  30. data/spec/liquid_variable_parsing_spec.rb +7 -6
  31. data/spec/mslinn_binary_search_spec.rb +47 -0
  32. data/spec/send_chain_spec.rb +72 -0
  33. data/spec/send_spec.rb +28 -0
  34. data/spec/sorted_lru_files_spec.rb +82 -0
  35. data/spec/spec_helper.rb +15 -3
  36. data/spec/status_persistence.txt +4 -7
  37. data/spec/testable_spec.rb +38 -0
  38. metadata +56 -5
@@ -1,8 +1,7 @@
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
- def shorten_backtrace(backtrace_element_count = 3)
3
+ def shorten_backtrace(backtrace_element_count = 5)
4
+ set_backtrace backtrace[0..backtrace_element_count]
6
5
  # self.backtrace = backtrace[0..backtrace_element_count].map do |x|
7
6
  # raise JekyllPluginSupportError, "backtrace contains a #{x.class} with value '#{x}'." unless x.instance_of? String
8
7
 
@@ -12,21 +11,21 @@ class StandardError
12
11
  end
13
12
 
14
13
  module JekyllSupport
15
- DISPLAYED_CALLS = 8
14
+ DISPLAYED_CALLS = 8 unless defined?(DISPLAYED_CALLS)
16
15
 
17
16
  def self.error_short_trace(logger, error)
18
17
  error.set_backtrace error.backtrace[0..DISPLAYED_CALLS]
19
- logger.error { error }
18
+ logger.error { error.full_message } # Are error and logger.error defined?
20
19
  error
21
20
  end
22
21
 
23
22
  # @return a new StandardError subclass containing the shorten_backtrace method
24
23
  def define_error
25
- Class.new JekyllSupport::CustomError
24
+ Class.new ::JekyllSupport::CustomError
26
25
  end
27
26
  module_function :define_error
28
27
 
29
- JekyllPluginSupportError = define_error
28
+ JekyllPluginSupportError = define_error unless defined?(JekyllPluginSupportError)
30
29
 
31
30
  def self.dump_vars(_logger, liquid_context)
32
31
  page = liquid_context.registers[:page]
@@ -1,6 +1,4 @@
1
- require 'jekyll'
2
-
3
- Registers = Struct.new(:page, :site)
1
+ Registers = Struct.new(:page, :site) unless defined?(Registers)
4
2
 
5
3
  # Mock for Collections
6
4
  class Collections
@@ -1,3 +1,3 @@
1
1
  module JekyllPluginSupportVersion
2
- VERSION = '1.1.0'.freeze
2
+ VERSION = '3.1.0'.freeze unless defined?(VERSION)
3
3
  end
@@ -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)
@@ -15,34 +28,36 @@ module JekyllSupport
15
28
  end
16
29
  end
17
30
 
31
+ module ToString
32
+ def to_s
33
+ "#{self}.class.name"
34
+ end
35
+ end
36
+
18
37
  module NoArgParsing
19
38
  attr_accessor :no_arg_parsing
20
39
 
21
40
  @no_arg_parsing = true
22
41
  end
23
42
 
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
43
  module JekyllSupport
33
44
  class JekyllTag
34
45
  include JekyllSupportError
46
+ include ToString
35
47
  end
36
48
 
37
49
  class JekyllTagNoArgParsing
38
50
  include JekyllSupportError
51
+ include ToString
39
52
  end
40
53
 
41
54
  class JekyllBlock
42
55
  include JekyllSupportError
56
+ include ToString
43
57
  end
44
58
 
45
59
  class JekyllBlockNoArgParsing
46
60
  include JekyllSupportError
61
+ include ToString
47
62
  end
48
63
  end
@@ -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.
@@ -1,5 +1,3 @@
1
- require_relative '../error/jekyll_plugin_error_handling'
2
-
3
1
  module JekyllSupport
4
2
  class JekyllTagNoArgParsing < JekyllTag
5
3
  attr_reader :argument_string, :helper, :line_number, :logger, :page, :site
@@ -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
@@ -0,0 +1,56 @@
1
+ # Supports one chain at a time
2
+ module SendChain
3
+ # See https://stackoverflow.com/a/79333706/553865
4
+ # This method can be called directly if no methods in the chain require arguments
5
+ # Does not use any external state
6
+ def send_chain(chain)
7
+ Array(chain).inject(self) { |o, a| o.send(*a) }
8
+ end
9
+
10
+ # Saves @chain structure containing :placeholders for arguments to be supplied later
11
+ # Call when a different chain with :placeholders is desired
12
+ def new_chain(chain)
13
+ abort "new_chain error: chain must be an array ('#{chain}' was an #{chain.class.name})" \
14
+ unless chain.instance_of?(Array)
15
+ @chain = chain
16
+ end
17
+
18
+ # Call after new_chain, to evaluate @chain with values
19
+ def substitute_and_send_chain_with(values)
20
+ send_chain substitute_chain_with values
21
+ end
22
+
23
+ alias evaluate_with substitute_and_send_chain_with
24
+
25
+ # Call this method after calling new_chain to perform error checking and replace :placeholders with values.
26
+ # @chain is not modified.
27
+ # @return [Array] Modified chain
28
+ def substitute_chain_with(values)
29
+ values = [values] unless values.instance_of?(Array)
30
+
31
+ placeholder_count = @chain.flatten.count { |x| x == :placeholder }
32
+ if values.length != placeholder_count
33
+ abort "with_values error: number of values (#{values.length}) does not match the number of placeholders (#{placeholder_count})"
34
+ end
35
+
36
+ eval_chain @chain, values
37
+ end
38
+
39
+ private
40
+
41
+ # Replaces :placeholders with values
42
+ # Does not use any external state
43
+ # @return modified chain
44
+ def eval_chain(chain, values)
45
+ chain.map do |c|
46
+ case c
47
+ when :placeholder
48
+ values.shift
49
+ when Array
50
+ eval_chain c, values
51
+ else
52
+ c
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,184 @@
1
+ require_relative '../spec_helper'
2
+ require_relative '../../lib/hooks/a_page'
3
+
4
+ class NullBinding < BasicObject
5
+ include ::Kernel
6
+
7
+ # Avoid error message "warning: undefining `object_id' may cause serious problems"
8
+ # https://stackoverflow.com/a/17791631/553865
9
+ (
10
+ ::Kernel.instance_methods(false) +
11
+ ::Kernel.private_instance_methods(false) -
12
+ [:binding]
13
+ ).each { |x| undef_method(x) unless x == :object_id }
14
+
15
+ def min_binding
16
+ binding
17
+ end
18
+ end
19
+
20
+ def show_dates(label, array)
21
+ puts " #{label} actual: #{array.map(&:title).join(', ')} <==> expected: #{expected.map(&:title).join(', ')}"
22
+ end
23
+
24
+ def show(label, lambda_string, actual, expected)
25
+ puts "#{label} - For lambda_string: #{lambda_string}"
26
+ puts " actual: #{actual.map(&:title).join(', ')}"
27
+ puts "expected: #{expected.map(&:title).join(', ')}"
28
+ actual_array = actual.map do |x|
29
+ [
30
+ (x.date.strftime '%Y-%m-%d' + '/' + x.title), (x.last_modified.strftime '%Y-%m-%d' + '/' + x.title)
31
+ ].join('/')
32
+ end
33
+ expected_array = expected.map do |x|
34
+ [
35
+ (x.date.strftime '%Y-%m-%d' + '/' + x.title), (x.last_modified.strftime '%Y-%m-%d' + '/' + x.title)
36
+ ].join('/')
37
+ end
38
+ puts ' actual date/last_modified: ' + actual_array.join(', ')
39
+ puts 'expected date/last_modified: ' + expected_array.join(', ')
40
+ end
41
+
42
+ logger = PluginMetaLogger.instance.new_logger(self, PluginMetaLogger.instance.config)
43
+
44
+ # See https://stackoverflow.com/a/75388137/553865
45
+ RSpec.describe(JekyllSupport) do
46
+ let(:o1) do
47
+ described_class.apage_from(
48
+ collection_name: '_posts',
49
+ date: '2020-01-20',
50
+ last_modified: '2020-01-20',
51
+ logger: logger,
52
+ title: 'a_A (o1)'
53
+ )
54
+ end
55
+ let(:o2) do
56
+ described_class.apage_from(
57
+ collection_name: '_posts',
58
+ date: '2021-01-21',
59
+ last_modified: '2021-01-21',
60
+ logger: logger,
61
+ title: 'b_B (o2)'
62
+ )
63
+ end
64
+ let(:o3) do
65
+ described_class.apage_from(
66
+ collection_name: '_posts',
67
+ date: '2021-01-21',
68
+ last_modified: '2022-01-22',
69
+ logger: logger,
70
+ title: 'b_C (o3)'
71
+ )
72
+ end
73
+ let(:o4) do
74
+ described_class.apage_from(
75
+ collection_name: '_posts',
76
+ date: '2022-01-22',
77
+ last_modified: '2022-01-22',
78
+ logger: logger,
79
+ title: 'c_C (o4)'
80
+ )
81
+ end
82
+ let(:objs) { [o1, o2, o3, o4] }
83
+
84
+ it '(1) defines sort_by lambda with last_modified (ascending)' do
85
+ sort_lambda = ->(a, b) { [a.last_modified] <=> [b.last_modified] }
86
+ actual = objs.sort(&sort_lambda)
87
+ expected = [o1, o2, o3, o4]
88
+ show('(1)', '[a.last_modified] <=> [b.last_modified]', actual, expected)
89
+ expect(actual).to eq(expected)
90
+ end
91
+
92
+ it '(2) makes sort_by lambdas from stringified comparison of last_modified (ascending)' do
93
+ sort_lambda_string = '->(a, b) { a.last_modified <=> b.last_modified }'
94
+ sort_lambda = eval sort_lambda_string, NullBinding.new.min_binding, __FILE__, __LINE__ - 1
95
+ actual = objs.sort(&sort_lambda)
96
+ expected = [o1, o2, o3, o4]
97
+ show('(2)', sort_lambda_string, actual, expected)
98
+ expect(actual).to eq(expected)
99
+ end
100
+
101
+ it '(3) makes sort_by lambda from stringified array of last_modified (ascending)' do
102
+ sort_lambda_string = '->(a, b) { [a.last_modified] <=> [b.last_modified] }'
103
+ sort_lambda = eval sort_lambda_string, NullBinding.new.min_binding, __FILE__, __LINE__ - 1
104
+ actual = objs.sort(&sort_lambda)
105
+ expected = [o1, o2, o3, o4]
106
+ show('(3)', sort_lambda_string, actual, expected)
107
+ expect(actual).to eq(expected)
108
+ end
109
+
110
+ it '(4) makes sort_by lambda with last_modified (descending) from stringified array' do
111
+ sort_lambda_string = '->(a, b) { [b.last_modified] <=> [a.last_modified] }'
112
+ sort_lambda = eval sort_lambda_string, NullBinding.new.min_binding, __FILE__, __LINE__ - 1
113
+ actual = objs.sort(&sort_lambda)
114
+ expected = [o4, o3, o2, o1]
115
+ show('(4)', sort_lambda_string, actual, expected)
116
+ expect([o3, o4]).to include(actual[0]) # The sort might yield o3 or o4 in this position
117
+ expect([o3, o4]).to include(actual[1]) # The sort might yield o3 or o4 in this position
118
+ expect(o2).to eq(actual[2])
119
+ expect(o1).to eq(actual[3])
120
+ end
121
+
122
+ it '(5) create_lambda with date (descending)' do
123
+ lambda_string = JekyllAllCollections::AllCollectionsTag.create_lambda_string('-last_modified')
124
+ expect(lambda_string).to eq('->(a, b) { [b.last_modified] <=> [a.last_modified] }')
125
+ sort_lambda = self.eval lambda_string, binding
126
+ actual = objs.sort(&sort_lambda)
127
+ expected = [o4, o3, o2, o1]
128
+ show('(5)', lambda_string, actual, expected)
129
+ expect([o3, o4]).to include(actual[0]) # The sort might yield o3 or o4 in this position
130
+ expect([o3, o4]).to include(actual[1]) # The sort might yield o3 or o4 in this position
131
+ expect(o2).to eq(actual[2])
132
+ expect(o1).to eq(actual[3])
133
+ end
134
+
135
+ it '(6) create_lambda with date (ascending)' do
136
+ lambda_string = JekyllAllCollections::AllCollectionsTag.create_lambda_string('date')
137
+ expect(lambda_string).to eq('->(a, b) { [a.date] <=> [b.date] }')
138
+ sort_lambda = self.eval lambda_string, binding
139
+ actual = objs.sort(&sort_lambda)
140
+ expected = [o1, o2, o3, o4]
141
+ show('(1)', lambda_string, actual, expected)
142
+ expect(actual).to eq(expected)
143
+ end
144
+
145
+ it '(7) create_lambda with date (ascending) and last_modified (ascending)' do
146
+ lambda_string = JekyllAllCollections::AllCollectionsTag.create_lambda_string(%w[date last_modified])
147
+ expect(lambda_string).to eq('->(a, b) { [a.date, a.last_modified] <=> [b.date, b.last_modified] }')
148
+ sort_lambda = self.eval lambda_string, binding
149
+ actual = objs.sort(&sort_lambda)
150
+ expected = [o1, o2, o3, o4]
151
+ show('(7)', lambda_string, actual, expected)
152
+ expect(actual).to eq(expected)
153
+ end
154
+
155
+ it '(8) create_lambda with date (descending) and last_modified (descending)' do
156
+ lambda_string = JekyllAllCollections::AllCollectionsTag.create_lambda_string(['-date', '-last_modified'])
157
+ expect(lambda_string).to eq('->(a, b) { [b.date, b.last_modified] <=> [a.date, a.last_modified] }')
158
+ sort_lambda = self.eval lambda_string, binding
159
+ actual = objs.sort(&sort_lambda)
160
+ expected = [o4, o3, o2, o1]
161
+ show('(8)', lambda_string, actual, expected)
162
+ expect(actual).to eq(expected)
163
+ end
164
+
165
+ it '(9) create_lambda with date (descending) and last_modified (ascending)' do
166
+ lambda_string = JekyllAllCollections::AllCollectionsTag.create_lambda_string(['-date', 'last_modified'])
167
+ expect(lambda_string).to eq('->(a, b) { [b.date, a.last_modified] <=> [a.date, b.last_modified] }')
168
+ sort_lambda = self.eval lambda_string, binding
169
+ actual = objs.sort(&sort_lambda)
170
+ expected = [o4, o2, o3, o1]
171
+ show('(9)', lambda_string, actual, expected)
172
+ expect(actual).to eq(expected)
173
+ end
174
+
175
+ it '(10) create_lambda with date (ascending) and last_modified (descending)' do
176
+ lambda_string = JekyllAllCollections::AllCollectionsTag.create_lambda_string(['date', '-last_modified'])
177
+ expect(lambda_string).to eq('->(a, b) { [a.date, b.last_modified] <=> [b.date, a.last_modified] }')
178
+ sort_lambda = self.eval lambda_string, binding
179
+ actual = objs.sort(&sort_lambda)
180
+ expected = [o1, o3, o2, o4]
181
+ show('(10)', lambda_string, actual, expected)
182
+ expect(actual).to eq(expected)
183
+ end
184
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ # Ruby's binary search is unsuitable because ordering requirements are not stable.
4
+ # the value to be searched for changes the required ordering
5
+
6
+ RSpec.describe(Array) do
7
+ before { skip('Never gonna give you up/Never gonna let you down') }
8
+
9
+ sorted_ints = [0, 4, 7, 10, 12]
10
+ sorted_strings = %w[aaa aab aac bbb bbc bbd ccc ccd cce].sort.reverse
11
+
12
+ it 'returns index of first int match' do
13
+ actual = sorted_ints.bsearch_index { |x| x >= 4 }
14
+ expect(actual).to eq(1)
15
+
16
+ actual = sorted_ints.bsearch_index { |x| x >= 6 }
17
+ expect(actual).to eq(2)
18
+
19
+ actual = sorted_ints.bsearch_index { |x| x >= -1 }
20
+ expect(actual).to eq(0)
21
+
22
+ actual = sorted_ints.bsearch_index { |x| x >= 100 }
23
+ expect(actual).to be_nil
24
+ end
25
+
26
+ # See https://stackoverflow.com/q/79333097/553865
27
+ it 'returns index of first string match' do
28
+ puts(sorted_strings.map { |x| x.start_with? 'a' })
29
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'a' }
30
+ expect(sorted_strings[index]).to eq('aac')
31
+
32
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'aa' }
33
+ expect(sorted_strings[index]).to eq('aac')
34
+
35
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'aaa' }
36
+ expect(sorted_strings[index]).to eq('aaa')
37
+
38
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'b' }
39
+ expect(sorted_strings[index]).to eq('bbd')
40
+
41
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'bb' }
42
+ expect(sorted_strings[index]).to eq('bbd')
43
+
44
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'bbc' }
45
+ expect(sorted_strings[index]).to eq('bbc')
46
+
47
+ index = sorted_strings.bsearch_index { |x| x.start_with? 'cc' }
48
+ expect(sorted_strings[index]).to eq('cce')
49
+ end
50
+ end
@@ -1,5 +1,7 @@
1
- require_relative '../lib/jekyll_custom_error'
2
- require_relative '../lib/jekyll_plugin_support_class'
1
+ require 'spec_helper'
2
+ require_relative '../lib/error/jekyll_custom_error'
3
+ require_relative '../lib/jekyll_plugin_support/jekyll_plugin_support_class'
4
+ require_relative '../lib/helper/jekyll_plugin_helper'
3
5
 
4
6
  class Dummy
5
7
  def just_for_testing; end
@@ -8,30 +10,30 @@ end
8
10
  class CustomErrorSpec
9
11
  tag_name = 'test_tag'
10
12
  argument_string = 'This is the argument string'
11
- AnError = JekyllSupport.define_error
13
+ AnError = ::JekyllSupport.define_error
12
14
  AnError.class_variable_set(:@@tag_name, tag_name)
13
15
  AnError.class_variable_set(:@@argument_string, argument_string)
14
16
 
15
17
  puts "AnError is a #{AnError.class}; StandardError is a #{StandardError.class}"
16
18
  begin
17
- raise AnError, 'Oops'
19
+ raise AnError, 'This error is expected'
18
20
  rescue AnError => e
19
21
  puts "Caught AnError: #{e.message}"
20
- rescue JekyllSupport::CustomError => e
22
+ rescue ::JekyllSupport::CustomError => e
21
23
  puts "Caught CustomError: #{e.message}"
22
24
  end
23
25
 
24
- RSpec.describe JekyllPluginHelper do
26
+ RSpec.describe ::JekyllSupport::JekyllPluginHelper do
25
27
  it 'generates messages' do
26
28
  msg = described_class.generate_message(Dummy, tag_name, '0.1.0')
27
29
  puts msg
28
- expect(msg).to include(match(/Error class. DummyError/))
29
- expect(msg).to include(match(/CSS class for error messages. dummy_error/))
30
- expect(msg).to include(match(/die_on_dummy_error. false/))
30
+ expect(msg).to include('Error class: DummyError')
31
+ expect(msg).to include('CSS class for error messages: dummy_error')
32
+ expect(msg).to include('die_on_dummy_error: false')
31
33
  end
32
34
  end
33
35
 
34
- RSpec.describe JekyllSupport::CustomError do
36
+ RSpec.describe ::JekyllSupport::CustomError do
35
37
  it 'can create custom errors' do
36
38
  expect { raise AnError, 'Oops' }.to raise_error(AnError)
37
39
  end