lib/cli/ui/stdout_router.rb in cli-ui-1.5.1 vs lib/cli/ui/stdout_router.rb in cli-ui-2.0.0
- old
+ new
@@ -1,21 +1,23 @@
+# typed: true
+
require 'cli/ui'
require 'stringio'
module CLI
module UI
module StdoutRouter
- class << self
- attr_accessor :duplicate_output_to
- end
-
class Writer
+ extend T::Sig
+
+ sig { params(stream: IOLike, name: Symbol).void }
def initialize(stream, name)
@stream = stream
@name = name
end
+ sig { params(args: String).void }
def write(*args)
args = args.map do |str|
if auto_frame_inset?
str = str.dup # unfreeze
str = str.force_encoding(Encoding::UTF_8)
@@ -29,38 +31,46 @@
# hook return of false suppresses output.
if (hook = Thread.current[:cliui_output_hook])
return if hook.call(args.map(&:to_s).join, @name) == false
end
- @stream.write_without_cli_ui(*prepend_id(@stream, args))
+ T.unsafe(@stream).write_without_cli_ui(*prepend_id(@stream, args))
if (dup = StdoutRouter.duplicate_output_to)
- dup.write(*prepend_id(dup, args))
+ T.unsafe(dup).write(*prepend_id(dup, args))
end
end
private
+ sig { params(stream: IOLike, args: T::Array[String]).returns(T::Array[String]) }
def prepend_id(stream, args)
return args unless prepend_id_for_stream(stream)
+
args.map do |a|
next a if a.chomp.empty? # allow new lines to be new lines
+
"[#{Thread.current[:cliui_output_id][:id]}] #{a}"
end
end
+ sig { params(stream: IOLike).returns(T::Boolean) }
def prepend_id_for_stream(stream)
return false unless Thread.current[:cliui_output_id]
return true if Thread.current[:cliui_output_id][:streams].include?(stream)
+
false
end
+ sig { returns(T::Boolean) }
def auto_frame_inset?
!Thread.current[:no_cliui_frame_inset]
end
+ sig { params(str: String, prefix: String).returns(String) }
def apply_line_prefix(str, prefix)
return '' if str.empty?
+
prefixed = +''
str.force_encoding(Encoding::UTF_8).lines.each do |line|
if @pending_newline
prefixed << line
@pending_newline = false
@@ -72,43 +82,56 @@
prefixed
end
end
class Capture
+ extend T::Sig
+
@m = Mutex.new
@active_captures = 0
@saved_stdin = nil
- def self.with_stdin_masked
- @m.synchronize do
- if @active_captures.zero?
- @saved_stdin = $stdin
- $stdin, w = IO.pipe
- $stdin.close
- w.close
+ class << self
+ extend T::Sig
+
+ sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
+ def with_stdin_masked(&block)
+ @m.synchronize do
+ if @active_captures.zero?
+ @saved_stdin = $stdin
+ $stdin, w = IO.pipe
+ $stdin.close
+ w.close
+ end
+ @active_captures += 1
end
- @active_captures += 1
- end
- yield
- ensure
- @m.synchronize do
- @active_captures -= 1
- if @active_captures.zero?
- $stdin = @saved_stdin
+ yield
+ ensure
+ @m.synchronize do
+ @active_captures -= 1
+ if @active_captures.zero?
+ $stdin = @saved_stdin
+ end
end
end
end
- def initialize(*block_args, with_frame_inset: true, &block)
+ sig do
+ params(with_frame_inset: T::Boolean, block: T.proc.void).void
+ end
+ def initialize(with_frame_inset: true, &block)
@with_frame_inset = with_frame_inset
- @block_args = block_args
@block = block
+ @stdout = ''
+ @stderr = ''
end
+ sig { returns(String) }
attr_reader :stdout, :stderr
+ sig { returns(T.untyped) }
def run
require 'stringio'
StdoutRouter.assert_enabled!
@@ -132,11 +155,11 @@
end
false # suppress writing to terminal
end
begin
- @block.call(*@block_args)
+ @block.call
ensure
@stdout = out.string
@stderr = err.string
end
end
@@ -145,83 +168,97 @@
Thread.current[:no_cliui_frame_inset] = prev_frame_inset
end
end
class << self
+ extend T::Sig
+
WRITE_WITHOUT_CLI_UI = :write_without_cli_ui
NotEnabled = Class.new(StandardError)
- def with_id(on_streams:)
- unless on_streams.is_a?(Array) && on_streams.all? { |s| s.respond_to?(:write) }
- raise ArgumentError, <<~EOF
- on_streams must be an array of objects that respond to `write`
- These do not respond to write
- #{on_streams.reject { |s| s.respond_to?(:write) }.map.with_index { |s| s.class.to_s }.join("\n")}
- EOF
- end
+ sig { returns(T.nilable(IOLike)) }
+ attr_accessor :duplicate_output_to
+ sig do
+ type_parameters(:T)
+ .params(on_streams: T::Array[IOLike], block: T.proc.params(id: String).returns(T.type_parameter(:T)))
+ .returns(T.type_parameter(:T))
+ end
+ def with_id(on_streams:, &block)
require 'securerandom'
id = format('%05d', rand(10**5))
Thread.current[:cliui_output_id] = {
id: id,
- streams: on_streams,
+ streams: on_streams.map { |stream| T.cast(stream, IOLike) },
}
yield(id)
ensure
Thread.current[:cliui_output_id] = nil
end
+ sig { returns(T.nilable(T::Hash[Symbol, T.any(String, IOLike)])) }
def current_id
Thread.current[:cliui_output_id]
end
+ sig { void }
def assert_enabled!
raise NotEnabled unless enabled?
end
- def with_enabled
+ sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) }
+ def with_enabled(&block)
enable
yield
ensure
disable
end
# TODO: remove this
+ sig { void }
def ensure_activated
enable unless enabled?
end
+ sig { returns(T::Boolean) }
def enable
return false if enabled?($stdout) || enabled?($stderr)
+
activate($stdout, :stdout)
activate($stderr, :stderr)
true
end
+ sig { params(stream: IOLike).returns(T::Boolean) }
def enabled?(stream = $stdout)
stream.respond_to?(WRITE_WITHOUT_CLI_UI)
end
+ sig { returns(T::Boolean) }
def disable
return false unless enabled?($stdout) && enabled?($stderr)
+
deactivate($stdout)
deactivate($stderr)
true
end
private
+ sig { params(stream: IOLike).void }
def deactivate(stream)
sc = stream.singleton_class
sc.send(:remove_method, :write)
sc.send(:alias_method, :write, WRITE_WITHOUT_CLI_UI)
end
+ sig { params(stream: IOLike, streamname: Symbol).void }
def activate(stream, streamname)
writer = StdoutRouter::Writer.new(stream, streamname)
raise if stream.respond_to?(WRITE_WITHOUT_CLI_UI)
+
stream.singleton_class.send(:alias_method, WRITE_WITHOUT_CLI_UI, :write)
stream.define_singleton_method(:write) do |*args|
writer.write(*args)
end
end