tty-pager 0.9.0 → 0.13.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.
@@ -1,17 +1,35 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
3
+ require_relative "abstract"
4
+
4
5
  module TTY
5
- class Pager
6
- class NullPager < Pager
6
+ module Pager
7
+ class NullPager < Abstract
8
+ # Pass output directly to stdout
9
+ #
10
+ # @api public
11
+ def write(text)
12
+ return text unless output.tty?
13
+
14
+ output.write(text)
15
+ end
16
+ alias << write
17
+
7
18
  # Pass output directly to stdout
8
19
  #
9
20
  # @api public
10
- def page(text, &callback)
21
+ def puts(text)
11
22
  return text unless output.tty?
12
23
 
13
24
  output.puts(text)
14
25
  end
26
+
27
+ # Do nothing, always return success
28
+ #
29
+ # @api public
30
+ def close
31
+ true
32
+ end
15
33
  end
16
34
  end # Pager
17
35
  end # TTY
@@ -1,51 +1,92 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'tty-which'
3
+ require "open3"
4
+
5
+ require_relative "abstract"
5
6
 
6
7
  module TTY
7
- class Pager
8
+ module Pager
8
9
  # A system pager is used on systems where native
9
10
  # pagination exists
10
11
  #
11
12
  # @api public
12
- class SystemPager < Pager
13
- # Create a system pager
13
+ class SystemPager < Abstract
14
+ # Check if command exists
14
15
  #
15
- # @param [Hash] options
16
- # @option options [String] :command
17
- # the command to use for paging
16
+ # @example
17
+ # command_exist?("less") # => true
18
18
  #
19
- # @api public
20
- def initialize(options = {})
21
- super
22
- @pager_command = options[:command]
23
- unless self.class.available?
24
- raise TTY::Pager::Error, "#{self.class.name} cannot be used on your" \
25
- " system due to lack of appropriate pager" \
26
- " executable. Install `less` like pager or" \
27
- " try using `BasicPager` instead." \
19
+ # @param [String] command
20
+ # the command to check
21
+ #
22
+ # @return [Boolean]
23
+ #
24
+ # @api private
25
+ def self.command_exist?(command)
26
+ exts = ENV.fetch("PATHEXT", "").split(::File::PATH_SEPARATOR)
27
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |dir|
28
+ file = ::File.join(dir, command)
29
+ ::File.exist?(file) || exts.any? { |ext| ::File.exist?("#{file}#{ext}") }
28
30
  end
29
31
  end
30
32
 
31
- # Find first available system command for paging
33
+ # Run pager command silently with no input and capture output
34
+ #
35
+ # @return [Boolean]
36
+ # true if command runs successfully, false otherwise
37
+ #
38
+ # @api private
39
+ def self.run_command(*args)
40
+ _, err, status = Open3.capture3(*args)
41
+ err.empty? && status.success?
42
+ rescue Errno::ENOENT
43
+ false
44
+ end
45
+
46
+ # List possible executables for output paging
47
+ #
48
+ # The UNIX systems often come with "pg" and "more" but no "less" utility.
49
+ # The Linux usually provides "less" and "more" pager, but often no "pg".
50
+ # MacOS comes with "less" and "more" pager and no "pg".
51
+ # Windows provides "more".
52
+ # The "more" pager is the oldest utility and thus most compatible
53
+ # with many systems.
54
+ #
55
+ # @return [Array[String]]
56
+ #
57
+ # @api private
58
+ def self.executables
59
+ [ENV["GIT_PAGER"], ENV["PAGER"], git_pager,
60
+ "less -r", "more -r", "most", "pg", "cat", "pager"].compact
61
+ end
62
+
63
+ # Finds git pager configuration
64
+ #
65
+ # @api private
66
+ def self.git_pager
67
+ command_exist?("git") ? `git config --get-all core.pager` : nil
68
+ end
69
+ private_class_method :git_pager
70
+
71
+ # Find first available termainal pager program executable
32
72
  #
33
73
  # @example Basic usage
34
- # available # => 'less'
74
+ # find_executable # => "less"
35
75
  #
36
76
  # @example Usage with commands
37
- # available('less', 'cat') # => 'less'
77
+ # find_executable("less", "cat") # => "less"
38
78
  #
