tty-pager 0.12.1 → 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,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tty-which'
3
+ require "open3"
4
+
5
+ require_relative "abstract"
4
6
 
5
7
  module TTY
6
- class Pager
8
+ module Pager
7
9
  # A system pager is used on systems where native
8
10
  # pagination exists
9
11
  #
10
12
  # @api public
11
- class SystemPager < Pager
13
+ class SystemPager < Abstract
12
14
  # Check if command exists
13
15
  #
14
16
  # @example
15
- # command_exists?('less) # => true
17
+ # command_exist?("less") # => true
16
18
  #
17
19
  # @param [String] command
18
20
  # the command to check
@@ -20,46 +22,59 @@ module TTY
20
22
  # @return [Boolean]
21
23
  #
22
24
  # @api private
23
- def self.command_exists?(command)
24
- TTY::Which.exist?(command)
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}") }
30
+ end
25
31
  end
26
32
 
27
33
  # Run pager command silently with no input and capture output
28
34
  #
29
- # @return [nil, String]
30
- # command output or nil if command fails to run
35
+ # @return [Boolean]
36
+ # true if command runs successfully, false otherwise
31
37
  #
32
38
  # @api private
33
39
  def self.run_command(*args)
34
- require 'tempfile'
35
- out = Tempfile.new('tty-pager')
36
- result = system(*args, out: out.path, err: out.path, in: ::File::NULL)
37
- return if result.nil?
38
- out.rewind
39
- out.read
40
- ensure
41
- out.close
42
- out.unlink
40
+ _, err, status = Open3.capture3(*args)
41
+ err.empty? && status.success?
42
+ rescue Errno::ENOENT
43
+ false
43
44
  end
44
45
 
45
46
  # List possible executables for output paging
46
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
+ #
47
55
  # @return [Array[String]]
48
56
  #
49
57
  # @api private
50
58
  def self.executables
51
- [ENV['GIT_PAGER'], ENV['PAGER'],
52
- command_exists?('git') ? `git config --get-all core.pager` : nil,
53
- 'less -r', 'more -r', 'most', 'cat', 'pager', 'pg'].compact
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
54
68
  end
69
+ private_class_method :git_pager
55
70
 
56
71
  # Find first available termainal pager program executable
57
72
  #
58
73
  # @example Basic usage
59
- # find_executable # => 'less'
74
+ # find_executable # => "less"
60
75
  #
61
76
  # @example Usage with commands
62
- # find_executable('less', 'cat') # => 'less'
77
+ # find_executable("less", "cat") # => "less"
63
78
  #
64
79
  # @param [Array[String]] commands
65
80
  #
@@ -71,7 +86,7 @@ module TTY
71
86
  execs = commands.empty? ? executables : commands
72
87
  execs
73
88
  .compact.map(&:strip).reject(&:empty?).uniq
74
- .find { |cmd| command_exists?(cmd.split.first) }
89
+ .find { |cmd| command_exist?(cmd.split.first) }
75
90
  end
76
91
 
77
92
  # Check if command is available
@@ -80,7 +95,7 @@ module TTY
80
95
  # available? # => true
81
96
  #
82
97
  # @example Usage with command
83
- # available?('less') # => true
98
+ # available?("less") # => true
84
99
  #
85
100
  # @return [Boolean]
86
101
  #
@@ -91,60 +106,73 @@ module TTY
91
106
 
92
107
  # Create a system pager
93
108
  #
94
- # @param [Hash] options
95
- # @option options [String] :command
109
+ # @param [String] :command
96
110
  # the command to use for paging
97
111
  #
98
112
  # @api public
99
- def initialize(**options)
100
- super
113
+ def initialize(command: nil, **options)
114
+ super(**options)
115
+ @pager_io = nil
101
116
  @pager_command = nil
102
- commands = Array(options[:command])
103
- pager_command(*commands)
104
-
105
- if @pager_command.nil?
106
- raise TTY::Pager::Error, "#{self.class.name} cannot be used on your" \
107
- " system due to lack of appropriate pager" \
108
- " executable. Install `less` like pager or" \
109
- " try using `BasicPager` instead." \
117
+ pager_command(*Array(command))
118
+
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."
110
124
  end
111
125
  end
112
126
 
113
- # Use system command to page output text
127
+ # Send text to the pager process. Starts a new process if it hasn't been
128
+ # started yet.
114
129
  #
115
- # @example
116
- # page('some long text...')
130
+ # @param [Array<String>] *args
131
+ # strings to send to the pager
117
132
  #
118
- # @param [String] text
119
- # the text to paginate
120
- #
121
- # @return [Boolean]
122
- # the success status of launching a process
133
+ # @raise [PagerClosed]
134
+ # strings to send to the pager
123
135
  #
124
136
  # @api public
125
- def page(text, &_callback)
126
- return text unless output.tty?
127
-
128
- command = pager_command
129
- out = self.class.run_command(command)
130
- # Issue running command, e.g. unsupported flag, fallback to just command
131
- if !out.empty?
132
- command = pager_command.split.first
133
- end
137
+ def write(*args)
138
+ @pager_io ||= spawn_pager
139
+ @pager_io.write(*args)
140
+ self
141
+ end
142
+ alias << write
134
143
 
