Class: Everywhere::LinePump
- Inherits:
-
Object
- Object
- Everywhere::LinePump
- Defined in:
- lib/everywhere/line_pump.rb
Overview
Turns the raw byte stream off a pty master into whole lines.
every dev relays the dev server and the desktop shell itself so it can tag
each line with its source. That relay has to be line-oriented — a half-line
written to the terminal would leave the cursor mid-row and the dock repaints
from column 1 — but a pty hands over arbitrary chunks, so this buffers.
Two pty-specific quirks it exists to absorb:
* ONLCR. The slave's line discipline turns every LF into CRLF, so lines
arrive as "text\r\n". The trailing CR is an artifact, not content.
* In-place rewrites. cargo (and anything with a progress bar) redraws a
row by writing CR and the new text without a newline. What the terminal
would have shown is whatever followed the LAST CR, so that is what gets
emitted — one settled line instead of thirty flickering ones.
Pure: feed it strings, get lines back. No IO, no threads, no clock beyond the idle deadline the caller passes in.
Constant Summary collapse
- IDLE_FLUSH =
How long a partial line may sit unterminated before it is emitted anyway. Without this a progress bar that never writes a newline — or a prompt like "Overwrite? [Yn]" — would be invisible for as long as it mattered.
0.2
Instance Method Summary collapse
-
#<<(chunk) ⇒ Object
Feed a chunk; returns the complete lines it produced (without newlines).
-
#drain ⇒ Object
Emit whatever is left, terminated or not.
-
#flush_idle ⇒ Object
Emit a still-unterminated partial line once it has gone quiet.
-
#initialize(idle_flush: IDLE_FLUSH, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }) ⇒ LinePump
constructor
A new instance of LinePump.
- #pending? ⇒ Boolean
Constructor Details
#initialize(idle_flush: IDLE_FLUSH, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }) ⇒ LinePump
Returns a new instance of LinePump.
28 29 30 31 32 33 34 35 36 37 |
# File 'lib/everywhere/line_pump.rb', line 28 def initialize(idle_flush: IDLE_FLUSH, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }) # Bytes in, text out. A pty hands over BINARY, and a chunk boundary can # land in the middle of a multi-byte character, so buffering and splitting # happen on bytes — UTF-8 is self-synchronizing, so a newline is never # inside a character — and each settled line is transcoded on the way out. @buffer = String.new(encoding: Encoding::BINARY) @idle_flush = idle_flush @clock = clock @touched = nil end |
Instance Method Details
#<<(chunk) ⇒ Object
Feed a chunk; returns the complete lines it produced (without newlines).
40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/everywhere/line_pump.rb', line 40 def <<(chunk) @buffer << chunk.b @touched = @clock.call lines = [] while (index = @buffer.index("\n")) raw = @buffer.slice!(0, index + 1) line = settle(raw.chomp("\n")) lines << line if line end lines end |
#drain ⇒ Object
Emit whatever is left, terminated or not. For end-of-stream.
64 65 66 67 68 69 70 |
# File 'lib/everywhere/line_pump.rb', line 64 def drain return [] if @buffer.empty? line = settle(@buffer) @buffer = +"" line ? [line] : [] end |
#flush_idle ⇒ Object
Emit a still-unterminated partial line once it has gone quiet. Call this from the relay loop whenever a read times out.
54 55 56 57 58 59 60 61 |
# File 'lib/everywhere/line_pump.rb', line 54 def flush_idle return [] if @buffer.empty? return [] if @touched && (@clock.call - @touched) < @idle_flush lines = drain @touched = @clock.call lines end |
#pending? ⇒ Boolean
72 |
# File 'lib/everywhere/line_pump.rb', line 72 def pending? = !@buffer.empty? |