j1_paginator 2019.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.
@@ -0,0 +1,181 @@
1
+ module Jekyll
2
+ module J1Paginator::Generator
3
+
4
+ #
5
+ # Static utility functions that are used in the code and
6
+ # don't belong in once place in particular
7
+ #
8
+ class Utils
9
+
10
+ # Static: Calculate the number of pages.
11
+ #
12
+ # all_posts - The Array of all Posts.
13
+ # per_page - The Integer of entries per page.
14
+ #
15
+ # Returns the Integer number of pages.
16
+ def self.calculate_number_of_pages(all_posts, per_page)
17
+ (all_posts.size.to_f / per_page.to_i).ceil
18
+ end
19
+
20
+ # Static: returns a fully formatted string with the current (:num) page number and maximum (:max) page count replaced if configured
21
+ #
22
+ def self.format_page_number(toFormat, cur_page_nr, total_page_count=nil)
23
+ s = toFormat.sub(':num', cur_page_nr.to_s)
24
+ if !total_page_count.nil?
25
+ s = s.sub(':max', total_page_count.to_s)
26
+ end
27
+ return s
28
+ end #function format_page_number
29
+
30
+ # Static: returns a fully formatted string with the :title variable and the current (:num) page number and maximum (:max) page count replaced
31
+ #
32
+ def self.format_page_title(toFormat, title, cur_page_nr=nil, total_page_count=nil)
33
+ return format_page_number(toFormat.sub(':title', title.to_s), cur_page_nr, total_page_count)
34
+ end #function format_page_title
35
+
36
+ # Static: Return a String version of the input which has a leading dot.
37
+ # If the input already has a dot in position zero, it will be
38
+ # returned unchanged.
39
+ #
40
+ # path - a String path
41
+ #
42
+ # Returns the path with a leading slash
43
+ def self.ensure_leading_dot(path)
44
+ path[0..0] == "." ? path : ".#{path}"
45
+ end
46
+
47
+ # Static: Return a String version of the input which has a leading slash.
48
+ # If the input already has a forward slash in position zero, it will be
49
+ # returned unchanged.
50
+ #
51
+ # path - a String path
52
+ #
53
+ # Returns the path with a leading slash
54
+ def self.ensure_leading_slash(path)
55
+ path[0..0] == "/" ? path : "/#{path}"
56
+ end
57
+
58
+ # Static: Return a String version of the input without a leading slash.
59
+ #
60
+ # path - a String path
61
+ #
62
+ # Returns the input without the leading slash
63
+ def self.remove_leading_slash(path)
64
+ path[0..0] == "/" ? path[1..-1] : path
65
+ end
66
+
67
+ # Static: Return a String version of the input which has a trailing slash.
68
+ # If the input already has a forward slash at the end, it will be
69
+ # returned unchanged.
70
+ #
71
+ # path - a String path
72
+ #
73
+ # Returns the path with a trailing slash
74
+ def self.ensure_trailing_slash(path)
75
+ path[-1] == "/" ? path : "#{path}/"
76
+ end
77
+
78
+ #
79
+ # Sorting routine used for ordering posts by custom fields.
80
+ # Handles Strings separately as we want a case-insenstive sorting
81
+ #
82
+ def self.sort_values(a, b)
83
+ if a.nil? && !b.nil?
84
+ return -1
85
+ elsif !a.nil? && b.nil?
86
+ return 1
87
+ end
88
+
89
+ if a.is_a?(String)
90
+ return a.downcase <=> b.downcase
91
+ end
92
+
93
+ if a.respond_to?('to_datetime') && b.respond_to?('to_datetime')
94
+ return a.to_datetime <=> b.to_datetime
95
+ end
96
+
97
+ # By default use the built in sorting for the data type
98
+ return a <=> b
99
+ end
100
+
101
+ # Retrieves the given sort field from the given post
102
+ # the sort_field variable can be a hierarchical value on the form "parent_field:child_field" repeated as many times as needed
103
+ # only the leaf child_field will be retrieved
104
+ def self.sort_get_post_data(post_data, sort_field)
105
+
106
+ # Begin by splitting up the sort_field by (;,:.)
107
+ sort_split = sort_field.split(":")
108
+ sort_value = post_data
109
+
110
+ sort_split.each do |r_key|
111
+ key = r_key.downcase.strip # Remove any erronious whitespace and convert to lower case
112
+ if !sort_value.has_key?(key)
113
+ return nil
114
+ end
115
+ # Work my way through the hash
116
+ sort_value = sort_value[key]
117
+ end
118
+
119
+ # If the sort value is a hash then return nil else return the value
120
+ if( sort_value.is_a?(Hash) )
121
+ return nil
122
+ else
123
+ return sort_value
124
+ end
125
+ end
126
+
127
+ # Ensures that the passed in url has a index and extension applied
128
+ def self.ensure_full_path(url, default_index, default_ext)
129
+ if( url.end_with?('/'))
130
+ return url + default_index + default_ext
131
+ elsif !url.include?('.')
132
+ return url + default_index
133
+ end
134
+ # Default
135
+ return url
136
+ end
137
+
138
+ # Constructs the plural for a key
139
+ def self.plural(config_key)
140
+ (config_key =~ /s$/) ? config_key :
141
+ (config_key.dup.sub!(/y$/, 'ies') || "#{config_key}s")
142
+ end
143
+
144
+ # Converts a string or array to a downcased, stripped array
145
+ def self.config_array(config, key, keepcase = nil)
146
+ [ config[key] ].flatten.compact.uniq.map { |c|
147
+ c.split(/[,;]\s*/).map { |v|
148
+ keepcase ? v.to_s.strip : v.to_s.downcase.strip
149
+ }
150
+ }.flatten.uniq
151
+ end
152
+
153
+ # Merges singular and plural config values into an array
154
+ def self.config_values(config, key, keepcase = nil)
155
+ singular = config_array(config, key, keepcase)
156
+ plural = config_array(config, plural(key), keepcase)
157
+ [ singular, plural ].flatten.uniq
158
+ end
159
+
160
+ def self.validate_url(template)
161
+ url = template.url
162
+ ext = template.output_ext
163
+ path = Jekyll::URL.unescape_path(url)
164
+ path = File.join(path, "index") if url.end_with?("/")
165
+ path << ext unless path.end_with?(ext)
166
+
167
+ dirname = File.dirname(path)
168
+ valid_values = [
169
+ File.join(dirname, "/"),
170
+ File.join(dirname, "index#{ext}")
171
+ ]
172
+
173
+ return url if valid_values.include?(url)
174
+ Jekyll.logger.error "Pagination Error:",
175
+ "Detected invalid url #{url.inspect} for #{template.relative_path.inspect}"
176
+ Jekyll.logger.abort_with "", "Expected #{valid_values.map(&:inspect).join(' or ')}"
177
+ end
178
+ end
179
+
180
+ end # module J1Paginator
181
+ end # module Jekyll
@@ -0,0 +1,10 @@
1
+ module Jekyll
2
+ module J1Paginator
3
+ VERSION = "2019.1.0"
4
+ # When modifying remember to issue a new tag command in git before committing, then push the new tag
5
+ # git tag -a v2.1.0 -m "Gem v2.1.0"
6
+ # git push origin --tags
7
+ # Yanking a published Gem
8
+ # gem yank j1_paginator -v VERSION
9
+ end # module J1Paginator
10
+ end # module Jekyll
@@ -0,0 +1,34 @@
1
+ require_relative '../spec_helper.rb'
2
+
3
+ module Jekyll::J1Paginator::Generator
4
+ describe "checking default config" do
5
+
6
+ it "should always contain the following keys" do
7
+ DEFAULT.must_include 'enabled'
8
+ DEFAULT.must_include 'collection'
9
+ DEFAULT.must_include 'per_page'
10
+ DEFAULT.must_include 'permalink'
11
+ DEFAULT.must_include 'title'
12
+ DEFAULT.must_include 'page_num'
13
+ DEFAULT.must_include 'sort_reverse'
14
+ DEFAULT.must_include 'sort_field'
15
+ DEFAULT.must_include 'limit'
16
+ DEFAULT.must_include 'debug'
17
+ DEFAULT.size.must_be :>=, 10
18
+ end
19
+
20
+ it "should always contain the following key defaults" do
21
+ DEFAULT['enabled'].must_equal false
22
+ DEFAULT['collection'].must_equal 'posts'
23
+ DEFAULT['per_page'].must_equal 10
24
+ DEFAULT['permalink'].must_equal '/page:num/'
25
+ DEFAULT['title'].must_equal ':title - page :num'
26
+ DEFAULT['page_num'].must_equal 1
27
+ DEFAULT['sort_reverse'].must_equal false
28
+ DEFAULT['sort_field'].must_equal 'date'
29
+ DEFAULT['limit'].must_equal 0
30
+ DEFAULT['debug'].must_equal false
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ require_relative '../spec_helper.rb'
2
+
3
+ module Jekyll::J1Paginator::Generator
4
+ describe "tesing pagination page implementation" do
5
+
6
+ it "sould always read the template file into itself" do
7
+ # DUE TO THE JEKYLL:PAGE CLASS ACCESSING FILE IO DIRECTLY
8
+ # I AM UNABLE TO MOCK OUT THE FILE OPERATIONS TO CREATE UNIT TESTS FOR THIS CLASS :/
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,197 @@
1
+ require_relative '../spec_helper.rb'
2
+
3
+ module Jekyll::J1Paginator::Generator
4
+ describe Paginator do
5
+
6
+ it "must include the necessary paginator attributes" do
7
+
8
+ # config_per_page, first_index_page_url, paginated_page_url, posts, cur_page_nr, num_pages
9
+ pager = Paginator.new(10, "index.html", "/page:num/", [], 1, 10, 'index', '.html')
10
+
11
+ # None of these accessors should throw errors, just run through them to test
12
+ val = pager.page
13
+ val = pager.per_page
14
+ val = pager.posts
15
+ val = pager.total_posts
16
+ val = pager.total_pages
17
+ val = pager.previous_page
18
+ val = pager.previous_page_path
19
+ val = pager.next_page
20
+ val = pager.next_page_path
21
+
22
+ end
23
+
24
+ it "must throw an error if the current page number is greater than the total pages" do
25
+ err = -> { pager = Paginator.new(10, "index.html", "/page:num/", [], 10, 8, 'index', '.html') }.must_raise RuntimeError
26
+
27
+ # No error should be raised below
28
+ pager = Paginator.new(10, "index.html", "/page:num/", [], 8, 10, 'index', '.html')
29
+ end
30
+
31
+ it "must trim the list of posts correctly based on the cur_page_nr and per_page" do
32
+ # Create a dummy list of posts that is easy to track
33
+ posts = [
34
+ '1','2','3','4','5',
35
+ '6','7','8','9','10',
36
+ '11','12','13','14','15',
37
+ '16','17','18','19','20',
38
+ '21','22','23','24','25',
39
+ '26','27','28','29','30',
40
+ '31','32','33','34','35'
41
+ ]
42
+
43
+ # Initialize a pager with
44
+ # 5 posts per page
45
+ # at page 2 out of 5 pages
46
+ pager = Paginator.new(5, "index.html", "/page:num/", posts, 2, 5, '', '')
47
+
48
+ pager.page.must_equal 2
49
+ pager.per_page.must_equal 5
50
+ pager.total_pages.must_equal 5
51
+
52
+ pager.total_posts.must_equal 35
53
+
54
+ pager.posts.size.must_equal 5
55
+ pager.posts[0].must_equal '6'
56
+ pager.posts[4].must_equal '10'
57
+
58
+ pager.previous_page.must_equal 1
59
+ pager.previous_page_path.must_equal 'index.html'
60
+ pager.next_page.must_equal 3
61
+ pager.next_page_path.must_equal '/page3/'
62
+ end
63
+
64
+ it "must not create a previous page if we're at first page" do
65
+ # Create a dummy list of posts that is easy to track
66
+ posts = [
67
+ '1','2','3','4','5',
68
+ '6','7','8','9','10',
69
+ '11','12','13','14','15',
70
+ '16','17','18','19','20',
71
+ '21','22','23','24','25',
72
+ '26','27','28','29','30',
73
+ '31','32','33','34','35'
74
+ ]
75
+
76
+ # Initialize a pager with
77
+ # 5 posts per page
78
+ # at page 1 out of 5 pages
79
+ pager = Paginator.new(5, "index.html", "/page:num/", posts, 1, 5, '', '')
80
+
81
+ pager.page.must_equal 1
82
+ pager.per_page.must_equal 5
83
+ pager.total_pages.must_equal 5
84
+
85
+ pager.total_posts.must_equal 35
86
+
87
+ pager.posts.size.must_equal 5
88
+ pager.posts[0].must_equal '1'
89
+ pager.posts[4].must_equal '5'
90
+
91
+ pager.previous_page.must_be_nil
92
+ pager.previous_page_path.must_be_nil
93
+ pager.next_page.must_equal 2
94
+ pager.next_page_path.must_equal '/page2/'
95
+ end
96
+
97
+ it "must not create a next page if we're at the final page" do
98
+ # Create a dummy list of posts that is easy to track
99
+ posts = [
100
+ '1','2','3','4','5',
101
+ '6','7','8','9','10',
102
+ '11','12','13','14','15',
103
+ '16','17','18','19','20',
104
+ '21','22','23','24','25',
105
+ '26','27','28','29','30',
106
+ '31','32','33','34','35'
107
+ ]
108
+
109
+ # Initialize a pager with
110
+ # 5 posts per page
111
+ # at page 5 out of 5 pages
112
+ pager = Paginator.new(5, "index.html", "/page:num/", posts, 5, 5, '', '')
113
+
114
+ pager.page.must_equal 5
115
+ pager.per_page.must_equal 5
116
+ pager.total_pages.must_equal 5
117
+
118
+ pager.total_posts.must_equal 35
119
+
120
+ pager.posts.size.must_equal 5
121
+ pager.posts[0].must_equal '21'
122
+ pager.posts[4].must_equal '25'
123
+
124
+ pager.previous_page.must_equal 4
125
+ pager.previous_page_path.must_equal '/page4/'
126
+ pager.next_page.must_be_nil
127
+ pager.next_page_path.must_be_nil
128
+ end
129
+
130
+ it "must create the explicit index page and index extension when specified" do
131
+ # Create a dummy list of posts that is easy to track
132
+ posts = [
133
+ '1','2','3','4','5',
134
+ '6','7','8','9','10',
135
+ '11','12','13','14','15',
136
+ '16','17','18','19','20',
137
+ '21','22','23','24','25',
138
+ '26','27','28','29','30',
139
+ '31','32','33','34','35'
140
+ ]
141
+
142
+ # Initialize a pager with
143
+ # 5 posts per page
144
+ # at page 2 out of 5 pages
145
+ pager = Paginator.new(5, "index.html", "/page:num/", posts, 2, 5, 'index', '.html')
146
+
147
+ pager.page.must_equal 2
148
+ pager.per_page.must_equal 5
149
+ pager.total_pages.must_equal 5
150
+
151
+ pager.total_posts.must_equal 35
152
+
153
+ pager.posts.size.must_equal 5
154
+ pager.posts[0].must_equal '6'
155
+ pager.posts[4].must_equal '10'
156
+
157
+ pager.previous_page.must_equal 1
158
+ pager.previous_page_path.must_equal 'index.html'
159
+ pager.next_page.must_equal 3
160
+ pager.next_page_path.must_equal '/page3/index.html'
161
+ end
162
+
163
+ it "must create the explicit index page and index extension when specified" do
164
+ # Create a dummy list of posts that is easy to track
165
+ posts = [
166
+ '1','2','3','4','5',
167
+ '6','7','8','9','10',
168
+ '11','12','13','14','15',
169
+ '16','17','18','19','20',
170
+ '21','22','23','24','25',
171
+ '26','27','28','29','30',
172
+ '31','32','33','34','35'
173
+ ]
174
+
175
+ # Initialize a pager with
176
+ # 5 posts per page
177
+ # at page 2 out of 5 pages
178
+ pager = Paginator.new(5, "/", "/", posts, 2, 5, 'feed:num', '.json')
179
+
180
+ pager.page.must_equal 2
181
+ pager.per_page.must_equal 5
182
+ pager.total_pages.must_equal 5
183
+
184
+ pager.total_posts.must_equal 35
185
+
186
+ pager.posts.size.must_equal 5
187
+ pager.posts[0].must_equal '6'
188
+ pager.posts[4].must_equal '10'
189
+
190
+ pager.previous_page.must_equal 1
191
+ pager.previous_page_path.must_equal '/feed1.json'
192
+ pager.next_page.must_equal 3
193
+ pager.next_page_path.must_equal '/feed3.json'
194
+ end
195
+
196
+ end
197
+ end
@@ -0,0 +1,67 @@
1
+ require_relative '../spec_helper.rb'
2
+
3
+ module Jekyll::J1Paginator::Generator
4
+ describe Utils do
5
+
6
+ it "should always replace num format with the specified number" do
7
+ Utils.format_page_number( ":num", 7).must_equal "7"
8
+ Utils.format_page_number( ":num", 13).must_equal "13"
9
+ Utils.format_page_number( ":num", -2).must_equal "-2"
10
+ Utils.format_page_number( ":num", 0).must_equal "0"
11
+ Utils.format_page_number( ":num", 1000).must_equal "1000"
12
+ end
13
+
14
+ it "should always replace num format with the specified number and keep rest of formatting" do
15
+ Utils.format_page_number( "/page:num/", 7).must_equal "/page7/"
16
+ Utils.format_page_number( "/page:num/", 50).must_equal "/page50/"
17
+ Utils.format_page_number( "/page:num/", -5).must_equal "/page-5/"
18
+
19
+ Utils.format_page_number( "/car/:num/", 1).must_equal "/car/1/"
20
+ Utils.format_page_number( "/car/:num", 1).must_equal "/car/1"
21
+ Utils.format_page_number( "car/:num", 1).must_equal "car/1"
22
+ Utils.format_page_number( "/car//:num", 1).must_equal "/car//1"
23
+ end
24
+
25
+ it "make sure there is a leading slash in path" do
26
+ Utils.ensure_leading_slash("path/to/file/wow").must_equal "/path/to/file/wow"
27
+ Utils.ensure_leading_slash("/no/place/wow/").must_equal "/no/place/wow/"
28
+ Utils.ensure_leading_slash("/no").must_equal "/no"
29
+ Utils.ensure_leading_slash("no").must_equal "/no"
30
+ end
31
+
32
+ it "make sure there is never a leading slash in path" do
33
+ Utils.remove_leading_slash("path/to/file/wow").must_equal "path/to/file/wow"
34
+ Utils.remove_leading_slash("/no/place/wow/").must_equal "no/place/wow/"
35
+ Utils.remove_leading_slash("/no").must_equal "no"
36
+ Utils.remove_leading_slash("no").must_equal "no"
37
+ end
38
+
39
+ it "sort must sort strings lowercase" do
40
+ Utils.sort_values( "AARON", "Aaron").must_equal 0
41
+ Utils.sort_values( "AARON", "aaron").must_equal 0
42
+ Utils.sort_values( "aaron", "AARON").must_equal 0
43
+ end
44
+
45
+ it "when sorting by nested post data the values must be resolved fully" do
46
+ data = {'book'=>{ 'name' => { 'first'=> 'John', 'last'=> 'Smith'}, 'rank'=>20}}
47
+ Utils.sort_get_post_data(data, "book:rank").must_equal 20
48
+ Utils.sort_get_post_data(data, "book:name:first").must_equal "John"
49
+ Utils.sort_get_post_data(data, "book:name:last").must_equal "Smith"
50
+
51
+ Utils.sort_get_post_data(data, "book:name").must_be_nil
52
+ Utils.sort_get_post_data(data, "name").must_be_nil
53
+ Utils.sort_get_post_data(data, "book").must_be_nil
54
+ end
55
+
56
+ it "should always replace max format with the specified number if specified" do
57
+ Utils.format_page_number( ":num-:max", 7, 16).must_equal "7-16"
58
+ Utils.format_page_number( ":num-:max", 13, 20).must_equal "13-20"
59
+ Utils.format_page_number( ":num-:max", -2, -4).must_equal "-2--4"
60
+ Utils.format_page_number( ":num_of_:max", 0, 10).must_equal "0_of_10"
61
+ Utils.format_page_number( ":num/:max", 1000, 2000).must_equal "1000/2000"
62
+ end
63
+
64
+
65
+ end
66
+ end
67
+