jekyll-paginate-v3 0.2.0.alpha → 0.2.1.alpha
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/lib/jekyll-paginate-v3/query/filter/evaluation.rb +13 -1
- data/lib/jekyll-paginate-v3/query/filter/normalisation.rb +8 -2
- data/lib/jekyll-paginate-v3/support/loose_scalar.rb +84 -9
- data/lib/jekyll-paginate-v3/templates/grouped_index/config_normalisation.rb +7 -4
- data/lib/jekyll-paginate-v3/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e41115e64f970d6f2d2daf447c81230cb2550ed6d38ac462e96c11a10681b7d3
|
|
4
|
+
data.tar.gz: 54778efed89955bd2095392a43f35e19958210ed4cb3d87855487087d3d8d81d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf2d428b82e079b9435959f1d58ba2da88464beb23fe0cc7d3e4c89f539e1abc93e7c4ebab825aace5f41424a37ec15111549fdc0b3f905024e7a853b7cf5939
|
|
7
|
+
data.tar.gz: ba8014531b022ea9bf76f2b078483f890d7725b3f2a118f1a0d675763b0a12a2a80589888ae4c80ccfe70077b27efeb9ba557f788e1cb8d09ed7aecf3a6a84ec
|
|
@@ -150,7 +150,7 @@ class Filter
|
|
|
150
150
|
|
|
151
151
|
# Checks one item value against an optional min/max range.
|
|
152
152
|
def range_match?(value, min_value, max_value, range_mode)
|
|
153
|
-
comparable_value =
|
|
153
|
+
comparable_value = normalise_range_candidate(value, min_value, max_value)
|
|
154
154
|
return false if comparable_value.nil?
|
|
155
155
|
|
|
156
156
|
min_inclusive, max_inclusive = parse_range_mode_flags(range_mode)
|
|
@@ -178,6 +178,18 @@ class Filter
|
|
|
178
178
|
false
|
|
179
179
|
end
|
|
180
180
|
|
|
181
|
+
# Coerces a range candidate according to its already-normalised boundary
|
|
182
|
+
# type. Date parsing is therefore explicit to range evaluation and never
|
|
183
|
+
# leaks into ordinary scalar equality.
|
|
184
|
+
def normalise_range_candidate(value, min_value, max_value)
|
|
185
|
+
boundary = min_value.nil? ? max_value : min_value
|
|
186
|
+
if boundary.is_a?(Date) || boundary.is_a?(DateTime) || boundary.is_a?(Time)
|
|
187
|
+
return Jekyll::Plugins::PaginateV3::Support::LooseScalar.datetime(value)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
Jekyll::Plugins::PaginateV3::Support::LooseScalar.number(value)
|
|
191
|
+
end
|
|
192
|
+
|
|
181
193
|
# Parses one canonical range mode string into inclusion flags.
|
|
182
194
|
def parse_range_mode_flags(range_mode)
|
|
183
195
|
mode_text = range_mode.to_s
|
|
@@ -531,7 +531,13 @@ class Filter
|
|
|
531
531
|
return today_expression unless today_expression.nil?
|
|
532
532
|
end
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
numeric_value = Jekyll::Plugins::PaginateV3::Support::LooseScalar.number(value)
|
|
535
|
+
return numeric_value unless numeric_value.nil?
|
|
536
|
+
|
|
537
|
+
datetime_value = Jekyll::Plugins::PaginateV3::Support::LooseScalar.datetime(value)
|
|
538
|
+
return datetime_value unless datetime_value.nil?
|
|
539
|
+
|
|
540
|
+
false
|
|
535
541
|
end
|
|
536
542
|
|
|
537
543
|
# Parses configured now-keyword expressions into DateTime values.
|
|
@@ -572,7 +578,7 @@ class Filter
|
|
|
572
578
|
end
|
|
573
579
|
end
|
|
574
580
|
|
|
575
|
-
# Casts string values to Integer
|
|
581
|
+
# Casts string values to Integer or Float when possible.
|
|
576
582
|
# Returns the original string unless strict casting is requested.
|
|
577
583
|
def interpret_numeric(value, must_cast: false)
|
|
578
584
|
comparable_value = Jekyll::Plugins::PaginateV3::Support::LooseScalar.comparable(value, must_cast: must_cast)
|
|
@@ -12,6 +12,42 @@ module Support
|
|
|
12
12
|
# Structure: callers can ask for one specific scalar family without
|
|
13
13
|
# committing the whole pipeline to one feature's semantics.
|
|
14
14
|
module LooseScalar
|
|
15
|
+
# Captures the supported ISO-shaped date/time profile without exposing
|
|
16
|
+
# callers to Ruby's permissive and time-dependent `DateTime.parse` rules.
|
|
17
|
+
# Date separators repeat independently of the time separator, which also
|
|
18
|
+
# repeats inside a numeric timezone offset.
|
|
19
|
+
DATETIME_STRING_PATTERN = %r{
|
|
20
|
+
\A
|
|
21
|
+
(?<year>\d{4})
|
|
22
|
+
(?<date_separator>[:.\/-])
|
|
23
|
+
(?<month>\d{2})
|
|
24
|
+
\k<date_separator>
|
|
25
|
+
(?<day>\d{2})
|
|
26
|
+
(?:
|
|
27
|
+
(?:T|[ \t]+)
|
|
28
|
+
(?<hour>\d{2})
|
|
29
|
+
(?<time_separator>[:.-])
|
|
30
|
+
(?<minute>\d{2})
|
|
31
|
+
(?:
|
|
32
|
+
\k<time_separator>
|
|
33
|
+
(?<second>\d{2})
|
|
34
|
+
(?<fraction>\.\d+)?
|
|
35
|
+
)?
|
|
36
|
+
(?:
|
|
37
|
+
[ \t]*
|
|
38
|
+
(?:
|
|
39
|
+
(?<utc_timezone>Z)
|
|
40
|
+
|
|
|
41
|
+
(?<timezone_sign>[+-])
|
|
42
|
+
(?<timezone_hour>\d{2})
|
|
43
|
+
\k<time_separator>
|
|
44
|
+
(?<timezone_minute>\d{2})
|
|
45
|
+
)
|
|
46
|
+
)?
|
|
47
|
+
)?
|
|
48
|
+
\z
|
|
49
|
+
}x.freeze
|
|
50
|
+
|
|
15
51
|
# Interprets one value as a strict boolean or a loose `true`/`false`
|
|
16
52
|
# string. Returns `nil` when no boolean meaning can be inferred.
|
|
17
53
|
def self.boolean(value)
|
|
@@ -50,8 +86,9 @@ module LooseScalar
|
|
|
50
86
|
(numeric_value % 1).zero?
|
|
51
87
|
end
|
|
52
88
|
|
|
53
|
-
# Interprets one value as a datetime
|
|
54
|
-
#
|
|
89
|
+
# Interprets one value as a datetime for explicitly date-aware operations.
|
|
90
|
+
# Strings must satisfy `DATETIME_STRING_PATTERN`; captured components are
|
|
91
|
+
# rebuilt as canonical ISO before the standard parser validates them.
|
|
55
92
|
def self.datetime(value)
|
|
56
93
|
return value if value.is_a?(DateTime)
|
|
57
94
|
return value.to_datetime if value.is_a?(Time)
|
|
@@ -61,7 +98,11 @@ module LooseScalar
|
|
|
61
98
|
stripped = value.strip
|
|
62
99
|
return nil if stripped.empty?
|
|
63
100
|
|
|
64
|
-
|
|
101
|
+
match = DATETIME_STRING_PATTERN.match(stripped)
|
|
102
|
+
return nil if match.nil?
|
|
103
|
+
return nil unless valid_datetime_components?(match)
|
|
104
|
+
|
|
105
|
+
DateTime.iso8601(canonical_datetime(match))
|
|
65
106
|
rescue ArgumentError
|
|
66
107
|
nil
|
|
67
108
|
end
|
|
@@ -71,9 +112,9 @@ module LooseScalar
|
|
|
71
112
|
datetime(value)
|
|
72
113
|
end
|
|
73
114
|
|
|
74
|
-
# Converts one value into the
|
|
75
|
-
# filtering. Strings become numbers
|
|
76
|
-
#
|
|
115
|
+
# Converts one value into the generic comparable scalar form used by
|
|
116
|
+
# exact filtering. Strings become numbers when possible, but date-shaped
|
|
117
|
+
# strings remain strings unless a date-aware caller invokes `datetime`.
|
|
77
118
|
def self.comparable(value, must_cast: false)
|
|
78
119
|
return value if value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(DateTime)
|
|
79
120
|
return value.to_datetime if value.is_a?(Time)
|
|
@@ -90,14 +131,48 @@ module LooseScalar
|
|
|
90
131
|
numeric_value = number(stripped)
|
|
91
132
|
return numeric_value unless numeric_value.nil?
|
|
92
133
|
|
|
93
|
-
datetime_value = datetime(stripped)
|
|
94
|
-
return datetime_value unless datetime_value.nil?
|
|
95
|
-
|
|
96
134
|
return nil if must_cast
|
|
97
135
|
|
|
98
136
|
stripped
|
|
99
137
|
end
|
|
100
138
|
|
|
139
|
+
# Rebuilds one lexical match as strict ISO text for calendar validation.
|
|
140
|
+
def self.canonical_datetime(match)
|
|
141
|
+
date = "#{match[:year]}-#{match[:month]}-#{match[:day]}"
|
|
142
|
+
return date if match[:hour].nil?
|
|
143
|
+
|
|
144
|
+
second = match[:second] || '00'
|
|
145
|
+
fraction = match[:fraction].to_s
|
|
146
|
+
timezone = canonical_timezone(match)
|
|
147
|
+
|
|
148
|
+
"#{date}T#{match[:hour]}:#{match[:minute]}:#{second}#{fraction}#{timezone}"
|
|
149
|
+
end
|
|
150
|
+
private_class_method :canonical_datetime
|
|
151
|
+
|
|
152
|
+
# Converts an optional loose timezone into canonical ISO syntax. Missing
|
|
153
|
+
# timezone information deliberately means offset zero rather than local time.
|
|
154
|
+
def self.canonical_timezone(match)
|
|
155
|
+
return 'Z' unless match[:utc_timezone].nil?
|
|
156
|
+
return 'Z' if match[:timezone_sign].nil?
|
|
157
|
+
|
|
158
|
+
"#{match[:timezone_sign]}#{match[:timezone_hour]}:#{match[:timezone_minute]}"
|
|
159
|
+
end
|
|
160
|
+
private_class_method :canonical_timezone
|
|
161
|
+
|
|
162
|
+
# Validates component ranges explicitly because `DateTime.iso8601` accepts
|
|
163
|
+
# and normalises some out-of-range timezone offsets on older Ruby versions.
|
|
164
|
+
def self.valid_datetime_components?(match)
|
|
165
|
+
return false unless Date.valid_date?(match[:year].to_i, match[:month].to_i, match[:day].to_i)
|
|
166
|
+
return true if match[:hour].nil?
|
|
167
|
+
return false unless (0..23).cover?(match[:hour].to_i)
|
|
168
|
+
return false unless (0..59).cover?(match[:minute].to_i)
|
|
169
|
+
return false unless match[:second].nil? || (0..59).cover?(match[:second].to_i)
|
|
170
|
+
return true if match[:timezone_sign].nil?
|
|
171
|
+
|
|
172
|
+
(0..23).cover?(match[:timezone_hour].to_i) && (0..59).cover?(match[:timezone_minute].to_i)
|
|
173
|
+
end
|
|
174
|
+
private_class_method :valid_datetime_components?
|
|
175
|
+
|
|
101
176
|
# Safe comparability check for mixed scalar types.
|
|
102
177
|
def self.comparable_values?(left, right)
|
|
103
178
|
return true if left.class == right.class
|
|
@@ -252,7 +252,10 @@ class GroupedIndex
|
|
|
252
252
|
keyword_value = interpret_datetime_keyword_expression(stripped, range_key: 'min')
|
|
253
253
|
return keyword_value unless keyword_value.nil?
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
datetime_value = Jekyll::Plugins::PaginateV3::Support::LooseScalar.datetime(stripped)
|
|
256
|
+
return datetime_value unless datetime_value.nil?
|
|
257
|
+
|
|
258
|
+
raise ArgumentError
|
|
256
259
|
rescue ArgumentError
|
|
257
260
|
raise ArgumentError, "`group.start` could not be parsed as datetime: #{raw_value.inspect}."
|
|
258
261
|
end
|
|
@@ -355,10 +358,10 @@ class GroupedIndex
|
|
|
355
358
|
return true if stripped.match?(/\A(?:#{precise_units})(?:\s*\(|\z)/i)
|
|
356
359
|
return true if stripped.match?(/\A#{Regexp.escape(@now_keyword)}(?:\s*[+-]\s*\d+)?\z/i)
|
|
357
360
|
|
|
358
|
-
parsed =
|
|
361
|
+
parsed = Jekyll::Plugins::PaginateV3::Support::LooseScalar.datetime(stripped)
|
|
362
|
+
return false if parsed.nil?
|
|
363
|
+
|
|
359
364
|
parsed.hour.positive? || parsed.min.positive? || parsed.sec.positive?
|
|
360
|
-
rescue ArgumentError
|
|
361
|
-
false
|
|
362
365
|
end
|
|
363
366
|
end
|
|
364
367
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-paginate-v3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1.alpha
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Convincible
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|