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.



737
738
739
740
741
742
743
744
745
# File 'lib/rubino/tools/shell_tool.rb', line 737

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).



751
752
753
754
755
756
757
758
759
760
761
762
763
# File 'lib/rubino/tools/shell_tool.rb', line 751

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)


765
766
767
# File 'lib/rubino/tools/shell_tool.rb', line 765

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.



772
773
774
775
776
777
778
779
780
781
# File 'lib/rubino/tools/shell_tool.rb', line 772

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