135
- pager_io = open("|#{command}", 'w')
136
- pid = pager_io.pid
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.
146
+ #
147
+ # @raise [PagerClosed]
148
+ # if the pager was closed
149
+ #
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
137
158
 
138
- pager_io.write(text)
139
- pager_io.close
159
+ # Stop the pager, wait for the process to finish. If no pager has been
160
+ # started, returns true.
161
+ #
162
+ # @return [Boolean]
163
+ # the exit status of the child process
164
+ #
165
+ # @api public
166
+ def close
167
+ return true unless @pager_io
140
168
 
141
- _, status = Process.waitpid2(pid, Process::WNOHANG)
142
- status.success?
143
- rescue Errno::ECHILD
144
- # on jruby 9x waiting on pid raises
145
- true
169
+ success = @pager_io.close
170
+ @pager_io = nil
171
+ success
146
172
  end
147
173
 
174
+ private
175
+
148
176
  # The pager command to run
149
177
  #
150
178
  # @return [String]
@@ -152,11 +180,70 @@ module TTY
152
180
  #
153
181
  # @api private
154
182
  def pager_command(*commands)
155
- @pager_command = if @pager_command && commands.empty?
156
- @pager_command
157
- else
158
- self.class.find_executable(*commands)
159
- 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
160
247
  end
161
248
  end # SystemPager
162
249
  end # Pager
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TTY
4
- class Pager
5
- VERSION = '0.12.1'
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.12.1
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: 2019-03-16 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,56 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.6'
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.6'
27
- - !ruby/object:Gem::Dependency
28
- name: tty-which
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.4'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.4'
26
+ version: '0.8'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: strings
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: 0.1.4
33
+ version: 0.1.8
48
34
  type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: 0.1.4
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
40
+ version: 0.1.8
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: rake
71
43
  requirement: !ruby/object:Gem::Requirement
@@ -84,55 +56,46 @@ dependencies:
84
56
  name: rspec
85
57
  requirement: !ruby/object:Gem::Requirement
86
58
  requirements:
87
- - - "~>"
59
+ - - ">="
88
60
  - !ruby/object:Gem::Version
89
- version: '3.6'
61
+ version: '3.0'
90
62
  type: :development
91
63
  prerelease: false
92
64
  version_requirements: !ruby/object:Gem::Requirement
93
65
  requirements:
94
- - - "~>"
66
+ - - ">="
95
67
  - !ruby/object:Gem::Version
96
- version: '3.6'
97
- description: Terminal output paging in a cross-platform way supporting all major ruby
98
- interpreters.
68
+ version: '3.0'
69
+ description: A cross-platform terminal pager that works on all major Ruby interpreters.
99
70
  email:
100
- - ''
71
+ - piotr@piotrmurach.com
101
72
  executables: []
102
73
  extensions: []
103
- extra_rdoc_files: []
74
+ extra_rdoc_files:
75
+ - README.md
76
+ - CHANGELOG.md
77
+ - LICENSE.txt
104
78
  files:
105
79
  - CHANGELOG.md
106
80
  - LICENSE.txt
107
81
  - README.md
108
- - Rakefile
109
- - examples/basic_pager.rb
110
- - examples/markdown.rb
111
- - examples/pager.rb
112
- - examples/system_pager.rb
113
82
  - lib/tty-pager.rb
114
83
  - lib/tty/pager.rb
84
+ - lib/tty/pager/abstract.rb
115
85
  - lib/tty/pager/basic.rb
116
86
  - lib/tty/pager/null.rb
117
87
  - lib/tty/pager/system.rb
118
88
  - lib/tty/pager/version.rb
119
- - spec/spec_helper.rb
120
- - spec/unit/basic/page_spec.rb
121
- - spec/unit/null/page_spec.rb
122
- - spec/unit/page_spec.rb
123
- - spec/unit/system/command_exists_spec.rb
124
- - spec/unit/system/executables_spec.rb
125
- - spec/unit/system/find_executable_spec.rb
126
- - spec/unit/system/new_spec.rb
127
- - spec/unit/system/page_spec.rb
128
- - tasks/console.rake
129
- - tasks/coverage.rake
130
- - tasks/spec.rake
131
- - tty-pager.gemspec
132
89
  homepage: https://piotrmurach.github.io/tty
133
90
  licenses:
134
91
  - MIT
135
- 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
136
99
  post_install_message:
137
100
  rdoc_options: []
138
101
  require_paths:
@@ -148,19 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
111
  - !ruby/object:Gem::Version
149
112
  version: '0'
150
113
  requirements: []
151
- rubyforge_project:
152
- rubygems_version: 2.7.3
114
+ rubygems_version: 3.1.2
153
115
  signing_key:
154
116
  specification_version: 4
155
- summary: Terminal output paging in a cross-platform way supporting all major ruby
156
- interpreters.
157
- test_files:
158
- - spec/spec_helper.rb
159
- - spec/unit/basic/page_spec.rb
160
- - spec/unit/null/page_spec.rb
161
- - spec/unit/page_spec.rb
162
- - spec/unit/system/command_exists_spec.rb
163
- - spec/unit/system/executables_spec.rb
164
- - spec/unit/system/find_executable_spec.rb
165
- - spec/unit/system/new_spec.rb
166
- - spec/unit/system/page_spec.rb
117
+ summary: A cross-platform terminal pager that works on all major Ruby interpreters.
118
+ test_files: []