/** * Human-readable output for the terminal. * * Everything here is plain text with no colour codes, so piping to a file or a log * produces exactly what appeared on screen. */ import type { Diagnostic } from '../edf/errors.js'; import type { EdfFile } from '../edf/reader.js'; import { printable } from '../format/unprintable.js'; import type { ConversionPlan } from '../convert/plan.js'; import type { ConvertResult } from '../convert/run.js'; /** * Greedy word wrap, `indent` on every line including the first. * * Only free prose goes through this. The aligned parts of `--info` — the `Format`/`Size` * key-value lines and the channel table — are laid out in columns, and re-flowing a column * is how you turn a table into a paragraph. * * A word wider than the column is left to overrun rather than broken. The long words here * are file paths and quoted channel labels, and neither survives being split across lines: * the point of printing a path is that it can be copied back out. * * A quoted span is one word for the same reason, however many spaces are inside it. The * sentence above is the promise, and it held only for the paths that have no spaces in them: * an interrupted conversion said `Files already written to "/tmp/a very long destination * folder name with` / `many spaces here indeed" are incomplete`, splitting the one thing on * the line that has to be copied whole — and quoting is what a path with spaces in it is * given in the first place. A word wider than the column already overruns rather than * breaking; this is that rule reaching the words it was written for. * * Which is also why the gap between two words is reproduced rather than normalised to one * space. Splitting on `\s+` and rejoining with `' '` re-flowed everything, and a quoted value * is not prose: a channel labelled `EEG A` was offered back as `Did you mean "EEG A"?`, and * a destination given as `-my nightly` as `Write it as one argument instead: * '--out=-my nightly'`. Following either gets a different channel or a different directory * than the one the sentence is about, and the run before it had already quoted the name * correctly on the line above. */ export declare function wrap(text: string, indent?: string, width?: number): string; /** * Make header text safe to print to a terminal. * * EDF identification fields and channel labels are free text copied verbatim out of the * file, and `--info` puts them straight on stdout. A header carrying ANSI escapes could * therefore drive the reader's terminal — `\x1b[2J\x1b[H` clears the screen and homes the * cursor, which is enough to hide the rest of the output or repaint it as something else. * Nobody writes an EDF header that way on purpose, which is exactly why a file that does * should not be trusted with the terminal. * * Control bytes are shown as their escape instead, so a corrupt field stays diagnosable * rather than being silently swallowed. This affects display only: `channels.csv` and * `metadata.json` still copy the field verbatim, and CSV quoting already makes that safe. */ export { printable }; /** * The same protection for text that is meant to span lines. * * `printable` escapes newlines along with everything else, which is right for a channel * label — one has no business containing a line break, and it would break the `--info` * table's alignment. It is wrong for a whole message: several are written on two lines, * and Node's own option errors run to three. Escaping those turned the break into text: * * error: No channel named "ECQ". Did you mean "ECG"?\x0aRun with --info to list ... * * Each line is escaped on its own, so nothing here gains the ability to drive a terminal. * A carriage return is still escaped, so no line can be repainted after it is printed — * which is the property that mattered. A newline can only add a line, never overwrite one. */ export declare function printableLines(text: string, indent?: string): string; /** The `--info` view: what is in this recording, and what would converting it produce. */ export declare function formatInfo(file: EdfFile, plan: ConversionPlan, /** * How many events a conversion would write, when that is already known. * * Null when it is not. `--info` reads the whole annotation channel of a discontinuous file, * because that is where its record times are; a continuous one it reads only far enough to * find the origin. So the count is in hand for one of the two, and the line below said "How * many events there are cannot be told from the header" for both — true of the header, and * beside the point on a file whose events had just been read and counted. */ events?: number | null, /** * Whether the run would stream to stdout, in which case no file is written at all. * * The OUTPUT column is "Named as they will be written. --info is read to find out what a * run leaves behind, and a script that opens the name it was given must find a file there" * — and under `--stdout` it named `signals.csv`, or `signals.csv.gz`, for a run that * creates no directory and no file. The same table already tells a channel that will not * be converted from one the file gives nothing to convert; this is the third thing it * could not say. */ toStdout?: boolean, /** * Whether `--stdout` would refuse the run this describes, in which case it writes nothing. * * The paragraph above the estimate already says so for the commonest of the three * refusals — "this recording makes 3 tables, one per rate — more than `--stdout` can * write" — and the line under it went on predicting the rows anyway: * * Sampling rates differ, so this recording makes 3 tables, one per rate — more * than --stdout can write. Converting into a directory writes one file each; * --layout long puts them all in one table. * Would write 14 rows, roughly 262 B. * * warning: --stdout would refuse this run: needs exactly one table, ... * * Three statements about one command, and the middle one is of a run that exits 1 having * written nothing. The estimate is still the answer to a question worth asking — it is * what converting into a directory would write — so it is kept and its subject corrected, * which is the same repair `--annotations-only` got on this line at 0.4.51. * * Taken from the caller rather than worked out here: `stdoutRefusal` is the conversion's * own guard, and the warning below the report is already built from it, so there is one * answer rather than two that can drift. */ stdoutRefused?: boolean): string; /** * The `--info` view as JSON, for surveying files from a script. * * `indent` is 2 for a single recording, matching what this has always printed, and null for * a batch — several pretty-printed documents run together are readable by a streaming parser * but not by anything that expects one record per line, and a batch is exactly where * line-oriented reading is wanted. null rather than undefined because a default parameter * takes effect when undefined is passed, which quietly restored the indentation this was * meant to drop; JSON.stringify itself wants undefined, so it is translated at the call. * * `--info` answers "what is in this recording and what would converting it cost", which * is exactly the question you want to ask across a directory of hundreds of recordings — * and the text table is the wrong shape for that. `--json` previously applied only to * conversions, so scripts had to parse the aligned columns or convert files just to learn * what was in them. * * Field names match `metadata.json` where the two describe the same thing, so a survey and * a conversion can be read by the same code. */ export declare function infoJson(file: EdfFile, plan: ConversionPlan, /** * The warnings as `--info` prints them, which is not the list they start as. * * This built its own — `withoutFileRateWarning(file.diagnostics).concat(plan.diagnostics)` — * and so missed every amendment the text form makes on the way out. `--info --json --gzip` * said a label "will appear as the channel's name in signals.csv" for a run that writes * `signals.csv.gz`; `--stdout` named the same file for a run that writes no file at all; * `--annotations-only` promised a signal table it does not write, and `--channels` promised * one for a channel it leaves out. Thirteen sentences across the fixtures, all of them in * the surface a script reads, and the text beside them already correct. */ warnings: readonly Diagnostic[], /** * The event count, on the files where `--info` has already read and counted them. * * `formatInfo` has taken this since 0.7.x and prints it — "Would write annotations.csv with * 3 events" — and the same number was dropped on the way to the JSON, which is the surface * a script reads and the reason `--info --json` exists. Null where the count is not in * hand, by the same rule `estimate` is null for a run that writes no signal table: a * continuous file is read only as far as its origin, so its events have not been counted * and there is no honest number to put here. */ events?: number | null, indent?: number | null, /** See `formatInfo`: under `--stdout` there is no file to name. */ toStdout?: boolean): string; /** * One diagnostic per `warning: ` line, prefixed so warnings are greppable; the hint below it * wrapped to the terminal. * * The hint has been on its own unprefixed continuation line since these gained hints at all, * so grepping for `warning:` never picked it up and wrapping it costs nothing that was being * relied on — which is what 0.6.132 got wrong when it left every diagnostic long on the * grounds that they are one line each. Half of that is true. The `warning:` head is a line * per diagnostic and stays one, at whatever width the message runs to; the hint underneath * it is prose addressed to a person reading a terminal, and 17 of them ran past 80 columns, * the widest to 180. At that width the second half of the advice is wherever the terminal * decided to put it, indented under nothing, and the 9-space rule that says "this belongs to * the warning above" is lost at exactly the moment there is enough text for it to matter. */ export declare function formatDiagnostics(diagnostics: readonly Diagnostic[]): string; export declare function formatSummary(result: ConvertResult): string; /** * How long the run took, in a unit it can be stated in. * * `elapsedMs` is a difference of two `Date.now()` readings, so it is whole milliseconds, and * `(ms / 1000).toFixed(1)` rounds everything under fifty of them to `0.0`. That is the last * line of every conversion of a small recording — `--annotations-only` on a three-record * file, every fixture in this repository, and the transcript on three documentation pages, * all of which read `Done in 0.0s.` for a run that read a file and wrote three. * * The same refusal `formatDuration` and `formatBytes` make one file over, and for the reason * stated there: none of them is a rounding rule, and each declines to print a form the * quantity cannot take — 1023.999 KB is not "1024 KB", two records of 1e-15s are not "0s", * and a conversion that happened did not take no time. * * A clock that reports no change at all has measured something below what it can resolve, * which is a different statement from zero and is what it says. */ export declare function elapsed(milliseconds: number): string; export declare function summaryJson(result: ConvertResult, indent?: number | null): string;