tty-pager 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []