pagy 43.2.0 → 43.2.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +5 -6
  3. data/apps/demo.ru +1 -1
  4. data/apps/enable_rails_page_segment.rb +50 -0
  5. data/apps/keynav+root_key.ru +2 -6
  6. data/apps/keynav.ru +2 -6
  7. data/apps/keyset.ru +2 -6
  8. data/apps/keyset_sequel.ru +7 -7
  9. data/apps/rails.ru +6 -9
  10. data/apps/repro.ru +1 -1
  11. data/bin/pagy +2 -94
  12. data/config/pagy.rb +1 -1
  13. data/javascripts/pagy.js +9 -8
  14. data/javascripts/pagy.js.map +3 -3
  15. data/javascripts/pagy.min.js +2 -2
  16. data/javascripts/pagy.mjs +8 -7
  17. data/lib/pagy/classes/calendar/calendar.rb +2 -2
  18. data/lib/pagy/classes/calendar/unit.rb +5 -9
  19. data/lib/pagy/classes/keyset/keynav.rb +4 -3
  20. data/lib/pagy/classes/keyset/keyset.rb +34 -12
  21. data/lib/pagy/classes/offset/countless.rb +1 -1
  22. data/lib/pagy/classes/offset/offset.rb +1 -1
  23. data/lib/pagy/classes/offset/search.rb +1 -1
  24. data/lib/pagy/classes/request.rb +5 -1
  25. data/lib/pagy/cli.rb +156 -0
  26. data/lib/pagy/modules/abilities/linkable.rb +6 -2
  27. data/lib/pagy/toolbox/helpers/data_hash.rb +14 -14
  28. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +2 -2
  29. data/lib/pagy/toolbox/helpers/loader.rb +3 -0
  30. data/lib/pagy/toolbox/helpers/page_url.rb +6 -8
  31. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +1 -1
  32. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +1 -1
  33. data/lib/pagy/toolbox/paginators/method.rb +5 -5
  34. data/lib/pagy.rb +1 -1
  35. metadata +3 -6
  36. data/lib/optimist.rb +0 -1022
  37. data/lib/pagy/classes/keyset/active_record.rb +0 -11
  38. data/lib/pagy/classes/keyset/keynav/active_record.rb +0 -13
  39. data/lib/pagy/classes/keyset/keynav/sequel.rb +0 -13
  40. data/lib/pagy/classes/keyset/sequel.rb +0 -11
@@ -8,9 +8,10 @@ class Pagy
8
8
  PRIOR_PREFIX = 'prior_'
9
9
  PAGE_PREFIX = 'page_'
10
10
 
11
- path = Pathname.new(__FILE__).sub_ext('')
12
- autoload :ActiveRecord, path.join('active_record')
13
- autoload :Sequel, path.join('sequel')
11
+ # Define empty subclasses to allow specific typing without triggering autoload.
12
+ # The .new factory in Keyset will handle mixing in the adapter logic from Pagy::Keyset::Adapters.
13
+ class ActiveRecord < self; end
14
+ class Sequel < self; end
14
15
 
15
16
  # Finalize the instance variables needed for the UI
16
17
  def initialize(set, **)
@@ -6,31 +6,53 @@ require_relative '../../modules/b64'
6
6
  class Pagy
7
7
  # Implement wicked-fast keyset pagination for big data
8
8
  class Keyset < Pagy
9
- path = Pathname.new(__dir__)
10
- autoload :ActiveRecord, path.join('active_record')
11
- autoload :Sequel, path.join('sequel')
12
- autoload :Keynav, path.join('keynav')
9
+ # Autoload adapters: files are loaded only when const_get accesses them
10
+ module Adapters
11
+ path = Pathname.new(__dir__)
12
+ autoload :ActiveRecord, path.join('adapters/active_record')
13
+ autoload :Sequel, path.join('adapters/sequel')
14
+ end
15
+
16
+ autoload :Keynav, Pathname.new(__dir__).join('keynav')
17
+
18
+ # Define empty subclasses to allow specific typing without triggering autoload
19
+ class ActiveRecord < self; end
20
+ class Sequel < self; end
13
21
 
14
22
  class TypeError < ::TypeError; end
15
23
 
16
- # Initialize Keyset* and Keyset::Keynav* classes and subclasses
24
+ # Factory method: detects the set type, configures the subclass, and instantiates
17
25
  def self.new(set, **)
18
- # Subclass instances run only the initializer
19
- if /::(?:ActiveRecord|Sequel)$/.match?(name) # check without triggering autoload
26
+ # 1. Handle direct subclass usage (e.g. Pagy::Keyset::ActiveRecord.new)
27
+ if /::(?:ActiveRecord|Sequel)$/.match?(name)
28
+ # Ensure the adapter is mixed in (lazy load)
29
+ mix_in_adapter(name.split('::').last)
20
30
  return allocate.tap { |instance| instance.send(:initialize, set, **) }
21
31
  end
22
32
 
23
- subclass = if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
24
- self::ActiveRecord
33
+ # 2. Handle Factory usage (Pagy::Keyset.new)
34
+ orm_name = if defined?(::ActiveRecord) && set.is_a?(::ActiveRecord::Relation)
35
+ :ActiveRecord
25
36
  elsif defined?(::Sequel) && set.is_a?(::Sequel::Dataset)
26
- self::Sequel
37
+ :Sequel
27
38
  else
28
39
  raise TypeError, "expected an ActiveRecord::Relation or Sequel::Dataset; got #{set.class}"
29
40
  end
41
+
42
+ # Get the specific subclass (self::ActiveRecord)
43
+ subclass = const_get(orm_name)
44
+ # Ensure the adapter is mixed in (lazy load)
45
+ subclass.mix_in_adapter(orm_name)
30
46
  subclass.new(set, **)
31
47
  end
32
48
 
33
- def initialize(set, **) # rubocop:disable Lint/MissingSuper
49
+ # Helper to lazy-include the adapter module
50
+ def self.mix_in_adapter(orm_name)
51
+ adapter_module = Pagy::Keyset::Adapters.const_get(orm_name)
52
+ include(adapter_module) unless self < adapter_module
53
+ end
54
+
55
+ def initialize(set, **)
34
56
  assign_options(**)
35
57
  assign_and_check(limit: 1)
36
58
  @set = set
@@ -102,7 +124,7 @@ class Pagy
102
124
  last_column, last_direction = keyset.pop
103
125
  intersection = +'('
104
126
  intersection << (keyset.map { |column, _d| "#{identifier[column]} = #{placeholder[column]}" } \
105
- << "#{identifier[last_column]} #{operator[last_direction]} #{placeholder[last_column]}").join(' AND ')
127
+ << "#{identifier[last_column]} #{operator[last_direction]} #{placeholder[last_column]}").join(' AND ')
106
128
  intersection << ')'
107
129
  union << intersection
108
130
  end
@@ -4,7 +4,7 @@ class Pagy
4
4
  class Offset
5
5
  # Offset pagination without a count
6
6
  class Countless < Offset
7
- def initialize(**) # rubocop:disable Lint/MissingSuper
7
+ def initialize(**)
8
8
  assign_options(**)
9
9
  assign_and_check(limit: 1, page: 1)
10
10
  @page = upto_max_pages(@page)
@@ -14,7 +14,7 @@ class Pagy
14
14
  include Rangeable
15
15
  include Shiftable
16
16
 
17
- def initialize(**) # rubocop:disable Lint/MissingSuper
17
+ def initialize(**)
18
18
  assign_options(**)
19
19
  assign_and_check(limit: 1, count: 0, page: 1)
20
20
  assign_last
@@ -3,7 +3,7 @@
3
3
  class Pagy
4
4
  module Search
5
5
  class Arguments < Array
6
- def respond_to_missing? = true
6
+ def respond_to_missing?(*) = true
7
7
 
8
8
  def method_missing(*) = push(*)
9
9
  end
@@ -11,7 +11,7 @@ class Pagy
11
11
  if request.is_a?(Hash)
12
12
  request.values_at(:base_url, :path, :params, :cookie)
13
13
  else
14
- [request.base_url, request.path, request.GET.merge(request.POST).to_h, request.cookies['pagy']]
14
+ [request.base_url, request.path, get_params(request), request.cookies['pagy']]
15
15
  end
16
16
  end
17
17
 
@@ -32,5 +32,9 @@ class Pagy
32
32
 
33
33
  [requested_limit.to_i, @options[:client_max_limit]].min
34
34
  end
35
+
36
+ private
37
+
38
+ def get_params(request) = request.GET.merge(request.POST).to_h
35
39
  end
36
40
  end
data/lib/pagy/cli.rb ADDED
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'fileutils'
5
+ require 'rbconfig'
6
+ require 'pagy'
7
+ require_relative '../../apps/index'
8
+
9
+ class Pagy
10
+ class CLI
11
+ LINUX = RbConfig::CONFIG['host_os'].include?('linux')
12
+ HOST = 'localhost'
13
+ PORT = '8000'
14
+
15
+ def start(args = ARGV)
16
+ options = parse_options(args)
17
+ run_command(args, options)
18
+ end
19
+
20
+ private
21
+
22
+ def parse_options(args)
23
+ options = { env: 'development', host: HOST, port: PORT,
24
+ rerun: false, clear: false, quiet: false }
25
+
26
+ parser = OptionParser.new do |opts|
27
+ opts.banner = <<~BANNER
28
+ Pagy #{VERSION} (https://ddnexus.github.io/pagy/playground)
29
+ Playground to showcase, clone and develop Pagy APPs
30
+
31
+ Usage:
32
+ pagy APP [opts] Showcase APP from the installed gem
33
+ pagy clone APP Clone APP to the current dir
34
+ pagy FILE [opts] Develop app FILE from local path
35
+ BANNER
36
+
37
+ opts.summary_indent = ' '
38
+ opts.summary_width = 18
39
+
40
+ opts.separator "\nAPPs"
41
+ PagyApps::INDEX.each do |name, path|
42
+ desc = File.readlines(path)[3].sub('# ', '').strip
43
+ opts.separator " #{name.ljust(18)}#{desc}"
44
+ end
45
+
46
+ opts.separator "\nRackup options"
47
+ opts.on('-e', '--env ENV', 'Environment') { |v| options[:env] = v }
48
+ opts.on('-o', '--host HOST', 'Host') { |v| options[:host] = v }
49
+ opts.on('-p', '--port PORT', 'Port') { |v| options[:port] = v }
50
+
51
+ if LINUX
52
+ opts.separator "\nRerun options"
53
+ opts.on('--rerun', 'Enable rerun for development') { options[:rerun] = true }
54
+ opts.on('--clear', 'Clear screen before each rerun') { options[:clear] = true }
55
+ end
56
+
57
+ opts.separator "\nOther options"
58
+ opts.on('-q', '--quiet', 'Quiet mode for development') { options[:quiet] = true }
59
+ opts.on('-v', '--version', 'Show version') do
60
+ puts VERSION
61
+ exit
62
+ end
63
+ opts.on('-h', '--help', 'Show this help') do
64
+ puts opts
65
+ exit
66
+ end
67
+
68
+ opts.separator "\nExamples"
69
+ opts.separator " pagy demo Showcase demo at http://#{HOST}:#{PORT}"
70
+ opts.separator ' pagy clone repro Clone repro to ./repro.ru (rename it)'
71
+ opts.separator " pagy ~/myapp.ru Develop ~/myapp.ru at #{HOST}:#{PORT}"
72
+ end
73
+
74
+ begin
75
+ parser.parse!(args)
76
+ rescue OptionParser::InvalidOption => e
77
+ abort e.message
78
+ end
79
+
80
+ if args.empty?
81
+ puts parser
82
+ exit
83
+ end
84
+
85
+ options
86
+ end
87
+
88
+ def run_command(args, options)
89
+ run_from_repo = Pagy::ROOT.join('pagy.gemspec').exist?
90
+ setup_gems(run_from_repo)
91
+
92
+ arg = args.shift
93
+
94
+ if arg.eql?('clone')
95
+ clone_app(args.shift)
96
+ else
97
+ serve_app(arg, options, run_from_repo)
98
+ end
99
+ end
100
+
101
+ def clone_app(name)
102
+ abort "Expected APP to be in [#{PagyApps::INDEX.keys.join(', ')}]; got #{name.inspect}" unless PagyApps::INDEX.key?(name)
103
+
104
+ if File.exist?(name)
105
+ print "Do you want to overwrite the #{name.inspect} file? (y/n)> "
106
+ answer = gets.chomp
107
+ abort "#{name.inspect} file already present" unless answer.start_with?(/y/i)
108
+ end
109
+ FileUtils.cp(PagyApps::INDEX[name], '.', verbose: true)
110
+ end
111
+
112
+ def serve_app(arg, options, run_from_repo)
113
+ if PagyApps::INDEX.key?(arg)
114
+ options[:env] = 'showcase'
115
+ options[:rerun] = false
116
+ options[:quiet] = true
117
+ # Avoid the creation of './tmp/local_secret.txt' for showcase env
118
+ ENV['SECRET_KEY_BASE'] = 'absolute secret!' if arg.eql?('rails')
119
+ file = PagyApps::INDEX[arg]
120
+ else
121
+ file = arg
122
+ end
123
+ abort "#{file.inspect} app not found" unless File.exist?(file)
124
+
125
+ gem_dir = File.expand_path('../..', __dir__)
126
+ rackup = "rackup -I #{gem_dir}/lib -r pagy -o #{options[:host]} -p #{options[:port]} -E #{options[:env]} #{file}"
127
+ rackup << ' -q' if options[:quiet]
128
+
129
+ if options[:rerun]
130
+ name = File.basename(file)
131
+ dir = File.dirname(file)
132
+ rerun = if run_from_repo
133
+ "rerun --name #{name} -d #{dir},#{gem_dir} -p **/*.{rb,js,css,scss,ru,yml}"
134
+ else
135
+ "rerun --name #{name} -d #{dir} -p #{name}"
136
+ end
137
+ rerun << ' -q' if options[:quiet]
138
+ rerun << ' -c' if options[:clear]
139
+ rerun << " -- #{rackup}"
140
+ end
141
+
142
+ exec(rerun || rackup)
143
+ end
144
+
145
+ # Kept as a separate method because mocking 'gemfile' (dsl) is complex otherwise
146
+ def setup_gems(run_from_repo)
147
+ require 'bundler/inline'
148
+ gemfile(!run_from_repo) do
149
+ source 'https://rubygems.org'
150
+ gem 'logger'
151
+ gem 'rackup'
152
+ gem 'rerun' if LINUX
153
+ end
154
+ end
155
+ end
156
+ end
@@ -47,13 +47,17 @@ class Pagy
47
47
  @options.merge(options)
48
48
  .values_at(:root_key, :page_key, :limit_key, :client_max_limit, :limit, :querify, :absolute, :path, :fragment)
49
49
  params = @request.params.clone(freeze: false)
50
- (root_key ? params[root_key] ||= {} : params).tap do |h|
50
+ (root_key ? params[root_key] = params[root_key]&.clone(freeze: false) || {} : params).tap do |h|
51
51
  { page_key => compose_page_param(page),
52
52
  limit_key => client_max_limit && limit }.each { |k, v| v ? h[k] = v : h.delete(k) }
53
53
  end
54
54
  querify&.(params) # Must modify the params: the returned value is ignored
55
+ fragment &&= "##{fragment.delete_prefix('#')}"
56
+ compose_url(absolute, path, params, fragment)
57
+ end
58
+
59
+ def compose_url(absolute, path, params, fragment)
55
60
  query_string = QueryUtils.build_nested_query(params).sub(/\A(?=.)/, '?')
56
- fragment &&= "##{fragment.delete_prefix('#')}"
57
61
  "#{@request.base_url if absolute}#{path || @request.path}#{query_string}#{fragment}"
58
62
  end
59
63
  end
@@ -1,27 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Pagy
4
- DEFAULT_DATA_KEYS = %i[url_template first_url previous_url page_url next_url last_url
4
+ DEFAULT_DATA_KEYS = %i[url_template first_url previous_url current_url page_url next_url last_url
5
5
  count page limit last in from to previous next options].freeze
6
6
 
7
7
  # Generate a hash of the wanted internal data
8
8
  def data_hash(data_keys: @options[:data_keys] || DEFAULT_DATA_KEYS, **)
9
- data_keys -= %i[count limit] if calendar?
10
9
  template = compose_page_url(PAGE_TOKEN, **)
11
- to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) }
12
-
10
+ to_url = ->(page) { template.sub(PAGE_TOKEN, page.to_s) if page }
11
+ data_keys -= %i[count limit] if calendar?
13
12
  data_keys.each_with_object({}) do |key, data|
14
- data[key] = case key
15
- when :url_template then template
16
- when :first_url then compose_page_url(nil, **)
17
- when :previous_url then to_url.(@previous)
18
- when :page_url then to_url.(@page)
19
- when :next_url then to_url.(@next)
20
- when :last_url then to_url.(@last)
21
- else send(key)
22
- end
13
+ value = case key
14
+ when :url_template then template
15
+ when :first_url then compose_page_url(nil, **)
16
+ when :previous_url then to_url.(@previous)
17
+ when :current_url, :page_url then to_url.(@page)
18
+ when :next_url then to_url.(@next)
19
+ when :last_url then to_url.(@last)
20
+ else send(key)
21
+ end
22
+ data[key] = value if value
23
23
  rescue NoMethodError
24
- raise OptionError.new(self, :data, 'to contain known keys', key)
24
+ raise OptionError.new(self, :data_keys, 'to contain known keys/methods', key)
25
25
  end
26
26
  end
27
27
  end
@@ -7,12 +7,12 @@ class Pagy
7
7
  def limit_tag_js(id: nil, item_name: nil, client_max_limit: @options[:client_max_limit], **)
8
8
  raise OptionError.new(self, :client_max_limit, 'to be truthy', client_max_limit) unless client_max_limit
9
9
 
10
- limit_input = %(<input name="limit" type="number" min="1" max="#{@options[:client_max_limit]}" value="#{
10
+ limit_input = %(<input name="limit" type="number" min="1" max="#{client_max_limit}" value="#{
11
11
  @limit}" style="padding: 0; text-align: center; width: #{@limit.to_s.length + 1}rem;">#{A_TAG})
12
12
  url_token = compose_page_url(PAGE_TOKEN, limit: LIMIT_TOKEN)
13
13
 
14
14
  %(<span#{%( id="#{id}") if id} class="pagy limit-tag-js" #{
15
- data_pagy_attribute(:ltj, @from, url_token)
15
+ data_pagy_attribute(:ltj, @from, url_token, PAGE_TOKEN, LIMIT_TOKEN)
16
16
  }><label>#{
17
17
  I18n.translate('pagy.limit_tag_js',
18
18
  item_name: item_name || I18n.translate('pagy.item_name', count: @limit),
@@ -24,8 +24,11 @@ class Pagy
24
24
  send(visibility)
25
25
  # Load the method, overriding its own alias. Next requests will call the method directly.
26
26
  define_method(:"load_#{visibility}") do |*args, **kwargs|
27
+ # Tests shadow the usage of these lines
28
+ # :nocov:
27
29
  require_relative methods[__callee__]
28
30
  send(__callee__, *args, **kwargs)
31
+ # :nocov:
29
32
  end
30
33
  methods.each_key { |method| alias_method method, :"load_#{visibility}" }
31
34
  end
@@ -2,17 +2,15 @@
2
2
 
3
3
  class Pagy
4
4
  # Return the page url for any page
5
- # :nocov:
6
5
  def page_url(page, **)
7
6
  target = case page
8
- when :first then nil
9
- when :current then @page
10
- when :previous then @previous
11
- when :next then @next
12
- when :last then @last
13
- else page
7
+ when :first then nil
8
+ when :current, :page then @page
9
+ when :previous then @previous
10
+ when :next then @next
11
+ when :last then @last
12
+ else page
14
13
  end
15
14
  compose_page_url(target, **) if target || page == :first
16
15
  end
17
- # :nocov:
18
16
  end
@@ -12,7 +12,7 @@ class Pagy
12
12
  def wrap_input_nav_js(html, nav_classes, id: nil, aria_label: nil, **)
13
13
  %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
14
14
  nav_aria_label_attribute(aria_label:)} #{
15
- data = [:inj, compose_page_url(PAGE_TOKEN, **)]
15
+ data = [:inj, compose_page_url(PAGE_TOKEN, **), PAGE_TOKEN]
16
16
  data.push(@update) if keynav?
17
17
  data_pagy_attribute(*data)
18
18
  }>#{html}</nav>)