39
79
  # @param [Array[String]] commands
40
80
  #
41
- # @return [String]
81
+ # @return [String, nil]
82
+ # the found executable or nil when not found
42
83
  #
43
84
  # @api public
44
- def self.available(*commands)
45
- commands = commands.empty? ? executables : commands
46
- commands
85
+ def self.find_executable(*commands)
86
+ execs = commands.empty? ? executables : commands
87
+ execs
47
88
  .compact.map(&:strip).reject(&:empty?).uniq
48
- .find { |cmd| command_exists?(cmd.split.first) }
89
+ .find { |cmd| command_exist?(cmd.split.first) }
49
90
  end
50
91
 
51
92
  # Check if command is available
@@ -54,72 +95,84 @@ module TTY
54
95
  # available? # => true
55
96
  #
56
97
  # @example Usage with command
57
- # available?('less') # => true
98
+ # available?("less") # => true
58
99
  #
59
100
  # @return [Boolean]
60
101
  #
61
102
  # @api public
62
- def self.available?(*commands)
63
- !available(*commands).nil?
103
+ def self.exec_available?(*commands)
104
+ !find_executable(*commands).nil?
64
105
  end
65
106
 
66
- # Use system command to page output text
67
- #
68
- # @example
69
- # page('some long text...')
70
- #
71
- # @param [String] text
72
- # the text to paginate
107
+ # Create a system pager
73
108
  #
74
- # @return [Boolean]
75
- # the success status of launching a process
109
+ # @param [String] :command
110
+ # the command to use for paging
76
111
  #
77
112
  # @api public
78
- def page(text, &callback)
79
- return text unless output.tty?
113
+ def initialize(command: nil, **options)
114
+ super(**options)
115
+ @pager_io = nil
116
+ @pager_command = nil
117
+ pager_command(*Array(command))
80
118
 
81
- write_io = open("|#{pager_command}", 'w')
82
- pid = write_io.pid
83
-
84
- write_io.write(text)
85
- write_io.close
86
-
87
- _, status = Process.waitpid2(pid, Process::WNOHANG)
88
- status.success?
89
- rescue Errno::ECHILD
90
- # on jruby 9x waiting on pid raises
91
- true
119
+ if pager_command.nil?
120
+ raise TTY::Pager::Error,
121
+ "#{self.class.name} cannot be used on your system due to " \
122
+ "lack of appropriate pager executable. Install `less` like " \
123
+ "pager or try using `BasicPager` instead."
124
+ end
92
125
  end
93
126
 
94
- private
95
-
96
- # List possible executables for output paging
127
+ # Send text to the pager process. Starts a new process if it hasn't been
128
+ # started yet.
97
129
  #
98
- # @return [Array[String]]
130
+ # @param [Array<String>] *args
131
+ # strings to send to the pager
99
132
  #
100
- # @api private
101
- def self.executables
102
- [ENV['GIT_PAGER'], ENV['PAGER'],
103
- `git config --get-all core.pager`,
104
- 'less', 'more', 'cat', 'pager']
133
+ # @raise [PagerClosed]
134
+ # strings to send to the pager
135
+ #
136
+ # @api public
137
+ def write(*args)
138
+ @pager_io ||= spawn_pager
139
+ @pager_io.write(*args)
140
+ self
105
141
  end
106
- private_class_method :executables
142
+ alias << write
107
143
 
108
- # Check if command exists
144
+ # Send a line of text, ending in a newline, to the pager process. Starts
145
+ # a new process if it hasn't been started yet.
109
146
  #
110
- # @example
111
- # command_exists?('less) # => true
147
+ # @raise [PagerClosed]
148
+ # if the pager was closed
112
149
  #
113
- # @param [String] command
114
- # the command to check
150
+ # @return [SystemPager]
151
+ #
152
+ # @api public
153
+ def puts(text)
154
+ @pager_io ||= spawn_pager
155
+ @pager_io.puts(text)
156
+ self
157
+ end
158
+
159
+ # Stop the pager, wait for the process to finish. If no pager has been
160
+ # started, returns true.
115
161
  #
116
162
  # @return [Boolean]
163
+ # the exit status of the child process
117
164
  #
