tty-pager 0.9.0 → 0.13.0

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