@@ -29,7 +29,7 @@ class Pagy
29
29
  nav_classes = "pagy-rjs #{nav_classes}" if sequels[0].size > 1
30
30
  %(<nav#{%( id="#{id}") if id} class="#{nav_classes}" #{
31
31
  nav_aria_label_attribute(aria_label:)} #{
32
- data = [:snj, tokens.values, sequels]
32
+ data = [:snj, tokens.values, PAGE_TOKEN, sequels]
33
33
  data.push(@update) if keynav?
34
34
  data_pagy_attribute(*data)
35
35
  }></nav>)
@@ -21,11 +21,11 @@ class Pagy
21
21
  protected
22
22
 
23
23
  define_method :pagy do |paginator = :offset, collection, **options|
24
- arguments = if paginator == :calendar
25
- [self, collection, options]
26
- else
27
- [collection, options = Pagy.options.merge(options)]
28
- end
24
+ arguments = if paginator == :calendar
25
+ [self, collection, options]
26
+ else
27
+ [collection, options = Pagy.options.merge(options)]
28
+ end
29
29
  options[:root_key] = 'page' if options[:jsonapi] # enforce 'page' root_key for JSON:API
30
30
  options[:request] ||= request # user set request or self.request
31
31
  options[:request] = Request.new(options) # Pagy::Request
data/lib/pagy.rb CHANGED
@@ -8,7 +8,7 @@ require_relative 'pagy/toolbox/helpers/loader'
8
8
 
9
9
  # Top superclass: it defines only what's common to all the subclasses
10
10
  class Pagy
11
- VERSION = '43.2.0'
11
+ VERSION = '43.2.2'
12
12
  ROOT = Pathname.new(__dir__).parent.freeze
13
13
  DEFAULT = { limit: 20, limit_key: 'limit', page_key: 'page' }.freeze
14
14
  PAGE_TOKEN = EscapedValue.new('P ')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pagy
3
3
  version: !ruby/object:Gem::Version
4
- version: 43.2.0
4
+ version: 43.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Domizio Demichelis
@@ -48,6 +48,7 @@ files:
48
48
  - LICENSE.txt
49
49
  - apps/calendar.ru
50
50
  - apps/demo.ru
51
+ - apps/enable_rails_page_segment.rb
51
52
  - apps/index.rb
52
53
  - apps/keynav+root_key.ru
53
54
  - apps/keynav.ru
@@ -64,7 +65,6 @@ files:
64
65
  - javascripts/pagy.min.js
65
66
  - javascripts/pagy.mjs
66
67
  - javascripts/wand.js
67
- - lib/optimist.rb
68
68
  - lib/pagy.rb
69
69
  - lib/pagy/classes/calendar/calendar.rb
70
70
  - lib/pagy/classes/calendar/day.rb
@@ -74,19 +74,16 @@ files:
74
74
  - lib/pagy/classes/calendar/week.rb
75
75
  - lib/pagy/classes/calendar/year.rb
76
76
  - lib/pagy/classes/exceptions.rb
77
- - lib/pagy/classes/keyset/active_record.rb
78
77
  - lib/pagy/classes/keyset/adapters/active_record.rb
79
78
  - lib/pagy/classes/keyset/adapters/sequel.rb
80
79
  - lib/pagy/classes/keyset/keynav.rb
81
- - lib/pagy/classes/keyset/keynav/active_record.rb
82
- - lib/pagy/classes/keyset/keynav/sequel.rb
83
80
  - lib/pagy/classes/keyset/keyset.rb
84
- - lib/pagy/classes/keyset/sequel.rb
85
81
  - lib/pagy/classes/offset/countish.rb
86
82
  - lib/pagy/classes/offset/countless.rb
87
83
  - lib/pagy/classes/offset/offset.rb
88
84
  - lib/pagy/classes/offset/search.rb
89
85
  - lib/pagy/classes/request.rb
86
+ - lib/pagy/cli.rb
90
87
  - lib/pagy/console.rb
91
88
  - lib/pagy/modules/abilities/configurable.rb
92
89
  - lib/pagy/modules/abilities/countable.rb