lib/toys/utils/pager.rb
# frozen_string_literal: true module Toys module Utils ## # A class that invokes an external pager. # # @example Using a pager for regular output # # Toys::Utils::Pager.start do |io| # io.puts "A long string\n" # end # # @example Piping output from a command # # exec_service = Toys::Utils::Exec.new # Toys::Utils::Pager.start(exec_service: exec_service) do |io| # exec_service.exec(["/bin/ls", "-alF"], out: io) # end # class Pager ## # Creates a new pager. # # @param command [String,Array<String>,boolean] The command to use to # invoke the pager. May be specified as a string to be passed to the # shell, an array of strings representing a posix command, the value # `true` to use the default (normally `less -FIRX`), or the value # `false` to disable the pager and write directly to the output # stream. Default is `true`. # @param exec_service [Toys::Utils::Exec] The service to use for # executing commands, or `nil` (the default) to use a default. # @param fallback_io [IO] An IO-like object to write to if the pager is # disabled. Defaults to `$stdout`. # @param rescue_broken_pipes [boolean] If `true` (the default), broken # pipes are silently rescued. This prevents the exception from # propagating out if the pager is interrupted. Set this parameter to # `false` to disable this behavior. # def initialize(command: true, exec_service: nil, fallback_io: nil, rescue_broken_pipes: true) @command = command == true ? Pager.default_command : command @command ||= nil @exec_service = exec_service || Pager.default_exec_service @fallback_io = fallback_io || $stdout @rescue_broken_pipes = rescue_broken_pipes end ## # Runs the pager. Takes a block and yields an IO-like object that passes # text to the pager. Can be called multiple times on the same pager. # # @yieldparam io [IO] An object that can be written to, to pass data to # the pager. # @return [Integer] The exit code of the pager process. # # @example # # pager = Toys::Utils::Pager.new # pager.start do |io| # io.puts "A long string\n" # end # def start if @command result = @exec_service.exec(@command, in: :controller) do |controller| begin yield controller.in if controller.pid rescue ::Errno::EPIPE => e raise e unless @rescue_broken_pipes end end return result.exit_code unless result.failed? end yield @fallback_io 0 end ## # The command for running the pager process. May be specified as a string # to be passed to the shell, an array of strings representing a posix # command, or `nil` to disable the pager and write directly to an output # stream. # # @return [String,Array<String>,nil] # attr_accessor :command ## # The IO stream used if the pager is disabled or could not be executed. # # @return [IO] # attr_accessor :fallback_io class << self ## # A convenience method that creates a pager and runs it once by calling # {Pager#start}. # # @param command [String,Array<String>,boolean] The command to use to # invoke the pager. May be specified as a string to be passed to the # shell, an array of strings representing a posix command, the value # `true` to use the default (normally `less -FIRX`), or the value # `false` to disable the pager and write directly to the output # stream. Default is `true`. # @param exec_service [Toys::Utils::Exec] The service to use for # executing commands, or `nil` (the default) to use a default. # @param fallback_io [IO] An IO-like object to write to if the pager is # disabled. Defaults to `$stdout`. # @param rescue_broken_pipes [boolean] If `true` (the default), broken # pipes are silently rescued. This prevents the exception from # propagating out if the pager is interrupted. Set this parameter to # `false` to disable this behavior. # @return [Integer] The exit code of the pager process. # # @example # # Toys::Utils::Pager.start do |io| # io.puts "A long string\n" # end # def start(command: true, exec_service: nil, fallback_io: nil, rescue_broken_pipes: true, &block) pager = new(command: command, exec_service: exec_service, fallback_io: fallback_io, rescue_broken_pipes: rescue_broken_pipes) pager.start(&block) end ## # @private # def default_command unless defined? @default_command @default_command = ::ENV["PAGER"] unless @default_command path = `which less`.strip @default_command = [path, "-FIRX"] unless path.empty? end end @default_command end ## # @private # def default_exec_service @default_exec_service ||= begin require "toys/utils/exec" Utils::Exec.new end end end end end end