pdftext: extract PDF text in a re-exec'd child (sift pdftext) with a deadline

The parser is pure Go but a goroutine cannot be killed, so extraction runs in
a child process: the binary re-executed as `sift pdftext`, reading the PDF on
stdin and writing JSON Lines on stdout, killed at a deadline by the parent.

pdftext.go holds the types, the five errors, Extract (reader.Open with
ErrWrongPassword -> ErrEncrypted, per-page recover, trailer /Info /Title, and
rule-3 UTF-16BE/Latin-1 text decode) and ChildMain (soft 768 MiB memory limit,
-no env read, no network, exit codes 0/3/4/1). outline.go walks /Outlines ->
/First -> /Next with /First children at level+1, resolving /Dest and /GoTo /A
dests to 1-based pages, bounded at 10,000 items and never visiting an object
twice. runner.go is the Runner: env-isolated child (nil env is an empty
environment), 60 s deadline, 64 MiB output cap, 200 MiB stdin cap, stderr
capped at 4 KiB, exit-code to error mapping, and Doc parsing with ErrNoText.
cmd/sift/main.go adds the unlisted `pdftext` case. tools.go is removed; the
go-pdfkit modules stay vendored.

Co-Authored-By: ornith-1.5-35b-a3b <ornith-1.5-35b-a3b@llama.cpp.invalid>
This commit is contained in:
2026-09-25 22:58:05 -07:00
co-authored by ornith-1.5-35b-a3b
parent 13365a2daa
commit f57aaef59e
11 changed files with 1112 additions and 10 deletions
+4
View File
@@ -3,6 +3,8 @@ package main
import (
"fmt"
"os"
"git.wntrmute.dev/kyle/sift/internal/pdftext"
)
// version is set by ldflags: -X main.version=…
@@ -24,6 +26,8 @@ func main() {
searchMain()
case "keys":
keysMain()
case "pdftext":
os.Exit(pdftext.ChildMain(os.Args[1:], os.Stdin, os.Stdout))
case "version", "-version", "--version":
fmt.Println(version)
case "-h", "--help", "help":