118
- # @api private
119
- def self.command_exists?(command)
120
- TTY::Which.exist?(command)
165
+ # @api public
166
+ def close
167
+ return true unless @pager_io
168
+
169
+ success = @pager_io.close
170
+ @pager_io = nil
171
+ success
121
172
  end
122
173
 
174
+ private
175
+
123
176
  # The pager command to run
124
177
  #
125
178
  # @return [String]
@@ -127,11 +180,70 @@ module TTY
127
180
  #
128
181
  # @api private
129
182
  def pager_command(*commands)
130
- @pager_command = if @pager_command && commands.empty?
131
- @pager_command
132
- else
133
- self.class.available(*commands)
134
- end
183
+ if @pager_command && commands.empty?
184
+ @pager_command
185
+ else
186
+ @pager_command = self.class.find_executable(*commands)
187
+ end
188
+ end
189
+
190
+ # Spawn the pager process
191
+ #
192
+ # @return [PagerIO]
193
+ # A wrapper for the external pager
194
+ #
195
+ # @api private
196
+ def spawn_pager
197
+ # In case there's a previous pager running:
198
+ close
199
+
200
+ command = pager_command
201
+ status = self.class.run_command(command)
202
+ # Issue running command, e.g. unsupported flag, fallback to just command
203
+ unless status
204
+ command = pager_command.split.first
205
+ end
206
+
207
+ PagerIO.new(command)
208
+ end
209
+
210
+ # A wrapper for an external process.
211
+ #
212
+ # @api private
213
+ class PagerIO
214
+ def initialize(command)
215
+ @command = command
216
+ @io = IO.popen(@command, "w")
217
+ @pid = @io.pid
218
+ end
219
+
220
+ def write(*args)
221
+ io_call(:write, *args)
222
+ end
223
+
224
+ def puts(*args)
225
+ io_call(:puts, *args)
226
+ end
227
+
228
+ def close
229
+ return true if @io.closed?
230
+
231
+ @io.close
232
+ _, status = Process.waitpid2(@pid, Process::WNOHANG)
233
+ status.success?
234
+ rescue Errno::ECHILD, Errno::EPIPE
235
+ # on jruby 9x waiting on pid raises ECHILD
236
+ # on ruby 2.5/2.6, closing a closed pipe raises EPIPE
237
+ true
238
+ end
239
+
240
+ private
241
+
242
+ def io_call(method_name, *args)
243
+ @io.public_send(method_name, *args)
244
+ rescue Errno::EPIPE
245
+ raise PagerClosed.new("The pager process (`#{@command}`) was closed")
246
+ end
135
247
  end
136
248
  end # SystemPager
137
249
  end # Pager
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module TTY
4
- class Pager
5
- VERSION = "0.9.0"
4
+ module Pager
5
+ VERSION = "0.13.0"
6
6
  end # Pager
7
7
  end # TTY
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tty-pager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-18 00:00:00.000000000 Z
11
+ date: 2020-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-screen
@@ -16,62 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.5.0
19
+ version: '0.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.5.0
26
+ version: '0.8'
27
27
  - !ruby/object:Gem::Dependency
28
- name: tty-which
28
+ name: strings
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.3.0
33
+ version: 0.1.8
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.3.0
41
- - !ruby/object:Gem::Dependency
42
- name: verse
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.5.0
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.5.0
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 1.5.0
62
- - - "<"
63
- - !ruby/object:Gem::Version
64
- version: '2.0'
65
- type: :development
66
- prerelease: false
67
- version_requirements: !ruby/object:Gem::Requirement
68
- requirements:
69
- - - ">="
70
- - !ruby/object:Gem::Version
71
- version: 1.5.0
72
- - - "<"
73
- - !ruby/object:Gem::Version
74
- version: '2.0'
40
+ version: 0.1.8
75
41
  - !ruby/object:Gem::Dependency
76
42
  name: rake
77
43
  requirement: !ruby/object:Gem::Requirement
@@ -90,59 +56,46 @@ dependencies:
90
56
  name: rspec
91
57
  requirement: !ruby/object:Gem::Requirement
92
58
  requirements:
93
- - - "~>"
59
+ - - ">="
94
60
  - !ruby/object:Gem::Version
