Class: Rubino::Tools::ShellTool::CappedCapture

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/tools/shell_tool.rb

Overview

Bounded head+tail accumulator for a subprocess's merged output (#539). Keeps at most cap bytes in memory no matter how much is appended: a ~10% HEAD slice (filled first) plus a sliding TAIL window (the rest of the budget), with the middle elided. Tail-biased because the bytes that matter on overflow — exit suffix, error, "N failures" — are at the end. capped? flips true once the producer has emitted MORE than the cap, so the reader can kill it; to_s renders the retained slice with a marker.

Instance Method Summary collapse

Constructor Details

#initialize(cap) ⇒ CappedCapture

Marker is a fixed worst-case width so it always fits inside the cap.



777
778
779
780
781
782
783
784
785
# File 'lib/rubino/tools/shell_tool.rb', line 777

def initialize(cap)
  @cap        = [cap.to_i, 1_024].max
  @head_limit = [(@cap * 0.1).to_i, 1].max
  @tail_limit = @cap - @head_limit
  @head       = +""
  @tail       = +""
  @total      = 0
  @capped     = false
end

Instance Method Details

#append(bytes, raw_bytes: bytes.bytesize) ⇒ Object

Append already-UTF-8-scrubbed bytes, retaining only head+tail. The cap is charged against raw_bytes (the bytes the pipe actually delivered) so a producer whose output scrubs to empty (cat /dev/zero → pure NUL, deleted) is still capped on volume read, not on retained size (#539).



791
792
793
794
795
796
797
798
799
800
801
802
803
# File 'lib/rubino/tools/shell_tool.rb', line 791

def append(bytes, raw_bytes: bytes.bytesize)
  @total += raw_bytes
  if @head.bytesize < @head_limit
    take = @head_limit - @head.bytesize
    @head << bytes.byteslice(0, take)
    rest  = bytes.byteslice(take, bytes.bytesize - take)
    push_tail(rest) if rest && !rest.empty?
  else
    push_tail(bytes)
  end
  @capped ||= @total > @cap
  self
end

#capped?Boolean

Returns:

  • (Boolean)


805
806
807
# File 'lib/rubino/tools/shell_tool.rb', line 805

def capped?
  @capped
end

#to_s(capped: @capped) ⇒ Object

Render the retained output. When capped, splice in a marker that names the cap and that the producer was terminated, mirroring the elision note Util::Output.truncate uses so the model knows output was cut.



812
813
814
815
816
817
818
819
820
821
# File 'lib/rubino/tools/shell_tool.rb', line 812

def to_s(capped: @capped)
  head = scrub(@head)
  tail = scrub(@tail)
  return head + tail unless capped || @capped

  elided = [@total - head.bytesize - tail.bytesize, 0].max
  marker = "\n... [#{elided} bytes elided · output capped at #{@cap} bytes " \
           "— command terminated] ...\n"
  head + marker + tail
end