edf2csv 0.7.46 → 0.7.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -215,6 +215,29 @@ export async function main(argv) {
215
215
  process.stderr.write(`No input file given.\n\n${USAGE}`);
216
216
  return EXIT_USAGE;
217
217
  }
218
+ /*
219
+ An empty --out, refused here rather than by the filesystem.
220
+
221
+ Every other option that takes a value refuses an empty one from the command line and says
222
+ what it wanted: `--start is empty`, `--channels was given but lists no channel names`,
223
+ `--layout must be "wide" or "long", got ""`. `--out ""` was the eighth of eight and the
224
+ exception — it went the whole way to `mkdir('')` and came back as a conversion failure
225
+ about a path:
226
+
227
+ error: Cannot create "": part of the path does not exist.
228
+ Check the path exists and that you have permission to write there.
229
+
230
+ Exit 1 where the other seven are exit 2, advice about a path and a permission for a value
231
+ that is neither, and in a batch it is decided once per recording rather than once for the
232
+ run. `--out "$DEST"` with `DEST` unset is how it gets written by accident, which is also
233
+ why the empty string alone is refused and the value is not trimmed first: a directory whose
234
+ name is a space is a strange thing to ask for, but it is a thing the filesystem has and a
235
+ path is not a keyword.
236
+ */
237
+ if (values['out'] === '') {
238
+ process.stderr.write('error: --out is empty. Give a directory, for example --out ./converted.\n');
239
+ return EXIT_USAGE;
240
+ }
218
241
  let expanded;
219
242
  let unreadable;
220
243
  let namedDirectory;
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Command-line entry point.\n *\n * Output discipline: anything that is the *result* of a command goes to stdout\n * (`--info`'s channel table, `--json`'s summary). Progress, warnings and the\n * conversion summary go to stderr, so a conversion can be run in a pipeline without\n * its chatter contaminating the data.\n */\n\nimport { parseArgs } from 'node:util';\nimport { pathToFileURL, fileURLToPath } from 'node:url';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { lstat, readdir, realpath, stat } from 'node:fs/promises';\nimport { cpus } from 'node:os';\nimport { fork } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { EdfError } from './edf/errors.js';\nimport { EdfFile } from './edf/reader.js';\nimport { buildPlan, withoutFileRateWarning } from './convert/plan.js';\nimport {\n ConversionError,\n USAGE_ERROR_CODES,\n auditStdout,\n convert,\n defaultOutputDir,\n durationDiagnostics,\n stdoutRefusal,\n} from './convert/run.js';\nimport { ChannelSelectionError } from './convert/channels.js';\n// Shared with the library so a bad option is the same error whichever way it arrived.\nimport { OptionError } from './convert/options.js';\nimport { TimeRangeError, parseTimeSpec } from './convert/time-range.js';\nimport { deriveRecordStarts, withTimingPromiseKept } from './convert/timing.js';\nimport { formatDiagnostics, formatInfo, infoJson, formatSummary, printable, printableLines, summaryJson, wrap } from './cli/report.js';\nimport { counted, listed } from './format/list.js';\nimport { VERSION } from './version.js';\n\nconst USAGE = `edf2csv ${VERSION}\nConvert EDF, EDF+ and BDF recordings to CSV\n\nUsage\n edf2csv <recording.edf | folder> [more ...] [options]\n\nOptions\n -i, --info Show the recording's structure and the estimated\n output size, without converting anything\n -o, --out <dir> Output directory (default: <recording>_csv, beside\n the input)\n -c, --channels <list> Only these channels, comma-separated. Use #N to pick a\n channel by position when two share a label\n --start <time> Begin at this offset (30s, 5m, 1h30m, 00:30:00)\n --duration <time> Convert this much\n --end <time> Stop at this offset (instead of --duration)\n --annotations-only Write only the EDF+ annotations, no signal data\n --decimals <n> Fix the decimal places instead of deriving them\n per channel\n --checksum Record a SHA-256 of the input in metadata.json\n --layout <kind> wide (default): one column per channel, one file per\n sampling rate. long: one file of time_s,channel,value,\n every rate together, one row per sample\n --gzip Compress every CSV, writing .csv.gz files\n --bom Start each CSV with a UTF-8 byte order mark, so Excel\n reads accented text and units like µV correctly\n -j, --jobs <n> Convert this many recordings at once, or \"auto\"\n (default: 1)\n -f, --force Write into the output directory if it already exists\n -q, --quiet Suppress the summary; warnings and errors still print\n --json Print machine-readable JSON to stdout (works with\n --info too)\n --strict Exit 1 if the recording raised any warning\n --stdout Write the signal CSV to stdout instead of a directory\n (one table only: one sampling rate, or --layout long)\n -h, --help Show this help\n -V, --version Show the version\n\nSeveral recordings\n A folder is expanded to every .edf and .bdf inside it, at any depth, and the\n layout is kept: recordings in sub-folders come out in sub-folders.\n Pass more than one and each is converted in turn. Without --out each lands\n beside itself as usual; with --out that directory becomes the parent and each\n recording gets its own inside it. A file that cannot be read is reported and\n the rest still convert, with a non-zero exit at the end. --jobs converts\n several at once, which is worth it for a folder of them.\n\nOutput\n A directory containing signals.csv, channels.csv, metadata.json, and\n annotations.csv when the recording carries EDF+ annotations. Channels recorded\n at different sampling rates are written to separate files, never resampled.\n With --gzip each CSV becomes a .csv.gz; metadata.json stays plain text so the\n directory can still be read at a glance. With --bom each CSV starts with a\n UTF-8 byte order mark and metadata.json does not, since JSON.parse rejects\n one.\n With --layout long every channel goes into one signals.csv as time_s, channel\n and value, one row per sample, whatever rates the recording mixes — which is\n also the one arrangement --stdout can stream for a mixed-rate file.\n\nExamples\n edf2csv recording.edf\n edf2csv recording.edf --info\n edf2csv recording.edf --channels \"EEG Fpz-Cz,ECG\" --out ./converted\n edf2csv recording.edf --start 30m --duration 5m\n edf2csv recording.edf --annotations-only\n edf2csv /data/*.edf --out ./converted\n edf2csv /data/*.edf --out ./converted --jobs auto\n edf2csv /data/study --out ./converted --jobs auto\n`;\n\n/**\n * A continuation line under an `error: ` or `interrupted (SIGINT): ` head.\n *\n * These are written straight to stderr rather than raised as an error, so they never passed\n * through `printableLines` and never wrapped. Most of them interpolate a destination or a\n * recording path, which makes the line as long as the caller's directory tree is deep: the\n * `--stdout` refusal for a folder reached 268 columns on a four-deep path, saying \"Name the\n * recording itself — <path> — or convert to a directory instead.\"\n *\n * A path has no spaces to break at, so it stays whole on its own line and the sentence\n * around it wraps. That is the useful shape: the path is the part that gets copied.\n */\nconst detail = (text: string): string => `${wrap(text, ' ')}\\n`;\n\n/**\n * Every option the command line takes.\n *\n * Its own constant so `parseArgs` and the \"did you mean\" suggestion read the same list. A\n * second copy of twenty flag names is a copy that will be missing the next one.\n */\nconst OPTIONS = {\n info: { type: 'boolean', short: 'i' },\n out: { type: 'string', short: 'o' },\n channels: { type: 'string', short: 'c', multiple: true },\n start: { type: 'string' },\n duration: { type: 'string' },\n end: { type: 'string' },\n 'annotations-only': { type: 'boolean' },\n decimals: { type: 'string' },\n checksum: { type: 'boolean' },\n layout: { type: 'string' },\n gzip: { type: 'boolean' },\n bom: { type: 'boolean' },\n jobs: { type: 'string', short: 'j' },\n force: { type: 'boolean', short: 'f' },\n quiet: { type: 'boolean', short: 'q' },\n json: { type: 'boolean' },\n strict: { type: 'boolean' },\n stdout: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'V' },\n} as const;\n\nconst EXIT_OK = 0;\nconst EXIT_ERROR = 1;\nconst EXIT_USAGE = 2;\n\n/**\n * Whether this run is already shutting its children down.\n *\n * A child dying by signal is normally worth a line of its own, but not when this process is\n * the one that killed it: the interrupt handler names every abandoned directory in a single\n * message, and a per-child line underneath it would say the same thing again, once per job.\n */\nlet stopping = false;\n\n/**\n * Piping into a consumer that exits early (`| head -1`) closes our stdout, and the\n * next write raises EPIPE. That is normal in a shell pipeline, not an error worth a\n * stack trace, so it is swallowed while anything else still surfaces.\n */\nfunction ignoreBrokenPipe(stream: NodeJS.WriteStream): void {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n // A reader closing early (`edf2csv ... --info | head -5`) is not a failure, so the\n // error is swallowed rather than thrown. It deliberately does NOT set an exit code:\n // forcing 0 here would erase a real failure whenever the pipe happened to close\n // after the run had already failed, reporting success for a conversion that did\n // not happen. Node exits 0 on its own when nothing sets a code.\n return;\n }\n\n /*\n Anything else is a real write failure, and this used to rethrow it.\n\n The throw lands on a nextTick, outside whatever try/catch the conversion is running\n inside, so it became an uncaught exception: `--stdout` redirected onto a full disk\n died with a raw stack trace headed `dist/cli.js: throw error;` and lost the warning\n that the CSV it had already produced was truncated. The same failure through `--out`\n printed the ordinary message, and so did `--stdout` through the library API, which\n never registers this listener — the designed path exists and works, and this was\n preempting it.\n\n A second listener means the conversion's writer is watching this stream and will\n turn the failure into that ordinary message. Saying it here too would report one\n failure twice, so this only speaks when nothing else will.\n */\n if (stream.listenerCount('error') > 1) return;\n process.stderr.write(`error: Writing to ${stream === process.stdout ? 'stdout' : 'stderr'} failed: ${error.message}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n\nexport async function main(argv: readonly string[]): Promise<number> {\n ignoreBrokenPipe(process.stdout);\n ignoreBrokenPipe(process.stderr);\n\n let values: Record<string, unknown>;\n let positionals: string[];\n\n try {\n const parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: OPTIONS,\n });\n values = parsed.values as Record<string, unknown>;\n positionals = parsed.positionals;\n } catch (error) {\n process.stderr.write(`${usageMessage(error, argv)}\\n\\nRun edf2csv --help to see the options.\\n`);\n return EXIT_USAGE;\n }\n\n if (values['help'] === true) {\n process.stdout.write(USAGE);\n return EXIT_OK;\n }\n if (values['version'] === true) {\n process.stdout.write(`${VERSION}\\n`);\n return EXIT_OK;\n }\n\n if (positionals.length === 0) {\n process.stderr.write(`No input file given.\\n\\n${USAGE}`);\n return EXIT_USAGE;\n }\n\n let expanded: Input[];\n let unreadable: string[];\n let namedDirectory: boolean;\n try {\n const found = await expandInputs(positionals);\n expanded = found.inputs;\n unreadable = found.unreadable;\n namedDirectory = found.namedDirectory;\n } catch (error) {\n return reportError(error);\n }\n\n // Reported before anything is converted, so it cannot be lost among the summaries, and\n // counted against the run so the exit code does not call a partial sweep a success.\n for (const entry of unreadable) {\n process.stderr.write(\n `error: ${printable(entry)}: could not be read, so any recordings inside it were skipped.\\n`,\n );\n }\n\n if (expanded.length === 0) {\n /*\n \"None here\" and \"could not look\" are different answers, and so are their exit codes.\n\n A folder the process cannot read gave the same exit 2 and the same \"No EDF or BDF\n recordings found\" as an empty one — while the line above it said the folder could not\n be read. Exit 2 is this tool's code for \"the command itself was wrong\", so a script\n was being told to fix its arguments when what needed fixing was a permission. The\n command was fine; the filesystem refused.\n\n And the sentence itself claimed a fact the run is in no position to state: nothing was\n found because nothing was looked at.\n */\n if (unreadable.length > 0) {\n process.stderr.write(\n `${wrap(\n `Nothing could be converted: ${unreadable.length === 1 ? 'that path' : 'those paths'} ` +\n `could not be read, so whether ${unreadable.length === 1 ? 'it holds' : 'they hold'} ` +\n `recordings is unknown.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n process.stderr.write(\n // Escaped, like every other path this prints. These come from the command line, which\n // makes them no safer: a shell glob expands to whatever the directory holds.\n `No EDF or BDF recordings found in ${listed(positionals.map((p) => `\"${printable(p)}\"`))}.\\n`,\n );\n return EXIT_USAGE;\n }\n const inputs = expanded.map((entry) => entry.path);\n /*\n A batch is what you asked for, not what happened to be there.\n\n Counting the recordings made the shape of the run depend on the contents of a folder.\n Under --json a study holding one night printed a pretty-printed object and the same study\n holding two printed two compact lines, so a script written against one of them broke on\n the other — and it broke the day a recording was added, not the day the script changed.\n An input going missing did it in reverse. This is the same count 0.4.20 took out of\n `--out` for the same reason.\n\n Counted from what was named, not from what survived deduplication. `edf2csv one.edf\n alias.edf --out o`, where `alias.edf` is a link to `one.edf`, is two names for one\n recording — converted once, which is right — and the count collapsing to 1 made the run\n stop being a batch: `--out` became the output directory itself, so the files landed in\n `o/` rather than `o/one/`, and `--json` printed one indented document where two\n recordings would have given JSON Lines. cli-reference says of exactly that command that\n it writes `out/one`.\n */\n const batch = positionals.length > 1 || namedDirectory;\n const quiet = values['quiet'] === true;\n const asJson = values['json'] === true;\n const strict = values['strict'] === true;\n const toStdout = values['stdout'] === true;\n\n // Both of these claim stdout. Allowing them together wrote the CSV and then the summary\n // object onto one stream, producing a document that is neither valid CSV nor valid\n // JSON — and silently, since each half looked right on its own.\n /*\n Not under --info, which writes no CSV for --json to collide with.\n\n The two claim stdout because one puts a CSV there and the other a summary. `--info` puts\n neither: it prints a description, and under --json that description *is* the JSON. So\n `--info --stdout --json` — a script asking \"would --stdout work on this recording, in a\n form I can parse\" — was refused for a conflict that cannot arise, which is the same shape\n as the destination guards 0.5.51 stopped applying to it. It is also the only way to see\n the STDOUT_UNSUPPORTED warning from a script.\n */\n if (toStdout && asJson && values['info'] !== true) {\n /*\n `error: ` and the seven-space continuation, like every other refusal.\n\n These two were written before the prefix was, and kept their own shape: every other\n usage error in this tool prints \"error: <what>\" with the advice indented under it, the\n documentation shows them that way, and stderr is what a script greps. A refusal that\n does not match `^error:` is invisible to that grep — and this pair is the one a script\n is most likely to hit, since both flags are things a script passes rather than a person.\n */\n process.stderr.write(\n 'error: --stdout and --json both write to stdout, so they cannot be combined.\\n' +\n ' Use --stdout for the CSV, or --json for the summary.\\n',\n );\n return EXIT_USAGE;\n }\n\n /*\n One stream holds one table, for the same reason it holds one recording: concatenating\n them would give a CSV whose rows come from different files with nothing marking where\n one ends. Naming the count makes it obvious a glob was the cause — except when the count\n is one, which happens for a folder holding a single recording. That read \"--stdout writes\n a single CSV, so it cannot take 1 recordings\": ungrammatical, and wrong on its face, since\n one recording is exactly what it can take. What it cannot take is a folder, whose contents\n are not known until they are walked.\n */\n /*\n Asked of the recordings to stream, not of the run's shape.\n\n These are different questions and 0.5.40 conflated them: it made `batch` count the names\n rather than the recordings, which is right for `--out` and for `--json`, and this guard\n read `batch`. So `edf2csv one.edf one.edf --stdout` — one recording, named twice,\n converted once, perfectly streamable — was refused with the message written for a folder,\n telling the reader to \"name the recording itself\" and quoting the name they had just\n given twice.\n\n A folder is still refused however few recordings it turns out to hold, for the reason\n 0.5.5 gives: what it holds is not known until it is walked.\n */\n if (toStdout && (inputs.length > 1 || namedDirectory)) {\n // Prefixed and indented like the rest; see the --json refusal above. The recording's\n // name goes through `printable` for the reason 0.5.67 gives: a path is untrusted text,\n // and this one is read straight out of a directory the caller named.\n process.stderr.write(\n inputs.length === 1\n ? `error: --stdout writes a single CSV, and a folder is converted as a batch even ` +\n `when it holds one recording.\\n` +\n detail(\n `Name the recording itself — ${printable(inputs[0] as string)} — or convert ` +\n `to a directory instead.`,\n )\n : `error: --stdout writes a single CSV, so it cannot take ` +\n `${counted(inputs.length, 'recording')}.\\n` +\n detail('Convert them to directories instead, or run edf2csv once per file.'),\n );\n return EXIT_USAGE;\n }\n\n /*\n Flags that --stdout has nowhere to put.\n\n Both were accepted and dropped in silence. `--out` names a directory that is never\n created, so the run looked like it had written one. `--checksum` is worse than useless:\n the hash is computed before the first record is read, which is a second full pass over\n the input, and then the only file it is ever written to — metadata.json — is not written\n at all. A recording large enough to want a checksum is large enough to notice reading it\n twice for nothing.\n\n Refusing rather than ignoring is what this tool already does for `--stdout --json` and\n `--stdout --annotations-only`.\n */\n /*\n `--force` and `--jobs` were the two left doing nothing in silence.\n\n `--force` means \"write into a directory that already exists\", and there is no directory.\n `--jobs` is deliberately not here. A job count is a property of the run rather than a\n request about the output, `--stdout` clamping it to one is documented, and a wrapper that\n passes `--jobs 4` to everything is not asking for something about this file.\n */\n for (const [flag, given] of [\n ['--out', values['out'] !== undefined],\n ['--checksum', values['checksum'] === true],\n ['--force', values['force'] === true],\n ] as const) {\n if (toStdout && given) {\n // `error:` and the seven-space continuation, like every other refusal — the shape\n // 0.5.79 gave the rest and these two were not enumerated in its test.\n process.stderr.write(\n `error: --stdout and ${flag} cannot be combined: --stdout writes no\\n` +\n detail(`files, and ${flag} has nothing to act on.`) +\n detail(`Drop ${flag}, or drop --stdout and convert to a directory.`),\n );\n return EXIT_USAGE;\n }\n }\n\n /*\n Compressed bytes at a terminal.\n\n `--stdout --gzip` is a documented pair, and every place it is documented redirects it:\n `edf2csv rec.edf --stdout --gzip > signals.csv.gz`. Without the redirect it wrote the\n deflate stream to the terminal — 46 control bytes and three ESCs out of 244 for the\n smallest fixture here, which is a terminal left in whatever state those happened to\n describe, and nothing usable on the screen either way.\n\n This tool escapes every control byte out of a header before printing it, on the reasoning\n that a file should not be able to drive the reader's terminal. Doing it to them directly,\n from output it generated itself, is the same hazard with the argument for it removed.\n\n Only when stdout is a terminal. A pipe and a regular file are both `isTTY === false`, so\n the documented command is unaffected — and that is also why this cannot be answered by\n refusing the flag pair outright, the way `--stdout --json` is.\n\n gzip itself has declined to write compressed data to a terminal for thirty years, which\n is where a reader's expectation comes from.\n */\n if (toStdout && values['gzip'] === true && process.stdout.isTTY === true) {\n /*\n `<recording>` is not a placeholder a shell leaves alone.\n\n This line is printed on its own, indented, in the shape every command this tool offers\n is printed in — and 0.7.18 and 0.7.19 went through the others making sure each one can\n be pasted. Pasted, `<recording>` is a redirect: the shell looks for a file called\n `recording` and the command never runs, or one exists and edf2csv is handed a recording\n on stdin, which it does not read. Either way the answer to \"what do I type instead\"\n does not survive being typed.\n\n The name is in the invocation being refused, and exactly one of them reaches here: a\n folder and a second recording are both turned away above. Quoted by the same rule as\n the other two hints, so a path with a space in it stays one argument.\n */\n const named = printable(inputs[0] as string);\n process.stderr.write(\n 'error: --stdout --gzip would write compressed bytes straight to the terminal.\\n' +\n detail('Redirect it to a file or a pipe:') +\n ` edf2csv ${survivesBare(named) ? named : singleQuoted(named)} ` +\n `--stdout --gzip > signals.csv.gz\\n`,\n );\n return EXIT_USAGE;\n }\n\n /*\n One recording prints the document it always printed; several print one per line.\n\n Pretty-printed objects run together are still readable by a streaming JSON parser, but\n not by anything that reads a record per line — and a batch is exactly where that is\n wanted. Emitting a batch in the single-file shape would repeat 0.2.28's mistake of\n putting two documents on one stream and leaving the caller to work out the boundary.\n\n null, not undefined: the serialisers default this argument to 2, and a default parameter\n takes effect for undefined, which silently gave a batch the indentation it was supposed\n to be dropping.\n */\n const jsonIndent = batch ? null : 2;\n\n try {\n const outOption = typeof values['out'] === 'string' ? values['out'] : undefined;\n const destinations = destinationsFor(expanded, outOption, batch);\n /*\n Where the output would land is not --info's problem to refuse over. It is still\n --info's job to mention it.\n\n 0.5.32 stopped the guards refusing `--info`, and left nothing in their place — while\n the sentence it added to cli-reference says \"`--info --out` is how you would want to\n find out about them\". So the one command documented as the way to learn about a\n collision said nothing at all about it. Reported as a warning now, using the guard's own\n message so there is one wording rather than two.\n */\n if (values['info'] === true) {\n try {\n assertDistinct(inputs, destinations);\n } catch (error) {\n if (!(error instanceof OptionError)) throw error;\n process.stderr.write(`warning: ${printableLines(error.message, ' ')}\\n`);\n }\n }\n\n /*\n Where the output would land is not --info's problem, because --info has no output.\n\n Both guards refused it: `edf2csv study --info --out yy` on a folder holding `rec.edf`\n beside `rec/inner.edf` exited 2 with \"would be converted into yy/rec/inner, which is\n inside yy/rec — where rec.edf is converted\", and printed nothing about either\n recording. Two recordings whose names collide got the overwrite refusal the same way.\n Both messages assert a conversion and an overwrite that --info does not perform, and\n the identical command without --out describes both files happily.\n\n Which makes the refused command the useful one: `--info --out` is how you ask what a\n run would produce before committing to it, and a collision is exactly the thing you\n would want it to tell you about rather than refuse to look.\n */\n if (values['info'] !== true) assertDistinct(inputs, destinations);\n\n const shared = {\n channels: splitChannels(values['channels']),\n start: optionalTime(values['start'], '--start'),\n startText: typeof values['start'] === 'string' ? values['start'] : undefined,\n duration: optionalTime(values['duration'], '--duration'),\n end: optionalTime(values['end'], '--end'),\n endText: typeof values['end'] === 'string' ? values['end'] : undefined,\n decimals: optionalDecimals(values['decimals']),\n annotationsOnly: values['annotations-only'] === true,\n gzip: values['gzip'] === true,\n bom: values['bom'] === true,\n layout: optionalLayout(values['layout']),\n };\n\n // Validated before the --info branch, not inside the conversion path: a flag that\n // cannot be honoured is a usage error whatever mode it was given in, and accepting\n // \"--jobs 0\" in silence under --info is the kind of quiet that this tool avoids.\n // Parsed before it is overridden: --stdout converts one recording however many jobs\n // were asked for, but \"--stdout --jobs 0\" is still a request that cannot be honoured,\n // and accepting it in silence is the thing 0.4.2 fixed for --info.\n const requestedJobs = parseJobs(values['jobs'], inputs.length);\n /*\n Asking for several at once, of a mode that converts one.\n\n After `parseJobs`, not before it: a malformed count is a fact about the value and gets\n the message about the value, which is what the check above this one has always done and\n what its own comment insists on — \"a request that cannot be met is a usage error rather\n than something to accept in silence\". So `--stdout --jobs 0x10` still reports the `0x10`.\n\n `--jobs 1` is not refused. It asks for precisely what happens, and a script passing it\n uniformly is not asking for anything it will not get; `auto` resolves to 1 on one\n recording. What is refused is a number greater than one, which was accepted and dropped.\n */\n const jobs = toStdout ? 1 : requestedJobs;\n\n if (values['info'] === true) {\n const failures: number[] = [];\n let warnings = 0;\n for (const [index, input] of inputs.entries()) {\n // A blank line between reports, so several tables read as one document.\n if (batch && !asJson && index > 0) process.stdout.write('\\n');\n try {\n warnings += await showInfo(\n input as string,\n shared,\n asJson,\n jsonIndent,\n batch,\n values['stdout'] === true,\n );\n } catch (error) {\n failures.push(reportError(error, batch ? (input as string) : undefined));\n }\n }\n if (unreadable.length > 0) failures.push(EXIT_ERROR);\n if (failures.length > 0) return worstOf(failures);\n return strict && warnings > 0 ? EXIT_ERROR : EXIT_OK;\n }\n\n // The meter redraws one line in place, which two conversions cannot share. Running them\n // at once replaces it with the completion count each file prints as it lands.\n const showProgress =\n !quiet && !asJson && !toStdout && jobs === 1 && process.stderr.isTTY === true;\n\n const failures: number[] = [];\n let warnings = 0;\n let converted = 0;\n\n /** Convert input `index`, sending its output wherever the caller wants it. */\n const runOne = async (index: number, emit: Emit): Promise<void> => {\n const input = inputs[index] as string;\n const destination = destinations[index] as string;\n // Which recording this is, before anything it prints. Without it a batch produces a\n // stack of summaries and errors with nothing saying which file each belongs to.\n if (batch && !quiet && !asJson) {\n emit('err', `[${index + 1}/${inputs.length}] ${printable(input)}\\n`);\n }\n try {\n warnings += await convertOne(\n input,\n destination,\n {\n ...shared,\n // With one input and no --out, convert() derives the default itself, exactly as\n // it did before batches existed.\n outputDir: outOption === undefined && !batch ? undefined : destination,\n checksum: values['checksum'] === true,\n force: values['force'] === true,\n toStdout,\n quiet,\n asJson,\n showProgress,\n jsonIndent,\n batch,\n },\n emit,\n );\n converted++;\n } catch (error) {\n /*\n A batch keeps going. One unreadable recording among five hundred is a reason to\n report that file, not to abandon the ones already converted and refuse the rest.\n The exit code still reports the run as failed, and the closing line says how many\n of them made it, so nothing about the failure is quiet.\n */\n failures.push(reportError(error, batch ? input : undefined, emit));\n }\n };\n\n if (jobs === 1) {\n for (let index = 0; index < inputs.length; index++) {\n await runOne(index, writeThrough);\n // A single recording keeps the exit code it has always had rather than the batch's.\n if (!batch && failures.length > 0) return failures[0] as number;\n }\n } else {\n /*\n Real processes, not concurrent promises.\n\n Converting is almost entirely arithmetic and string building — 1.17 s of CPU for\n 1.24 s of wall clock on a 168 MB conversion — and Node runs that on one thread. An\n in-process pool was tried first and gained 6% on eight recordings, which is the\n overlap in the file reads and nothing else. Each conversion is already a whole\n command, so each one gets its own process, which is what `xargs -P` would do by hand.\n\n Workers take the next recording as they free up rather than splitting the list into\n equal shares. Recordings in a folder differ wildly in length, and a fixed split\n leaves every worker but one idle behind the longest file.\n\n Each child's output is held until it exits, so two finishing together cannot\n interleave one's summary with the other's warnings.\n */\n const running = new Map<ChildProcess, string>();\n\n /*\n Ctrl-C in a terminal reaches every process in the group, so the children would stop\n anyway; a signal sent to this process alone does not, and that is how a batch gets\n run from a script or a CI job. Interrupting one left four conversions writing\n gigabytes into a directory their owner believed abandoned, and the last thing on\n screen was a successful \"Done in 1.6s\" from whichever recording had just landed —\n the run read as if it had finished.\n */\n const onInterrupt = (signal: NodeJS.Signals): void => {\n stopping = true;\n const abandoned = [...running.values()];\n for (const child of running.keys()) child.kill('SIGTERM');\n process.stderr.write(\n `\\ninterrupted (${signal}): ${abandoned.length} conversion` +\n `${abandoned.length === 1 ? '' : 's'} stopped part way through.\\n` +\n (abandoned.length > 0\n ? detail(`Incomplete, and should not be used: ${listed(abandoned.map(printable))}`)\n : ''),\n );\n process.exit(signal === 'SIGINT' ? 130 : 143);\n };\n process.once('SIGINT', onInterrupt);\n process.once('SIGTERM', onInterrupt);\n\n let next = 0;\n const worker = async (): Promise<void> => {\n for (let index = next++; index < inputs.length; index = next++) {\n const input = inputs[index] as string;\n const sink = buffered();\n if (!quiet && !asJson) {\n sink.emit('err', `[${index + 1}/${inputs.length}] ${printable(input)}\\n`);\n }\n const child = await convertInChild(\n input,\n destinations[index] as string,\n values,\n running,\n );\n // The report says what the child actually did; the exit code says whether it\n // got there. A child that converted and warned has a report and exits 0, since\n // --strict is the parent's to apply.\n if (child.report) {\n converted += child.report.converted;\n warnings += child.report.warnings;\n } else if (child.code === 0) {\n converted++;\n }\n if (child.code !== 0) failures.push(child.code);\n // The child saw one recording, so it printed the indented document a single\n // conversion prints. A batch is one object per line.\n sink.emit('out', asJson ? compactJson(child.out) : child.out);\n /*\n The child converted a single recording, so it named no file in its errors the way\n a batch does. Naming it here keeps the two paths identical to a reader and to\n anything grepping a log, where the [n/m] header may not be alongside.\n\n Warnings too, under --quiet, and for exactly the reason 0.5.49 gave for the serial\n path: the `[n/m] <path>` header is what pairs a warning with the file that raised\n it, --quiet suppresses that header, and it took the attribution with it. That fix\n keyed off the child's own `batch` flag, which a forked child does not have — it\n was handed one recording and one destination, so it believes it is a single\n conversion and says nothing. Two recordings, two warnings, no way to tell which\n raised which, and under --jobs not even a stable order to guess from.\n\n Not when the header is printed, or every warning would carry the name twice.\n */\n sink.emit('err', named(child.err, input, quiet));\n sink.flush();\n }\n };\n try {\n await Promise.all(Array.from({ length: jobs }, () => worker()));\n } finally {\n process.off('SIGINT', onInterrupt);\n process.off('SIGTERM', onInterrupt);\n }\n }\n\n /*\n A path that could not be read is a failure of this run, and the closing line has to\n count it before it prints rather than after.\n\n It was counted afterwards, so a folder holding one recording beside a sub-directory\n without read permission printed \"Converted 1 of 1 recordings.\" and exited 1. The line\n agreed with itself and with nothing else — which is the failure the walk's own comment\n says was fixed, quoting that very sentence. What was fixed then was the error line and\n the exit code; the summary went on saying everything worked.\n\n Counted separately from the recordings, because an unreadable path is not one of them:\n how many recordings it held is the thing nobody knows.\n */\n if (unreadable.length > 0) failures.push(EXIT_ERROR);\n\n if (batch && !quiet && !asJson) {\n const unread =\n unreadable.length > 0\n ? `; ${unreadable.length} path${unreadable.length === 1 ? '' : 's'} could not be read`\n : '';\n const failed = converted < inputs.length ? `; ${inputs.length - converted} failed` : '';\n process.stderr.write(\n `\\nConverted ${converted} of ${counted(inputs.length, 'recording')}${failed}${unread}.\\n`,\n );\n }\n\n /*\n A parent that forked this process needs the counts, not just an exit status.\n\n An exit code cannot separate \"converted, and raised warnings\" from \"did not convert\",\n and under --strict those are the same code. The parent read it as a failure, so a\n parallel run of two recordings — one of which merely warned — reported \"Converted 1 of\n 2 recordings; 1 failed\" for a run in which both converted, while the serial path said\n \"Converted 2 of 2\". `process.send` exists only when this process was forked with a\n channel, so nothing changes for an ordinary invocation.\n */\n process.send?.({ edf2csv: { converted, warnings } });\n\n if (failures.length > 0) return worstOf(failures);\n\n /*\n --strict turns any warning into a non-zero exit, for pipelines that would rather stop\n than proceed on a recording the tool had something to say about.\n\n The output is still written. A warning describes the recording, not a failure to\n convert it — a truncated file converts correctly for the records that are there — so\n destroying that work would be the wrong response. The exit code is the signal; what\n to do about it is the caller's decision, and they still have the files to inspect.\n */\n if (strict && warnings > 0) {\n process.stderr.write(\n `\\n${wrap(\n `--strict: ${warnings} warning${warnings === 1 ? '' : 's'} raised, so this run is ` +\n `reported as a failure. The output was still written.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n return EXIT_OK;\n } catch (error) {\n return reportError(error);\n }\n}\n\n/** Print one recording's `--info`, and return how many diagnostics it raised. */\nasync function showInfo(\n input: string,\n shared: Record<string, unknown>,\n asJson: boolean,\n jsonIndent: number | null,\n batch = false,\n toStdout = false,\n): Promise<number> {\n const file = await EdfFile.open(input);\n try {\n /*\n Read the annotation channel only when the timing actually depends on it.\n\n --info is documented as a header-only summary that returns immediately whatever\n the file's size, but it called readAnnotations() on every EDF+/BDF+ file — a seek\n into every data record. deriveRecordStarts discards that data unless the file is\n EDF+D, where record start times are stored rather than arithmetic, so on a\n continuous recording the whole scan was thrown away. It cost 0.29 s on a 12 MB\n file and scaled with record count.\n\n A discontinuous file still needs the scan: without it the reported span and row\n estimate are wrong, which is a bug that has already been fixed once here.\n */\n const hasAnnotations = file.annotationSignals.length > 0;\n const needsEveryRecordStart = file.header.continuity === 'EDF+D' && hasAnnotations;\n /*\n A continuous recording needs its origin too, which is one read rather than one per\n record: 0.4.9 made its first record's timekeeping TAL the point the samples are timed\n from, and this report went on placing a requested window against zero. `--info --start 1`\n predicted 8 rows where the conversion wrote 10, on a file whose discontinuous twin —\n identical but for the reserved field — agreed with itself.\n */\n const scan =\n !needsEveryRecordStart && file.header.continuity === 'EDF+C' && hasAnnotations\n ? await file.scanOrigin()\n : null;\n const annotationData = needsEveryRecordStart\n ? await file.readAnnotations()\n : {\n annotations: [],\n recordStarts: scan ? [scan.origin] : [],\n malformed: 0,\n // Counted rather than assumed: see EdfFile.scanOrigin.\n malformedTimekeeping: scan?.malformedTimekeeping ?? 0,\n };\n const timing = deriveRecordStarts(file, annotationData);\n const plan = buildPlan(\n {\n signals: file.header.signals,\n recordDuration: file.header.recordDuration,\n recordCount: file.recordCount,\n hasAnnotationChannel: file.annotationSignals.length > 0,\n recordStarts: timing.starts,\n },\n shared,\n );\n plan.diagnostics.push(...timing.diagnostics);\n /*\n What --stdout would do with this recording, which --info did not ask.\n\n `--info --stdout` on a three-rate file predicted \"Would write 1,155 rows, roughly\n 22.2 KB\" and said the channels \"are written to one file per rate\" — for a command that\n refuses to run, writes nothing and names no file. --info exists to say what a\n conversion will do, and refusing is one of the things it does.\n\n A warning rather than a refusal, for the reason 0.5.51 gives about the destination\n guards: --info writes nothing, so a rule about the output has no business stopping it\n from describing the recording — and being told the command will not work is exactly\n what was asked. The conversion's own guard supplies the words, so there is one wording.\n */\n /*\n What the conversion would say about the durations it would write.\n\n Same window the conversion applies, so --info predicts the warnings rather than a\n different set. Raised here rather than in the parser because the counts have to be of\n the rows that reach annotations.csv — see durationDiagnostics.\n */\n plan.diagnostics.push(\n ...durationDiagnostics(annotationData.annotations, {\n from: plan.range.startSeconds,\n to: plan.range.endSeconds,\n }),\n );\n\n if (toStdout) {\n const refusal = stdoutRefusal(file, plan);\n if (refusal) {\n plan.diagnostics.push({\n code: 'STDOUT_UNSUPPORTED',\n severity: 'warning',\n message: `--stdout would refuse this recording: ${refusal.message.replace(/^--stdout /u, '')}`,\n ...(refusal.hint === undefined ? {} : { hint: refusal.hint }),\n });\n }\n }\n /*\n Checked, like a conversion's stdout is.\n\n This wrote and looked at nothing, so `--info > desc.txt` into a filesystem with no room\n produced a zero-byte file and exited 0. The same audit the `--stdout` path uses: it\n declines anything that is not a regular file, so a pipe or a terminal is unaffected, and\n `--info | head` keeps exiting 0.\n */\n const audit = auditStdout();\n const description = asJson\n ? `${infoJson(file, plan, jsonIndent)}\\n`\n : `${formatInfo(file, plan)}\\n`;\n process.stdout.write(description);\n audit?.count(Buffer.byteLength(description));\n audit?.verify();\n\n // Under --json the warnings travel inside the document, exactly as they do for a\n // conversion, so stderr stays empty and the whole result is one parseable thing.\n const diagnostics = [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics];\n if (!asJson && diagnostics.length > 0) {\n /*\n Named when there is more than one recording to confuse it with.\n\n The table goes to stdout and the warnings to stderr, which is the point of the split\n — but over a folder that left several warnings in a row on stderr with nothing saying\n which recording raised any of them. Two recordings, two warnings, and no way to pair\n them up short of running the tool again one file at a time. A batch conversion has\n named its recordings since 0.4.20; this is the same report from the same tool about\n the same files.\n */\n const where = batch ? `${printable(input)}: ` : '';\n process.stderr.write(\n `\\n${formatDiagnostics(diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n`,\n );\n }\n return diagnostics.length;\n } finally {\n await file.close();\n }\n}\n\n/** Convert one recording, and return how many diagnostics it raised. */\nasync function convertOne(\n input: string,\n destination: string,\n options: Record<string, unknown> & {\n quiet: boolean;\n asJson: boolean;\n showProgress: boolean;\n toStdout: boolean;\n jsonIndent: number | null;\n batch?: boolean;\n },\n emit: Emit = writeThrough,\n): Promise<number> {\n const { quiet, asJson, showProgress, toStdout } = options;\n let lastTick = 0;\n\n /*\n Interrupting a conversion leaves a CSV that stops mid-recording but is still\n perfectly well-formed, so nothing about the file itself reveals that half the\n data is missing. Saying so on the way out is the whole point of a tool that\n claims it will not go quiet when something is wrong.\n */\n /*\n Whether the destination was already there before this run touched it.\n\n Read once, up front, because at interrupt time it is the difference between three\n different true statements and there is no other way to tell them apart. A directory that\n exists now and did not exist then was created by this conversion; one that existed then\n holds files this conversion may never have reached.\n */\n const destinationExistedBefore = toStdout ? false : existsSync(destination);\n\n /*\n Take the progress meter off the line before anything else is printed on it.\n\n The meter writes `\\r converting\\u2026 47%` and leaves the cursor after the percentage, so\n whatever is written next continues that line. Success cleared it and an interrupt cleared\n it; a conversion that *failed* did not, and the error landed on the end of the meter:\n\n converting\\u2026 96%error: Expected 317440 bytes of data at record 1638 but only 0 ...\n\n Which is the one thing this output is shaped to avoid. Every release since 0.7.1 has kept\n `error: ` and `warning: ` at the start of a line so a batch's stderr can be grepped for\n them, and here the prefix sat mid-line, where `grep \\'^error:\\'` over a failed run finds\n nothing at all. Only reachable on a terminal, which is why nothing caught it: the meter is\n off whenever stderr is not a TTY, and a captured stderr never is.\n */\n const clearProgress = (): void => {\n if (showProgress) process.stderr.write('\\r\\u001b[K');\n };\n\n const onInterrupt = (signal: NodeJS.Signals): void => {\n clearProgress();\n /*\n Say which of the three happened, rather than the middle one every time.\n\n --stdout writes no directory, so there is none to warn about. Naming one anyway\n pointed at a path that was never created — the same \"files that were never written\"\n that 0.2.30 removed from this path's error message. The directory case had the same\n defect one step further in: `convert()` hashes the input under --checksum and scans\n the whole annotation channel for record start times *before* it claims the directory,\n so a Ctrl-C in the first second of a large EDF+ printed \"Files already written to\n \"oa\" are incomplete and should not be used\" about a directory that `ls` then reported\n did not exist. Nothing had been written, and the advice was to distrust nothing.\n */\n const wrote = !toStdout && existsSync(destination);\n process.stderr.write(\n `\\ninterrupted (${signal}): the conversion stopped part way through.\\n` +\n (toStdout\n ? detail('The CSV on stdout stops mid-recording and should not be used.')\n : !wrote\n ? detail(`Nothing was written: \"${printable(destination)}\" was never created.`)\n : destinationExistedBefore\n ? detail(\n `\"${printable(destination)}\" was already there, so what is in it may be ` +\n `this run's incomplete output.`,\n )\n : detail(\n `Files already written to \"${printable(destination)}\" are incomplete and ` +\n `should not be used.`,\n )),\n );\n // 128 + signal number, the conventional exit status for dying to a signal.\n process.exit(signal === 'SIGINT' ? 130 : 143);\n };\n process.once('SIGINT', onInterrupt);\n process.once('SIGTERM', onInterrupt);\n\n try {\n const result = await convert(input, {\n ...options,\n onProgress: showProgress\n ? (progress): void => {\n const now = Date.now();\n if (now - lastTick < 100) return;\n lastTick = now;\n const percent =\n progress.recordsTotal === 0\n ? 100\n : Math.floor((progress.recordsDone / progress.recordsTotal) * 100);\n process.stderr.write(`\\r converting… ${percent}%`);\n }\n : undefined,\n });\n\n clearProgress();\n\n if (result.diagnostics.length > 0 && !asJson) {\n /*\n Named when nothing else is naming it.\n\n A batch prints a `[n/m] <path>` header before each recording, and that header is what\n pairs a warning with the file it came from. `--quiet` suppresses it — it is the\n summary line it is documented to suppress, and it took the attribution with it. Two\n recordings, two warnings, and no way to tell which raised which, while `error:` lines\n in the same run stay named because 0.4.20 prefixes those separately.\n\n Same fix 0.5.34 made for `--info` over a folder, which has no header to lose.\n */\n const where = options.batch === true && quiet ? `${printable(input)}: ` : '';\n /*\n The trailing blank line separates these warnings from the summary underneath them,\n so it belongs to the summary and goes when the summary does.\n\n `--quiet` printed it anyway: one stray blank line per recording, in the mode whose\n entire purpose is to print less. On a batch of five hundred that is five hundred of\n them in the log, and on a single recording it left `--quiet --strict` showing two\n blank lines between the warning and the \"--strict:\" line where the ordinary run\n shows one.\n */\n emit(\n 'err',\n `${formatDiagnostics(result.diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n${quiet ? '' : '\\n'}`,\n );\n }\n if (asJson) {\n // One object per line, so a batch is JSON Lines — `jq` reads a record at a time\n // rather than waiting for the whole run to finish.\n emit('out', `${summaryJson(result, options.jsonIndent as number | null)}\\n`);\n } else if (!quiet) {\n // With --stdout there is no directory to summarise, and the row count is the only\n // thing worth saying — on stderr, so the CSV on stdout stays clean.\n if (toStdout) {\n const rows = result.files[0]?.rows ?? 0;\n /*\n A reader that closed the pipe did not receive a conversion, so it does not get a\n conversion's summary. `edf2csv rec.edf --stdout | head -1` announced \"Wrote 52,507\n rows to stdout\" — a number that is neither the recording's 102,400 nor the one row\n head took, but however many had been formatted before the close was noticed.\n\n Whether the *conversion* stopped early is a separate question from whether the\n reader did, and 0.5.12 answered the first with the second. A 10,000-row recording\n whose CSV outruns the pipe buffer but fits one flush is written in full and only\n then meets the closed pipe: every row formatted, every row handed over, and the\n summary said \"The recording was not converted in full.\" The estimate's row count is\n exact, so the two cases can be told apart and told apart honestly — what reached\n the reader is not knowable from this side either way.\n */\n const expected = result.plan.estimate.rows;\n // Singular at one, like every other count. A window narrow enough to select a single\n // sample is an ordinary thing to pipe, and this said \"Wrote 1 rows to stdout.\" — the\n // third message in this family, after 0.5.74 and 0.5.78, each found because the sweep\n // that checks for it was narrower than the set of modes that print a count.\n const written = `${rows.toLocaleString('en-US')} ${rows === 1 ? 'row' : 'rows'}`;\n emit(\n 'err',\n !result.readerHungUp\n ? `Wrote ${written} to stdout.\\n`\n : rows < expected\n ? `Stopped: the reader closed the pipe after ${rows.toLocaleString('en-US')} of ` +\n `${expected.toLocaleString('en-US')} ${expected === 1 ? 'row' : 'rows'} had been ` +\n `written. The recording was ` +\n `not converted in full.\\n`\n : `Wrote ${written} to stdout, but the reader closed ` +\n `the pipe before the end, so not all of them reached it.\\n`,\n );\n } else {\n emit('err', `${formatSummary(result)}\\n`);\n }\n }\n return result.diagnostics.length;\n } catch (error) {\n // The meter is still on the line when a conversion throws, and the caller's `error: `\n // is the next thing written.\n clearProgress();\n throw error;\n } finally {\n process.off('SIGINT', onInterrupt);\n process.off('SIGTERM', onInterrupt);\n }\n}\n\n/** A recording to convert, and the path its output should be named after. */\ninterface Input {\n /** Where the recording is. */\n path: string;\n /**\n * What to call its output, relative to `--out`.\n *\n * For a file named on the command line this is just its own name. For one found by\n * expanding a directory it keeps the position it had inside that directory, so a study\n * laid out as one folder per night comes out the same shape — and, more to the point, so\n * that fifty recordings all named `rec.edf` do not all claim `<out>/rec`.\n */\n name: string;\n /**\n * Whether this recording was found by expanding a directory rather than named directly.\n *\n * `--out` means two different things — the output directory itself, or a parent to put one\n * directory per recording inside — and this is what decides which. See `destinationsFor`.\n */\n fromDirectory: boolean;\n}\n\n/**\n * Recordings to convert, with directories expanded to what is inside them.\n *\n * A directory yields every `.edf` and `.bdf` beneath it, at any depth. Recordings arrive\n * organised into folders, and a shell has no tidy way to reach them — which is why the\n * recipes here carried a `find` incantation to do it. Passing the folder is the obvious\n * thing to try, and it used to fail with \"is a directory, not an EDF file\".\n */\nasync function expandInputs(\n positionals: readonly string[],\n): Promise<{ inputs: Input[]; unreadable: string[]; namedDirectory: boolean }> {\n const found: Input[] = [];\n const unreadable: string[] = [];\n /*\n Whether a directory was NAMED, which is not the same as whether one yielded anything.\n\n `fromDirectory` on the inputs answers the second question, and using it for the first put\n the 0.4.20 defect back one step along: `edf2csv study named.edf --out csv` wrote\n csv/named/ while the study held recordings and csv/signals.csv once it held none, because\n the surviving input then looked like a lone file. Whether some unrelated folder happens\n to contain anything decided where a different recording's output went.\n */\n let namedDirectory = false;\n for (const given of positionals) {\n const info = await stat(given).catch(() => null);\n if (info === null || !info.isDirectory()) {\n // Anything that is not a directory is passed through untouched, so a file that does\n // not exist still reports itself rather than vanishing from the list.\n found.push({ path: given, name: path.basename(given), fromDirectory: false });\n continue;\n }\n namedDirectory = true;\n const walked = await walk(given);\n unreadable.push(...walked.unreadable);\n for (const file of walked.files) {\n found.push({ path: file, name: path.relative(given, file), fromDirectory: true });\n }\n }\n /*\n One recording, however many ways it was named.\n\n The walk already does this for links inside a folder, and the two halves disagreed:\n `edf2csv study` converted a recording reached twice once, while `edf2csv a.edf a.edf`\n refused the whole run for the collision it would cause. A shell makes the second easy\n to produce by accident — `edf2csv *.edf recording.edf` — and there is nothing ambiguous\n about it: it is one recording, and converting it once is what was meant.\n\n A path that does not resolve keeps its own identity so that a file which is not there\n still reports itself rather than being folded into another entry.\n\n Which of the names survives is decided by the names themselves, not by the order they\n turned up in. Keeping the first arrival meant the output directory was named by argument\n order — `edf2csv data/one.edf data/alias.edf` wrote out/one and the same two swapped\n wrote out/alias — and a shell orders a glob however it likes. Inside a folder it was the\n order `readdir` returned, which differs between filesystems, so copying a study to\n another machine could rename its output. A recording that is not a link is preferred over\n a link to it, since that is the name the recording actually has; two of a kind are\n settled by the path that sorts first.\n */\n const identified: { entry: Input; identity: string; link: boolean }[] = [];\n for (const entry of found) {\n const identity = await realpath(entry.path).catch(() => `?${path.resolve(entry.path)}`);\n const own = await lstat(entry.path).catch(() => null);\n identified.push({ entry, identity, link: own?.isSymbolicLink() ?? false });\n }\n\n const byIdentity = new Map<string, Input>();\n const winners = new Map<string, (typeof identified)[number]>();\n for (const candidate of identified) {\n const held = winners.get(candidate.identity);\n if (held !== undefined && !outnames(candidate, held)) continue;\n winners.set(candidate.identity, candidate);\n byIdentity.set(candidate.identity, candidate.entry);\n }\n\n // A directory hands its entries back in whatever order the filesystem stored them.\n const unique = [...byIdentity.values()];\n unique.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n\n /*\n One unreadable path, however many ways it was named — the same rule the recordings above\n get, which this did not.\n\n Each named directory is walked separately and its findings appended, so a folder given\n twice, given relatively and absolutely, or given alongside a link to it, reported every\n path it could not read once per name. `edf2csv study study` on a study holding one locked\n sub-directory printed the error twice and closed with \"Converted 1 of 1 recordings; 2\n paths could not be read\" — one path, counted two — while the recordings beside it were\n correctly converted once. The count is the part that matters: it is what tells someone how\n much of their study was not looked at.\n\n Identity, not spelling, for the reason 0.5.64 gives: `study/locked` and `alias/locked` are\n one directory. `realpathSync` resolves it even when the directory itself cannot be opened,\n since that needs search permission on the parent rather than on the target. A path that\n cannot be resolved at all keeps its own identity, so a name that is simply not there still\n reports itself.\n */\n const seen = new Set<string>();\n const distinct: string[] = [];\n for (const entry of unreadable) {\n let identity: string;\n try {\n identity = realpathSync(entry);\n } catch {\n identity = `?${path.resolve(entry)}`;\n }\n if (seen.has(identity)) continue;\n seen.add(identity);\n // The first spelling is kept, which is the one the caller typed first.\n distinct.push(entry);\n }\n\n return { inputs: unique, unreadable: distinct, namedDirectory };\n}\n\n/**\n * Which of two names for one recording the output should be called after.\n *\n * A name the recording actually has beats a link pointing at it, and two of a kind are\n * settled by sort order. Both are properties of the names, so the answer does not move when\n * a shell expands a glob differently or a filesystem enumerates a folder in another order.\n */\nfunction outnames(\n candidate: { entry: Input; link: boolean },\n held: { entry: Input; link: boolean },\n): boolean {\n if (candidate.link !== held.link) return !candidate.link;\n /*\n Resolved, not as typed. `study/night-01/rec.edf` and `./study/night-01/rec.edf` are the\n same file spelled two ways, and comparing the raw strings made them two different names —\n so this returned here, on lexicographic order of the spelling, and never reached the depth\n rule below that exists for exactly this case.\n\n What that cost is not cosmetic. `edf2csv study study/night-01/rec.edf` converted both\n recordings and exited 0; the identical request written `edf2csv study\n ./study/night-01/rec.edf` was refused with \"would both be converted into \"out/rec\", so one\n would overwrite the other\", exit 2, nothing written. Same files, same folder, same\n intention — and a leading `./` decided whether the run happened. Without the sibling that\n collides it was quieter and no better: one spelling wrote `out/night-01/rec` and the other\n `out/rec`, so a script that started passing absolute paths moved its output.\n\n Through `realpathSync`, not `path.resolve`, because a lexical resolve is not enough: on\n macOS `$TMPDIR` sits under `/var`, which is a link to `/private/var`, so a folder walked\n from the name the caller gave and a recording named relative to the process's own\n directory come out with different absolute prefixes for the same file. Falls back to the\n lexical form for a path that cannot be resolved, which is the same answer as before.\n\n Following links here is safe: a link and its target reach this as two genuinely different\n names, and the rule above has already preferred the real one, so the only pairs left with\n one identity are two spellings or two links — and both should fall through to the depth\n rule below rather than be settled by how they were typed.\n */\n const spelled = (entry: Input): string => {\n try {\n return realpathSync(entry.path);\n } catch {\n return path.resolve(entry.path);\n }\n };\n if (spelled(candidate.entry) !== spelled(held.entry)) {\n return spelled(candidate.entry) < spelled(held.entry);\n }\n /*\n One recording, one path, two names — reached directly and through a named folder.\n\n `edf2csv study study/night-01/rec.edf` and the same two swapped are the same request, and\n they disagreed: the folder gives the recording its position inside the folder\n (`night-01/rec`), a direct mention gives it its bare name (`rec`), and whichever spelling\n the loop met first won. So argument order decided the output directory's name, and, once\n a second `study/rec.edf` was in play, decided whether the run happened at all — the bare\n name collides with it and the run is refused, exit 2, while the other order converts both.\n\n The nested name wins. It is what the folder promised — \"the layout is kept: recordings in\n sub-folders come out in sub-folders\" — and it is the one that does not collide, since\n collapsing a recording to its bare name is what puts it on top of a sibling.\n */\n const depth = (entry: Input): number => entry.name.split(path.sep).length;\n if (depth(candidate.entry) !== depth(held.entry)) return depth(candidate.entry) > depth(held.entry);\n return candidate.entry.name < held.entry.name;\n}\n\n/**\n * Every recording under a directory, following symbolic links.\n *\n * Written by hand rather than with a recursive `readdir` because that reports a symlink as\n * a symlink and never as a file, so a linked recording was skipped without a word — and the\n * closing \"converted 3 of 3\" then described the three it had noticed rather than what was in\n * the folder. Data organised by linking recordings into a working directory is ordinary, and\n * quietly converting fewer files than were asked for is the failure this tool exists to\n * avoid. Naming the same link on the command line always worked, which made the omission\n * harder to notice rather than easier.\n *\n * Following links means they can form a cycle, so directories are recorded by their resolved\n * identity and visited once. A link to a file already reached another way is likewise\n * converted once.\n */\nasync function walk(root: string): Promise<{ files: string[]; unreadable: string[] }> {\n const files: string[] = [];\n const unreadable: string[] = [];\n const seen = new Set<string>();\n /*\n A queue, taken from the front, and each directory's children entered in a settled order.\n\n This was a stack popped from the back, so which of two names for one directory was\n visited first came down to the order `readdir` happened to return them — and the loser\n was then skipped as already seen, taking its name out of the run with it. A folder\n holding `aaa-real/` beside `zzz-alias -> aaa-real` converted into `<out>/zzz-alias/`: the\n link's name, chosen by a hash order that differs between filesystems. 0.4.29 settled this\n for two names of one *file* and left the directory above it deciding by accident.\n\n Breadth first, and each directory's sub-directories entered with real names before links\n and alphabetically within each, so the name that survives is a property of the tree: the\n shallowest, then the one that is not a link, then the first in sort order.\n */\n const queue = [root];\n let next = 0;\n\n while (next < queue.length) {\n const directory = queue[next++] as string;\n const real = await realpath(directory).catch(() => directory);\n if (seen.has(real)) continue;\n seen.add(real);\n\n /*\n A directory that cannot be listed is reported, not stepped over.\n\n Skipping it in silence meant a folder holding three recordings, one of them inside a\n sub-directory without read permission, converted two and said \"Converted 2 of 2\n recordings\" — a total that agreed with itself and with nothing else. That is the same\n failure 0.4.4 fixed for symbolic links, arriving by a different route: converting fewer\n recordings than were asked for and reporting success.\n */\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n unreadable.push(directory);\n continue;\n }\n\n // A real name before a link to the same thing, then alphabetically. The dirent describes\n // the entry itself rather than its target, which is exactly the question here.\n const ordered = [...entries].sort((a, b) => {\n const link = Number(a.isSymbolicLink()) - Number(b.isSymbolicLink());\n return link !== 0 ? link : a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n\n for (const entry of ordered) {\n const full = path.join(directory, entry.name);\n // stat, not the dirent: a dirent describes the link, and what matters is its target.\n const info = await stat(full).catch(() => null);\n if (info === null) {\n /*\n Anything that cannot be inspected is reported, whatever it is called.\n\n This used to report only names ending in `.edf` or `.bdf`, on the reasoning that a\n broken link to something else is nobody's business. A directory carries no such\n name. A study kept as one folder per night, with one night linked to an external\n drive, converted the nights that were mounted and said nothing about the one that\n was not — and because losing that input left a single recording, `--out` stopped\n meaning \"a parent to fill\" and started meaning \"the directory to write\", so the\n survivor landed somewhere else as well. Whether a drive happened to be mounted\n changed both what was converted and where it went, in silence, exit 0.\n\n The walk cannot know what was behind a link it cannot follow, which is exactly the\n reason to say so rather than to guess.\n */\n unreadable.push(full);\n continue;\n }\n if (info.isDirectory()) {\n queue.push(full);\n } else if (info.isFile() && /\\.(edf|bdf)$/iu.test(entry.name)) {\n // Duplicates are collapsed once for the whole list in expandInputs; this only has\n // to find them. The `seen` set here is for directory cycles.\n files.push(full);\n }\n }\n }\n return { files, unreadable };\n}\n\n/**\n * Where each recording's output goes.\n *\n * Naming one recording, `--out` is the output directory itself, which is what it has always\n * meant. Naming a folder, or several recordings, it is a parent and each recording gets its\n * own directory inside it, because writing several recordings into one directory would have\n * them overwrite each other's `signals.csv` — the one thing a batch must not do quietly.\n *\n * With no `--out` at all, every recording converts beside itself exactly as it would have\n * done alone, so a glob behaves like the shell loop it replaces.\n *\n * What a folder means is decided by the folder, not by how much is in it. Counting the\n * recordings instead meant `edf2csv study --out csv` wrote `csv/signals.csv` while the study\n * held one night and `csv/night-01/rec/signals.csv` once it held two: adding a recording\n * moved the output of a recording that had not changed. The same count made the destination\n * depend on things no one had touched either — a night on an unmounted drive, a\n * sub-directory that could not be read — so where the data landed turned on the state of the\n * machine rather than on the command.\n */\nfunction destinationsFor(\n inputs: readonly Input[],\n out: string | undefined,\n /** True when a folder was named, or more than one recording was. See `batch` in `main`. */\n batch: boolean,\n): string[] {\n if (out === undefined) return inputs.map((input) => defaultOutputDir(input.path));\n if (!batch) return [out];\n return inputs.map((input) => path.join(out, stemOf(input.name)));\n}\n\n/** A name without its extension, keeping a leading dot: `.hidden.edf` -> `.hidden`. */\nfunction stemOf(name: string): string {\n const dot = name.lastIndexOf('.');\n const slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\\\'));\n return dot > slash + 1 ? name.slice(0, dot) : name;\n}\n\n/**\n * Refuse a batch in which two recordings would land in the same directory.\n *\n * Two files with the same name in different folders — `night-1/rec.edf` and\n * `night-2/rec.edf`, which is how recordings usually get organised — both resolve to\n * `<out>/rec`. Converting them in turn would leave one recording's data sitting under the\n * other's name with nothing to show it had happened, so the run stops before writing\n * anything at all.\n */\nfunction assertDistinct(inputs: readonly string[], destinations: readonly string[]): void {\n /*\n Two names that differ only in case are one directory on a filesystem that does not\n distinguish them, which is the default on macOS and the rule on Windows.\n\n `a/REC.edf` and `b/rec.edf` produce `<out>/REC` and `<out>/rec`. Compared exactly those\n are different, so both went through, and with --force the second conversion wrote into\n the directory the first had made: one directory holding one recording's `signals.csv`\n beside the other's `signals_256hz.csv`, under a single `metadata.json` naming only one\n of them. The run reported \"Converted 2 of 2 recordings\" and exited 0. A directory whose\n provenance file describes a recording other than the data beside it is the one outcome\n this tool exists to prevent.\n\n The comparison follows the platform rather than being applied everywhere, so a\n case-sensitive filesystem — where those really are two directories — keeps converting\n both. A case-sensitive volume on macOS is the exception it gets wrong, and it gets it\n wrong in the safe direction: a refusal that names both recordings, not a silent merge.\n */\n const foldsCase = process.platform === 'darwin' || process.platform === 'win32';\n /*\n macOS filesystems fold Unicode normalisation as well as case.\n\n HFS+ and APFS compare names in a normalised form, so `café` written as e + U+0301 and\n `café` written as U+00E9 are one directory — while remaining two different JavaScript\n strings, which is all this guard was comparing. Two recordings whose stems differ only\n that way therefore both passed the check and both converted into the same place:\n\n study/café.edf (NFC) -> csv/café/signals.csv\n study/café.bdf (NFD) -> csv/café/signals_256hz.csv, _128hz, _1hz\n\n one directory holding both, under a single metadata.json naming one of them, reported as\n \"Converted 2 of 2 recordings\" and exit 0 under --force. Without --force the second run\n happened to hit \"already exists\", which is the accidental save rather than the check\n doing its job — and it named the wrong problem.\n\n Not folded on Linux, where the two names are genuinely two directories and refusing them\n would be inventing a collision that does not exist. Windows preserves normalisation too,\n so only darwin. This is the same platform-shaped assumption the case fold above already\n makes, and it has the same limit: a network or removable volume that normalises while the\n running platform does not is not covered by either.\n */\n const identity = (destination: string): string => {\n const resolved = path.resolve(destination);\n if (!foldsCase) return resolved;\n const folded = resolved.toLowerCase();\n return process.platform === 'darwin' ? folded.normalize('NFC') : folded;\n };\n\n const claimed = new Map<string, string>();\n for (const [index, destination] of destinations.entries()) {\n const key = identity(destination);\n const first = claimed.get(key);\n if (first !== undefined) {\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" and \"${printable(first)}\" would both be converted into \"${printable(destination)}\", ` +\n `so one would overwrite the other.\\n` +\n `Convert them separately, or rename one of them.`,\n );\n }\n claimed.set(key, inputs[index] as string);\n }\n\n /*\n And refuse one destination sitting inside another.\n\n A recording named `rec.edf` beside a folder named `rec` is enough: their outputs are\n `<out>/rec` and `<out>/rec/inner`, which are not equal, so the check above let both\n through. What happened next depended on which conversion got there first. Each creates\n its own directory with a single non-recursive mkdir but its parents recursively, so\n whichever started second either claimed a directory the other had already made as a\n parent — failing with \"already exists\" — or did not. Five runs in twenty failed that way\n under --jobs 2, converting one recording of two; the same command succeeded the other\n fifteen times.\n\n Sorting first puts an ancestor next to its descendant: anything sorting between them\n shares the same prefix, and would be caught as its own adjacent pair.\n */\n /*\n Each destination is checked against its own ancestors, by name.\n\n The first version of this sorted the resolved paths and compared neighbours, on the\n reasoning that an ancestor and its descendant end up adjacent. They do not: the\n separator is not the lowest character, so any sibling whose name begins with one of the\n thirteen printable characters below '/' lands between them. With `rec.edf`, `rec!x.edf`\n and `rec/inner.edf` in one folder, '!' sorts between `out/rec` and `out/rec/inner` and\n the pair was never compared — three recordings converted, one of them inside another's\n directory, reported as \"Converted 3 of 3\".\n\n Walking up from each destination has no such gap. Output trees are shallow, so this is a\n handful of string lookups per recording.\n */\n const byDestination = new Map<string, number>();\n for (const [index, destination] of destinations.entries()) {\n byDestination.set(identity(destination), index);\n }\n\n for (const [index, destination] of destinations.entries()) {\n let ancestor = path.dirname(path.resolve(destination));\n for (let parent = ''; ancestor !== parent; ancestor = path.dirname(ancestor)) {\n parent = ancestor;\n const owner = byDestination.get(identity(ancestor));\n if (owner === undefined || owner === index) continue;\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" would be converted into \"${printable(destination)}\", which is inside ` +\n `\"${destinations[owner]}\" — where \"${inputs[owner]}\" is converted.\\n` +\n `One recording's output cannot sit inside another's. Convert them separately, or ` +\n `rename one of them.`,\n );\n }\n }\n}\n\n/**\n * The exit code for a run in which several files failed for different reasons.\n *\n * 2 means \"you invoked it wrong\" and 1 means \"something went wrong with a file\". When both\n * happened, 1 is the honest answer: the invocation cannot be the whole story once a file has\n * genuinely failed. With a single input there is only ever one code, so its exit status is\n * exactly what it always was.\n */\nfunction worstOf(codes: readonly number[]): number {\n /*\n Anything that is not \"the command line is the problem\" is a failure of the run.\n\n This tested for `EXIT_ERROR` and fell through to `EXIT_USAGE` for everything else, on the\n assumption that a child exits 1 or 2. A child killed by a signal exits 130 or 143 — its\n own interrupt handler does that — so a `--jobs` worker stopped by SIGTERM made the batch\n exit 2, which cli-reference's table defines as \"The command line is the problem\" and\n warnings-and-errors as \"The command was invoked incorrectly, or asked for something the\n recording can't provide\". The command was fine; something killed a worker. The\n out-of-memory killer and a scheduler's time limit both arrive exactly this way, and the\n serial path reports the same event as the failure it is.\n\n Asking the question the other way round is also the honest one: 2 is the narrow claim,\n so it should be the one that has to be earned by every code in the list.\n */\n return codes.some((code) => code !== EXIT_USAGE) ? EXIT_ERROR : EXIT_USAGE;\n}\n\n/**\n * Convert one recording in a separate process, and collect everything it printed.\n *\n * The arguments are rebuilt from the parsed options rather than sliced out of argv, so the\n * child receives exactly the flags this run was given and nothing that only makes sense to\n * the parent: not the other recordings, not --jobs, and not --info or --stdout, neither of\n * which reaches this path.\n */\nasync function convertInChild(\n input: string,\n destination: string,\n values: Record<string, unknown>,\n running: Map<ChildProcess, string>,\n): Promise<{\n code: number;\n out: string;\n err: string;\n report: { converted: number; warnings: number } | null;\n}> {\n /*\n The recording goes last, behind `--`.\n\n As the first argument it was parsed as an option whenever its path began with a dash,\n which `path.join` produces from a folder given as `.` — `./-lead.edf` normalises to\n `-lead.edf`. The child then failed on a file the parent had converted happily, so the\n same command converted two recordings serially and one under --jobs.\n */\n /*\n Option values go in the `--flag=value` form, never as two arguments.\n\n Split across two arguments, a value beginning with a dash is another option as far as the\n child's parser is concerned: `--out ./-nightly` reached it as `--out` followed by\n `-nightly`, and the child died on \"Option '--out' argument is ambiguous\" while the serial\n path converted the same command without complaint. A leading dash is not exotic —\n `path.join` produces one from a folder given as `.`, and directories get named after\n dates and flags often enough. Same failure 0.4.19 fixed for the recording's own path, on\n everything that carries a value rather than on the positional.\n */\n const args = [`--out=${destination}`];\n /*\n --strict is deliberately absent: it is a verdict on the whole run, and a child converting\n one recording is not the whole run. Passing it down made each child announce \"--strict: 1\n warning raised, so this run is reported as a failure\" about its own single file, and exit\n 1 for it, which the parent then counted as a conversion that had not happened. The parent\n applies it once, from the counts the children report.\n */\n for (const flag of ['annotations-only', 'checksum', 'gzip', 'bom', 'force', 'quiet', 'json']) {\n if (values[flag] === true) args.push(`--${flag}`);\n }\n for (const flag of ['start', 'duration', 'end', 'decimals', 'layout']) {\n if (typeof values[flag] === 'string') args.push(`--${flag}=${values[flag] as string}`);\n }\n // --channels is repeatable, and each term is passed as given so that a label containing a\n // comma survives: joining them back into one list would split it in the child.\n const channels = values['channels'];\n if (channels !== undefined) {\n for (const term of Array.isArray(channels) ? (channels as string[]) : [String(channels)]) {\n args.push(`--channels=${term}`);\n }\n }\n args.push('--', input);\n\n return new Promise((resolve) => {\n const child = fork(fileURLToPath(import.meta.url), args, {\n stdio: ['ignore', 'pipe', 'pipe', 'ipc'],\n });\n running.set(child, destination);\n let out = '';\n let err = '';\n let report: { converted: number; warnings: number } | null = null;\n child.on('message', (message: unknown) => {\n const payload = (message as { edf2csv?: { converted: number; warnings: number } })?.edf2csv;\n if (payload) report = payload;\n });\n child.stdout?.setEncoding('utf8').on('data', (chunk: string) => {\n out += chunk;\n });\n child.stderr?.setEncoding('utf8').on('data', (chunk: string) => {\n err += chunk;\n });\n child.on('error', (error) => {\n running.delete(child);\n resolve({ code: EXIT_ERROR, out, err: `${err}error: ${input}: ${error.message}\\n`, report });\n });\n child.on('close', (code, signal) => {\n running.delete(child);\n /*\n A child that was killed says nothing on its way out, so the parent has to.\n\n `code` is null when a process dies by signal, which left the parent with `code ??\n EXIT_ERROR` — a failure with an empty `err`, so the run printed nothing but \"Converted\n 1 of 2 recordings; 1 failed.\" and stopped. Nothing said which recording, nothing said\n why, and nothing said that its directory held a 194 MB signals.csv cut off mid-row\n with no channels.csv beside it. That file looks exactly like a finished one to\n anything that opens it.\n\n The out-of-memory killer, a job scheduler's time limit and `kill` all arrive this way.\n A run stopped from the keyboard is reported by the interrupt handler instead, which\n names every directory at once rather than one line per child.\n */\n if (signal !== null && !stopping) {\n err +=\n `error: stopped by ${signal} before it finished.\\n` +\n detail(`Incomplete, and should not be used: ${printable(destination)}`);\n }\n resolve({ code: code ?? EXIT_ERROR, out, err, report });\n });\n });\n}\n\n/**\n * Put the recording's name into the lines a child produced.\n *\n * Errors always: a failure has to say which recording failed, whatever else is on screen.\n * Warnings only when `alsoWarnings` — under --quiet, where the `[n/m]` header that would\n * otherwise carry the attribution is not printed. See the call site.\n */\nfunction named(text: string, input: string, alsoWarnings = false): string {\n // A function, not a string: `$&`, `$\\'`, `` $` `` and `$1` in a replacement string are\n // patterns, and a file may legitimately be called any of them. `bad$&name.edf` re-injected\n // the text it had just matched and reported itself as `baderror: name.edf`.\n const heads = alsoWarnings ? /^(error|warning|note): /gmu : /^error: /gmu;\n return text.replace(heads, (head) => `${head}${printable(input)}: `);\n}\n\n/** Re-render a child's pretty-printed summary onto one line, leaving anything else alone. */\nfunction compactJson(text: string): string {\n const trimmed = text.trim();\n if (trimmed === '') return '';\n try {\n return `${JSON.stringify(JSON.parse(trimmed))}\\n`;\n } catch {\n // Not the document expected; passing it through unchanged beats losing it.\n return text;\n }\n}\n\n/**\n * Where a conversion's output goes.\n *\n * Serial runs write straight through, which is what they have always done. Running several\n * conversions at once needs the alternative: each one's lines are collected and released in\n * a block when it finishes, so two recordings finishing together cannot interleave a summary\n * with a warning belonging to the other file.\n */\ntype Emit = (stream: 'out' | 'err', text: string) => void;\n\nconst writeThrough: Emit = (stream, text) => {\n (stream === 'out' ? process.stdout : process.stderr).write(text);\n};\n\n/** Collects output so it can be released in one piece when a conversion finishes. */\nfunction buffered(): { emit: Emit; flush: () => void } {\n const parts: [('out' | 'err'), string][] = [];\n return {\n emit: (stream, text) => parts.push([stream, text]),\n flush: () => {\n for (const [stream, text] of parts) writeThrough(stream, text);\n },\n };\n}\n\n/**\n * Report an error the way this command always has, and give the exit code it implies.\n *\n * `input` names the recording while a batch is running: several failures otherwise arrive\n * as a stack of messages with nothing saying which file each belongs to.\n */\nfunction reportError(error: unknown, input?: string, emit: Emit = writeThrough): number {\n const where = input === undefined ? '' : `${printable(input)}: `;\n if (error instanceof EdfError || error instanceof ConversionError) {\n emit('err', `error: ${where}${printableLines(error.message, ' ')}\\n`);\n /*\n Wrapped, for the same reason the hint under a warning is — and because on this file\n it is frequently the same sentence.\n\n A mixed-rate recording refused by --stdout and the same recording described by --info\n both end in \"Narrow it to one rate with --channels, write --layout long ...\". One\n arrived as a ConversionError hint and printed on a single 130-column line; the other\n arrived as a Diagnostic hint and wrapped at 80 from 0.7.1. Identical advice about an\n identical file, laid out two different ways depending on whether the tool went on to\n convert it.\n */\n /*\n Escaped, which the message above it has been since 0.5.67 and this line never was.\n\n A hint is usually a fixed sentence, which is why it looked like text nobody supplies.\n One is not: a read that fails part way through a conversion reports \"What was written\n to <outputDir> before it failed is incomplete\", and `--out` takes whatever path the\n caller gives it. A directory may be named with an ESC byte on every platform this\n runs on, so `--out $'out\\x1b[31mred'` on a recording that shrinks mid-read put a live\n colour change on stderr — under an `error:` line that had been escaped correctly, two\n lines above.\n */\n if (error.hint) emit('err', `${wrap(printable(error.hint), ' ')}\\n`);\n // A request the tool cannot carry out is the command line's problem, not the file's,\n // whatever layer noticed it. See USAGE_ERROR_CODES.\n return error instanceof ConversionError && USAGE_ERROR_CODES.has(error.code)\n ? EXIT_USAGE\n : EXIT_ERROR;\n }\n if (\n error instanceof ChannelSelectionError ||\n error instanceof TimeRangeError ||\n error instanceof OptionError\n ) {\n emit('err', `error: ${where}${printableLines(error.message, ' ')}\\n`);\n return EXIT_USAGE;\n }\n emit('err', `error: ${where}${message(error, ' ')}\\n`);\n return EXIT_ERROR;\n}\n\n/**\n * How many conversions to run at once.\n *\n * `auto` is one per core, less one, so a long batch does not take the machine over. A batch\n * is the only place this means anything: a single recording is a single conversion however\n * many jobs are asked for.\n */\nfunction parseJobs(raw: unknown, inputs: number): number {\n if (raw === undefined) return 1;\n const text = String(raw).trim();\n if (text === 'auto') return Math.max(1, Math.min(inputs, cpus().length - 1));\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x10`, `1e3` and `999999999999999999999` — the\n last of which is not an integer any more, only the nearest double to one — while refusing\n `4.7`. The message says \"a whole number of 1 or more\" and the refusal of 4.7 is what makes\n a reader believe it, so the others read as the tool silently reinterpreting what they\n typed. `--jobs` is the flag 0.4.2 hardened because accepting `--jobs 0` in silence \"is the\n kind of quiet this tool avoids\".\n\n Surrounding space is trimmed above and is not one of those: ` 4 ` is four to anyone who\n reads it, and a value assembled by a shell out of a file or a `$(...)` routinely carries\n some. What matters is that the refusal quote what was typed rather than what survived the\n trim — see below.\n */\n if (!/^\\d+$/u.test(text)) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n const value = Number(text);\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n return Math.min(value, Math.max(1, inputs));\n}\n\nfunction splitChannels(raw: unknown): string[] | undefined {\n if (raw === undefined) return undefined;\n const list = Array.isArray(raw) ? (raw as string[]) : [String(raw)];\n const terms = list.flatMap((entry) => entry.split(',')).map((t) => t.trim()).filter((t) => t !== '');\n // Returning undefined here would mean \"no --channels given\" and convert everything,\n // which is the opposite of what someone passing an empty list is asking for.\n if (terms.length === 0) {\n throw new OptionError('--channels was given but lists no channel names.');\n }\n return terms;\n}\n\n/**\n * `--start`, `--duration` and `--end`, in seconds.\n *\n * The first two name a position on the recording's own clock, which a file timed from before\n * zero puts below it, so those take a sign. `--duration` is a length and does not.\n */\nfunction optionalTime(raw: unknown, option: string): number | undefined {\n if (raw === undefined) return undefined;\n return parseTimeSpec(String(raw), option, option !== '--duration');\n}\n\n/** `--layout`, which is one of two words and not a guess at what was meant. */\nfunction optionalLayout(raw: unknown): 'wide' | 'long' | undefined {\n if (raw === undefined) return undefined;\n // Trimmed, like every other option's value. This one alone refused ` long`, so a value that\n // came out of a file or a `$(...)` with a newline on it was rejected for a character nobody\n // typed and which the message could not show.\n const text = String(raw).trim();\n if (text === 'wide' || text === 'long') return text;\n throw new OptionError(`--layout must be \"wide\" or \"long\", got \"${quotedValue(raw)}\".`);\n}\n\n/**\n * An option's value as the user typed it, for a message that quotes it back.\n *\n * The point of the quotation marks is to show where the value begins and ends, which matters\n * most for the values that went wrong because of what surrounds them. `--jobs` trimmed first\n * and then quoted the remains, so `--jobs \" x\"` came back as `got \"x\"` and `--jobs \" \"` as\n * `got \"\"` — the second reading as though nothing had been given at all, when what was given\n * is the whole reason it failed. `--start` has quoted the typed value since 0.5.60 and\n * `--decimals` since 0.5.86; this is the same rule, spelled once.\n */\nfunction quotedValue(raw: unknown): string {\n return String(raw);\n}\n\nfunction optionalDecimals(raw: unknown): number | undefined {\n if (raw === undefined) return undefined;\n const text = String(raw).trim();\n // Number('') is 0, which would quietly round every physical value to a whole number.\n if (text === '') {\n throw new OptionError('--decimals needs a number, for example --decimals 3.');\n }\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x3`, `0b11`, `0o5`, `3e0` and `+3`,\n while refusing `3.5` — and the refusal of `3.5` is what makes a reader believe the message\n that says \"a whole number between 0 and 20\". So `--decimals 0o5` wrote five decimals for\n an argument that reads as five to nobody, and `--decimals 0b11` wrote three, in silence,\n exit 0, with a CSV that looks exactly as intended.\n\n The same fix `--jobs` got, and the same one `--channels '#N'` got — where the comment\n records why: \"Every one of them selected a channel and exited 0, so a slip did not fail —\n it quietly converted a different channel than the one asked for, which for this tool is\n the worst way to be wrong.\" A precision is the same kind of quiet.\n */\n const value = Number(text);\n if (!/^\\d+$/u.test(text) || !Number.isInteger(value) || value < 0 || value > 20) {\n throw new OptionError(`--decimals must be a whole number between 0 and 20, got \"${String(raw)}\".`);\n }\n return value;\n}\n\n/*\n Error text is escaped for the same reason --info's table is: a fatal header error quotes\n the channel label that caused it, and that label is free text out of the file. A recording\n declaring a negative sample count under a label containing `\\x1b[2J` cleared the reader's\n screen on the way out.\n*/\n/**\n * A `parseArgs` failure, in this tool's words where they are better than Node's.\n *\n * Node refuses `--out -nightly` with \"Option '--out' argument is ambiguous. Did you forget to\n * specify the option argument for '--out'? To specify an option argument starting with a dash\n * use '--out=-XYZ'.\" The user did not forget anything — they gave a value — and `-XYZ` is a\n * placeholder, where every other message this tool prints quotes what was actually typed and\n * says exactly what to type instead. A destination whose name starts with a dash is not exotic,\n * and neither is a negative `--start` on a recording timed from before zero.\n *\n * 0.4.34 already fixed this message where the tool produced it internally, building child argv\n * for `--jobs` — `--out ./-nightly` reached the child as two arguments and died on it, while the\n * serial path converted the same command. The half a user can hit was left as Node wrote it.\n *\n * An unknown option is reworded too, and for a plainer reason than tone. Node's sentence is\n * \"Unknown option '--chanels'. To specify a positional argument starting with a '-', place it at\n * the end of the command after '--', as in '-- \"--chanels\"' — except the closing quote is not\n * there. Node opens one before `--` and never closes it, so this tool printed an unfinished\n * sentence, without the `error:` prefix every other refusal carries, at the single most common way\n * to get a command wrong. The advice is also for a different mistake than the one almost everyone\n * makes: it explains how to pass a *file* whose name begins with a dash, where the reader has\n * mistyped one of twenty flags.\n *\n * Node's remaining two — a switch given a value, an option missing its value — say something true\n * in words a reader can act on, and replacing them with near-identical sentences would be churn.\n */\n/**\n * A token as it has to be typed for a shell to hand it over unchanged.\n *\n * Every hint in this file that prints a command has to print one that works — the comment on\n * the `--channels` advice in the header parser lists three separate times it did not. These\n * two are the same failure at the command line rather than in a channel label. `--out \"-my\n * nightly\"` was answered with `Write it as one argument instead: --out=-my nightly`, which is\n * two arguments; `--chan\"nels` was answered with `edf2csv -- \"--chan\"nels\"`, whose quotes\n * collapse into something else again.\n *\n * Left bare when a shell would read it as written, so the ordinary `--out=-nightly` reads as\n * it always has. Otherwise single-quoted, the one POSIX form with no escapes inside it: a\n * single quote in the token closes, escapes and reopens.\n */\nfunction survivesBare(text: string): boolean {\n return text !== '' && !/[^\\w@%+=:,./-]/u.test(text);\n}\n\nfunction singleQuoted(text: string): string {\n return `'${text.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction usageMessage(error: unknown, argv: readonly string[]): string {\n const raw = message(error);\n const code = (error as { code?: unknown } | null)?.code;\n if (code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') return unknownOption(raw);\n if (code !== 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE') {\n return raw;\n }\n const ambiguous = /^Option '(-[^']+)' argument is ambiguous/u.exec(raw);\n if (!ambiguous) return raw;\n\n const flag = ambiguous[1] as string;\n // The value is whatever followed the flag. Read from argv rather than from the message,\n // which does not carry it — and if it is not there after all, Node's text is still true.\n const at = argv.indexOf(flag);\n const value = at >= 0 ? argv[at + 1] : undefined;\n if (value === undefined) return raw;\n\n /*\n How the two shapes join, which is not the same and matters.\n\n A long option takes `--out=-nightly`. A short one does NOT take `-o=-nightly`: parseArgs\n reads the equals sign as the first character of the value and hands back \"=-nightly\", so\n that advice would have produced a directory named `=-nightly` without a word — a worse\n failure than the message it replaced, and a silent one. Short options join directly.\n */\n const joined = flag.startsWith('--') ? `${flag}=${value}` : `${flag}${value}`;\n const typeable = survivesBare(joined) ? joined : singleQuoted(joined);\n return printableLines(\n `error: ${flag} was given \"${value}\", which begins with a dash and so reads as another ` +\n `flag rather than as its value.\\n` +\n ` Write it as one argument instead: ${typeable}`,\n );\n}\n\n/** Levenshtein distance, two rows at a time. Twenty names of under twenty characters. */\nfunction editDistance(a: string, b: string): number {\n const row = Array.from({ length: b.length + 1 }, (_unused, i) => i);\n for (let i = 1; i <= a.length; i++) {\n let diagonal = row[0] as number;\n row[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const above = row[j] as number;\n row[j] = Math.min(above + 1, (row[j - 1] as number) + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));\n diagonal = above;\n }\n }\n return row[b.length] as number;\n}\n\n/**\n * The option they probably meant, or null when nothing is close enough to say.\n *\n * Long options only. A stray `-Z` carries one character of evidence, and guessing a word\n * from it would be guessing.\n *\n * Two rules, because typos come in two shapes. An unfinished name is a prefix — `--chan`,\n * `--decim` — and is only taken from three characters up, so `--c` does not become\n * `--channels` or `--checksum` by coin toss. A misspelt one is within a couple of edits,\n * scaled to the name's length so `--jobs` is not reached from three characters away.\n */\nfunction nearestOption(flag: string): string | null {\n if (!flag.startsWith('--')) return null;\n const typed = flag.slice(2).toLowerCase();\n if (typed.length < 2) return null;\n\n const names = Object.keys(OPTIONS);\n if (typed.length >= 3) {\n const prefixed = names.filter((name) => name.startsWith(typed));\n if (prefixed.length === 1) return `--${prefixed[0] as string}`;\n }\n\n let best: string | null = null;\n let score = Infinity;\n let tied = false;\n for (const name of names) {\n const d = editDistance(typed, name);\n if (d < score) {\n score = d;\n best = name;\n tied = false;\n } else if (d === score) {\n tied = true;\n }\n }\n if (best === null || tied) return null;\n return score <= Math.max(1, Math.min(3, Math.floor(best.length / 3))) ? `--${best}` : null;\n}\n\n/** An option this tool does not have, in a finished sentence, naming the likely one. */\nfunction unknownOption(raw: string): string {\n const found = /^Unknown option '([^']+)'/u.exec(raw);\n // If Node ever rewords it, its text is still true; only the polish is lost.\n if (!found) return raw;\n const flag = found[1] as string;\n const near = nearestOption(flag);\n return printableLines(\n `error: There is no ${flag} option.${near === null ? '' : ` Did you mean ${near}?`}\\n` +\n ` If it is the name of a file, pass it after -- instead:\\n` +\n ` edf2csv -- ${survivesBare(flag) ? `\"${flag}\"` : singleQuoted(flag)}`,\n );\n}\n\nfunction message(error: unknown, indent = ''): string {\n // The indent lines continuation up under an \"error: \" prefix. Usage problems print without\n // one, alongside the usage text, so they pass nothing and stay flush left.\n return printableLines(error instanceof Error ? error.message : String(error), indent);\n}\n\n// `worstOf` is exported for the test that pins the exit-code mapping. A batch's verdict is\n// one of the few things a script branches on, and the mapping is easier to check directly\n// than by racing a signal at a worker.\nexport { defaultOutputDir, worstOf };\n\n/**\n * Whether this file was executed rather than imported.\n *\n * npm installs a bin as a symlink (node_modules/.bin/edf2csv -> ../edf2csv/dist/cli.js),\n * and that is the path `npx` runs. In that case process.argv[1] is the symlink while\n * import.meta.url is already resolved to the real file, so comparing the two directly\n * reports \"imported\" and the command silently does nothing. Both sides are resolved\n * through realpath before comparing.\n */\nfunction isMainModule(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n // An unreadable or deleted entry path is not a reason to refuse to run.\n return pathToFileURL(entry).href === import.meta.url;\n }\n}\n\nconst invokedDirectly = isMainModule();\n\nif (invokedDirectly) {\n main(process.argv.slice(2))\n .then((code) => {\n /*\n A write failure already reported is not erased by a run that thought it finished.\n\n The stdout listener above prints `Writing to stdout failed: ...` and sets EXIT_ERROR,\n and assigning `code` over it put that back to 0 — so `--info > desc.txt` onto a full\n filesystem printed the error, left a zero-byte file, and exited 0. It only looked\n right because the stdout audit threw a second error on the way out, whose message was\n about a short write that had not happened.\n\n A closed pipe deliberately sets no code (see ignoreBrokenPipe), so `--info | head`\n still exits 0 through here.\n */\n process.exitCode = code === EXIT_OK ? (process.exitCode ?? EXIT_OK) : code;\n })\n .catch((error: unknown) => {\n process.stderr.write(`error: ${message(error)}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Command-line entry point.\n *\n * Output discipline: anything that is the *result* of a command goes to stdout\n * (`--info`'s channel table, `--json`'s summary). Progress, warnings and the\n * conversion summary go to stderr, so a conversion can be run in a pipeline without\n * its chatter contaminating the data.\n */\n\nimport { parseArgs } from 'node:util';\nimport { pathToFileURL, fileURLToPath } from 'node:url';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { lstat, readdir, realpath, stat } from 'node:fs/promises';\nimport { cpus } from 'node:os';\nimport { fork } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { EdfError } from './edf/errors.js';\nimport { EdfFile } from './edf/reader.js';\nimport { buildPlan, withoutFileRateWarning } from './convert/plan.js';\nimport {\n ConversionError,\n USAGE_ERROR_CODES,\n auditStdout,\n convert,\n defaultOutputDir,\n durationDiagnostics,\n stdoutRefusal,\n} from './convert/run.js';\nimport { ChannelSelectionError } from './convert/channels.js';\n// Shared with the library so a bad option is the same error whichever way it arrived.\nimport { OptionError } from './convert/options.js';\nimport { TimeRangeError, parseTimeSpec } from './convert/time-range.js';\nimport { deriveRecordStarts, withTimingPromiseKept } from './convert/timing.js';\nimport { formatDiagnostics, formatInfo, infoJson, formatSummary, printable, printableLines, summaryJson, wrap } from './cli/report.js';\nimport { counted, listed } from './format/list.js';\nimport { VERSION } from './version.js';\n\nconst USAGE = `edf2csv ${VERSION}\nConvert EDF, EDF+ and BDF recordings to CSV\n\nUsage\n edf2csv <recording.edf | folder> [more ...] [options]\n\nOptions\n -i, --info Show the recording's structure and the estimated\n output size, without converting anything\n -o, --out <dir> Output directory (default: <recording>_csv, beside\n the input)\n -c, --channels <list> Only these channels, comma-separated. Use #N to pick a\n channel by position when two share a label\n --start <time> Begin at this offset (30s, 5m, 1h30m, 00:30:00)\n --duration <time> Convert this much\n --end <time> Stop at this offset (instead of --duration)\n --annotations-only Write only the EDF+ annotations, no signal data\n --decimals <n> Fix the decimal places instead of deriving them\n per channel\n --checksum Record a SHA-256 of the input in metadata.json\n --layout <kind> wide (default): one column per channel, one file per\n sampling rate. long: one file of time_s,channel,value,\n every rate together, one row per sample\n --gzip Compress every CSV, writing .csv.gz files\n --bom Start each CSV with a UTF-8 byte order mark, so Excel\n reads accented text and units like µV correctly\n -j, --jobs <n> Convert this many recordings at once, or \"auto\"\n (default: 1)\n -f, --force Write into the output directory if it already exists\n -q, --quiet Suppress the summary; warnings and errors still print\n --json Print machine-readable JSON to stdout (works with\n --info too)\n --strict Exit 1 if the recording raised any warning\n --stdout Write the signal CSV to stdout instead of a directory\n (one table only: one sampling rate, or --layout long)\n -h, --help Show this help\n -V, --version Show the version\n\nSeveral recordings\n A folder is expanded to every .edf and .bdf inside it, at any depth, and the\n layout is kept: recordings in sub-folders come out in sub-folders.\n Pass more than one and each is converted in turn. Without --out each lands\n beside itself as usual; with --out that directory becomes the parent and each\n recording gets its own inside it. A file that cannot be read is reported and\n the rest still convert, with a non-zero exit at the end. --jobs converts\n several at once, which is worth it for a folder of them.\n\nOutput\n A directory containing signals.csv, channels.csv, metadata.json, and\n annotations.csv when the recording carries EDF+ annotations. Channels recorded\n at different sampling rates are written to separate files, never resampled.\n With --gzip each CSV becomes a .csv.gz; metadata.json stays plain text so the\n directory can still be read at a glance. With --bom each CSV starts with a\n UTF-8 byte order mark and metadata.json does not, since JSON.parse rejects\n one.\n With --layout long every channel goes into one signals.csv as time_s, channel\n and value, one row per sample, whatever rates the recording mixes — which is\n also the one arrangement --stdout can stream for a mixed-rate file.\n\nExamples\n edf2csv recording.edf\n edf2csv recording.edf --info\n edf2csv recording.edf --channels \"EEG Fpz-Cz,ECG\" --out ./converted\n edf2csv recording.edf --start 30m --duration 5m\n edf2csv recording.edf --annotations-only\n edf2csv /data/*.edf --out ./converted\n edf2csv /data/*.edf --out ./converted --jobs auto\n edf2csv /data/study --out ./converted --jobs auto\n`;\n\n/**\n * A continuation line under an `error: ` or `interrupted (SIGINT): ` head.\n *\n * These are written straight to stderr rather than raised as an error, so they never passed\n * through `printableLines` and never wrapped. Most of them interpolate a destination or a\n * recording path, which makes the line as long as the caller's directory tree is deep: the\n * `--stdout` refusal for a folder reached 268 columns on a four-deep path, saying \"Name the\n * recording itself — <path> — or convert to a directory instead.\"\n *\n * A path has no spaces to break at, so it stays whole on its own line and the sentence\n * around it wraps. That is the useful shape: the path is the part that gets copied.\n */\nconst detail = (text: string): string => `${wrap(text, ' ')}\\n`;\n\n/**\n * Every option the command line takes.\n *\n * Its own constant so `parseArgs` and the \"did you mean\" suggestion read the same list. A\n * second copy of twenty flag names is a copy that will be missing the next one.\n */\nconst OPTIONS = {\n info: { type: 'boolean', short: 'i' },\n out: { type: 'string', short: 'o' },\n channels: { type: 'string', short: 'c', multiple: true },\n start: { type: 'string' },\n duration: { type: 'string' },\n end: { type: 'string' },\n 'annotations-only': { type: 'boolean' },\n decimals: { type: 'string' },\n checksum: { type: 'boolean' },\n layout: { type: 'string' },\n gzip: { type: 'boolean' },\n bom: { type: 'boolean' },\n jobs: { type: 'string', short: 'j' },\n force: { type: 'boolean', short: 'f' },\n quiet: { type: 'boolean', short: 'q' },\n json: { type: 'boolean' },\n strict: { type: 'boolean' },\n stdout: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'V' },\n} as const;\n\nconst EXIT_OK = 0;\nconst EXIT_ERROR = 1;\nconst EXIT_USAGE = 2;\n\n/**\n * Whether this run is already shutting its children down.\n *\n * A child dying by signal is normally worth a line of its own, but not when this process is\n * the one that killed it: the interrupt handler names every abandoned directory in a single\n * message, and a per-child line underneath it would say the same thing again, once per job.\n */\nlet stopping = false;\n\n/**\n * Piping into a consumer that exits early (`| head -1`) closes our stdout, and the\n * next write raises EPIPE. That is normal in a shell pipeline, not an error worth a\n * stack trace, so it is swallowed while anything else still surfaces.\n */\nfunction ignoreBrokenPipe(stream: NodeJS.WriteStream): void {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n // A reader closing early (`edf2csv ... --info | head -5`) is not a failure, so the\n // error is swallowed rather than thrown. It deliberately does NOT set an exit code:\n // forcing 0 here would erase a real failure whenever the pipe happened to close\n // after the run had already failed, reporting success for a conversion that did\n // not happen. Node exits 0 on its own when nothing sets a code.\n return;\n }\n\n /*\n Anything else is a real write failure, and this used to rethrow it.\n\n The throw lands on a nextTick, outside whatever try/catch the conversion is running\n inside, so it became an uncaught exception: `--stdout` redirected onto a full disk\n died with a raw stack trace headed `dist/cli.js: throw error;` and lost the warning\n that the CSV it had already produced was truncated. The same failure through `--out`\n printed the ordinary message, and so did `--stdout` through the library API, which\n never registers this listener — the designed path exists and works, and this was\n preempting it.\n\n A second listener means the conversion's writer is watching this stream and will\n turn the failure into that ordinary message. Saying it here too would report one\n failure twice, so this only speaks when nothing else will.\n */\n if (stream.listenerCount('error') > 1) return;\n process.stderr.write(`error: Writing to ${stream === process.stdout ? 'stdout' : 'stderr'} failed: ${error.message}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n\nexport async function main(argv: readonly string[]): Promise<number> {\n ignoreBrokenPipe(process.stdout);\n ignoreBrokenPipe(process.stderr);\n\n let values: Record<string, unknown>;\n let positionals: string[];\n\n try {\n const parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: OPTIONS,\n });\n values = parsed.values as Record<string, unknown>;\n positionals = parsed.positionals;\n } catch (error) {\n process.stderr.write(`${usageMessage(error, argv)}\\n\\nRun edf2csv --help to see the options.\\n`);\n return EXIT_USAGE;\n }\n\n if (values['help'] === true) {\n process.stdout.write(USAGE);\n return EXIT_OK;\n }\n if (values['version'] === true) {\n process.stdout.write(`${VERSION}\\n`);\n return EXIT_OK;\n }\n\n if (positionals.length === 0) {\n process.stderr.write(`No input file given.\\n\\n${USAGE}`);\n return EXIT_USAGE;\n }\n\n /*\n An empty --out, refused here rather than by the filesystem.\n\n Every other option that takes a value refuses an empty one from the command line and says\n what it wanted: `--start is empty`, `--channels was given but lists no channel names`,\n `--layout must be \"wide\" or \"long\", got \"\"`. `--out \"\"` was the eighth of eight and the\n exception — it went the whole way to `mkdir('')` and came back as a conversion failure\n about a path:\n\n error: Cannot create \"\": part of the path does not exist.\n Check the path exists and that you have permission to write there.\n\n Exit 1 where the other seven are exit 2, advice about a path and a permission for a value\n that is neither, and in a batch it is decided once per recording rather than once for the\n run. `--out \"$DEST\"` with `DEST` unset is how it gets written by accident, which is also\n why the empty string alone is refused and the value is not trimmed first: a directory whose\n name is a space is a strange thing to ask for, but it is a thing the filesystem has and a\n path is not a keyword.\n */\n if (values['out'] === '') {\n process.stderr.write('error: --out is empty. Give a directory, for example --out ./converted.\\n');\n return EXIT_USAGE;\n }\n\n let expanded: Input[];\n let unreadable: string[];\n let namedDirectory: boolean;\n try {\n const found = await expandInputs(positionals);\n expanded = found.inputs;\n unreadable = found.unreadable;\n namedDirectory = found.namedDirectory;\n } catch (error) {\n return reportError(error);\n }\n\n // Reported before anything is converted, so it cannot be lost among the summaries, and\n // counted against the run so the exit code does not call a partial sweep a success.\n for (const entry of unreadable) {\n process.stderr.write(\n `error: ${printable(entry)}: could not be read, so any recordings inside it were skipped.\\n`,\n );\n }\n\n if (expanded.length === 0) {\n /*\n \"None here\" and \"could not look\" are different answers, and so are their exit codes.\n\n A folder the process cannot read gave the same exit 2 and the same \"No EDF or BDF\n recordings found\" as an empty one — while the line above it said the folder could not\n be read. Exit 2 is this tool's code for \"the command itself was wrong\", so a script\n was being told to fix its arguments when what needed fixing was a permission. The\n command was fine; the filesystem refused.\n\n And the sentence itself claimed a fact the run is in no position to state: nothing was\n found because nothing was looked at.\n */\n if (unreadable.length > 0) {\n process.stderr.write(\n `${wrap(\n `Nothing could be converted: ${unreadable.length === 1 ? 'that path' : 'those paths'} ` +\n `could not be read, so whether ${unreadable.length === 1 ? 'it holds' : 'they hold'} ` +\n `recordings is unknown.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n process.stderr.write(\n // Escaped, like every other path this prints. These come from the command line, which\n // makes them no safer: a shell glob expands to whatever the directory holds.\n `No EDF or BDF recordings found in ${listed(positionals.map((p) => `\"${printable(p)}\"`))}.\\n`,\n );\n return EXIT_USAGE;\n }\n const inputs = expanded.map((entry) => entry.path);\n /*\n A batch is what you asked for, not what happened to be there.\n\n Counting the recordings made the shape of the run depend on the contents of a folder.\n Under --json a study holding one night printed a pretty-printed object and the same study\n holding two printed two compact lines, so a script written against one of them broke on\n the other — and it broke the day a recording was added, not the day the script changed.\n An input going missing did it in reverse. This is the same count 0.4.20 took out of\n `--out` for the same reason.\n\n Counted from what was named, not from what survived deduplication. `edf2csv one.edf\n alias.edf --out o`, where `alias.edf` is a link to `one.edf`, is two names for one\n recording — converted once, which is right — and the count collapsing to 1 made the run\n stop being a batch: `--out` became the output directory itself, so the files landed in\n `o/` rather than `o/one/`, and `--json` printed one indented document where two\n recordings would have given JSON Lines. cli-reference says of exactly that command that\n it writes `out/one`.\n */\n const batch = positionals.length > 1 || namedDirectory;\n const quiet = values['quiet'] === true;\n const asJson = values['json'] === true;\n const strict = values['strict'] === true;\n const toStdout = values['stdout'] === true;\n\n // Both of these claim stdout. Allowing them together wrote the CSV and then the summary\n // object onto one stream, producing a document that is neither valid CSV nor valid\n // JSON — and silently, since each half looked right on its own.\n /*\n Not under --info, which writes no CSV for --json to collide with.\n\n The two claim stdout because one puts a CSV there and the other a summary. `--info` puts\n neither: it prints a description, and under --json that description *is* the JSON. So\n `--info --stdout --json` — a script asking \"would --stdout work on this recording, in a\n form I can parse\" — was refused for a conflict that cannot arise, which is the same shape\n as the destination guards 0.5.51 stopped applying to it. It is also the only way to see\n the STDOUT_UNSUPPORTED warning from a script.\n */\n if (toStdout && asJson && values['info'] !== true) {\n /*\n `error: ` and the seven-space continuation, like every other refusal.\n\n These two were written before the prefix was, and kept their own shape: every other\n usage error in this tool prints \"error: <what>\" with the advice indented under it, the\n documentation shows them that way, and stderr is what a script greps. A refusal that\n does not match `^error:` is invisible to that grep — and this pair is the one a script\n is most likely to hit, since both flags are things a script passes rather than a person.\n */\n process.stderr.write(\n 'error: --stdout and --json both write to stdout, so they cannot be combined.\\n' +\n ' Use --stdout for the CSV, or --json for the summary.\\n',\n );\n return EXIT_USAGE;\n }\n\n /*\n One stream holds one table, for the same reason it holds one recording: concatenating\n them would give a CSV whose rows come from different files with nothing marking where\n one ends. Naming the count makes it obvious a glob was the cause — except when the count\n is one, which happens for a folder holding a single recording. That read \"--stdout writes\n a single CSV, so it cannot take 1 recordings\": ungrammatical, and wrong on its face, since\n one recording is exactly what it can take. What it cannot take is a folder, whose contents\n are not known until they are walked.\n */\n /*\n Asked of the recordings to stream, not of the run's shape.\n\n These are different questions and 0.5.40 conflated them: it made `batch` count the names\n rather than the recordings, which is right for `--out` and for `--json`, and this guard\n read `batch`. So `edf2csv one.edf one.edf --stdout` — one recording, named twice,\n converted once, perfectly streamable — was refused with the message written for a folder,\n telling the reader to \"name the recording itself\" and quoting the name they had just\n given twice.\n\n A folder is still refused however few recordings it turns out to hold, for the reason\n 0.5.5 gives: what it holds is not known until it is walked.\n */\n if (toStdout && (inputs.length > 1 || namedDirectory)) {\n // Prefixed and indented like the rest; see the --json refusal above. The recording's\n // name goes through `printable` for the reason 0.5.67 gives: a path is untrusted text,\n // and this one is read straight out of a directory the caller named.\n process.stderr.write(\n inputs.length === 1\n ? `error: --stdout writes a single CSV, and a folder is converted as a batch even ` +\n `when it holds one recording.\\n` +\n detail(\n `Name the recording itself — ${printable(inputs[0] as string)} — or convert ` +\n `to a directory instead.`,\n )\n : `error: --stdout writes a single CSV, so it cannot take ` +\n `${counted(inputs.length, 'recording')}.\\n` +\n detail('Convert them to directories instead, or run edf2csv once per file.'),\n );\n return EXIT_USAGE;\n }\n\n /*\n Flags that --stdout has nowhere to put.\n\n Both were accepted and dropped in silence. `--out` names a directory that is never\n created, so the run looked like it had written one. `--checksum` is worse than useless:\n the hash is computed before the first record is read, which is a second full pass over\n the input, and then the only file it is ever written to — metadata.json — is not written\n at all. A recording large enough to want a checksum is large enough to notice reading it\n twice for nothing.\n\n Refusing rather than ignoring is what this tool already does for `--stdout --json` and\n `--stdout --annotations-only`.\n */\n /*\n `--force` and `--jobs` were the two left doing nothing in silence.\n\n `--force` means \"write into a directory that already exists\", and there is no directory.\n `--jobs` is deliberately not here. A job count is a property of the run rather than a\n request about the output, `--stdout` clamping it to one is documented, and a wrapper that\n passes `--jobs 4` to everything is not asking for something about this file.\n */\n for (const [flag, given] of [\n ['--out', values['out'] !== undefined],\n ['--checksum', values['checksum'] === true],\n ['--force', values['force'] === true],\n ] as const) {\n if (toStdout && given) {\n // `error:` and the seven-space continuation, like every other refusal — the shape\n // 0.5.79 gave the rest and these two were not enumerated in its test.\n process.stderr.write(\n `error: --stdout and ${flag} cannot be combined: --stdout writes no\\n` +\n detail(`files, and ${flag} has nothing to act on.`) +\n detail(`Drop ${flag}, or drop --stdout and convert to a directory.`),\n );\n return EXIT_USAGE;\n }\n }\n\n /*\n Compressed bytes at a terminal.\n\n `--stdout --gzip` is a documented pair, and every place it is documented redirects it:\n `edf2csv rec.edf --stdout --gzip > signals.csv.gz`. Without the redirect it wrote the\n deflate stream to the terminal — 46 control bytes and three ESCs out of 244 for the\n smallest fixture here, which is a terminal left in whatever state those happened to\n describe, and nothing usable on the screen either way.\n\n This tool escapes every control byte out of a header before printing it, on the reasoning\n that a file should not be able to drive the reader's terminal. Doing it to them directly,\n from output it generated itself, is the same hazard with the argument for it removed.\n\n Only when stdout is a terminal. A pipe and a regular file are both `isTTY === false`, so\n the documented command is unaffected — and that is also why this cannot be answered by\n refusing the flag pair outright, the way `--stdout --json` is.\n\n gzip itself has declined to write compressed data to a terminal for thirty years, which\n is where a reader's expectation comes from.\n */\n if (toStdout && values['gzip'] === true && process.stdout.isTTY === true) {\n /*\n `<recording>` is not a placeholder a shell leaves alone.\n\n This line is printed on its own, indented, in the shape every command this tool offers\n is printed in — and 0.7.18 and 0.7.19 went through the others making sure each one can\n be pasted. Pasted, `<recording>` is a redirect: the shell looks for a file called\n `recording` and the command never runs, or one exists and edf2csv is handed a recording\n on stdin, which it does not read. Either way the answer to \"what do I type instead\"\n does not survive being typed.\n\n The name is in the invocation being refused, and exactly one of them reaches here: a\n folder and a second recording are both turned away above. Quoted by the same rule as\n the other two hints, so a path with a space in it stays one argument.\n */\n const named = printable(inputs[0] as string);\n process.stderr.write(\n 'error: --stdout --gzip would write compressed bytes straight to the terminal.\\n' +\n detail('Redirect it to a file or a pipe:') +\n ` edf2csv ${survivesBare(named) ? named : singleQuoted(named)} ` +\n `--stdout --gzip > signals.csv.gz\\n`,\n );\n return EXIT_USAGE;\n }\n\n /*\n One recording prints the document it always printed; several print one per line.\n\n Pretty-printed objects run together are still readable by a streaming JSON parser, but\n not by anything that reads a record per line — and a batch is exactly where that is\n wanted. Emitting a batch in the single-file shape would repeat 0.2.28's mistake of\n putting two documents on one stream and leaving the caller to work out the boundary.\n\n null, not undefined: the serialisers default this argument to 2, and a default parameter\n takes effect for undefined, which silently gave a batch the indentation it was supposed\n to be dropping.\n */\n const jsonIndent = batch ? null : 2;\n\n try {\n const outOption = typeof values['out'] === 'string' ? values['out'] : undefined;\n const destinations = destinationsFor(expanded, outOption, batch);\n /*\n Where the output would land is not --info's problem to refuse over. It is still\n --info's job to mention it.\n\n 0.5.32 stopped the guards refusing `--info`, and left nothing in their place — while\n the sentence it added to cli-reference says \"`--info --out` is how you would want to\n find out about them\". So the one command documented as the way to learn about a\n collision said nothing at all about it. Reported as a warning now, using the guard's own\n message so there is one wording rather than two.\n */\n if (values['info'] === true) {\n try {\n assertDistinct(inputs, destinations);\n } catch (error) {\n if (!(error instanceof OptionError)) throw error;\n process.stderr.write(`warning: ${printableLines(error.message, ' ')}\\n`);\n }\n }\n\n /*\n Where the output would land is not --info's problem, because --info has no output.\n\n Both guards refused it: `edf2csv study --info --out yy` on a folder holding `rec.edf`\n beside `rec/inner.edf` exited 2 with \"would be converted into yy/rec/inner, which is\n inside yy/rec — where rec.edf is converted\", and printed nothing about either\n recording. Two recordings whose names collide got the overwrite refusal the same way.\n Both messages assert a conversion and an overwrite that --info does not perform, and\n the identical command without --out describes both files happily.\n\n Which makes the refused command the useful one: `--info --out` is how you ask what a\n run would produce before committing to it, and a collision is exactly the thing you\n would want it to tell you about rather than refuse to look.\n */\n if (values['info'] !== true) assertDistinct(inputs, destinations);\n\n const shared = {\n channels: splitChannels(values['channels']),\n start: optionalTime(values['start'], '--start'),\n startText: typeof values['start'] === 'string' ? values['start'] : undefined,\n duration: optionalTime(values['duration'], '--duration'),\n end: optionalTime(values['end'], '--end'),\n endText: typeof values['end'] === 'string' ? values['end'] : undefined,\n decimals: optionalDecimals(values['decimals']),\n annotationsOnly: values['annotations-only'] === true,\n gzip: values['gzip'] === true,\n bom: values['bom'] === true,\n layout: optionalLayout(values['layout']),\n };\n\n // Validated before the --info branch, not inside the conversion path: a flag that\n // cannot be honoured is a usage error whatever mode it was given in, and accepting\n // \"--jobs 0\" in silence under --info is the kind of quiet that this tool avoids.\n // Parsed before it is overridden: --stdout converts one recording however many jobs\n // were asked for, but \"--stdout --jobs 0\" is still a request that cannot be honoured,\n // and accepting it in silence is the thing 0.4.2 fixed for --info.\n const requestedJobs = parseJobs(values['jobs'], inputs.length);\n /*\n Asking for several at once, of a mode that converts one.\n\n After `parseJobs`, not before it: a malformed count is a fact about the value and gets\n the message about the value, which is what the check above this one has always done and\n what its own comment insists on — \"a request that cannot be met is a usage error rather\n than something to accept in silence\". So `--stdout --jobs 0x10` still reports the `0x10`.\n\n `--jobs 1` is not refused. It asks for precisely what happens, and a script passing it\n uniformly is not asking for anything it will not get; `auto` resolves to 1 on one\n recording. What is refused is a number greater than one, which was accepted and dropped.\n */\n const jobs = toStdout ? 1 : requestedJobs;\n\n if (values['info'] === true) {\n const failures: number[] = [];\n let warnings = 0;\n for (const [index, input] of inputs.entries()) {\n // A blank line between reports, so several tables read as one document.\n if (batch && !asJson && index > 0) process.stdout.write('\\n');\n try {\n warnings += await showInfo(\n input as string,\n shared,\n asJson,\n jsonIndent,\n batch,\n values['stdout'] === true,\n );\n } catch (error) {\n failures.push(reportError(error, batch ? (input as string) : undefined));\n }\n }\n if (unreadable.length > 0) failures.push(EXIT_ERROR);\n if (failures.length > 0) return worstOf(failures);\n return strict && warnings > 0 ? EXIT_ERROR : EXIT_OK;\n }\n\n // The meter redraws one line in place, which two conversions cannot share. Running them\n // at once replaces it with the completion count each file prints as it lands.\n const showProgress =\n !quiet && !asJson && !toStdout && jobs === 1 && process.stderr.isTTY === true;\n\n const failures: number[] = [];\n let warnings = 0;\n let converted = 0;\n\n /** Convert input `index`, sending its output wherever the caller wants it. */\n const runOne = async (index: number, emit: Emit): Promise<void> => {\n const input = inputs[index] as string;\n const destination = destinations[index] as string;\n // Which recording this is, before anything it prints. Without it a batch produces a\n // stack of summaries and errors with nothing saying which file each belongs to.\n if (batch && !quiet && !asJson) {\n emit('err', `[${index + 1}/${inputs.length}] ${printable(input)}\\n`);\n }\n try {\n warnings += await convertOne(\n input,\n destination,\n {\n ...shared,\n // With one input and no --out, convert() derives the default itself, exactly as\n // it did before batches existed.\n outputDir: outOption === undefined && !batch ? undefined : destination,\n checksum: values['checksum'] === true,\n force: values['force'] === true,\n toStdout,\n quiet,\n asJson,\n showProgress,\n jsonIndent,\n batch,\n },\n emit,\n );\n converted++;\n } catch (error) {\n /*\n A batch keeps going. One unreadable recording among five hundred is a reason to\n report that file, not to abandon the ones already converted and refuse the rest.\n The exit code still reports the run as failed, and the closing line says how many\n of them made it, so nothing about the failure is quiet.\n */\n failures.push(reportError(error, batch ? input : undefined, emit));\n }\n };\n\n if (jobs === 1) {\n for (let index = 0; index < inputs.length; index++) {\n await runOne(index, writeThrough);\n // A single recording keeps the exit code it has always had rather than the batch's.\n if (!batch && failures.length > 0) return failures[0] as number;\n }\n } else {\n /*\n Real processes, not concurrent promises.\n\n Converting is almost entirely arithmetic and string building — 1.17 s of CPU for\n 1.24 s of wall clock on a 168 MB conversion — and Node runs that on one thread. An\n in-process pool was tried first and gained 6% on eight recordings, which is the\n overlap in the file reads and nothing else. Each conversion is already a whole\n command, so each one gets its own process, which is what `xargs -P` would do by hand.\n\n Workers take the next recording as they free up rather than splitting the list into\n equal shares. Recordings in a folder differ wildly in length, and a fixed split\n leaves every worker but one idle behind the longest file.\n\n Each child's output is held until it exits, so two finishing together cannot\n interleave one's summary with the other's warnings.\n */\n const running = new Map<ChildProcess, string>();\n\n /*\n Ctrl-C in a terminal reaches every process in the group, so the children would stop\n anyway; a signal sent to this process alone does not, and that is how a batch gets\n run from a script or a CI job. Interrupting one left four conversions writing\n gigabytes into a directory their owner believed abandoned, and the last thing on\n screen was a successful \"Done in 1.6s\" from whichever recording had just landed —\n the run read as if it had finished.\n */\n const onInterrupt = (signal: NodeJS.Signals): void => {\n stopping = true;\n const abandoned = [...running.values()];\n for (const child of running.keys()) child.kill('SIGTERM');\n process.stderr.write(\n `\\ninterrupted (${signal}): ${abandoned.length} conversion` +\n `${abandoned.length === 1 ? '' : 's'} stopped part way through.\\n` +\n (abandoned.length > 0\n ? detail(`Incomplete, and should not be used: ${listed(abandoned.map(printable))}`)\n : ''),\n );\n process.exit(signal === 'SIGINT' ? 130 : 143);\n };\n process.once('SIGINT', onInterrupt);\n process.once('SIGTERM', onInterrupt);\n\n let next = 0;\n const worker = async (): Promise<void> => {\n for (let index = next++; index < inputs.length; index = next++) {\n const input = inputs[index] as string;\n const sink = buffered();\n if (!quiet && !asJson) {\n sink.emit('err', `[${index + 1}/${inputs.length}] ${printable(input)}\\n`);\n }\n const child = await convertInChild(\n input,\n destinations[index] as string,\n values,\n running,\n );\n // The report says what the child actually did; the exit code says whether it\n // got there. A child that converted and warned has a report and exits 0, since\n // --strict is the parent's to apply.\n if (child.report) {\n converted += child.report.converted;\n warnings += child.report.warnings;\n } else if (child.code === 0) {\n converted++;\n }\n if (child.code !== 0) failures.push(child.code);\n // The child saw one recording, so it printed the indented document a single\n // conversion prints. A batch is one object per line.\n sink.emit('out', asJson ? compactJson(child.out) : child.out);\n /*\n The child converted a single recording, so it named no file in its errors the way\n a batch does. Naming it here keeps the two paths identical to a reader and to\n anything grepping a log, where the [n/m] header may not be alongside.\n\n Warnings too, under --quiet, and for exactly the reason 0.5.49 gave for the serial\n path: the `[n/m] <path>` header is what pairs a warning with the file that raised\n it, --quiet suppresses that header, and it took the attribution with it. That fix\n keyed off the child's own `batch` flag, which a forked child does not have — it\n was handed one recording and one destination, so it believes it is a single\n conversion and says nothing. Two recordings, two warnings, no way to tell which\n raised which, and under --jobs not even a stable order to guess from.\n\n Not when the header is printed, or every warning would carry the name twice.\n */\n sink.emit('err', named(child.err, input, quiet));\n sink.flush();\n }\n };\n try {\n await Promise.all(Array.from({ length: jobs }, () => worker()));\n } finally {\n process.off('SIGINT', onInterrupt);\n process.off('SIGTERM', onInterrupt);\n }\n }\n\n /*\n A path that could not be read is a failure of this run, and the closing line has to\n count it before it prints rather than after.\n\n It was counted afterwards, so a folder holding one recording beside a sub-directory\n without read permission printed \"Converted 1 of 1 recordings.\" and exited 1. The line\n agreed with itself and with nothing else — which is the failure the walk's own comment\n says was fixed, quoting that very sentence. What was fixed then was the error line and\n the exit code; the summary went on saying everything worked.\n\n Counted separately from the recordings, because an unreadable path is not one of them:\n how many recordings it held is the thing nobody knows.\n */\n if (unreadable.length > 0) failures.push(EXIT_ERROR);\n\n if (batch && !quiet && !asJson) {\n const unread =\n unreadable.length > 0\n ? `; ${unreadable.length} path${unreadable.length === 1 ? '' : 's'} could not be read`\n : '';\n const failed = converted < inputs.length ? `; ${inputs.length - converted} failed` : '';\n process.stderr.write(\n `\\nConverted ${converted} of ${counted(inputs.length, 'recording')}${failed}${unread}.\\n`,\n );\n }\n\n /*\n A parent that forked this process needs the counts, not just an exit status.\n\n An exit code cannot separate \"converted, and raised warnings\" from \"did not convert\",\n and under --strict those are the same code. The parent read it as a failure, so a\n parallel run of two recordings — one of which merely warned — reported \"Converted 1 of\n 2 recordings; 1 failed\" for a run in which both converted, while the serial path said\n \"Converted 2 of 2\". `process.send` exists only when this process was forked with a\n channel, so nothing changes for an ordinary invocation.\n */\n process.send?.({ edf2csv: { converted, warnings } });\n\n if (failures.length > 0) return worstOf(failures);\n\n /*\n --strict turns any warning into a non-zero exit, for pipelines that would rather stop\n than proceed on a recording the tool had something to say about.\n\n The output is still written. A warning describes the recording, not a failure to\n convert it — a truncated file converts correctly for the records that are there — so\n destroying that work would be the wrong response. The exit code is the signal; what\n to do about it is the caller's decision, and they still have the files to inspect.\n */\n if (strict && warnings > 0) {\n process.stderr.write(\n `\\n${wrap(\n `--strict: ${warnings} warning${warnings === 1 ? '' : 's'} raised, so this run is ` +\n `reported as a failure. The output was still written.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n return EXIT_OK;\n } catch (error) {\n return reportError(error);\n }\n}\n\n/** Print one recording's `--info`, and return how many diagnostics it raised. */\nasync function showInfo(\n input: string,\n shared: Record<string, unknown>,\n asJson: boolean,\n jsonIndent: number | null,\n batch = false,\n toStdout = false,\n): Promise<number> {\n const file = await EdfFile.open(input);\n try {\n /*\n Read the annotation channel only when the timing actually depends on it.\n\n --info is documented as a header-only summary that returns immediately whatever\n the file's size, but it called readAnnotations() on every EDF+/BDF+ file — a seek\n into every data record. deriveRecordStarts discards that data unless the file is\n EDF+D, where record start times are stored rather than arithmetic, so on a\n continuous recording the whole scan was thrown away. It cost 0.29 s on a 12 MB\n file and scaled with record count.\n\n A discontinuous file still needs the scan: without it the reported span and row\n estimate are wrong, which is a bug that has already been fixed once here.\n */\n const hasAnnotations = file.annotationSignals.length > 0;\n const needsEveryRecordStart = file.header.continuity === 'EDF+D' && hasAnnotations;\n /*\n A continuous recording needs its origin too, which is one read rather than one per\n record: 0.4.9 made its first record's timekeeping TAL the point the samples are timed\n from, and this report went on placing a requested window against zero. `--info --start 1`\n predicted 8 rows where the conversion wrote 10, on a file whose discontinuous twin —\n identical but for the reserved field — agreed with itself.\n */\n const scan =\n !needsEveryRecordStart && file.header.continuity === 'EDF+C' && hasAnnotations\n ? await file.scanOrigin()\n : null;\n const annotationData = needsEveryRecordStart\n ? await file.readAnnotations()\n : {\n annotations: [],\n recordStarts: scan ? [scan.origin] : [],\n malformed: 0,\n // Counted rather than assumed: see EdfFile.scanOrigin.\n malformedTimekeeping: scan?.malformedTimekeeping ?? 0,\n };\n const timing = deriveRecordStarts(file, annotationData);\n const plan = buildPlan(\n {\n signals: file.header.signals,\n recordDuration: file.header.recordDuration,\n recordCount: file.recordCount,\n hasAnnotationChannel: file.annotationSignals.length > 0,\n recordStarts: timing.starts,\n },\n shared,\n );\n plan.diagnostics.push(...timing.diagnostics);\n /*\n What --stdout would do with this recording, which --info did not ask.\n\n `--info --stdout` on a three-rate file predicted \"Would write 1,155 rows, roughly\n 22.2 KB\" and said the channels \"are written to one file per rate\" — for a command that\n refuses to run, writes nothing and names no file. --info exists to say what a\n conversion will do, and refusing is one of the things it does.\n\n A warning rather than a refusal, for the reason 0.5.51 gives about the destination\n guards: --info writes nothing, so a rule about the output has no business stopping it\n from describing the recording — and being told the command will not work is exactly\n what was asked. The conversion's own guard supplies the words, so there is one wording.\n */\n /*\n What the conversion would say about the durations it would write.\n\n Same window the conversion applies, so --info predicts the warnings rather than a\n different set. Raised here rather than in the parser because the counts have to be of\n the rows that reach annotations.csv — see durationDiagnostics.\n */\n plan.diagnostics.push(\n ...durationDiagnostics(annotationData.annotations, {\n from: plan.range.startSeconds,\n to: plan.range.endSeconds,\n }),\n );\n\n if (toStdout) {\n const refusal = stdoutRefusal(file, plan);\n if (refusal) {\n plan.diagnostics.push({\n code: 'STDOUT_UNSUPPORTED',\n severity: 'warning',\n message: `--stdout would refuse this recording: ${refusal.message.replace(/^--stdout /u, '')}`,\n ...(refusal.hint === undefined ? {} : { hint: refusal.hint }),\n });\n }\n }\n /*\n Checked, like a conversion's stdout is.\n\n This wrote and looked at nothing, so `--info > desc.txt` into a filesystem with no room\n produced a zero-byte file and exited 0. The same audit the `--stdout` path uses: it\n declines anything that is not a regular file, so a pipe or a terminal is unaffected, and\n `--info | head` keeps exiting 0.\n */\n const audit = auditStdout();\n const description = asJson\n ? `${infoJson(file, plan, jsonIndent)}\\n`\n : `${formatInfo(file, plan)}\\n`;\n process.stdout.write(description);\n audit?.count(Buffer.byteLength(description));\n audit?.verify();\n\n // Under --json the warnings travel inside the document, exactly as they do for a\n // conversion, so stderr stays empty and the whole result is one parseable thing.\n const diagnostics = [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics];\n if (!asJson && diagnostics.length > 0) {\n /*\n Named when there is more than one recording to confuse it with.\n\n The table goes to stdout and the warnings to stderr, which is the point of the split\n — but over a folder that left several warnings in a row on stderr with nothing saying\n which recording raised any of them. Two recordings, two warnings, and no way to pair\n them up short of running the tool again one file at a time. A batch conversion has\n named its recordings since 0.4.20; this is the same report from the same tool about\n the same files.\n */\n const where = batch ? `${printable(input)}: ` : '';\n process.stderr.write(\n `\\n${formatDiagnostics(diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n`,\n );\n }\n return diagnostics.length;\n } finally {\n await file.close();\n }\n}\n\n/** Convert one recording, and return how many diagnostics it raised. */\nasync function convertOne(\n input: string,\n destination: string,\n options: Record<string, unknown> & {\n quiet: boolean;\n asJson: boolean;\n showProgress: boolean;\n toStdout: boolean;\n jsonIndent: number | null;\n batch?: boolean;\n },\n emit: Emit = writeThrough,\n): Promise<number> {\n const { quiet, asJson, showProgress, toStdout } = options;\n let lastTick = 0;\n\n /*\n Interrupting a conversion leaves a CSV that stops mid-recording but is still\n perfectly well-formed, so nothing about the file itself reveals that half the\n data is missing. Saying so on the way out is the whole point of a tool that\n claims it will not go quiet when something is wrong.\n */\n /*\n Whether the destination was already there before this run touched it.\n\n Read once, up front, because at interrupt time it is the difference between three\n different true statements and there is no other way to tell them apart. A directory that\n exists now and did not exist then was created by this conversion; one that existed then\n holds files this conversion may never have reached.\n */\n const destinationExistedBefore = toStdout ? false : existsSync(destination);\n\n /*\n Take the progress meter off the line before anything else is printed on it.\n\n The meter writes `\\r converting\\u2026 47%` and leaves the cursor after the percentage, so\n whatever is written next continues that line. Success cleared it and an interrupt cleared\n it; a conversion that *failed* did not, and the error landed on the end of the meter:\n\n converting\\u2026 96%error: Expected 317440 bytes of data at record 1638 but only 0 ...\n\n Which is the one thing this output is shaped to avoid. Every release since 0.7.1 has kept\n `error: ` and `warning: ` at the start of a line so a batch's stderr can be grepped for\n them, and here the prefix sat mid-line, where `grep \\'^error:\\'` over a failed run finds\n nothing at all. Only reachable on a terminal, which is why nothing caught it: the meter is\n off whenever stderr is not a TTY, and a captured stderr never is.\n */\n const clearProgress = (): void => {\n if (showProgress) process.stderr.write('\\r\\u001b[K');\n };\n\n const onInterrupt = (signal: NodeJS.Signals): void => {\n clearProgress();\n /*\n Say which of the three happened, rather than the middle one every time.\n\n --stdout writes no directory, so there is none to warn about. Naming one anyway\n pointed at a path that was never created — the same \"files that were never written\"\n that 0.2.30 removed from this path's error message. The directory case had the same\n defect one step further in: `convert()` hashes the input under --checksum and scans\n the whole annotation channel for record start times *before* it claims the directory,\n so a Ctrl-C in the first second of a large EDF+ printed \"Files already written to\n \"oa\" are incomplete and should not be used\" about a directory that `ls` then reported\n did not exist. Nothing had been written, and the advice was to distrust nothing.\n */\n const wrote = !toStdout && existsSync(destination);\n process.stderr.write(\n `\\ninterrupted (${signal}): the conversion stopped part way through.\\n` +\n (toStdout\n ? detail('The CSV on stdout stops mid-recording and should not be used.')\n : !wrote\n ? detail(`Nothing was written: \"${printable(destination)}\" was never created.`)\n : destinationExistedBefore\n ? detail(\n `\"${printable(destination)}\" was already there, so what is in it may be ` +\n `this run's incomplete output.`,\n )\n : detail(\n `Files already written to \"${printable(destination)}\" are incomplete and ` +\n `should not be used.`,\n )),\n );\n // 128 + signal number, the conventional exit status for dying to a signal.\n process.exit(signal === 'SIGINT' ? 130 : 143);\n };\n process.once('SIGINT', onInterrupt);\n process.once('SIGTERM', onInterrupt);\n\n try {\n const result = await convert(input, {\n ...options,\n onProgress: showProgress\n ? (progress): void => {\n const now = Date.now();\n if (now - lastTick < 100) return;\n lastTick = now;\n const percent =\n progress.recordsTotal === 0\n ? 100\n : Math.floor((progress.recordsDone / progress.recordsTotal) * 100);\n process.stderr.write(`\\r converting… ${percent}%`);\n }\n : undefined,\n });\n\n clearProgress();\n\n if (result.diagnostics.length > 0 && !asJson) {\n /*\n Named when nothing else is naming it.\n\n A batch prints a `[n/m] <path>` header before each recording, and that header is what\n pairs a warning with the file it came from. `--quiet` suppresses it — it is the\n summary line it is documented to suppress, and it took the attribution with it. Two\n recordings, two warnings, and no way to tell which raised which, while `error:` lines\n in the same run stay named because 0.4.20 prefixes those separately.\n\n Same fix 0.5.34 made for `--info` over a folder, which has no header to lose.\n */\n const where = options.batch === true && quiet ? `${printable(input)}: ` : '';\n /*\n The trailing blank line separates these warnings from the summary underneath them,\n so it belongs to the summary and goes when the summary does.\n\n `--quiet` printed it anyway: one stray blank line per recording, in the mode whose\n entire purpose is to print less. On a batch of five hundred that is five hundred of\n them in the log, and on a single recording it left `--quiet --strict` showing two\n blank lines between the warning and the \"--strict:\" line where the ordinary run\n shows one.\n */\n emit(\n 'err',\n `${formatDiagnostics(result.diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n${quiet ? '' : '\\n'}`,\n );\n }\n if (asJson) {\n // One object per line, so a batch is JSON Lines — `jq` reads a record at a time\n // rather than waiting for the whole run to finish.\n emit('out', `${summaryJson(result, options.jsonIndent as number | null)}\\n`);\n } else if (!quiet) {\n // With --stdout there is no directory to summarise, and the row count is the only\n // thing worth saying — on stderr, so the CSV on stdout stays clean.\n if (toStdout) {\n const rows = result.files[0]?.rows ?? 0;\n /*\n A reader that closed the pipe did not receive a conversion, so it does not get a\n conversion's summary. `edf2csv rec.edf --stdout | head -1` announced \"Wrote 52,507\n rows to stdout\" — a number that is neither the recording's 102,400 nor the one row\n head took, but however many had been formatted before the close was noticed.\n\n Whether the *conversion* stopped early is a separate question from whether the\n reader did, and 0.5.12 answered the first with the second. A 10,000-row recording\n whose CSV outruns the pipe buffer but fits one flush is written in full and only\n then meets the closed pipe: every row formatted, every row handed over, and the\n summary said \"The recording was not converted in full.\" The estimate's row count is\n exact, so the two cases can be told apart and told apart honestly — what reached\n the reader is not knowable from this side either way.\n */\n const expected = result.plan.estimate.rows;\n // Singular at one, like every other count. A window narrow enough to select a single\n // sample is an ordinary thing to pipe, and this said \"Wrote 1 rows to stdout.\" — the\n // third message in this family, after 0.5.74 and 0.5.78, each found because the sweep\n // that checks for it was narrower than the set of modes that print a count.\n const written = `${rows.toLocaleString('en-US')} ${rows === 1 ? 'row' : 'rows'}`;\n emit(\n 'err',\n !result.readerHungUp\n ? `Wrote ${written} to stdout.\\n`\n : rows < expected\n ? `Stopped: the reader closed the pipe after ${rows.toLocaleString('en-US')} of ` +\n `${expected.toLocaleString('en-US')} ${expected === 1 ? 'row' : 'rows'} had been ` +\n `written. The recording was ` +\n `not converted in full.\\n`\n : `Wrote ${written} to stdout, but the reader closed ` +\n `the pipe before the end, so not all of them reached it.\\n`,\n );\n } else {\n emit('err', `${formatSummary(result)}\\n`);\n }\n }\n return result.diagnostics.length;\n } catch (error) {\n // The meter is still on the line when a conversion throws, and the caller's `error: `\n // is the next thing written.\n clearProgress();\n throw error;\n } finally {\n process.off('SIGINT', onInterrupt);\n process.off('SIGTERM', onInterrupt);\n }\n}\n\n/** A recording to convert, and the path its output should be named after. */\ninterface Input {\n /** Where the recording is. */\n path: string;\n /**\n * What to call its output, relative to `--out`.\n *\n * For a file named on the command line this is just its own name. For one found by\n * expanding a directory it keeps the position it had inside that directory, so a study\n * laid out as one folder per night comes out the same shape — and, more to the point, so\n * that fifty recordings all named `rec.edf` do not all claim `<out>/rec`.\n */\n name: string;\n /**\n * Whether this recording was found by expanding a directory rather than named directly.\n *\n * `--out` means two different things — the output directory itself, or a parent to put one\n * directory per recording inside — and this is what decides which. See `destinationsFor`.\n */\n fromDirectory: boolean;\n}\n\n/**\n * Recordings to convert, with directories expanded to what is inside them.\n *\n * A directory yields every `.edf` and `.bdf` beneath it, at any depth. Recordings arrive\n * organised into folders, and a shell has no tidy way to reach them — which is why the\n * recipes here carried a `find` incantation to do it. Passing the folder is the obvious\n * thing to try, and it used to fail with \"is a directory, not an EDF file\".\n */\nasync function expandInputs(\n positionals: readonly string[],\n): Promise<{ inputs: Input[]; unreadable: string[]; namedDirectory: boolean }> {\n const found: Input[] = [];\n const unreadable: string[] = [];\n /*\n Whether a directory was NAMED, which is not the same as whether one yielded anything.\n\n `fromDirectory` on the inputs answers the second question, and using it for the first put\n the 0.4.20 defect back one step along: `edf2csv study named.edf --out csv` wrote\n csv/named/ while the study held recordings and csv/signals.csv once it held none, because\n the surviving input then looked like a lone file. Whether some unrelated folder happens\n to contain anything decided where a different recording's output went.\n */\n let namedDirectory = false;\n for (const given of positionals) {\n const info = await stat(given).catch(() => null);\n if (info === null || !info.isDirectory()) {\n // Anything that is not a directory is passed through untouched, so a file that does\n // not exist still reports itself rather than vanishing from the list.\n found.push({ path: given, name: path.basename(given), fromDirectory: false });\n continue;\n }\n namedDirectory = true;\n const walked = await walk(given);\n unreadable.push(...walked.unreadable);\n for (const file of walked.files) {\n found.push({ path: file, name: path.relative(given, file), fromDirectory: true });\n }\n }\n /*\n One recording, however many ways it was named.\n\n The walk already does this for links inside a folder, and the two halves disagreed:\n `edf2csv study` converted a recording reached twice once, while `edf2csv a.edf a.edf`\n refused the whole run for the collision it would cause. A shell makes the second easy\n to produce by accident — `edf2csv *.edf recording.edf` — and there is nothing ambiguous\n about it: it is one recording, and converting it once is what was meant.\n\n A path that does not resolve keeps its own identity so that a file which is not there\n still reports itself rather than being folded into another entry.\n\n Which of the names survives is decided by the names themselves, not by the order they\n turned up in. Keeping the first arrival meant the output directory was named by argument\n order — `edf2csv data/one.edf data/alias.edf` wrote out/one and the same two swapped\n wrote out/alias — and a shell orders a glob however it likes. Inside a folder it was the\n order `readdir` returned, which differs between filesystems, so copying a study to\n another machine could rename its output. A recording that is not a link is preferred over\n a link to it, since that is the name the recording actually has; two of a kind are\n settled by the path that sorts first.\n */\n const identified: { entry: Input; identity: string; link: boolean }[] = [];\n for (const entry of found) {\n const identity = await realpath(entry.path).catch(() => `?${path.resolve(entry.path)}`);\n const own = await lstat(entry.path).catch(() => null);\n identified.push({ entry, identity, link: own?.isSymbolicLink() ?? false });\n }\n\n const byIdentity = new Map<string, Input>();\n const winners = new Map<string, (typeof identified)[number]>();\n for (const candidate of identified) {\n const held = winners.get(candidate.identity);\n if (held !== undefined && !outnames(candidate, held)) continue;\n winners.set(candidate.identity, candidate);\n byIdentity.set(candidate.identity, candidate.entry);\n }\n\n // A directory hands its entries back in whatever order the filesystem stored them.\n const unique = [...byIdentity.values()];\n unique.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n\n /*\n One unreadable path, however many ways it was named — the same rule the recordings above\n get, which this did not.\n\n Each named directory is walked separately and its findings appended, so a folder given\n twice, given relatively and absolutely, or given alongside a link to it, reported every\n path it could not read once per name. `edf2csv study study` on a study holding one locked\n sub-directory printed the error twice and closed with \"Converted 1 of 1 recordings; 2\n paths could not be read\" — one path, counted two — while the recordings beside it were\n correctly converted once. The count is the part that matters: it is what tells someone how\n much of their study was not looked at.\n\n Identity, not spelling, for the reason 0.5.64 gives: `study/locked` and `alias/locked` are\n one directory. `realpathSync` resolves it even when the directory itself cannot be opened,\n since that needs search permission on the parent rather than on the target. A path that\n cannot be resolved at all keeps its own identity, so a name that is simply not there still\n reports itself.\n */\n const seen = new Set<string>();\n const distinct: string[] = [];\n for (const entry of unreadable) {\n let identity: string;\n try {\n identity = realpathSync(entry);\n } catch {\n identity = `?${path.resolve(entry)}`;\n }\n if (seen.has(identity)) continue;\n seen.add(identity);\n // The first spelling is kept, which is the one the caller typed first.\n distinct.push(entry);\n }\n\n return { inputs: unique, unreadable: distinct, namedDirectory };\n}\n\n/**\n * Which of two names for one recording the output should be called after.\n *\n * A name the recording actually has beats a link pointing at it, and two of a kind are\n * settled by sort order. Both are properties of the names, so the answer does not move when\n * a shell expands a glob differently or a filesystem enumerates a folder in another order.\n */\nfunction outnames(\n candidate: { entry: Input; link: boolean },\n held: { entry: Input; link: boolean },\n): boolean {\n if (candidate.link !== held.link) return !candidate.link;\n /*\n Resolved, not as typed. `study/night-01/rec.edf` and `./study/night-01/rec.edf` are the\n same file spelled two ways, and comparing the raw strings made them two different names —\n so this returned here, on lexicographic order of the spelling, and never reached the depth\n rule below that exists for exactly this case.\n\n What that cost is not cosmetic. `edf2csv study study/night-01/rec.edf` converted both\n recordings and exited 0; the identical request written `edf2csv study\n ./study/night-01/rec.edf` was refused with \"would both be converted into \"out/rec\", so one\n would overwrite the other\", exit 2, nothing written. Same files, same folder, same\n intention — and a leading `./` decided whether the run happened. Without the sibling that\n collides it was quieter and no better: one spelling wrote `out/night-01/rec` and the other\n `out/rec`, so a script that started passing absolute paths moved its output.\n\n Through `realpathSync`, not `path.resolve`, because a lexical resolve is not enough: on\n macOS `$TMPDIR` sits under `/var`, which is a link to `/private/var`, so a folder walked\n from the name the caller gave and a recording named relative to the process's own\n directory come out with different absolute prefixes for the same file. Falls back to the\n lexical form for a path that cannot be resolved, which is the same answer as before.\n\n Following links here is safe: a link and its target reach this as two genuinely different\n names, and the rule above has already preferred the real one, so the only pairs left with\n one identity are two spellings or two links — and both should fall through to the depth\n rule below rather than be settled by how they were typed.\n */\n const spelled = (entry: Input): string => {\n try {\n return realpathSync(entry.path);\n } catch {\n return path.resolve(entry.path);\n }\n };\n if (spelled(candidate.entry) !== spelled(held.entry)) {\n return spelled(candidate.entry) < spelled(held.entry);\n }\n /*\n One recording, one path, two names — reached directly and through a named folder.\n\n `edf2csv study study/night-01/rec.edf` and the same two swapped are the same request, and\n they disagreed: the folder gives the recording its position inside the folder\n (`night-01/rec`), a direct mention gives it its bare name (`rec`), and whichever spelling\n the loop met first won. So argument order decided the output directory's name, and, once\n a second `study/rec.edf` was in play, decided whether the run happened at all — the bare\n name collides with it and the run is refused, exit 2, while the other order converts both.\n\n The nested name wins. It is what the folder promised — \"the layout is kept: recordings in\n sub-folders come out in sub-folders\" — and it is the one that does not collide, since\n collapsing a recording to its bare name is what puts it on top of a sibling.\n */\n const depth = (entry: Input): number => entry.name.split(path.sep).length;\n if (depth(candidate.entry) !== depth(held.entry)) return depth(candidate.entry) > depth(held.entry);\n return candidate.entry.name < held.entry.name;\n}\n\n/**\n * Every recording under a directory, following symbolic links.\n *\n * Written by hand rather than with a recursive `readdir` because that reports a symlink as\n * a symlink and never as a file, so a linked recording was skipped without a word — and the\n * closing \"converted 3 of 3\" then described the three it had noticed rather than what was in\n * the folder. Data organised by linking recordings into a working directory is ordinary, and\n * quietly converting fewer files than were asked for is the failure this tool exists to\n * avoid. Naming the same link on the command line always worked, which made the omission\n * harder to notice rather than easier.\n *\n * Following links means they can form a cycle, so directories are recorded by their resolved\n * identity and visited once. A link to a file already reached another way is likewise\n * converted once.\n */\nasync function walk(root: string): Promise<{ files: string[]; unreadable: string[] }> {\n const files: string[] = [];\n const unreadable: string[] = [];\n const seen = new Set<string>();\n /*\n A queue, taken from the front, and each directory's children entered in a settled order.\n\n This was a stack popped from the back, so which of two names for one directory was\n visited first came down to the order `readdir` happened to return them — and the loser\n was then skipped as already seen, taking its name out of the run with it. A folder\n holding `aaa-real/` beside `zzz-alias -> aaa-real` converted into `<out>/zzz-alias/`: the\n link's name, chosen by a hash order that differs between filesystems. 0.4.29 settled this\n for two names of one *file* and left the directory above it deciding by accident.\n\n Breadth first, and each directory's sub-directories entered with real names before links\n and alphabetically within each, so the name that survives is a property of the tree: the\n shallowest, then the one that is not a link, then the first in sort order.\n */\n const queue = [root];\n let next = 0;\n\n while (next < queue.length) {\n const directory = queue[next++] as string;\n const real = await realpath(directory).catch(() => directory);\n if (seen.has(real)) continue;\n seen.add(real);\n\n /*\n A directory that cannot be listed is reported, not stepped over.\n\n Skipping it in silence meant a folder holding three recordings, one of them inside a\n sub-directory without read permission, converted two and said \"Converted 2 of 2\n recordings\" — a total that agreed with itself and with nothing else. That is the same\n failure 0.4.4 fixed for symbolic links, arriving by a different route: converting fewer\n recordings than were asked for and reporting success.\n */\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n unreadable.push(directory);\n continue;\n }\n\n // A real name before a link to the same thing, then alphabetically. The dirent describes\n // the entry itself rather than its target, which is exactly the question here.\n const ordered = [...entries].sort((a, b) => {\n const link = Number(a.isSymbolicLink()) - Number(b.isSymbolicLink());\n return link !== 0 ? link : a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n\n for (const entry of ordered) {\n const full = path.join(directory, entry.name);\n // stat, not the dirent: a dirent describes the link, and what matters is its target.\n const info = await stat(full).catch(() => null);\n if (info === null) {\n /*\n Anything that cannot be inspected is reported, whatever it is called.\n\n This used to report only names ending in `.edf` or `.bdf`, on the reasoning that a\n broken link to something else is nobody's business. A directory carries no such\n name. A study kept as one folder per night, with one night linked to an external\n drive, converted the nights that were mounted and said nothing about the one that\n was not — and because losing that input left a single recording, `--out` stopped\n meaning \"a parent to fill\" and started meaning \"the directory to write\", so the\n survivor landed somewhere else as well. Whether a drive happened to be mounted\n changed both what was converted and where it went, in silence, exit 0.\n\n The walk cannot know what was behind a link it cannot follow, which is exactly the\n reason to say so rather than to guess.\n */\n unreadable.push(full);\n continue;\n }\n if (info.isDirectory()) {\n queue.push(full);\n } else if (info.isFile() && /\\.(edf|bdf)$/iu.test(entry.name)) {\n // Duplicates are collapsed once for the whole list in expandInputs; this only has\n // to find them. The `seen` set here is for directory cycles.\n files.push(full);\n }\n }\n }\n return { files, unreadable };\n}\n\n/**\n * Where each recording's output goes.\n *\n * Naming one recording, `--out` is the output directory itself, which is what it has always\n * meant. Naming a folder, or several recordings, it is a parent and each recording gets its\n * own directory inside it, because writing several recordings into one directory would have\n * them overwrite each other's `signals.csv` — the one thing a batch must not do quietly.\n *\n * With no `--out` at all, every recording converts beside itself exactly as it would have\n * done alone, so a glob behaves like the shell loop it replaces.\n *\n * What a folder means is decided by the folder, not by how much is in it. Counting the\n * recordings instead meant `edf2csv study --out csv` wrote `csv/signals.csv` while the study\n * held one night and `csv/night-01/rec/signals.csv` once it held two: adding a recording\n * moved the output of a recording that had not changed. The same count made the destination\n * depend on things no one had touched either — a night on an unmounted drive, a\n * sub-directory that could not be read — so where the data landed turned on the state of the\n * machine rather than on the command.\n */\nfunction destinationsFor(\n inputs: readonly Input[],\n out: string | undefined,\n /** True when a folder was named, or more than one recording was. See `batch` in `main`. */\n batch: boolean,\n): string[] {\n if (out === undefined) return inputs.map((input) => defaultOutputDir(input.path));\n if (!batch) return [out];\n return inputs.map((input) => path.join(out, stemOf(input.name)));\n}\n\n/** A name without its extension, keeping a leading dot: `.hidden.edf` -> `.hidden`. */\nfunction stemOf(name: string): string {\n const dot = name.lastIndexOf('.');\n const slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\\\'));\n return dot > slash + 1 ? name.slice(0, dot) : name;\n}\n\n/**\n * Refuse a batch in which two recordings would land in the same directory.\n *\n * Two files with the same name in different folders — `night-1/rec.edf` and\n * `night-2/rec.edf`, which is how recordings usually get organised — both resolve to\n * `<out>/rec`. Converting them in turn would leave one recording's data sitting under the\n * other's name with nothing to show it had happened, so the run stops before writing\n * anything at all.\n */\nfunction assertDistinct(inputs: readonly string[], destinations: readonly string[]): void {\n /*\n Two names that differ only in case are one directory on a filesystem that does not\n distinguish them, which is the default on macOS and the rule on Windows.\n\n `a/REC.edf` and `b/rec.edf` produce `<out>/REC` and `<out>/rec`. Compared exactly those\n are different, so both went through, and with --force the second conversion wrote into\n the directory the first had made: one directory holding one recording's `signals.csv`\n beside the other's `signals_256hz.csv`, under a single `metadata.json` naming only one\n of them. The run reported \"Converted 2 of 2 recordings\" and exited 0. A directory whose\n provenance file describes a recording other than the data beside it is the one outcome\n this tool exists to prevent.\n\n The comparison follows the platform rather than being applied everywhere, so a\n case-sensitive filesystem — where those really are two directories — keeps converting\n both. A case-sensitive volume on macOS is the exception it gets wrong, and it gets it\n wrong in the safe direction: a refusal that names both recordings, not a silent merge.\n */\n const foldsCase = process.platform === 'darwin' || process.platform === 'win32';\n /*\n macOS filesystems fold Unicode normalisation as well as case.\n\n HFS+ and APFS compare names in a normalised form, so `café` written as e + U+0301 and\n `café` written as U+00E9 are one directory — while remaining two different JavaScript\n strings, which is all this guard was comparing. Two recordings whose stems differ only\n that way therefore both passed the check and both converted into the same place:\n\n study/café.edf (NFC) -> csv/café/signals.csv\n study/café.bdf (NFD) -> csv/café/signals_256hz.csv, _128hz, _1hz\n\n one directory holding both, under a single metadata.json naming one of them, reported as\n \"Converted 2 of 2 recordings\" and exit 0 under --force. Without --force the second run\n happened to hit \"already exists\", which is the accidental save rather than the check\n doing its job — and it named the wrong problem.\n\n Not folded on Linux, where the two names are genuinely two directories and refusing them\n would be inventing a collision that does not exist. Windows preserves normalisation too,\n so only darwin. This is the same platform-shaped assumption the case fold above already\n makes, and it has the same limit: a network or removable volume that normalises while the\n running platform does not is not covered by either.\n */\n const identity = (destination: string): string => {\n const resolved = path.resolve(destination);\n if (!foldsCase) return resolved;\n const folded = resolved.toLowerCase();\n return process.platform === 'darwin' ? folded.normalize('NFC') : folded;\n };\n\n const claimed = new Map<string, string>();\n for (const [index, destination] of destinations.entries()) {\n const key = identity(destination);\n const first = claimed.get(key);\n if (first !== undefined) {\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" and \"${printable(first)}\" would both be converted into \"${printable(destination)}\", ` +\n `so one would overwrite the other.\\n` +\n `Convert them separately, or rename one of them.`,\n );\n }\n claimed.set(key, inputs[index] as string);\n }\n\n /*\n And refuse one destination sitting inside another.\n\n A recording named `rec.edf` beside a folder named `rec` is enough: their outputs are\n `<out>/rec` and `<out>/rec/inner`, which are not equal, so the check above let both\n through. What happened next depended on which conversion got there first. Each creates\n its own directory with a single non-recursive mkdir but its parents recursively, so\n whichever started second either claimed a directory the other had already made as a\n parent — failing with \"already exists\" — or did not. Five runs in twenty failed that way\n under --jobs 2, converting one recording of two; the same command succeeded the other\n fifteen times.\n\n Sorting first puts an ancestor next to its descendant: anything sorting between them\n shares the same prefix, and would be caught as its own adjacent pair.\n */\n /*\n Each destination is checked against its own ancestors, by name.\n\n The first version of this sorted the resolved paths and compared neighbours, on the\n reasoning that an ancestor and its descendant end up adjacent. They do not: the\n separator is not the lowest character, so any sibling whose name begins with one of the\n thirteen printable characters below '/' lands between them. With `rec.edf`, `rec!x.edf`\n and `rec/inner.edf` in one folder, '!' sorts between `out/rec` and `out/rec/inner` and\n the pair was never compared — three recordings converted, one of them inside another's\n directory, reported as \"Converted 3 of 3\".\n\n Walking up from each destination has no such gap. Output trees are shallow, so this is a\n handful of string lookups per recording.\n */\n const byDestination = new Map<string, number>();\n for (const [index, destination] of destinations.entries()) {\n byDestination.set(identity(destination), index);\n }\n\n for (const [index, destination] of destinations.entries()) {\n let ancestor = path.dirname(path.resolve(destination));\n for (let parent = ''; ancestor !== parent; ancestor = path.dirname(ancestor)) {\n parent = ancestor;\n const owner = byDestination.get(identity(ancestor));\n if (owner === undefined || owner === index) continue;\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" would be converted into \"${printable(destination)}\", which is inside ` +\n `\"${destinations[owner]}\" — where \"${inputs[owner]}\" is converted.\\n` +\n `One recording's output cannot sit inside another's. Convert them separately, or ` +\n `rename one of them.`,\n );\n }\n }\n}\n\n/**\n * The exit code for a run in which several files failed for different reasons.\n *\n * 2 means \"you invoked it wrong\" and 1 means \"something went wrong with a file\". When both\n * happened, 1 is the honest answer: the invocation cannot be the whole story once a file has\n * genuinely failed. With a single input there is only ever one code, so its exit status is\n * exactly what it always was.\n */\nfunction worstOf(codes: readonly number[]): number {\n /*\n Anything that is not \"the command line is the problem\" is a failure of the run.\n\n This tested for `EXIT_ERROR` and fell through to `EXIT_USAGE` for everything else, on the\n assumption that a child exits 1 or 2. A child killed by a signal exits 130 or 143 — its\n own interrupt handler does that — so a `--jobs` worker stopped by SIGTERM made the batch\n exit 2, which cli-reference's table defines as \"The command line is the problem\" and\n warnings-and-errors as \"The command was invoked incorrectly, or asked for something the\n recording can't provide\". The command was fine; something killed a worker. The\n out-of-memory killer and a scheduler's time limit both arrive exactly this way, and the\n serial path reports the same event as the failure it is.\n\n Asking the question the other way round is also the honest one: 2 is the narrow claim,\n so it should be the one that has to be earned by every code in the list.\n */\n return codes.some((code) => code !== EXIT_USAGE) ? EXIT_ERROR : EXIT_USAGE;\n}\n\n/**\n * Convert one recording in a separate process, and collect everything it printed.\n *\n * The arguments are rebuilt from the parsed options rather than sliced out of argv, so the\n * child receives exactly the flags this run was given and nothing that only makes sense to\n * the parent: not the other recordings, not --jobs, and not --info or --stdout, neither of\n * which reaches this path.\n */\nasync function convertInChild(\n input: string,\n destination: string,\n values: Record<string, unknown>,\n running: Map<ChildProcess, string>,\n): Promise<{\n code: number;\n out: string;\n err: string;\n report: { converted: number; warnings: number } | null;\n}> {\n /*\n The recording goes last, behind `--`.\n\n As the first argument it was parsed as an option whenever its path began with a dash,\n which `path.join` produces from a folder given as `.` — `./-lead.edf` normalises to\n `-lead.edf`. The child then failed on a file the parent had converted happily, so the\n same command converted two recordings serially and one under --jobs.\n */\n /*\n Option values go in the `--flag=value` form, never as two arguments.\n\n Split across two arguments, a value beginning with a dash is another option as far as the\n child's parser is concerned: `--out ./-nightly` reached it as `--out` followed by\n `-nightly`, and the child died on \"Option '--out' argument is ambiguous\" while the serial\n path converted the same command without complaint. A leading dash is not exotic —\n `path.join` produces one from a folder given as `.`, and directories get named after\n dates and flags often enough. Same failure 0.4.19 fixed for the recording's own path, on\n everything that carries a value rather than on the positional.\n */\n const args = [`--out=${destination}`];\n /*\n --strict is deliberately absent: it is a verdict on the whole run, and a child converting\n one recording is not the whole run. Passing it down made each child announce \"--strict: 1\n warning raised, so this run is reported as a failure\" about its own single file, and exit\n 1 for it, which the parent then counted as a conversion that had not happened. The parent\n applies it once, from the counts the children report.\n */\n for (const flag of ['annotations-only', 'checksum', 'gzip', 'bom', 'force', 'quiet', 'json']) {\n if (values[flag] === true) args.push(`--${flag}`);\n }\n for (const flag of ['start', 'duration', 'end', 'decimals', 'layout']) {\n if (typeof values[flag] === 'string') args.push(`--${flag}=${values[flag] as string}`);\n }\n // --channels is repeatable, and each term is passed as given so that a label containing a\n // comma survives: joining them back into one list would split it in the child.\n const channels = values['channels'];\n if (channels !== undefined) {\n for (const term of Array.isArray(channels) ? (channels as string[]) : [String(channels)]) {\n args.push(`--channels=${term}`);\n }\n }\n args.push('--', input);\n\n return new Promise((resolve) => {\n const child = fork(fileURLToPath(import.meta.url), args, {\n stdio: ['ignore', 'pipe', 'pipe', 'ipc'],\n });\n running.set(child, destination);\n let out = '';\n let err = '';\n let report: { converted: number; warnings: number } | null = null;\n child.on('message', (message: unknown) => {\n const payload = (message as { edf2csv?: { converted: number; warnings: number } })?.edf2csv;\n if (payload) report = payload;\n });\n child.stdout?.setEncoding('utf8').on('data', (chunk: string) => {\n out += chunk;\n });\n child.stderr?.setEncoding('utf8').on('data', (chunk: string) => {\n err += chunk;\n });\n child.on('error', (error) => {\n running.delete(child);\n resolve({ code: EXIT_ERROR, out, err: `${err}error: ${input}: ${error.message}\\n`, report });\n });\n child.on('close', (code, signal) => {\n running.delete(child);\n /*\n A child that was killed says nothing on its way out, so the parent has to.\n\n `code` is null when a process dies by signal, which left the parent with `code ??\n EXIT_ERROR` — a failure with an empty `err`, so the run printed nothing but \"Converted\n 1 of 2 recordings; 1 failed.\" and stopped. Nothing said which recording, nothing said\n why, and nothing said that its directory held a 194 MB signals.csv cut off mid-row\n with no channels.csv beside it. That file looks exactly like a finished one to\n anything that opens it.\n\n The out-of-memory killer, a job scheduler's time limit and `kill` all arrive this way.\n A run stopped from the keyboard is reported by the interrupt handler instead, which\n names every directory at once rather than one line per child.\n */\n if (signal !== null && !stopping) {\n err +=\n `error: stopped by ${signal} before it finished.\\n` +\n detail(`Incomplete, and should not be used: ${printable(destination)}`);\n }\n resolve({ code: code ?? EXIT_ERROR, out, err, report });\n });\n });\n}\n\n/**\n * Put the recording's name into the lines a child produced.\n *\n * Errors always: a failure has to say which recording failed, whatever else is on screen.\n * Warnings only when `alsoWarnings` — under --quiet, where the `[n/m]` header that would\n * otherwise carry the attribution is not printed. See the call site.\n */\nfunction named(text: string, input: string, alsoWarnings = false): string {\n // A function, not a string: `$&`, `$\\'`, `` $` `` and `$1` in a replacement string are\n // patterns, and a file may legitimately be called any of them. `bad$&name.edf` re-injected\n // the text it had just matched and reported itself as `baderror: name.edf`.\n const heads = alsoWarnings ? /^(error|warning|note): /gmu : /^error: /gmu;\n return text.replace(heads, (head) => `${head}${printable(input)}: `);\n}\n\n/** Re-render a child's pretty-printed summary onto one line, leaving anything else alone. */\nfunction compactJson(text: string): string {\n const trimmed = text.trim();\n if (trimmed === '') return '';\n try {\n return `${JSON.stringify(JSON.parse(trimmed))}\\n`;\n } catch {\n // Not the document expected; passing it through unchanged beats losing it.\n return text;\n }\n}\n\n/**\n * Where a conversion's output goes.\n *\n * Serial runs write straight through, which is what they have always done. Running several\n * conversions at once needs the alternative: each one's lines are collected and released in\n * a block when it finishes, so two recordings finishing together cannot interleave a summary\n * with a warning belonging to the other file.\n */\ntype Emit = (stream: 'out' | 'err', text: string) => void;\n\nconst writeThrough: Emit = (stream, text) => {\n (stream === 'out' ? process.stdout : process.stderr).write(text);\n};\n\n/** Collects output so it can be released in one piece when a conversion finishes. */\nfunction buffered(): { emit: Emit; flush: () => void } {\n const parts: [('out' | 'err'), string][] = [];\n return {\n emit: (stream, text) => parts.push([stream, text]),\n flush: () => {\n for (const [stream, text] of parts) writeThrough(stream, text);\n },\n };\n}\n\n/**\n * Report an error the way this command always has, and give the exit code it implies.\n *\n * `input` names the recording while a batch is running: several failures otherwise arrive\n * as a stack of messages with nothing saying which file each belongs to.\n */\nfunction reportError(error: unknown, input?: string, emit: Emit = writeThrough): number {\n const where = input === undefined ? '' : `${printable(input)}: `;\n if (error instanceof EdfError || error instanceof ConversionError) {\n emit('err', `error: ${where}${printableLines(error.message, ' ')}\\n`);\n /*\n Wrapped, for the same reason the hint under a warning is — and because on this file\n it is frequently the same sentence.\n\n A mixed-rate recording refused by --stdout and the same recording described by --info\n both end in \"Narrow it to one rate with --channels, write --layout long ...\". One\n arrived as a ConversionError hint and printed on a single 130-column line; the other\n arrived as a Diagnostic hint and wrapped at 80 from 0.7.1. Identical advice about an\n identical file, laid out two different ways depending on whether the tool went on to\n convert it.\n */\n /*\n Escaped, which the message above it has been since 0.5.67 and this line never was.\n\n A hint is usually a fixed sentence, which is why it looked like text nobody supplies.\n One is not: a read that fails part way through a conversion reports \"What was written\n to <outputDir> before it failed is incomplete\", and `--out` takes whatever path the\n caller gives it. A directory may be named with an ESC byte on every platform this\n runs on, so `--out $'out\\x1b[31mred'` on a recording that shrinks mid-read put a live\n colour change on stderr — under an `error:` line that had been escaped correctly, two\n lines above.\n */\n if (error.hint) emit('err', `${wrap(printable(error.hint), ' ')}\\n`);\n // A request the tool cannot carry out is the command line's problem, not the file's,\n // whatever layer noticed it. See USAGE_ERROR_CODES.\n return error instanceof ConversionError && USAGE_ERROR_CODES.has(error.code)\n ? EXIT_USAGE\n : EXIT_ERROR;\n }\n if (\n error instanceof ChannelSelectionError ||\n error instanceof TimeRangeError ||\n error instanceof OptionError\n ) {\n emit('err', `error: ${where}${printableLines(error.message, ' ')}\\n`);\n return EXIT_USAGE;\n }\n emit('err', `error: ${where}${message(error, ' ')}\\n`);\n return EXIT_ERROR;\n}\n\n/**\n * How many conversions to run at once.\n *\n * `auto` is one per core, less one, so a long batch does not take the machine over. A batch\n * is the only place this means anything: a single recording is a single conversion however\n * many jobs are asked for.\n */\nfunction parseJobs(raw: unknown, inputs: number): number {\n if (raw === undefined) return 1;\n const text = String(raw).trim();\n if (text === 'auto') return Math.max(1, Math.min(inputs, cpus().length - 1));\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x10`, `1e3` and `999999999999999999999` — the\n last of which is not an integer any more, only the nearest double to one — while refusing\n `4.7`. The message says \"a whole number of 1 or more\" and the refusal of 4.7 is what makes\n a reader believe it, so the others read as the tool silently reinterpreting what they\n typed. `--jobs` is the flag 0.4.2 hardened because accepting `--jobs 0` in silence \"is the\n kind of quiet this tool avoids\".\n\n Surrounding space is trimmed above and is not one of those: ` 4 ` is four to anyone who\n reads it, and a value assembled by a shell out of a file or a `$(...)` routinely carries\n some. What matters is that the refusal quote what was typed rather than what survived the\n trim — see below.\n */\n if (!/^\\d+$/u.test(text)) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n const value = Number(text);\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n return Math.min(value, Math.max(1, inputs));\n}\n\nfunction splitChannels(raw: unknown): string[] | undefined {\n if (raw === undefined) return undefined;\n const list = Array.isArray(raw) ? (raw as string[]) : [String(raw)];\n const terms = list.flatMap((entry) => entry.split(',')).map((t) => t.trim()).filter((t) => t !== '');\n // Returning undefined here would mean \"no --channels given\" and convert everything,\n // which is the opposite of what someone passing an empty list is asking for.\n if (terms.length === 0) {\n throw new OptionError('--channels was given but lists no channel names.');\n }\n return terms;\n}\n\n/**\n * `--start`, `--duration` and `--end`, in seconds.\n *\n * The first two name a position on the recording's own clock, which a file timed from before\n * zero puts below it, so those take a sign. `--duration` is a length and does not.\n */\nfunction optionalTime(raw: unknown, option: string): number | undefined {\n if (raw === undefined) return undefined;\n return parseTimeSpec(String(raw), option, option !== '--duration');\n}\n\n/** `--layout`, which is one of two words and not a guess at what was meant. */\nfunction optionalLayout(raw: unknown): 'wide' | 'long' | undefined {\n if (raw === undefined) return undefined;\n // Trimmed, like every other option's value. This one alone refused ` long`, so a value that\n // came out of a file or a `$(...)` with a newline on it was rejected for a character nobody\n // typed and which the message could not show.\n const text = String(raw).trim();\n if (text === 'wide' || text === 'long') return text;\n throw new OptionError(`--layout must be \"wide\" or \"long\", got \"${quotedValue(raw)}\".`);\n}\n\n/**\n * An option's value as the user typed it, for a message that quotes it back.\n *\n * The point of the quotation marks is to show where the value begins and ends, which matters\n * most for the values that went wrong because of what surrounds them. `--jobs` trimmed first\n * and then quoted the remains, so `--jobs \" x\"` came back as `got \"x\"` and `--jobs \" \"` as\n * `got \"\"` — the second reading as though nothing had been given at all, when what was given\n * is the whole reason it failed. `--start` has quoted the typed value since 0.5.60 and\n * `--decimals` since 0.5.86; this is the same rule, spelled once.\n */\nfunction quotedValue(raw: unknown): string {\n return String(raw);\n}\n\nfunction optionalDecimals(raw: unknown): number | undefined {\n if (raw === undefined) return undefined;\n const text = String(raw).trim();\n // Number('') is 0, which would quietly round every physical value to a whole number.\n if (text === '') {\n throw new OptionError('--decimals needs a number, for example --decimals 3.');\n }\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x3`, `0b11`, `0o5`, `3e0` and `+3`,\n while refusing `3.5` — and the refusal of `3.5` is what makes a reader believe the message\n that says \"a whole number between 0 and 20\". So `--decimals 0o5` wrote five decimals for\n an argument that reads as five to nobody, and `--decimals 0b11` wrote three, in silence,\n exit 0, with a CSV that looks exactly as intended.\n\n The same fix `--jobs` got, and the same one `--channels '#N'` got — where the comment\n records why: \"Every one of them selected a channel and exited 0, so a slip did not fail —\n it quietly converted a different channel than the one asked for, which for this tool is\n the worst way to be wrong.\" A precision is the same kind of quiet.\n */\n const value = Number(text);\n if (!/^\\d+$/u.test(text) || !Number.isInteger(value) || value < 0 || value > 20) {\n throw new OptionError(`--decimals must be a whole number between 0 and 20, got \"${String(raw)}\".`);\n }\n return value;\n}\n\n/*\n Error text is escaped for the same reason --info's table is: a fatal header error quotes\n the channel label that caused it, and that label is free text out of the file. A recording\n declaring a negative sample count under a label containing `\\x1b[2J` cleared the reader's\n screen on the way out.\n*/\n/**\n * A `parseArgs` failure, in this tool's words where they are better than Node's.\n *\n * Node refuses `--out -nightly` with \"Option '--out' argument is ambiguous. Did you forget to\n * specify the option argument for '--out'? To specify an option argument starting with a dash\n * use '--out=-XYZ'.\" The user did not forget anything — they gave a value — and `-XYZ` is a\n * placeholder, where every other message this tool prints quotes what was actually typed and\n * says exactly what to type instead. A destination whose name starts with a dash is not exotic,\n * and neither is a negative `--start` on a recording timed from before zero.\n *\n * 0.4.34 already fixed this message where the tool produced it internally, building child argv\n * for `--jobs` — `--out ./-nightly` reached the child as two arguments and died on it, while the\n * serial path converted the same command. The half a user can hit was left as Node wrote it.\n *\n * An unknown option is reworded too, and for a plainer reason than tone. Node's sentence is\n * \"Unknown option '--chanels'. To specify a positional argument starting with a '-', place it at\n * the end of the command after '--', as in '-- \"--chanels\"' — except the closing quote is not\n * there. Node opens one before `--` and never closes it, so this tool printed an unfinished\n * sentence, without the `error:` prefix every other refusal carries, at the single most common way\n * to get a command wrong. The advice is also for a different mistake than the one almost everyone\n * makes: it explains how to pass a *file* whose name begins with a dash, where the reader has\n * mistyped one of twenty flags.\n *\n * Node's remaining two — a switch given a value, an option missing its value — say something true\n * in words a reader can act on, and replacing them with near-identical sentences would be churn.\n */\n/**\n * A token as it has to be typed for a shell to hand it over unchanged.\n *\n * Every hint in this file that prints a command has to print one that works — the comment on\n * the `--channels` advice in the header parser lists three separate times it did not. These\n * two are the same failure at the command line rather than in a channel label. `--out \"-my\n * nightly\"` was answered with `Write it as one argument instead: --out=-my nightly`, which is\n * two arguments; `--chan\"nels` was answered with `edf2csv -- \"--chan\"nels\"`, whose quotes\n * collapse into something else again.\n *\n * Left bare when a shell would read it as written, so the ordinary `--out=-nightly` reads as\n * it always has. Otherwise single-quoted, the one POSIX form with no escapes inside it: a\n * single quote in the token closes, escapes and reopens.\n */\nfunction survivesBare(text: string): boolean {\n return text !== '' && !/[^\\w@%+=:,./-]/u.test(text);\n}\n\nfunction singleQuoted(text: string): string {\n return `'${text.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction usageMessage(error: unknown, argv: readonly string[]): string {\n const raw = message(error);\n const code = (error as { code?: unknown } | null)?.code;\n if (code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') return unknownOption(raw);\n if (code !== 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE') {\n return raw;\n }\n const ambiguous = /^Option '(-[^']+)' argument is ambiguous/u.exec(raw);\n if (!ambiguous) return raw;\n\n const flag = ambiguous[1] as string;\n // The value is whatever followed the flag. Read from argv rather than from the message,\n // which does not carry it — and if it is not there after all, Node's text is still true.\n const at = argv.indexOf(flag);\n const value = at >= 0 ? argv[at + 1] : undefined;\n if (value === undefined) return raw;\n\n /*\n How the two shapes join, which is not the same and matters.\n\n A long option takes `--out=-nightly`. A short one does NOT take `-o=-nightly`: parseArgs\n reads the equals sign as the first character of the value and hands back \"=-nightly\", so\n that advice would have produced a directory named `=-nightly` without a word — a worse\n failure than the message it replaced, and a silent one. Short options join directly.\n */\n const joined = flag.startsWith('--') ? `${flag}=${value}` : `${flag}${value}`;\n const typeable = survivesBare(joined) ? joined : singleQuoted(joined);\n return printableLines(\n `error: ${flag} was given \"${value}\", which begins with a dash and so reads as another ` +\n `flag rather than as its value.\\n` +\n ` Write it as one argument instead: ${typeable}`,\n );\n}\n\n/** Levenshtein distance, two rows at a time. Twenty names of under twenty characters. */\nfunction editDistance(a: string, b: string): number {\n const row = Array.from({ length: b.length + 1 }, (_unused, i) => i);\n for (let i = 1; i <= a.length; i++) {\n let diagonal = row[0] as number;\n row[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const above = row[j] as number;\n row[j] = Math.min(above + 1, (row[j - 1] as number) + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));\n diagonal = above;\n }\n }\n return row[b.length] as number;\n}\n\n/**\n * The option they probably meant, or null when nothing is close enough to say.\n *\n * Long options only. A stray `-Z` carries one character of evidence, and guessing a word\n * from it would be guessing.\n *\n * Two rules, because typos come in two shapes. An unfinished name is a prefix — `--chan`,\n * `--decim` — and is only taken from three characters up, so `--c` does not become\n * `--channels` or `--checksum` by coin toss. A misspelt one is within a couple of edits,\n * scaled to the name's length so `--jobs` is not reached from three characters away.\n */\nfunction nearestOption(flag: string): string | null {\n if (!flag.startsWith('--')) return null;\n const typed = flag.slice(2).toLowerCase();\n if (typed.length < 2) return null;\n\n const names = Object.keys(OPTIONS);\n if (typed.length >= 3) {\n const prefixed = names.filter((name) => name.startsWith(typed));\n if (prefixed.length === 1) return `--${prefixed[0] as string}`;\n }\n\n let best: string | null = null;\n let score = Infinity;\n let tied = false;\n for (const name of names) {\n const d = editDistance(typed, name);\n if (d < score) {\n score = d;\n best = name;\n tied = false;\n } else if (d === score) {\n tied = true;\n }\n }\n if (best === null || tied) return null;\n return score <= Math.max(1, Math.min(3, Math.floor(best.length / 3))) ? `--${best}` : null;\n}\n\n/** An option this tool does not have, in a finished sentence, naming the likely one. */\nfunction unknownOption(raw: string): string {\n const found = /^Unknown option '([^']+)'/u.exec(raw);\n // If Node ever rewords it, its text is still true; only the polish is lost.\n if (!found) return raw;\n const flag = found[1] as string;\n const near = nearestOption(flag);\n return printableLines(\n `error: There is no ${flag} option.${near === null ? '' : ` Did you mean ${near}?`}\\n` +\n ` If it is the name of a file, pass it after -- instead:\\n` +\n ` edf2csv -- ${survivesBare(flag) ? `\"${flag}\"` : singleQuoted(flag)}`,\n );\n}\n\nfunction message(error: unknown, indent = ''): string {\n // The indent lines continuation up under an \"error: \" prefix. Usage problems print without\n // one, alongside the usage text, so they pass nothing and stay flush left.\n return printableLines(error instanceof Error ? error.message : String(error), indent);\n}\n\n// `worstOf` is exported for the test that pins the exit-code mapping. A batch's verdict is\n// one of the few things a script branches on, and the mapping is easier to check directly\n// than by racing a signal at a worker.\nexport { defaultOutputDir, worstOf };\n\n/**\n * Whether this file was executed rather than imported.\n *\n * npm installs a bin as a symlink (node_modules/.bin/edf2csv -> ../edf2csv/dist/cli.js),\n * and that is the path `npx` runs. In that case process.argv[1] is the symlink while\n * import.meta.url is already resolved to the real file, so comparing the two directly\n * reports \"imported\" and the command silently does nothing. Both sides are resolved\n * through realpath before comparing.\n */\nfunction isMainModule(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n // An unreadable or deleted entry path is not a reason to refuse to run.\n return pathToFileURL(entry).href === import.meta.url;\n }\n}\n\nconst invokedDirectly = isMainModule();\n\nif (invokedDirectly) {\n main(process.argv.slice(2))\n .then((code) => {\n /*\n A write failure already reported is not erased by a run that thought it finished.\n\n The stdout listener above prints `Writing to stdout failed: ...` and sets EXIT_ERROR,\n and assigning `code` over it put that back to 0 — so `--info > desc.txt` onto a full\n filesystem printed the error, left a zero-byte file, and exited 0. It only looked\n right because the stdout audit threw a second error on the way out, whose message was\n about a short write that had not happened.\n\n A closed pipe deliberately sets no code (see ignoreBrokenPipe), so `--info | head`\n still exits 0 through here.\n */\n process.exitCode = code === EXIT_OK ? (process.exitCode ?? EXIT_OK) : code;\n })\n .catch((error: unknown) => {\n process.stderr.write(`error: ${message(error)}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n"]}
@@ -230,11 +230,47 @@ export function newOffsetBudget() {
230
230
  * where that record actually starts.
231
231
  */
232
232
  export function makeTimeFormatter(samplesPerRecord, rate, decimals, budget = newOffsetBudget()) {
233
- const direct = (recordStart, sample) => fixed(recordStart + sample / rate, decimals);
234
- if (!(rate > 0) ||
235
- !Number.isFinite(rate) ||
236
- samplesPerRecord <= 0 ||
237
- samplesPerRecord > budget.remaining) {
233
+ const usable = rate > 0 && Number.isFinite(rate);
234
+ /** The sum, formatted. Right only where the decomposition below does not apply. */
235
+ const summed = (recordStart, sample) => fixed(recordStart + sample / rate, decimals);
236
+ /*
237
+ The same decomposition the table holds, computed on the spot.
238
+
239
+ This used to be the sum, and that made the time column two columns depending on how much
240
+ of the budget above was left. `recordStart + sample / rate` cannot carry the fraction once
241
+ the record start is large: at a start of 1e9 and 30 kHz the exact instant is
242
+ 1000000000.0000333333, and the double nearest the sum prints as 1000000000.00003338 —
243
+ the last two places are the addition's rounding, not the recording's clock.
244
+
245
+ Which of the two a rate group got was decided by the groups ahead of it, since they ask
246
+ from one budget fastest first. So a recording whose fast channel takes the table left its
247
+ slow channel adding doubles, and the same channel asked for on its own got the table:
248
+
249
+ edf2csv far.edf --out whole -> 1000000000.00003338
250
+ edf2csv far.edf --out one --channels slow -> 1000000000.00003333
251
+
252
+ Two files from one recording disagreeing about when a sample was taken, in a tool whose
253
+ documentation says `--channels` selects columns and changes nothing else. The narrowing
254
+ sweep asserts exactly that and could not see it: its fixtures are small enough that the
255
+ budget never runs out, so both conversions took the table.
256
+
257
+ The sum is still the answer where the decomposition has nothing to stand on — a record
258
+ starting on a fraction or before zero, where the whole part and the fraction are not
259
+ separable — and past 1e21, where the whole part stops printing in full.
260
+ */
261
+ const composed = (recordStart, sample) => {
262
+ const text = fixed(sample / rate, decimals);
263
+ const dot = text.indexOf('.');
264
+ const whole = recordStart + (dot === -1 ? Number(text) : Number(text.slice(0, dot)));
265
+ if (whole >= 1e21)
266
+ return summed(recordStart, sample);
267
+ return dot === -1 ? `${whole}` : `${whole}${text.slice(dot)}`;
268
+ };
269
+ /** What every row gets when the table is not there to be read. */
270
+ const direct = (recordStart, sample) => usable && sample >= 0 && Number.isInteger(recordStart) && recordStart >= 0
271
+ ? composed(recordStart, sample)
272
+ : summed(recordStart, sample);
273
+ if (!usable || samplesPerRecord <= 0 || samplesPerRecord > budget.remaining) {
238
274
  return direct;
239
275
  }
240
276
  budget.remaining -= samplesPerRecord;
@@ -278,7 +314,7 @@ export function makeTimeFormatter(samplesPerRecord, rate, decimals, budget = new
278
314
  correct without giving up the cache for the other twenty million.
279
315
  */
280
316
  if (whole >= 1e21)
281
- return direct(recordStart, sample);
317
+ return summed(recordStart, sample);
282
318
  return `${whole}${fractionText[sample]}`;
283
319
  };
284
320
  }
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/format/number.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Number formatting for CSV cells.\n *\n * A one-hour, 23-channel, 256 Hz recording is about 21 million numeric cells, so\n * this is the hottest code in a conversion. Two things keep it cheap:\n *\n * - Every sample in a channel comes from a bounded set of integers (digitalMin to\n * digitalMax, typically 4096 distinct values for a 12-bit ADC). The formatted\n * text for a digital code never changes, so it is computed once and reused.\n * - The cache fills lazily. Real recordings visit only a fraction of the range,\n * and a channel with an implausibly wide range falls back to direct formatting\n * rather than reserving memory it will never use.\n */\n\nimport type { EdfSignal } from '../edf/header.js';\nimport { makeScaler } from '../edf/scale.js';\n\n/** Widest digital range worth caching: 16 bits of pointers is 512 KB per channel. */\nconst MAX_CACHED_SPAN = 1 << 16;\n\n/**\n * How many cached sample slots a conversion has left to spend.\n *\n * MAX_CACHED_SPAN is a bound on one channel, and a bound on one channel is not a bound: a\n * file may declare as many channels as it likes, and each was handed its own cache. A\n * channel declaring the ordinary full 16-bit digital range takes the whole 512 KB, so a\n * 256-channel montage reserved 134 MB of pointers before writing a row — a 7.9 MB\n * recording that needed a 192 MB heap and died with a V8 out-of-memory fatal error under\n * anything smaller. The caches were the live set; nothing else in the conversion came near\n * them. It is the same shape of mistake the offset budget below was made to fix, one level\n * over: there the unbounded count was rate groups, here it is channels.\n *\n * One budget for the whole conversion leaves the ordinary recording exactly as it was and\n * puts a ceiling on the dense montage: the same 256-channel file now holds its caches to\n * 16 MB and converts under a 48 MB heap. Channels ask in the order the groups are written,\n * which is fastest rate first, so the cache goes to the channels with the most cells to\n * format. The ones that miss out fall back to formatting directly, which produces\n * identical text — the output is byte-for-byte what it was.\n */\nexport interface SampleCacheBudget {\n remaining: number;\n}\n\n/**\n * Slots for the whole conversion: 16 MB of pointers if every one is claimed.\n *\n * Enough that a full 32-channel montage declaring the whole 16-bit range keeps every cache\n * it had before, and 512 channels of an ordinary 12-bit ADC do too. Past that the extra\n * channels format directly, which costs about a quarter of the conversion's time on a\n * recording where almost none of them are cached — against a file that did not convert at\n * all under a 128 MB heap.\n */\nconst MAX_CACHED_SAMPLES = 1 << 21;\n\nexport function newSampleCacheBudget(): SampleCacheBudget {\n return { remaining: MAX_CACHED_SAMPLES };\n}\n\n/**\n * Format with a fixed number of decimals, normalising negative zero.\n *\n * Without this, a sample that scales to a very small negative value prints as\n * \"-0.000\", which looks like a distinct measurement but is not.\n */\nexport function fixed(value: number, decimals: number): string {\n // An undefined value becomes an empty cell rather than the text \"NaN\" or \"Infinity\".\n // A channel whose header leaves the digital-to-physical mapping undefined scales to\n // NaN, and an empty field is the CSV convention for \"no value here\" — the same one\n // annotations.csv uses for an absent duration. Readers parse it back as NaN / NA\n // rather than as a measurement.\n if (!Number.isFinite(value)) return '';\n\n // toFixed switches to exponent notation at 1e21, which would put \"1e+21\" in a column\n // whose every other cell is plain fixed-decimal — and a reader parsing the column as\n // decimal text has no reason to expect it. Reachable because EDF's 8-character physical\n // range fields accept exponent form, so a header may legitimately say \"1e30\".\n //\n // Above 2^53 a double carries no fractional part anyway, so the integer expansion is\n // exact rather than an approximation of one.\n if (Math.abs(value) >= 1e21) {\n const whole = BigInt(Math.trunc(value)).toString();\n return decimals > 0 ? `${whole}.${'0'.repeat(decimals)}` : whole;\n }\n\n const text = value.toFixed(decimals);\n if (text.charCodeAt(0) !== 45 /* - */) return text;\n // Cheap check for \"-0\", \"-0.0\", \"-0.000\"...\n for (let i = 1; i < text.length; i++) {\n const c = text.charCodeAt(i);\n if (c === 46 /* . */ || c === 48 /* 0 */) continue;\n return text;\n }\n return text.slice(1);\n}\n\n/** Maps a raw digital sample to its formatted physical value. */\nexport type SampleFormatter = (digital: number) => string;\n\nexport function makeSampleFormatter(\n signal: EdfSignal,\n decimals: number,\n budget: SampleCacheBudget = newSampleCacheBudget(),\n): SampleFormatter {\n const scale = makeScaler(signal);\n const low = Math.min(signal.digitalMin, signal.digitalMax);\n const high = Math.max(signal.digitalMin, signal.digitalMax);\n const span = high - low + 1;\n\n if (\n !Number.isFinite(span) ||\n span <= 0 ||\n span > MAX_CACHED_SPAN ||\n span > budget.remaining\n ) {\n return (digital: number): string => fixed(scale(digital), decimals);\n }\n budget.remaining -= span;\n\n /*\n The cache covers the channel's declared digital range, not the whole int16 domain.\n\n Allocating 65536 slots regardless of span cost 512 KB of pointers per channel, which a\n dense montage cannot afford: a 400-channel recording needed over 200 MB of cache alone\n and died with a V8 out-of-memory fatal error before writing a row. Sizing to the\n declared span makes the ordinary 12-bit channel 32 KB instead — the same 400 channels\n now fit in about 13 MB. Channels that declare the full range still take the whole 512 KB,\n which is what the budget above is for.\n\n Samples outside the declared range still occur in non-conforming files. They simply\n miss the cache and are formatted directly, which produces identical text.\n */\n const cache = new Array<string | undefined>(span);\n return (digital: number): string => {\n const slot = digital - low;\n if (slot >= 0 && slot < span) {\n const hit = cache[slot];\n if (hit !== undefined) return hit;\n const text = fixed(scale(digital), decimals);\n cache[slot] = text;\n return text;\n }\n return fixed(scale(digital), decimals);\n };\n}\n\n/**\n * Decimals for the time column.\n *\n * The interval between samples is 1/rate, which has a terminating decimal expansion of d\n * places exactly when 10^d divides evenly by the rate. Writing that many places makes sample\n * times exact rather than rounded, so `time_s * rate` comes back as a whole number instead\n * of 8191.999999.\n *\n * The search used to stop at nine places, and the comment here claimed \"every rate in common\n * use clears this — 256 Hz needs 8 places, 512 Hz needs 9\". The next two powers of two do\n * not: 1/1024 needs ten places and 1/2048 needs eleven, and those are the rates a BioSemi\n * ActiveTwo records at by default. Both fell through to the rounding fallback, so the two\n * most common high-rate EEG recordings got exactly the behaviour this function exists to\n * avoid — 0.0009766 for an interval of 0.0009765625.\n *\n * A rate of 2^a * 5^b terminates in max(a, b) places, so fifteen covers every power of two up\n * to 32768 Hz, far past anything that records biosignals. Rates with a repeating expansion\n * (3 Hz, say) still fall back to enough places to keep consecutive samples distinct, and that\n * fallback keeps its own cap.\n *\n * Fifteen and not more, because the test below has to stay exact: 10^16 is past 2^53, where a\n * double can no longer hold every integer, and `Number.isInteger(10 ** 17 / 3)` is true — so a\n * larger bound reports a terminating expansion for rates that have none, and 3 Hz would ask\n * for seventeen decimals of a number that repeats forever.\n *\n * The fallback is bounded by the same fifteen. It used to stop at nine, which defeated the\n * one thing it exists to do: at 3e10 Hz — non-terminating, so no exact expansion to find —\n * the interval is 3.3e-11 and nine places rounded every sample in a record to the same\n * timestamp. A column that cannot tell two samples apart is not keeping them distinct, which\n * is what this branch is for.\n */\nconst MAX_TIME_DECIMALS = 15;\n\nexport function timeDecimals(samplingRate: number): number {\n if (!(samplingRate > 0) || !Number.isFinite(samplingRate)) return 3;\n for (let d = 0; d <= MAX_TIME_DECIMALS; d++) {\n if (Number.isInteger(10 ** d / samplingRate)) return Math.max(3, d);\n }\n return Math.min(MAX_TIME_DECIMALS, Math.max(3, Math.ceil(Math.log10(samplingRate)) + 3));\n}\n\n/** Human-readable byte size for warnings and summaries. */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n let rounded = value >= 100 || unit === 0 ? Math.round(value) : Number(value.toFixed(1));\n // Rounding can carry into the next unit, and the unit was chosen before it: 1,048,575 bytes\n // is 1023.999 KB, which printed as \"1024 KB\". The same slip formatDuration below fixed by\n // rounding before splitting, one function up.\n if (rounded === 1024 && unit < units.length - 1) {\n unit++;\n rounded = 1;\n }\n return `${rounded} ${units[unit]}`;\n}\n\n/** Human-readable duration: 1h 05m 12s. */\nexport function formatDuration(seconds: number): string {\n // A duration that is not a number cannot be broken into hours and minutes, and saying so\n // beats the alternative: the fallback below rendered these as \"NaNs\" and \"Infinitys\".\n if (!Number.isFinite(seconds)) return 'unknown';\n\n /*\n Past 2^53 the decomposition stops being arithmetic and starts being noise.\n\n `total - h * 3600 - m * 60` cannot be exact once `total` exceeds what a double can hold\n as a whole number, and the error lands in the seconds field, where it shows up as a\n value that cannot exist. A header declaring a record duration of 1e300 printed:\n\n Duration 8.333333333333333e+296h 48m -2880s\n\n Forty-eight minutes and minus forty-eight seconds, under an hours field in exponent\n notation. The seconds are the honest form for a figure this size — nobody reads\n 285 million years as hours — and the record count and duration are printed beside it\n anyway, so a corrupt header stays just as visible.\n */\n if (seconds < 0 || seconds >= Number.MAX_SAFE_INTEGER) return `${seconds}s`;\n\n // Round to the precision that will actually be printed BEFORE splitting into units.\n // Splitting first left the remainder to be rounded on its own, so 3599.9996 s decomposed\n // as 59 minutes and 59.9996 seconds and then printed as \"59m 60s\" — a duration that\n // cannot exist. Rounding first carries the extra second into the minute where it belongs.\n const total = Math.round(seconds * 1000) / 1000;\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = Math.round((total - h * 3600 - m * 60) * 1000) / 1000;\n const sText = Number.isInteger(s) ? String(s) : s.toFixed(3).replace(/\\.?0+$/u, '');\n if (h > 0) return `${h}h ${String(m).padStart(2, '0')}m ${sText}s`;\n if (m > 0) return `${m}m ${sText}s`;\n return `${sText}s`;\n}\n\n/** A record can declare a great many samples; two arrays this size is the cost of caching. */\nconst MAX_CACHED_OFFSETS = 1 << 20;\n\n/**\n * How many cached offsets a conversion has left to spend.\n *\n * The cap used to be per rate group, and a file may hold as many rate groups as it has\n * channels. Twelve channels at twelve rates just under the cap — a 25 MB file — took\n * 1.66 GB and 36 seconds, where a 92 MB file at one rate takes 283 MB and finishes in a\n * fraction of that; twenty-four of them never finished at all. A per-group limit is not a\n * limit, since nothing bounds the number of groups.\n *\n * One budget for the whole conversion makes the single-group case identical to what it was\n * and the many-group case bounded. Groups ask in order of rate, fastest first, so the cache\n * goes to the tables with the most rows to write and the ones that miss out are the ones\n * that would have gained least.\n */\nexport interface OffsetBudget {\n remaining: number;\n}\n\nexport function newOffsetBudget(): OffsetBudget {\n return { remaining: MAX_CACHED_OFFSETS };\n}\n\n/**\n * Formats the time column, reusing the part of it that repeats.\n *\n * Every value cell is already cached — a channel has at most `digitalMax - digitalMin + 1`\n * distinct readings, so the same handful of strings serve millions of rows. The time column\n * had no such luck: it rises monotonically, so no two rows share a string and `toFixed` ran\n * once per row. On a ten-million-row conversion that was a third of the total time, more\n * than reading the file and writing the CSV put together.\n *\n * What repeats is the offset within a record. Sample `s` sits at `s / rate` from the start of\n * whichever record holds it, and there are only `samplesPerRecord` such offsets in the whole\n * recording. Splitting each into whole seconds and printed fraction turns the per-row work\n * into one integer addition and a concatenation:\n *\n * record starting at 42s, sample 7 of a 100 Hz record\n * -> 42 + 0 whole seconds, fraction \".070\" -> \"42.070\"\n *\n * The decomposition is only valid when the record starts on a whole, non-negative second,\n * which is what lets the fraction come entirely from the offset. A record starting at 0.5 s would mix the\n * two, so those fall back to formatting the sum directly. Continuous recordings start every\n * record at `index * recordDuration`, so this holds for all of them whose record duration is\n * a whole number of seconds, and for discontinuous files it holds per record depending on\n * where that record actually starts.\n */\nexport function makeTimeFormatter(\n samplesPerRecord: number,\n rate: number,\n decimals: number,\n budget: OffsetBudget = newOffsetBudget(),\n): (recordStart: number, sample: number) => string {\n const direct = (recordStart: number, sample: number): string =>\n fixed(recordStart + sample / rate, decimals);\n\n if (\n !(rate > 0) ||\n !Number.isFinite(rate) ||\n samplesPerRecord <= 0 ||\n samplesPerRecord > budget.remaining\n ) {\n return direct;\n }\n budget.remaining -= samplesPerRecord;\n\n // Whole seconds and printed fraction of each offset, taken from the formatted offset\n // itself so that an offset which rounds up to the next second (0.9996 at three places)\n // carries that second rather than losing it.\n const wholeOffset = new Float64Array(samplesPerRecord);\n const fractionText: string[] = new Array<string>(samplesPerRecord);\n for (let sample = 0; sample < samplesPerRecord; sample++) {\n const text = fixed(sample / rate, decimals);\n const dot = text.indexOf('.');\n wholeOffset[sample] = dot === -1 ? Number(text) : Number(text.slice(0, dot));\n fractionText[sample] = dot === -1 ? '' : text.slice(dot);\n }\n\n return (recordStart: number, sample: number): string => {\n /*\n Non-negative only. Appending a fraction to a negative whole part moves the time the\n wrong way: a record at -5 s and an offset of half a second is -4.5, but \"-5\" and\n \".500\" concatenate to -5.500. Recording times start at zero, so this is unreachable\n from a well-formed file — an EDF+ timekeeping TAL is free to carry a negative onset\n though, and that is enough reason for the fast path to decline it.\n */\n if (\n sample < 0 ||\n sample >= samplesPerRecord ||\n !Number.isInteger(recordStart) ||\n recordStart < 0\n ) {\n return direct(recordStart, sample);\n }\n const whole = recordStart + (wholeOffset[sample] as number);\n /*\n The same 1e21 cliff `fixed` guards against, arriving through the back door.\n\n `${whole}` is the implicit Number-to-String conversion, which switches to exponent\n notation at 1e21 exactly as `toFixed` does — and then the cached fraction is glued onto\n the end of it, so the cell reads \"1e+21.000\". That is not a number in any notation:\n pandas and R both parse it as NaN, and a column of ordinary decimals ends in a run of\n them. A header may legitimately say `1e21` in its 8-character record-duration field, so\n three records are enough to reach it.\n\n The slow path already expands these with BigInt. One comparison per row keeps that\n correct without giving up the cache for the other twenty million.\n */\n if (whole >= 1e21) return direct(recordStart, sample);\n return `${whole}${fractionText[sample] as string}`;\n };\n}\n"]}
1
+ {"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/format/number.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Number formatting for CSV cells.\n *\n * A one-hour, 23-channel, 256 Hz recording is about 21 million numeric cells, so\n * this is the hottest code in a conversion. Two things keep it cheap:\n *\n * - Every sample in a channel comes from a bounded set of integers (digitalMin to\n * digitalMax, typically 4096 distinct values for a 12-bit ADC). The formatted\n * text for a digital code never changes, so it is computed once and reused.\n * - The cache fills lazily. Real recordings visit only a fraction of the range,\n * and a channel with an implausibly wide range falls back to direct formatting\n * rather than reserving memory it will never use.\n */\n\nimport type { EdfSignal } from '../edf/header.js';\nimport { makeScaler } from '../edf/scale.js';\n\n/** Widest digital range worth caching: 16 bits of pointers is 512 KB per channel. */\nconst MAX_CACHED_SPAN = 1 << 16;\n\n/**\n * How many cached sample slots a conversion has left to spend.\n *\n * MAX_CACHED_SPAN is a bound on one channel, and a bound on one channel is not a bound: a\n * file may declare as many channels as it likes, and each was handed its own cache. A\n * channel declaring the ordinary full 16-bit digital range takes the whole 512 KB, so a\n * 256-channel montage reserved 134 MB of pointers before writing a row — a 7.9 MB\n * recording that needed a 192 MB heap and died with a V8 out-of-memory fatal error under\n * anything smaller. The caches were the live set; nothing else in the conversion came near\n * them. It is the same shape of mistake the offset budget below was made to fix, one level\n * over: there the unbounded count was rate groups, here it is channels.\n *\n * One budget for the whole conversion leaves the ordinary recording exactly as it was and\n * puts a ceiling on the dense montage: the same 256-channel file now holds its caches to\n * 16 MB and converts under a 48 MB heap. Channels ask in the order the groups are written,\n * which is fastest rate first, so the cache goes to the channels with the most cells to\n * format. The ones that miss out fall back to formatting directly, which produces\n * identical text — the output is byte-for-byte what it was.\n */\nexport interface SampleCacheBudget {\n remaining: number;\n}\n\n/**\n * Slots for the whole conversion: 16 MB of pointers if every one is claimed.\n *\n * Enough that a full 32-channel montage declaring the whole 16-bit range keeps every cache\n * it had before, and 512 channels of an ordinary 12-bit ADC do too. Past that the extra\n * channels format directly, which costs about a quarter of the conversion's time on a\n * recording where almost none of them are cached — against a file that did not convert at\n * all under a 128 MB heap.\n */\nconst MAX_CACHED_SAMPLES = 1 << 21;\n\nexport function newSampleCacheBudget(): SampleCacheBudget {\n return { remaining: MAX_CACHED_SAMPLES };\n}\n\n/**\n * Format with a fixed number of decimals, normalising negative zero.\n *\n * Without this, a sample that scales to a very small negative value prints as\n * \"-0.000\", which looks like a distinct measurement but is not.\n */\nexport function fixed(value: number, decimals: number): string {\n // An undefined value becomes an empty cell rather than the text \"NaN\" or \"Infinity\".\n // A channel whose header leaves the digital-to-physical mapping undefined scales to\n // NaN, and an empty field is the CSV convention for \"no value here\" — the same one\n // annotations.csv uses for an absent duration. Readers parse it back as NaN / NA\n // rather than as a measurement.\n if (!Number.isFinite(value)) return '';\n\n // toFixed switches to exponent notation at 1e21, which would put \"1e+21\" in a column\n // whose every other cell is plain fixed-decimal — and a reader parsing the column as\n // decimal text has no reason to expect it. Reachable because EDF's 8-character physical\n // range fields accept exponent form, so a header may legitimately say \"1e30\".\n //\n // Above 2^53 a double carries no fractional part anyway, so the integer expansion is\n // exact rather than an approximation of one.\n if (Math.abs(value) >= 1e21) {\n const whole = BigInt(Math.trunc(value)).toString();\n return decimals > 0 ? `${whole}.${'0'.repeat(decimals)}` : whole;\n }\n\n const text = value.toFixed(decimals);\n if (text.charCodeAt(0) !== 45 /* - */) return text;\n // Cheap check for \"-0\", \"-0.0\", \"-0.000\"...\n for (let i = 1; i < text.length; i++) {\n const c = text.charCodeAt(i);\n if (c === 46 /* . */ || c === 48 /* 0 */) continue;\n return text;\n }\n return text.slice(1);\n}\n\n/** Maps a raw digital sample to its formatted physical value. */\nexport type SampleFormatter = (digital: number) => string;\n\nexport function makeSampleFormatter(\n signal: EdfSignal,\n decimals: number,\n budget: SampleCacheBudget = newSampleCacheBudget(),\n): SampleFormatter {\n const scale = makeScaler(signal);\n const low = Math.min(signal.digitalMin, signal.digitalMax);\n const high = Math.max(signal.digitalMin, signal.digitalMax);\n const span = high - low + 1;\n\n if (\n !Number.isFinite(span) ||\n span <= 0 ||\n span > MAX_CACHED_SPAN ||\n span > budget.remaining\n ) {\n return (digital: number): string => fixed(scale(digital), decimals);\n }\n budget.remaining -= span;\n\n /*\n The cache covers the channel's declared digital range, not the whole int16 domain.\n\n Allocating 65536 slots regardless of span cost 512 KB of pointers per channel, which a\n dense montage cannot afford: a 400-channel recording needed over 200 MB of cache alone\n and died with a V8 out-of-memory fatal error before writing a row. Sizing to the\n declared span makes the ordinary 12-bit channel 32 KB instead — the same 400 channels\n now fit in about 13 MB. Channels that declare the full range still take the whole 512 KB,\n which is what the budget above is for.\n\n Samples outside the declared range still occur in non-conforming files. They simply\n miss the cache and are formatted directly, which produces identical text.\n */\n const cache = new Array<string | undefined>(span);\n return (digital: number): string => {\n const slot = digital - low;\n if (slot >= 0 && slot < span) {\n const hit = cache[slot];\n if (hit !== undefined) return hit;\n const text = fixed(scale(digital), decimals);\n cache[slot] = text;\n return text;\n }\n return fixed(scale(digital), decimals);\n };\n}\n\n/**\n * Decimals for the time column.\n *\n * The interval between samples is 1/rate, which has a terminating decimal expansion of d\n * places exactly when 10^d divides evenly by the rate. Writing that many places makes sample\n * times exact rather than rounded, so `time_s * rate` comes back as a whole number instead\n * of 8191.999999.\n *\n * The search used to stop at nine places, and the comment here claimed \"every rate in common\n * use clears this — 256 Hz needs 8 places, 512 Hz needs 9\". The next two powers of two do\n * not: 1/1024 needs ten places and 1/2048 needs eleven, and those are the rates a BioSemi\n * ActiveTwo records at by default. Both fell through to the rounding fallback, so the two\n * most common high-rate EEG recordings got exactly the behaviour this function exists to\n * avoid — 0.0009766 for an interval of 0.0009765625.\n *\n * A rate of 2^a * 5^b terminates in max(a, b) places, so fifteen covers every power of two up\n * to 32768 Hz, far past anything that records biosignals. Rates with a repeating expansion\n * (3 Hz, say) still fall back to enough places to keep consecutive samples distinct, and that\n * fallback keeps its own cap.\n *\n * Fifteen and not more, because the test below has to stay exact: 10^16 is past 2^53, where a\n * double can no longer hold every integer, and `Number.isInteger(10 ** 17 / 3)` is true — so a\n * larger bound reports a terminating expansion for rates that have none, and 3 Hz would ask\n * for seventeen decimals of a number that repeats forever.\n *\n * The fallback is bounded by the same fifteen. It used to stop at nine, which defeated the\n * one thing it exists to do: at 3e10 Hz — non-terminating, so no exact expansion to find —\n * the interval is 3.3e-11 and nine places rounded every sample in a record to the same\n * timestamp. A column that cannot tell two samples apart is not keeping them distinct, which\n * is what this branch is for.\n */\nconst MAX_TIME_DECIMALS = 15;\n\nexport function timeDecimals(samplingRate: number): number {\n if (!(samplingRate > 0) || !Number.isFinite(samplingRate)) return 3;\n for (let d = 0; d <= MAX_TIME_DECIMALS; d++) {\n if (Number.isInteger(10 ** d / samplingRate)) return Math.max(3, d);\n }\n return Math.min(MAX_TIME_DECIMALS, Math.max(3, Math.ceil(Math.log10(samplingRate)) + 3));\n}\n\n/** Human-readable byte size for warnings and summaries. */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n let rounded = value >= 100 || unit === 0 ? Math.round(value) : Number(value.toFixed(1));\n // Rounding can carry into the next unit, and the unit was chosen before it: 1,048,575 bytes\n // is 1023.999 KB, which printed as \"1024 KB\". The same slip formatDuration below fixed by\n // rounding before splitting, one function up.\n if (rounded === 1024 && unit < units.length - 1) {\n unit++;\n rounded = 1;\n }\n return `${rounded} ${units[unit]}`;\n}\n\n/** Human-readable duration: 1h 05m 12s. */\nexport function formatDuration(seconds: number): string {\n // A duration that is not a number cannot be broken into hours and minutes, and saying so\n // beats the alternative: the fallback below rendered these as \"NaNs\" and \"Infinitys\".\n if (!Number.isFinite(seconds)) return 'unknown';\n\n /*\n Past 2^53 the decomposition stops being arithmetic and starts being noise.\n\n `total - h * 3600 - m * 60` cannot be exact once `total` exceeds what a double can hold\n as a whole number, and the error lands in the seconds field, where it shows up as a\n value that cannot exist. A header declaring a record duration of 1e300 printed:\n\n Duration 8.333333333333333e+296h 48m -2880s\n\n Forty-eight minutes and minus forty-eight seconds, under an hours field in exponent\n notation. The seconds are the honest form for a figure this size — nobody reads\n 285 million years as hours — and the record count and duration are printed beside it\n anyway, so a corrupt header stays just as visible.\n */\n if (seconds < 0 || seconds >= Number.MAX_SAFE_INTEGER) return `${seconds}s`;\n\n // Round to the precision that will actually be printed BEFORE splitting into units.\n // Splitting first left the remainder to be rounded on its own, so 3599.9996 s decomposed\n // as 59 minutes and 59.9996 seconds and then printed as \"59m 60s\" — a duration that\n // cannot exist. Rounding first carries the extra second into the minute where it belongs.\n const total = Math.round(seconds * 1000) / 1000;\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = Math.round((total - h * 3600 - m * 60) * 1000) / 1000;\n const sText = Number.isInteger(s) ? String(s) : s.toFixed(3).replace(/\\.?0+$/u, '');\n if (h > 0) return `${h}h ${String(m).padStart(2, '0')}m ${sText}s`;\n if (m > 0) return `${m}m ${sText}s`;\n return `${sText}s`;\n}\n\n/** A record can declare a great many samples; two arrays this size is the cost of caching. */\nconst MAX_CACHED_OFFSETS = 1 << 20;\n\n/**\n * How many cached offsets a conversion has left to spend.\n *\n * The cap used to be per rate group, and a file may hold as many rate groups as it has\n * channels. Twelve channels at twelve rates just under the cap — a 25 MB file — took\n * 1.66 GB and 36 seconds, where a 92 MB file at one rate takes 283 MB and finishes in a\n * fraction of that; twenty-four of them never finished at all. A per-group limit is not a\n * limit, since nothing bounds the number of groups.\n *\n * One budget for the whole conversion makes the single-group case identical to what it was\n * and the many-group case bounded. Groups ask in order of rate, fastest first, so the cache\n * goes to the tables with the most rows to write and the ones that miss out are the ones\n * that would have gained least.\n */\nexport interface OffsetBudget {\n remaining: number;\n}\n\nexport function newOffsetBudget(): OffsetBudget {\n return { remaining: MAX_CACHED_OFFSETS };\n}\n\n/**\n * Formats the time column, reusing the part of it that repeats.\n *\n * Every value cell is already cached — a channel has at most `digitalMax - digitalMin + 1`\n * distinct readings, so the same handful of strings serve millions of rows. The time column\n * had no such luck: it rises monotonically, so no two rows share a string and `toFixed` ran\n * once per row. On a ten-million-row conversion that was a third of the total time, more\n * than reading the file and writing the CSV put together.\n *\n * What repeats is the offset within a record. Sample `s` sits at `s / rate` from the start of\n * whichever record holds it, and there are only `samplesPerRecord` such offsets in the whole\n * recording. Splitting each into whole seconds and printed fraction turns the per-row work\n * into one integer addition and a concatenation:\n *\n * record starting at 42s, sample 7 of a 100 Hz record\n * -> 42 + 0 whole seconds, fraction \".070\" -> \"42.070\"\n *\n * The decomposition is only valid when the record starts on a whole, non-negative second,\n * which is what lets the fraction come entirely from the offset. A record starting at 0.5 s would mix the\n * two, so those fall back to formatting the sum directly. Continuous recordings start every\n * record at `index * recordDuration`, so this holds for all of them whose record duration is\n * a whole number of seconds, and for discontinuous files it holds per record depending on\n * where that record actually starts.\n */\nexport function makeTimeFormatter(\n samplesPerRecord: number,\n rate: number,\n decimals: number,\n budget: OffsetBudget = newOffsetBudget(),\n): (recordStart: number, sample: number) => string {\n const usable = rate > 0 && Number.isFinite(rate);\n\n /** The sum, formatted. Right only where the decomposition below does not apply. */\n const summed = (recordStart: number, sample: number): string =>\n fixed(recordStart + sample / rate, decimals);\n\n /*\n The same decomposition the table holds, computed on the spot.\n\n This used to be the sum, and that made the time column two columns depending on how much\n of the budget above was left. `recordStart + sample / rate` cannot carry the fraction once\n the record start is large: at a start of 1e9 and 30 kHz the exact instant is\n 1000000000.0000333333, and the double nearest the sum prints as 1000000000.00003338 —\n the last two places are the addition's rounding, not the recording's clock.\n\n Which of the two a rate group got was decided by the groups ahead of it, since they ask\n from one budget fastest first. So a recording whose fast channel takes the table left its\n slow channel adding doubles, and the same channel asked for on its own got the table:\n\n edf2csv far.edf --out whole -> 1000000000.00003338\n edf2csv far.edf --out one --channels slow -> 1000000000.00003333\n\n Two files from one recording disagreeing about when a sample was taken, in a tool whose\n documentation says `--channels` selects columns and changes nothing else. The narrowing\n sweep asserts exactly that and could not see it: its fixtures are small enough that the\n budget never runs out, so both conversions took the table.\n\n The sum is still the answer where the decomposition has nothing to stand on — a record\n starting on a fraction or before zero, where the whole part and the fraction are not\n separable — and past 1e21, where the whole part stops printing in full.\n */\n const composed = (recordStart: number, sample: number): string => {\n const text = fixed(sample / rate, decimals);\n const dot = text.indexOf('.');\n const whole = recordStart + (dot === -1 ? Number(text) : Number(text.slice(0, dot)));\n if (whole >= 1e21) return summed(recordStart, sample);\n return dot === -1 ? `${whole}` : `${whole}${text.slice(dot)}`;\n };\n\n /** What every row gets when the table is not there to be read. */\n const direct = (recordStart: number, sample: number): string =>\n usable && sample >= 0 && Number.isInteger(recordStart) && recordStart >= 0\n ? composed(recordStart, sample)\n : summed(recordStart, sample);\n\n if (!usable || samplesPerRecord <= 0 || samplesPerRecord > budget.remaining) {\n return direct;\n }\n budget.remaining -= samplesPerRecord;\n\n // Whole seconds and printed fraction of each offset, taken from the formatted offset\n // itself so that an offset which rounds up to the next second (0.9996 at three places)\n // carries that second rather than losing it.\n const wholeOffset = new Float64Array(samplesPerRecord);\n const fractionText: string[] = new Array<string>(samplesPerRecord);\n for (let sample = 0; sample < samplesPerRecord; sample++) {\n const text = fixed(sample / rate, decimals);\n const dot = text.indexOf('.');\n wholeOffset[sample] = dot === -1 ? Number(text) : Number(text.slice(0, dot));\n fractionText[sample] = dot === -1 ? '' : text.slice(dot);\n }\n\n return (recordStart: number, sample: number): string => {\n /*\n Non-negative only. Appending a fraction to a negative whole part moves the time the\n wrong way: a record at -5 s and an offset of half a second is -4.5, but \"-5\" and\n \".500\" concatenate to -5.500. Recording times start at zero, so this is unreachable\n from a well-formed file — an EDF+ timekeeping TAL is free to carry a negative onset\n though, and that is enough reason for the fast path to decline it.\n */\n if (\n sample < 0 ||\n sample >= samplesPerRecord ||\n !Number.isInteger(recordStart) ||\n recordStart < 0\n ) {\n return direct(recordStart, sample);\n }\n const whole = recordStart + (wholeOffset[sample] as number);\n /*\n The same 1e21 cliff `fixed` guards against, arriving through the back door.\n\n `${whole}` is the implicit Number-to-String conversion, which switches to exponent\n notation at 1e21 exactly as `toFixed` does — and then the cached fraction is glued onto\n the end of it, so the cell reads \"1e+21.000\". That is not a number in any notation:\n pandas and R both parse it as NaN, and a column of ordinary decimals ends in a run of\n them. A header may legitimately say `1e21` in its 8-character record-duration field, so\n three records are enough to reach it.\n\n The slow path already expands these with BigInt. One comparison per row keeps that\n correct without giving up the cache for the other twenty million.\n */\n if (whole >= 1e21) return summed(recordStart, sample);\n return `${whole}${fractionText[sample] as string}`;\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edf2csv",
3
- "version": "0.7.46",
3
+ "version": "0.7.48",
4
4
  "description": "Convert EDF, EDF+ and BDF biosignal recordings (European Data Format) to CSV from the command line. Local, streaming, and never resamples or alters units.",
5
5
  "keywords": [
6
6
  "edf",