| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Cursor
Description
A position in an append-only log of lines.
Two storage backends, one type:
newMem—IORef(ephemeral; dies with the process)newFile— file holding a decimal line count (survives restart)
Both answer the same question: what is new since I last asked?
This module has no dependency on muster or circuits-io. Either consumer
can hold a Cursor and call pollLines / pollFile without caring
where the position lives.
Line index convention
Positions are zero-based counts of complete lines (same as
length (T.lines content) when every record ends in \n, and same as
wc -l). pollLines advances to length xs after returning the suffix.
Synopsis
- data Cursor
- newMem :: Int -> IO Cursor
- newFile :: FilePath -> IO Cursor
- get :: Cursor -> IO Int
- set :: Cursor -> Int -> IO ()
- pollLines :: Cursor -> [Text] -> IO [Text]
- pollFile :: Cursor -> FilePath -> IO [Text]
- pollNumberedFile :: Cursor -> FilePath -> IO [(Int, Text)]
- seekEnd :: Cursor -> [Text] -> IO ()
- seekEndFile :: Cursor -> FilePath -> IO ()
- readLogLinesComplete :: FilePath -> IO [Text]
Documentation
newMem :: Int -> IO Cursor Source #
In-memory cursor (IORef). Position dies with the process.
>>>c <- newMem 0>>>get c0>>>pollLines c ["a", "b" :: Text]["a","b"]>>>pollLines c ["a", "b", "c"]["c"]>>>get c3
newFile :: FilePath -> IO Cursor Source #
File-backed cursor. Contents are a decimal integer plus newline
(muster-compatible: show n <> "\n"). Missing file reads as 0;
first set creates it.
pollLines :: Cursor -> [Text] -> IO [Text] Source #
Given the full current log as lines, return those after the cursor
and advance the cursor to length xs.
Idempotent on a frozen log: a second call with the same xs yields [].
If the cursor is past the end of the current content (for example because
the log was deleted or truncated), it resets to 0 and the current content
is returned as new. This keeps cursors from becoming permanently stale.
>>>c <- newMem 0>>>pollLines c ["x" :: Text]["x"]>>>pollLines c ["x"][]
pollFile :: Cursor -> FilePath -> IO [Text] Source #
Read a log file as lines (lines), then pollLines.
Missing file → empty log. Empty file → empty log.
Partial last lines (no trailing newline) are kept as a final element of
lines only when content is non-empty and does not end in \n —
actually lines drops a trailing empty segment, so a file ending
without \n still yields its last partial line. Prompt-style partial
lines are therefore visible to the cursor; completeness is the caller's
concern (prompt detection lives above this layer).
pollNumberedFile :: Cursor -> FilePath -> IO [(Int, Text)] Source #
Like pollFile, but consumes only newline-terminated lines and
returns absolute 1-based line numbers alongside.
A partial trailing line (file not ending in \n) is left unconsumed:
the cursor stays before it, so once completed the line is delivered
exactly once, on a later poll. This is the completeness discipline
pollFile leaves to the caller, made the default. Truncation resets
to 0, same as pollLines.