95
- version: 3.6.0
61
+ version: '3.0'
96
62
  type: :development
97
63
  prerelease: false
98
64
  version_requirements: !ruby/object:Gem::Requirement
99
65
  requirements:
100
- - - "~>"
66
+ - - ">="
101
67
  - !ruby/object:Gem::Version
102
- version: 3.6.0
103
- description: Terminal output paging in a cross-platform way supporting all major ruby
104
- interpreters.
68
+ version: '3.0'
69
+ description: A cross-platform terminal pager that works on all major Ruby interpreters.
105
70
  email:
106
- - ''
71
+ - piotr@piotrmurach.com
107
72
  executables: []
108
73
  extensions: []
109
- extra_rdoc_files: []
74
+ extra_rdoc_files:
75
+ - README.md
76
+ - CHANGELOG.md
77
+ - LICENSE.txt
110
78
  files:
111
- - ".gitignore"
112
- - ".rspec"
113
- - ".travis.yml"
114
79
  - CHANGELOG.md
115
- - CODE_OF_CONDUCT.md
116
- - Gemfile
117
80
  - LICENSE.txt
118
81
  - README.md
119
- - Rakefile
120
- - appveyor.yml
121
- - examples/basic_pager.rb
122
- - examples/system_pager.rb
123
- - examples/temp.txt
124
82
  - lib/tty-pager.rb
125
83
  - lib/tty/pager.rb
84
+ - lib/tty/pager/abstract.rb
126
85
  - lib/tty/pager/basic.rb
127
86
  - lib/tty/pager/null.rb
128
87
  - lib/tty/pager/system.rb
129
88
  - lib/tty/pager/version.rb
130
- - spec/spec_helper.rb
131
- - spec/unit/basic/page_spec.rb
132
- - spec/unit/null/page_spec.rb
133
- - spec/unit/page_spec.rb
134
- - spec/unit/system/available_spec.rb
135
- - spec/unit/system/command_exists_spec.rb
136
- - spec/unit/system/new_spec.rb
137
- - spec/unit/system/page_spec.rb
138
- - tasks/console.rake
139
- - tasks/coverage.rake
140
- - tasks/spec.rake
141
- - tty-pager.gemspec
142
- homepage: https://github.com/piotrmurach/tty-pager
89
+ homepage: https://piotrmurach.github.io/tty
143
90
  licenses:
144
91
  - MIT
145
- metadata: {}
92
+ metadata:
93
+ allowed_push_host: https://rubygems.org
94
+ bug_tracker_uri: https://github.com/piotrmurach/tty-pager/issues
95
+ changelog_uri: https://github.com/piotrmurach/tty-pager/blob/master/CHANGELOG.md
96
+ documentation_uri: https://www.rubydoc.info/gems/tty-pager
97
+ homepage_uri: https://piotrmurach.github.io/tty
98
+ source_code_uri: https://github.com/piotrmurach/tty-pager
146
99
  post_install_message:
147
100
  rdoc_options: []
148
101
  require_paths:
@@ -151,26 +104,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
104
  requirements:
152
105
  - - ">="
153
106
  - !ruby/object:Gem::Version
154
- version: '0'
107
+ version: 2.0.0
155
108
  required_rubygems_version: !ruby/object:Gem::Requirement
156
109
  requirements:
157
110
  - - ">="
158
111
  - !ruby/object:Gem::Version
159
112
  version: '0'
160
113
  requirements: []
161
- rubyforge_project:
162
- rubygems_version: 2.5.1
114
+ rubygems_version: 3.1.2
163
115
  signing_key:
164
116
  specification_version: 4
165
- summary: Terminal output paging in a cross-platform way supporting all major ruby
166
- interpreters.
167
- test_files:
168
- - spec/spec_helper.rb
169
- - spec/unit/basic/page_spec.rb
170
- - spec/unit/null/page_spec.rb
171
- - spec/unit/page_spec.rb
172
- - spec/unit/system/available_spec.rb
173
- - spec/unit/system/command_exists_spec.rb
174
- - spec/unit/system/new_spec.rb
175
- - spec/unit/system/page_spec.rb
176
- has_rdoc:
117
+ summary: A cross-platform terminal pager that works on all major Ruby interpreters.
118
+ test_files: []