stopwhen

command
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: BSD-3-Clause Imports: 7 Imported by: 0

Documentation

Overview

Command stopwhen rewrites a stream that never ends and stops when it has what it came for.

$ stopwhen -want 3
wanted 3 headings, saw 3
  mechanism        a sentinel error from the handler
  bytes accepted   0 of 4096 written - a refused write reports none, though 72 were rewritten
  bytes out        72
  the sink holds   a rewrite of the first 72 bytes of the input
  write error      lolhtml: element handler for "h2": found heading 3: stopwhen: done
  errors.Is(done)  true
  close error      lolhtml: writer is poisoned by an earlier error: … stopwhen: done
  errors.Is(done)  true
  errors.Is(poison) true

$ stopwhen -want 3 -how quiet
wanted 3 headings, saw 114
  mechanism        stopped writing and closed
  bytes accepted   4096 of 4096 written
  bytes out        4096
  the sink holds   a rewrite of the first 4096 bytes of the input
  close error      <nil>

There are two ways to stop, and they answer different questions. The two runs above are the difference: one stopped at the third heading and one stopped after the write that contained it, having seen 114.

A sentinel error from the handler

Return an error and the rewrite ends where the handler said. The error identity survives: the handler's error is wrapped, so errors.Is finds the caller's sentinel both in what Write returns and in what Close returns - Close reports it under ErrPoisoned. Use this when the condition is discovered inside a handler and the caller has to know why it stopped.

What reaches the sink is the useful part, and it is a stronger guarantee than it looks: the output is a rewrite of a prefix of the input, byte for byte the same as feeding that prefix to a fresh rewriter. Not a truncation mid-token, and not a partially serialised element - the unit whose handler stopped is not emitted at all. So a caller can keep or serve what it has.

Where the prefix ends depends on which handler stopped:

an element handler   the bytes before that element's start tag
an end-tag handler   the bytes before that end tag
a text handler       the bytes before that chunk - and which chunk is the nth depends
                     on the write sizes, so this position is not a property of the
                     document

The last row is the reason to count text nodes rather than chunks: a chunk is a fact about how the input arrived. Accumulate to IsLastInTextNode and stop there, and the position is the document's again.

Stop writing and close

If the condition is about the caller rather than the document - enough data, long enough, bored - the mechanism is to stop feeding it. Close returns nil, the output is a rewrite of what was fed, and nothing is poisoned.

The cost is granularity. The condition is checked between writes, so the rewrite overshoots by up to one write's worth of document: with 4 KB reads and this generated stream, asking to stop at the third heading stops after the 114th. That is fine when the point is to bound the work and wrong when the point is to stop at a place in the document, which is what the sentinel is for.

Either way

Close still has to be called, and it releases everything: the handles a rewrite held are gone afterwards whether it ended at the document's end, at a handler's error, or in the middle of a stream nobody intends to finish reading.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL