feat: Validate + keypress subpackage; RFC-style design.md (v1.2.0)
Validate(root): optional startup/test check for tree authoring faults — >1 slot child per node, empty word, duplicate sibling words, dead-end node — traversing circular slots without looping (#3). keypress subpackage: WaitForKey(ctx, cancel) cancels a context on any keystroke for watch-style streaming commands, with per-GOOS cbreak (linux TCGETS/TCSETS, BSD TIOCGETA/TIOCSETA) and a non-tty/unsupported fallback that just waits on ctx. Lifts the last OpenBSD-specific bit out of evpnc/maglevc's watch.go (#6). docs: replace PROPOSAL.md with an RFC-2119 design.md (FR/NFR for the library). Example now dogfoods Validate (a unit test) and keypress (a bounded `watch` command). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: (C) Copyright 2026 Pim van Pelt <pim@ipng.ch>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package keypress lets a streaming command stop on any keystroke. A
|
||||
// watch-style command runs WaitForKey in a goroutine with a cancellable
|
||||
// context; the first key pressed cancels the context, tearing down the stream.
|
||||
//
|
||||
// ctx, cancel := context.WithCancel(ctx)
|
||||
// defer cancel()
|
||||
// go keypress.WaitForKey(ctx, cancel)
|
||||
// for { ev, err := stream.Recv(); ... } // returns when ctx is cancelled
|
||||
//
|
||||
// When standard input is not a terminal (piped, redirected, backgrounded) there
|
||||
// is no keystroke to wait for, so WaitForKey just blocks until ctx ends and
|
||||
// never cancels on its own.
|
||||
package keypress
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
)
|
||||
|
||||
// WaitForKey blocks until a key is pressed on standard input or ctx is done,
|
||||
// whichever comes first; on a keypress it calls cancel. If stdin is not a
|
||||
// terminal it waits on ctx only (it neither reads input nor calls cancel). The
|
||||
// terminal is placed in cbreak mode for the duration and restored on return.
|
||||
//
|
||||
// Run it in its own goroutine. If ctx ends first, WaitForKey returns and
|
||||
// restores the terminal; a read already blocked on stdin is left to be reaped
|
||||
// when the process exits (there is no portable way to interrupt it).
|
||||
func WaitForKey(ctx context.Context, cancel context.CancelFunc) {
|
||||
fd := int(os.Stdin.Fd())
|
||||
old, err := cbreak(fd)
|
||||
if err != nil {
|
||||
<-ctx.Done() // stdin is not a tty: nothing to read, just honor ctx
|
||||
return
|
||||
}
|
||||
defer func() { _ = restore(fd, old) }()
|
||||
|
||||
readDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(readDone)
|
||||
buf := make([]byte, 1)
|
||||
_, _ = os.Stdin.Read(buf)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-readDone:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user