edf2csv 0.5.67 → 0.5.69

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/CHANGELOG.md CHANGED
@@ -3,6 +3,56 @@
3
3
  Notable changes to edf2csv. Versions follow [semantic versioning](https://semver.org); while the
4
4
  major version is 0, a minor bump may contain breaking changes.
5
5
 
6
+ ## 0.5.69
7
+
8
+ ### Fixed: metadata.json described a long table with the wide layout's contract
9
+
10
+ `rate_groups` is documented as "for each output file, its sampling rate, the columns it
11
+ contains in order". Under `--layout long` that is three false statements at once. A mixed-rate
12
+ recording produced:
13
+
14
+ ```json
15
+ "rate_groups": [
16
+ { "file": "signals.csv", "sampling_rate_hz": 256, "channels": ["EEG Fpz-Cz"], "decimals": [3] },
17
+ { "file": "signals.csv", "sampling_rate_hz": 128, "channels": ["ECG"], "decimals": [5] },
18
+ { "file": "signals.csv", "sampling_rate_hz": 1, "channels": ["Temp rectal"],"decimals": [5] }
19
+ ]
20
+ ```
21
+
22
+ for a file whose columns are `time_s,channel,value`. One file named three times, three
23
+ different sampling rates for it, and channel names that are values in a column rather than
24
+ columns of the table.
25
+
26
+ The array itself is right — it is the grouping decision, and the grouping is real under both
27
+ layouts. What was missing is the one fact that makes it readable: **`metadata.json` now records
28
+ `layout`**, `"wide"` or `"long"`, so a pipeline handed an output directory can tell which shape
29
+ `signals.csv` is in. Nothing else in the archive distinguished them, and the two files have
30
+ different columns. The `rate_groups` documentation now states both readings.
31
+
32
+ The invariant sweep that would have caught this — "what the run says it produced must match
33
+ what is on disk", run over every fixture — only ever ran the wide layout, plain, windowed and
34
+ annotations-only. It runs the long layout now, with the expectation each layout actually
35
+ promises.
36
+
37
+ ## 0.5.68
38
+
39
+ ### Fixed: `--decimals` read its value the way `Number()` felt like reading it
40
+
41
+ `--decimals 0o5` wrote five decimal places. `--decimals 0b11` wrote three. So did `0x3`,
42
+ `3e0`, `+3` and ` 3 `. Every one converted, exited 0, and produced a CSV that looks exactly as
43
+ intended — at a precision that reads as that precision to nobody.
44
+
45
+ `3.5` was refused all along, which is what makes the message believable: a reader who sees
46
+ "must be a whole number between 0 and 20" reject `3.5` has no reason to suspect `0o5` of
47
+ quietly meaning five.
48
+
49
+ Plain digits now, the same rule `--jobs` was given in 0.5.50 and `--channels "#N"` before it —
50
+ where the comment records why it matters: "Every one of them selected a channel and exited 0,
51
+ so a slip did not fail — it quietly converted a different channel than the one asked for, which
52
+ for this tool is the worst way to be wrong." A precision is the same kind of quiet. The value
53
+ still comes back quoted as typed, and a leading or trailing space is still trimmed, exactly as
54
+ `--jobs` does.
55
+
6
56
  ## 0.5.67
7
57
 
8
58
  ### Fixed: a path reached the terminal unescaped, on the lines that report where output went
package/dist/cli.js CHANGED
@@ -1438,8 +1438,22 @@ function optionalDecimals(raw) {
1438
1438
  if (text === '') {
1439
1439
  throw new OptionError('--decimals needs a number, for example --decimals 3.');
1440
1440
  }
1441
+ /*
1442
+ A whole number as written, not as `Number()` chooses to read it.
1443
+
1444
+ `Number.isInteger(Number(text))` accepted `0x3`, `0b11`, `0o5`, `3e0`, `+3` and ` 3 `,
1445
+ while refusing `3.5` — and the refusal of `3.5` is what makes a reader believe the message
1446
+ that says "a whole number between 0 and 20". So `--decimals 0o5` wrote five decimals for
1447
+ an argument that reads as five to nobody, and `--decimals 0b11` wrote three, in silence,
1448
+ exit 0, with a CSV that looks exactly as intended.
1449
+
1450
+ The same fix `--jobs` got, and the same one `--channels '#N'` got — where the comment
1451
+ records why: "Every one of them selected a channel and exited 0, so a slip did not fail —
1452
+ it quietly converted a different channel than the one asked for, which for this tool is
1453
+ the worst way to be wrong." A precision is the same kind of quiet.
1454
+ */
1441
1455
  const value = Number(text);
1442
- if (!Number.isInteger(value) || value < 0 || value > 20) {
1456
+ if (!/^\d+$/u.test(text) || !Number.isInteger(value) || value < 0 || value > 20) {
1443
1457
  throw new OptionError(`--decimals must be a whole number between 0 and 20, got "${String(raw)}".`);
1444
1458
  }
1445
1459
  return value;
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 { ConversionError, USAGE_ERROR_CODES, convert, defaultOutputDir } 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 } from './convert/timing.js';\nimport { formatDiagnostics, formatInfo, infoJson, formatSummary, printable, printableLines, summaryJson } from './cli/report.js';\nimport { 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 estimated output size,\n without converting anything\n -o, --out <dir> Output directory (default: <recording>_csv beside 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 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\" (default: 1)\n -f, --force Overwrite the output directory if it exists\n -q, --quiet Suppress the summary; warnings and errors still print\n --json Print machine-readable JSON to stdout (works with --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 one.\n With --layout long every channel goes into one signals.csv as time_s, channel\n and value, in time order, whatever rates the recording mixes — which is also\n 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\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: {\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 },\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 `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 return EXIT_ERROR;\n }\n process.stderr.write(\n `No EDF or BDF recordings found in ${listed(positionals.map((p) => `\"${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 if (toStdout && asJson) {\n process.stderr.write(\n '--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 process.stderr.write(\n inputs.length === 1\n ? `--stdout writes a single CSV, and a folder is converted as a batch even when it ` +\n `holds one recording.\\nName the recording itself — ${inputs[0]} — or convert to a ` +\n `directory instead.\\n`\n : `--stdout writes a single CSV, so it cannot take ${inputs.length} recordings.\\n` +\n `Convert them to directories instead, or run edf2csv once per file.\\n`,\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 for (const [flag, given] of [\n ['--out', values['out'] !== undefined],\n ['--checksum', values['checksum'] === true],\n ] as const) {\n if (toStdout && given) {\n process.stderr.write(\n `--stdout and ${flag} cannot be combined: --stdout writes no files, and ${flag} has ` +\n `nothing to act on.\\nDrop ${flag}, or drop --stdout and convert to a directory.\\n`,\n );\n return EXIT_USAGE;\n }\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 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(input as string, shared, asJson, jsonIndent, batch);\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 ? ` Incomplete, and should not be used: ${listed(abandoned)}\\n`\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 ${inputs.length} recordings${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--strict: ${warnings} warning${warnings === 1 ? '' : 's'} raised, so this run is ` +\n `reported as a failure. The output was still written.\\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): 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 process.stdout.write(\n asJson ? `${infoJson(file, plan, jsonIndent)}\\n` : `${formatInfo(file, plan)}\\n`,\n );\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 = [...withoutFileRateWarning(file.diagnostics), ...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 const onInterrupt = (signal: NodeJS.Signals): void => {\n if (showProgress) process.stderr.write('\\r\\u001b[K');\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 ? ` The CSV on stdout stops mid-recording and should not be used.\\n`\n : !wrote\n ? ` Nothing was written: \"${printable(destination)}\" was never created.\\n`\n : destinationExistedBefore\n ? ` \"${printable(destination)}\" was already there, so what is in it may be this run's ` +\n `incomplete output.\\n`\n : ` Files already written to \"${printable(destination)}\" are incomplete and should not ` +\n `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 if (showProgress) process.stderr.write('\\r\\u001b[K');\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 emit(\n 'err',\n `${formatDiagnostics(result.diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n\\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 emit(\n 'err',\n !result.readerHungUp\n ? `Wrote ${rows.toLocaleString('en-US')} rows to stdout.\\n`\n : rows < expected\n ? `Stopped: the reader closed the pipe after ${rows.toLocaleString('en-US')} of ` +\n `${expected.toLocaleString('en-US')} rows had been written. The recording was ` +\n `not converted in full.\\n`\n : `Wrote ${rows.toLocaleString('en-US')} rows 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 } 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 return { inputs: unique, unreadable, 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 return codes.includes(EXIT_ERROR) ? 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 ` Incomplete, and should not be used: ${printable(destination)}\\n`;\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 if (error.hint) emit('err', ` ${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`, ` 4 ` and\n `999999999999999999999` — the last of which is not an integer any more, only the nearest\n double to one — while refusing `4.7`. The message says \"a whole number of 1 or more\" and\n the refusal of 4.7 is what makes a reader believe it, so the others read as the tool\n silently reinterpreting what they typed. `--jobs` is the flag 0.4.2 hardened because\n accepting `--jobs 0` in silence \"is the kind of quiet this tool avoids\".\n */\n if (!/^\\d+$/u.test(text)) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${text}\".`);\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 \"${text}\".`);\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\nfunction optionalTime(raw: unknown, option: string): number | undefined {\n if (raw === undefined) return undefined;\n return parseTimeSpec(String(raw), option);\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 if (raw === 'wide' || raw === 'long') return raw;\n throw new OptionError(`--layout must be \"wide\" or \"long\", got \"${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 const value = Number(text);\n if (!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 * Only this case is reworded. Node's other three — an unknown option, a switch given a value, an\n * option missing its value — say something true in words a reader can act on, and replacing them\n * with near-identical sentences would be churn.\n */\nfunction usageMessage(error: unknown, argv: readonly string[]): string {\n const raw = message(error);\n if ((error as { code?: unknown } | null)?.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 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: ${joined}`,\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\nexport { defaultOutputDir };\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 process.exitCode = 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 { ConversionError, USAGE_ERROR_CODES, convert, defaultOutputDir } 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 } from './convert/timing.js';\nimport { formatDiagnostics, formatInfo, infoJson, formatSummary, printable, printableLines, summaryJson } from './cli/report.js';\nimport { 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 estimated output size,\n without converting anything\n -o, --out <dir> Output directory (default: <recording>_csv beside 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 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\" (default: 1)\n -f, --force Overwrite the output directory if it exists\n -q, --quiet Suppress the summary; warnings and errors still print\n --json Print machine-readable JSON to stdout (works with --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 one.\n With --layout long every channel goes into one signals.csv as time_s, channel\n and value, in time order, whatever rates the recording mixes — which is also\n 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\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: {\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 },\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 `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 return EXIT_ERROR;\n }\n process.stderr.write(\n `No EDF or BDF recordings found in ${listed(positionals.map((p) => `\"${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 if (toStdout && asJson) {\n process.stderr.write(\n '--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 process.stderr.write(\n inputs.length === 1\n ? `--stdout writes a single CSV, and a folder is converted as a batch even when it ` +\n `holds one recording.\\nName the recording itself — ${inputs[0]} — or convert to a ` +\n `directory instead.\\n`\n : `--stdout writes a single CSV, so it cannot take ${inputs.length} recordings.\\n` +\n `Convert them to directories instead, or run edf2csv once per file.\\n`,\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 for (const [flag, given] of [\n ['--out', values['out'] !== undefined],\n ['--checksum', values['checksum'] === true],\n ] as const) {\n if (toStdout && given) {\n process.stderr.write(\n `--stdout and ${flag} cannot be combined: --stdout writes no files, and ${flag} has ` +\n `nothing to act on.\\nDrop ${flag}, or drop --stdout and convert to a directory.\\n`,\n );\n return EXIT_USAGE;\n }\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 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(input as string, shared, asJson, jsonIndent, batch);\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 ? ` Incomplete, and should not be used: ${listed(abandoned)}\\n`\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 ${inputs.length} recordings${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--strict: ${warnings} warning${warnings === 1 ? '' : 's'} raised, so this run is ` +\n `reported as a failure. The output was still written.\\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): 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 process.stdout.write(\n asJson ? `${infoJson(file, plan, jsonIndent)}\\n` : `${formatInfo(file, plan)}\\n`,\n );\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 = [...withoutFileRateWarning(file.diagnostics), ...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 const onInterrupt = (signal: NodeJS.Signals): void => {\n if (showProgress) process.stderr.write('\\r\\u001b[K');\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 ? ` The CSV on stdout stops mid-recording and should not be used.\\n`\n : !wrote\n ? ` Nothing was written: \"${printable(destination)}\" was never created.\\n`\n : destinationExistedBefore\n ? ` \"${printable(destination)}\" was already there, so what is in it may be this run's ` +\n `incomplete output.\\n`\n : ` Files already written to \"${printable(destination)}\" are incomplete and should not ` +\n `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 if (showProgress) process.stderr.write('\\r\\u001b[K');\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 emit(\n 'err',\n `${formatDiagnostics(result.diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n\\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 emit(\n 'err',\n !result.readerHungUp\n ? `Wrote ${rows.toLocaleString('en-US')} rows to stdout.\\n`\n : rows < expected\n ? `Stopped: the reader closed the pipe after ${rows.toLocaleString('en-US')} of ` +\n `${expected.toLocaleString('en-US')} rows had been written. The recording was ` +\n `not converted in full.\\n`\n : `Wrote ${rows.toLocaleString('en-US')} rows 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 } 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 return { inputs: unique, unreadable, 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 return codes.includes(EXIT_ERROR) ? 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 ` Incomplete, and should not be used: ${printable(destination)}\\n`;\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 if (error.hint) emit('err', ` ${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`, ` 4 ` and\n `999999999999999999999` — the last of which is not an integer any more, only the nearest\n double to one — while refusing `4.7`. The message says \"a whole number of 1 or more\" and\n the refusal of 4.7 is what makes a reader believe it, so the others read as the tool\n silently reinterpreting what they typed. `--jobs` is the flag 0.4.2 hardened because\n accepting `--jobs 0` in silence \"is the kind of quiet this tool avoids\".\n */\n if (!/^\\d+$/u.test(text)) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${text}\".`);\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 \"${text}\".`);\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\nfunction optionalTime(raw: unknown, option: string): number | undefined {\n if (raw === undefined) return undefined;\n return parseTimeSpec(String(raw), option);\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 if (raw === 'wide' || raw === 'long') return raw;\n throw new OptionError(`--layout must be \"wide\" or \"long\", got \"${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`, `+3` 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 * Only this case is reworded. Node's other three — an unknown option, a switch given a value, an\n * option missing its value — say something true in words a reader can act on, and replacing them\n * with near-identical sentences would be churn.\n */\nfunction usageMessage(error: unknown, argv: readonly string[]): string {\n const raw = message(error);\n if ((error as { code?: unknown } | null)?.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 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: ${joined}`,\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\nexport { defaultOutputDir };\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 process.exitCode = code;\n })\n .catch((error: unknown) => {\n process.stderr.write(`error: ${message(error)}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n"]}
@@ -1094,6 +1094,18 @@ checksum) {
1094
1094
  whole_recording: plan.range.isWholeRecording,
1095
1095
  records_converted: [plan.range.startRecord, plan.range.endRecord],
1096
1096
  annotations_written: annotationCount,
1097
+ /*
1098
+ Which shape the signal table is in, which nothing recorded.
1099
+
1100
+ A wide `signals.csv` and a long one are different files with different columns, and
1101
+ metadata.json described them identically — so a pipeline handed an output directory
1102
+ could not tell from the archive which it had. It matters most for `rate_groups` right
1103
+ below: in the wide layout those entries are one per file and their `channels` are that
1104
+ file's columns, and in the long layout every entry names the one shared table and its
1105
+ `channels` are values in that table's `channel` column. Same array, two readings, and
1106
+ no way to know which applied.
1107
+ */
1108
+ layout: plan.layout,
1097
1109
  files: written.map((f) => ({ name: f.name, rows: f.rows })),
1098
1110
  rate_groups: plan.groups.map((g) => ({
1099
1111
  file: g.fileName,
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/convert/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executing a conversion.\n *\n * Everything is written in a single pass over the data records. All rate groups are\n * open at once and fed from the same batch of bytes, so a file is read once no\n * matter how many output tables it produces, and memory stays flat.\n */\n\nimport { createWriteStream, fstatSync } from 'node:fs';\nimport { mkdir, readdir, stat, writeFile } from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { finished, pipeline } from 'node:stream/promises';\nimport type { Writable } from 'node:stream';\nimport { createGzip, gzipSync } from 'node:zlib';\nimport path from 'node:path';\n\nimport type { RecordBatch } from '../edf/reader.js';\nimport { EdfFile } from '../edf/reader.js';\nimport { describeFormat, formatRates, formatWallClock } from '../edf/header.js';\nimport type { Diagnostic } from '../edf/errors.js';\nimport { EdfError } from '../edf/errors.js';\nimport type { Annotation } from '../edf/annotations.js';\nimport {\n BufferedLineWriter,\n DEFAULT_FLUSH_THRESHOLD,\n UTF8_BOM,\n csvRow,\n escapeCsvField,\n} from '../format/csv.js';\nimport { listed } from '../format/list.js';\nimport {\n fixed,\n makeSampleFormatter,\n makeTimeFormatter,\n newOffsetBudget,\n newSampleCacheBudget,\n} from '../format/number.js';\nimport type { SampleFormatter } from '../format/number.js';\nimport { buildPlan, withoutFileRateWarning } from './plan.js';\nimport type { ConversionPlan, PlanOptions, RateGroup } from './plan.js';\nimport { deriveRecordStarts } from './timing.js';\nimport { sampleTimeIsInRange, toleranceFor } from './time-range.js';\nimport { VERSION as TOOL_VERSION } from '../version.js';\n\nexport { TOOL_VERSION };\n\nexport type ConversionErrorCode =\n | 'OUTPUT_EXISTS'\n | 'OUTPUT_UNWRITABLE'\n | 'INPUT_OUTPUT_COLLISION'\n | 'INPUT_UNREADABLE'\n | 'UNSUPPORTED_REQUEST'\n | 'CALLBACK_FAILED'\n | 'WRITE_FAILED';\n\n/**\n * Codes that mean the command cannot be carried out as written, rather than that something\n * about the file or the destination went wrong.\n *\n * The distinction is the one the exit codes draw: 1 is \"the file or the destination is the\n * problem\", 2 is \"the command line is the problem\". A caller with a `--stdout` conflict is\n * being told to change the flags — the hints say exactly that — so filing it under 1 sent\n * scripts looking at the disk. Exit 2 already covers checks that need the header first,\n * such as a `--channels` term matching nothing.\n */\nexport const USAGE_ERROR_CODES: ReadonlySet<ConversionErrorCode> = new Set(['UNSUPPORTED_REQUEST']);\n\nexport class ConversionError extends Error {\n readonly code: ConversionErrorCode;\n readonly hint: string | undefined;\n constructor(code: ConversionErrorCode, message: string, hint?: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ConversionError';\n this.code = code;\n this.hint = hint;\n }\n}\n\nexport interface ConvertOptions extends PlanOptions {\n /** Destination directory. Defaults to the input file's name without its extension. */\n outputDir?: string | undefined;\n /** Overwrite an existing output directory. */\n force?: boolean | undefined;\n /** Record a SHA-256 of the input in metadata.json. Costs one extra read of the file. */\n checksum?: boolean | undefined;\n /**\n * Write the signal CSV to stdout instead of to a directory.\n *\n * Only valid when the conversion produces exactly one signal file. In the default wide\n * layout a mixed-rate recording becomes several tables, and merging them into one stream\n * would mean inventing the samples this tool exists not to invent; `layout: 'long'` gives\n * one table for any recording, so it lifts the restriction. No sidecar files are written.\n */\n toStdout?: boolean | undefined;\n onProgress?: ((progress: ConversionProgress) => void) | undefined;\n}\n\nexport interface ConversionProgress {\n recordsDone: number;\n recordsTotal: number;\n bytesWritten: number;\n}\n\nexport interface WrittenFile {\n name: string;\n rows: number;\n}\n\nexport interface ConvertResult {\n outputDir: string;\n files: WrittenFile[];\n /**\n * True when a `--stdout` reader closed the pipe before the conversion finished.\n *\n * `edf2csv rec.edf --stdout | head -1` is an ordinary thing to type and not a failure, but\n * it is also not a conversion: the row count is rows formatted before the close was\n * noticed, which is neither the recording's total nor what the reader received.\n */\n readerHungUp: boolean;\n annotationCount: number;\n diagnostics: Diagnostic[];\n plan: ConversionPlan;\n file: EdfFile;\n elapsedMs: number;\n}\n\ninterface OpenGroup {\n group: RateGroup;\n writer: BufferedLineWriter;\n formatters: SampleFormatter[];\n formatTime: (recordStart: number, sample: number) => string;\n rows: number;\n /** Resolves once a compressed stream's bytes have reached the file behind it. */\n settled: Promise<void>;\n}\n\nexport async function convert(inputPath: string, options: ConvertOptions = {}): Promise<ConvertResult> {\n const startedAt = Date.now();\n const file = await EdfFile.open(inputPath);\n\n try {\n /*\n Hashed before a record is read, and only published if the file held still.\n\n A checksum taken afterwards cannot be trusted whichever descriptor it goes through. A\n file overwritten in place keeps its inode, so the open handle sees the new bytes too,\n and the old ones are simply gone — there is nowhere left to read what was converted.\n Taking it first at least means the hash describes the file the header was read from.\n\n What makes it a guarantee rather than a hope is the check at the end: if size or\n modification time moved at any point, the hash is dropped and the run says why. So\n `sha256` present means the file demonstrably did not change while it was read, and a\n recording still being written gets a null and a warning instead of a plausible hash of\n the wrong bytes. (A change reverted within the same modification timestamp would slip\n through; nothing short of copying the input first can close that.)\n */\n const checksumAtOpen = options.checksum === true ? await file.sha256() : null;\n\n // One pass over the annotation channel supplies everything annotation-related:\n // where each record sits in time, and the full event list. It reads the whole\n // file even when a window was requested, because an annotation inside the window\n // may be stored in a record outside it.\n const annotationData =\n file.annotationSignals.length > 0\n ? await file.readAnnotations()\n : { annotations: [] as Annotation[], recordStarts: [] as (number | null)[], malformed: 0 };\n\n const timing = deriveRecordStarts(file, annotationData);\n\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 options,\n );\n plan.diagnostics.push(...timing.diagnostics);\n\n if (options.toStdout === true) {\n if (!plan.writeSignals) {\n throw new ConversionError(\n 'UNSUPPORTED_REQUEST',\n '--stdout has no signal data to write because --annotations-only was given.',\n 'Drop one of the two flags.',\n );\n }\n /*\n No table at all is its own answer, and neither layout gave it.\n\n A recording with no signal channels — one holding only EDF+ annotations — produced\n zero rate groups. The wide layout then said \"--stdout needs exactly one table, but\n this recording produces 0, one for each sampling rate its channels use ()\", with an\n empty parenthetical, advice to narrow to one of no rates, and advice to use\n `--layout long` — which wrote zero bytes to stdout, not even a header row, and exited\n 0 while warning that \"the signal files hold their headers and no data\". There were no\n files and there was no header. The one path that was right about this is\n `--annotations-only`, which refuses outright, and this is the same situation reached\n by a different route.\n */\n if (plan.groups.length === 0) {\n throw new ConversionError(\n 'UNSUPPORTED_REQUEST',\n file.dataSignals.length === 0\n ? '--stdout has no signal data to write: this recording has no signal channels, ' +\n 'only EDF+ annotations.'\n : '--stdout has no signal data to write: nothing was selected that carries samples.',\n file.dataSignals.length === 0\n ? 'Convert to a directory to get its annotations.csv, or drop --stdout.'\n : 'Check --channels and the requested window, or convert to a directory instead.',\n );\n }\n\n // The long layout is one table whatever the rates are, so it has nothing to refuse.\n if (plan.layout !== 'long' && plan.groups.length !== 1) {\n throw new ConversionError(\n 'UNSUPPORTED_REQUEST',\n // Naming the rates is the point: the hint says to narrow the selection, and this\n // is what there is to narrow it to. The parenthetical used to repeat the count\n // that had just been given — \"produces 3 (its channels use 3 different sampling\n // rates)\" — which told nobody anything they could act on.\n `--stdout needs exactly one table, but this recording produces ${plan.groups.length}, ` +\n `one for each sampling rate its channels use ` +\n `(${listed(formatRates(plan.groups.map((g) => g.rate)).map((r) => `${r} Hz`))}).`,\n 'Narrow it to one rate with --channels, write --layout long to get them all in ' +\n 'one table, or convert to a directory instead.',\n );\n }\n let readerHungUp = false;\n const written = await writeSignalFiles(file, plan, null, timing.starts, options, (hungUp) => {\n readerHungUp = hungUp;\n });\n if (await file.changedSinceOpen()) plan.diagnostics.push(inputChanged(false));\n return {\n outputDir: '-',\n files: written,\n readerHungUp,\n annotationCount: 0,\n diagnostics: [...withoutFileRateWarning(file.diagnostics), ...plan.diagnostics],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n }\n\n const outputDir = options.outputDir ?? defaultOutputDir(inputPath);\n await assertInputDoesNotOverlapOutputs(inputPath, outputDir, file, plan, options);\n await prepareOutputDir(outputDir, options.force === true);\n\n const written: WrittenFile[] = [];\n\n if (plan.writeSignals && plan.groups.length > 0) {\n const signals = await writeSignalFiles(file, plan, outputDir, timing.starts, options);\n written.push(...signals);\n } else if (plan.writeSignals) {\n /*\n Asked for signal data and given none to write.\n\n Every channel selected carries zero samples per record, so there is no table to make\n — `edf2csv rec.edf --channels unused` writes channels.csv and metadata.json and no\n signals.csv at all. The NO_SAMPLES warning explains the channel; nothing explained the\n missing file, and the documentation says signals.csv is written unless\n --annotations-only was passed. Someone looking for it should be told where it went.\n\n There are two ways to arrive with no groups, and the wording above is only true of\n one of them. A recording that holds nothing but EDF+ annotations has no channel that\n could have been selected, its channels.csv is a header row and nothing else, and no\n channel of it carries samples — so \"every channel selected\", \"channels.csv still\n describes them\" and \"which channels do carry samples\" were three false statements in\n one warning, printed under a warning that had just said the file has no signal\n channels. `--stdout` distinguishes the two cases a few lines up and `--info` prints\n one accurate line; this path was the only one that did not.\n */\n const noChannelsAtAll = file.dataSignals.length === 0;\n plan.diagnostics.push({\n code: 'NO_SAMPLES',\n severity: 'warning',\n message: noChannelsAtAll\n ? 'No signal file was written: there is no signal data in this recording to put in ' +\n 'one.'\n : 'No signal file was written: every channel selected carries zero samples per data ' +\n 'record, so there is nothing to put in one.',\n hint: noChannelsAtAll\n ? 'annotations.csv holds whatever events it carries. channels.csv lists signal ' +\n 'channels, so it has none to list.'\n : 'channels.csv still describes them. Run with --info to see which channels do carry ' +\n 'samples.',\n });\n }\n\n if (options.annotationsOnly === true && file.annotationSignals.length === 0) {\n plan.diagnostics.push({\n code: 'NO_ANNOTATIONS',\n severity: 'warning',\n message:\n '--annotations-only was requested but this recording has no annotation channel, ' +\n 'so there are no events to export.',\n hint: 'Plain EDF files carry no annotations. Convert without --annotations-only to get the signals.',\n });\n }\n\n let annotationsWritten = 0;\n if (file.annotationSignals.length > 0) {\n const result = await writeAnnotationsCsv(\n outputDir,\n annotationData.annotations,\n requestedAnnotationWindow(options, plan.range.recordingStartSeconds),\n options.gzip === true,\n options.bom === true,\n );\n written.push(result);\n annotationsWritten = result.rows;\n }\n\n written.push(\n await writeChannelsCsv(outputDir, file, plan, options.gzip === true, options.bom === true),\n );\n\n /*\n The file moved under the conversion. Said out loud, because nothing else shows it.\n\n The CSVs are still correct for the records that were read, and metadata.json still\n describes the file as it was opened — so the record is consistent with the output\n whatever happens here. What stops being true is that the checksum describes the bytes\n that were converted, since an in-place overwrite leaves nowhere to read them from. It\n is dropped rather than guessed at.\n */\n const changed = await file.changedSinceOpen();\n if (changed) plan.diagnostics.push(inputChanged(options.checksum === true));\n\n await writeMetadata(\n outputDir,\n inputPath,\n file,\n plan,\n written,\n annotationsWritten,\n changed ? null : checksumAtOpen,\n );\n\n const stale = await findStaleOutput(outputDir, written);\n\n return {\n outputDir,\n files: written,\n readerHungUp: false,\n annotationCount: annotationsWritten,\n diagnostics: [...withoutFileRateWarning(file.diagnostics), ...plan.diagnostics, ...stale],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n } finally {\n await file.close();\n }\n}\n\n/*\n Files this tool produces, used to spot leftovers from an earlier conversion.\n\n The rate part has to allow every shape a filename can now take, not just the plain ones:\n\n signals_256hz.csv an integer rate\n signals_12_5hz.csv a fractional rate, decimal point written as an underscore\n signals_1_000e-7hz.csv a rate small enough to need exponent form (0.2.2)\n signals_256hz.csv.gz any of the above, compressed (0.3.0)\n signals_0hz_2.csv a second group whose rate slug collided (0.2.1)\n\n The previous `[\\w.]+hz` matched neither of the last two — `-` and `+` are not word\n characters, and the collision suffix falls after the `hz`. Both were introduced by\n recent changes and both silently stopped being recognised as this tool's own output, so\n leftovers of exactly those kinds went unreported: the one situation the warning exists\n for. Requiring a digit after the underscore keeps a user's own `signals_notes.csv` out.\n*/\nconst OUTPUT_PATTERN =\n /^(signals(_\\d[\\w.+-]*hz(_\\d+)?)?\\.csv(\\.gz)?|annotations\\.csv(\\.gz)?|channels\\.csv(\\.gz)?|metadata\\.json)$/u;\n\n/**\n * Detect output from a previous run that this one did not replace.\n *\n * `--force` overwrites files but does not empty the directory, so converting a\n * mixed-rate recording and then a single-rate one into the same place leaves\n * `signals_256hz.csv` sitting next to a fresh `signals.csv`. Both look current.\n * Nothing is deleted here — the user is told, and decides.\n */\nasync function findStaleOutput(\n outputDir: string,\n written: readonly WrittenFile[],\n): Promise<Diagnostic[]> {\n const entries = await readdir(outputDir).catch(() => null);\n if (!entries) return [];\n\n // metadata.json is rewritten on every run but is not part of the reported file list.\n const fresh = new Set([...written.map((f) => f.name), 'metadata.json']);\n const stale = entries.filter((name) => OUTPUT_PATTERN.test(name) && !fresh.has(name)).sort();\n if (stale.length === 0) return [];\n\n return [\n {\n code: 'STALE_OUTPUT',\n severity: 'warning',\n message:\n `${stale.join(', ')} ${stale.length === 1 ? 'is' : 'are'} left over from an earlier ` +\n `conversion into this directory and ${stale.length === 1 ? 'was' : 'were'} not rewritten.`,\n hint: 'Delete them, or convert into a fresh directory, so the two runs do not get mixed up.',\n },\n ];\n}\n\nexport function defaultOutputDir(inputPath: string): string {\n const base = path.basename(inputPath, path.extname(inputPath));\n return path.join(path.dirname(inputPath), `${base}_csv`);\n}\n\n/** Never let an output target resolve to the recording being read, even with --force. */\nasync function assertInputDoesNotOverlapOutputs(\n inputPath: string,\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n options: ConvertOptions,\n): Promise<void> {\n /*\n The names this run will actually write, compressed ones included.\n\n The rate files come from the plan and already carry `.csv.gz` under --gzip; the sidecars\n were spelled out here and did not. So a compressed run checked two names it would never\n write and missed the two it would: a recording sitting at <outdir>/channels.csv.gz was\n overwritten by its own conversion, with --force, reported as a success. The same file\n named signals.csv.gz was refused, which is what gives the oversight away.\n */\n const suffix = options.gzip === true ? '.csv.gz' : '.csv';\n const names = new Set(plan.groups.map((group) => group.fileName));\n if (file.annotationSignals.length > 0) names.add(`annotations${suffix}`);\n names.add(`channels${suffix}`);\n names.add('metadata.json');\n\n const inputResolved = path.resolve(inputPath);\n const inputInfo = await stat(inputPath);\n\n for (const name of names) {\n const target = path.join(outputDir, name);\n const targetResolved = path.resolve(target);\n const targetInfo = await stat(target).catch(() => null);\n const samePath = targetResolved === inputResolved;\n const sameFile =\n targetInfo !== null && targetInfo.dev === inputInfo.dev && targetInfo.ino === inputInfo.ino;\n if (!samePath && !sameFile) continue;\n\n throw new ConversionError(\n 'INPUT_OUTPUT_COLLISION',\n `Output file \"${target}\" is the same file as the input recording.`,\n 'Choose a separate directory with --out. The input was not modified.',\n );\n }\n}\n\nasync function prepareOutputDir(dir: string, force: boolean): Promise<void> {\n /*\n Claim the directory with a single atomic mkdir rather than asking whether it exists\n and then creating it.\n\n Checking first left a window between the two: two conversions started together both\n saw \"not there\", both proceeded, and both opened write streams on the same signals.csv.\n Neither reported anything — both exited 0, having half-written one file between them.\n A non-recursive mkdir cannot do that. Exactly one caller creates the directory; every\n other one gets EEXIST from the filesystem and takes the already-exists path below.\n\n Parents are still created recursively, since --out ./a/b/c should work. Only the final\n component is the claim.\n */\n const parent = path.dirname(dir);\n if (parent && parent !== dir) {\n try {\n await mkdir(parent, { recursive: true });\n } catch (cause: unknown) {\n // A parent that is a regular file surfaces as EEXIST naming the parent, which reads\n // as though the destination already exists rather than as \"you cannot put a\n // directory inside a file\". Name the real obstacle instead of the errno.\n const info = await stat(parent).catch(() => null);\n if (info && !info.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": \"${parent}\" is a file, not a directory.`,\n 'Choose a destination whose parent directories are directories, with --out.',\n );\n }\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n }\n\n let claimed = true;\n try {\n await mkdir(dir);\n } catch (cause: unknown) {\n if ((cause as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n claimed = false;\n }\n\n if (!claimed) {\n const existing = await stat(dir).catch(() => null);\n\n // --force means \"replace my previous output\", not \"write output files into\n // whatever this happens to be\". Pointing it at a regular file needs saying so.\n if (existing && !existing.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `\"${dir}\" is a file, but the converted data needs a directory.`,\n 'Choose a directory with --out.',\n );\n }\n if (!force) {\n throw new ConversionError(\n 'OUTPUT_EXISTS',\n `\"${dir}\" already exists.`,\n 'Pass --force to overwrite it, or --out to choose a different directory.',\n );\n }\n }\n}\n\n/** Turn a Node filesystem error into something a person can act on. */\nfunction describeFsError(cause: unknown): string {\n const code = (cause as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'EACCES' || code === 'EPERM') return 'permission denied';\n if (code === 'ENOSPC') return 'the disk is full';\n if (code === 'ENOTDIR') return 'part of the path is a file, not a directory';\n if (code === 'EROFS') return 'the filesystem is read-only';\n if (code === 'ENAMETOOLONG') return 'the path is too long';\n return cause instanceof Error ? cause.message : String(cause);\n}\n\nasync function writeSignalFiles(\n file: EdfFile,\n plan: ConversionPlan,\n outputDir: string | null,\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n onHangUp?: (hungUp: boolean) => void,\n): Promise<WrittenFile[]> {\n // One budget for every table in this conversion, not one per table: see OffsetBudget.\n const offsets = newOffsetBudget();\n // Likewise one for every channel, not one per channel: see SampleCacheBudget.\n const sampleCaches = newSampleCacheBudget();\n // Only the stdout path needs this; --out finds a full disk on its own, because it always\n // has another file to write afterwards. See auditStdout.\n const audit = outputDir === null ? auditStdout() : null;\n /*\n In the long layout every group writes into one table, so they share one stream. Opening\n a stream per group on the same path is what the rate-slug collision fix in groupByRate\n was about: two writers on one file interleave rows under a header naming one of them.\n */\n let shared: {\n stream: Writable;\n settled: Promise<void>;\n target: Writable;\n writer: BufferedLineWriter;\n } | null = null;\n\n /*\n One buffer's worth of memory for the conversion, not one per table.\n\n Every group had its own writer at the default 1 MiB threshold, so pending output was\n group count × 1 MiB before anything drained. A 6.5 MB recording with 40 sampling rates\n — the header allows thousands of channels, and a research montage really does mix a\n dozen rates — died with a raw V8 heap out-of-memory and exit 134 under a 96 MB cap,\n while the site advertises 48 MB. The recording is small; it is the fan-out that is not.\n\n Split evenly with a floor, so the single-rate case, which is nearly every recording,\n keeps exactly the buffer it always had, and forty tables cost 2.5 MB rather than 40.\n The long layout shares one writer already and is unaffected either way.\n */\n const MIN_FLUSH_THRESHOLD = 8 * 1024;\n const flushThreshold = Math.max(\n MIN_FLUSH_THRESHOLD,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n /*\n The stream's own buffer is the other half of the same sum.\n\n 0.5.6 shared the line-buffer budget and left `createWriteStream` at its 64 KiB default,\n which is per stream: 200 rate groups meant 12.8 MB of stream buffer on top of 12.8 MB of\n line buffer, and an 855 KB recording still died at a 48 MB cap. Shared the same way, with\n a floor that keeps an ordinary conversion writing in useful-sized pieces.\n */\n const streamBuffer = Math.max(\n 16 * 1024,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n\n const open: OpenGroup[] = plan.groups.map((group, groupIndex) => {\n // A null directory means the single table goes to stdout. process.stdout is already a\n // writable stream, so the same buffered writer and backpressure handling apply.\n const target =\n shared?.target ??\n (outputDir === null\n ? process.stdout\n : createWriteStream(path.join(outputDir, group.fileName), {\n highWaterMark: streamBuffer,\n }));\n const { stream, settled } = shared ?? compressed(target, options.gzip === true);\n /*\n Under --gzip the writer feeds the compressor, so its byte count is the CSV before\n compression and says nothing about what reached the descriptor. The compressor's own\n output is what stdout is handed, so that is what is counted. `pipe` uses a 'data'\n listener of its own and a second one is delivered the same chunks.\n\n Once per stream, which in the long layout is once for all the groups. Attaching per\n group put N listeners on the one shared compressor, so every chunk was counted N\n times: `--stdout --layout long --gzip` on a 40-rate recording claimed 622,240 of\n 622,240 bytes where 15,556 had been written, failed with a disk-full error over a\n perfectly good file, and printed Node's MaxListenersExceededWarning to stderr on the\n way past ten. 0.5.4 fixed the same arithmetic in the uncompressed branch and left\n this one, because the uncompressed branch is where the count is a sum and this one is\n where it is a subscription.\n */\n if (audit && stream !== target && !shared) {\n stream.on('data', (chunk: Buffer) => audit.count(chunk.length));\n }\n /*\n One writer, not one per group, when the table is shared. Separate writers over one\n stream each hold their own buffer and flush on their own schedule, so the rows would\n reach the file in whatever order the buffers filled — which is not the order they\n were produced in, and the long layout's whole claim is that its rows are in time\n order.\n */\n const writer = shared?.writer ?? new BufferedLineWriter(stream, flushThreshold);\n if (plan.layout === 'long' && !shared) shared = { stream, settled, target, writer };\n\n // Only the first group writes the header of a shared table, and the mark before it.\n if (plan.layout !== 'long' || groupIndex === 0) {\n if (options.bom === true) writer.push(UTF8_BOM);\n writer.pushLine(\n plan.layout === 'long'\n ? csvRow(['time_s', 'channel', 'value'])\n : csvRow(['time_s', ...group.channels.map((c) => c.column)]),\n );\n }\n return {\n group,\n writer,\n formatters: group.channels.map((c) => makeSampleFormatter(c.signal, c.decimals, sampleCaches)),\n formatTime: makeTimeFormatter(\n group.samplesPerRecord,\n group.rate,\n group.timeDecimals,\n offsets,\n ),\n rows: 0,\n settled,\n };\n });\n\n try {\n const written = await streamSignalRows(file, plan, open, recordStarts, options);\n\n /*\n Checked once everything has been flushed and ended, and not when the reader hung up.\n\n `--stdout | head -1` closes the pipe on purpose, which is a shell idiom rather than a\n failure. A pipe is not a regular file, so auditStdout declines it anyway — the second\n guard is here because the cost of getting this one wrong is reporting a failure for a\n command that worked.\n */\n if (audit) {\n // Uncompressed, the writer hands its bytes straight to the descriptor; compressed,\n // they were counted on the compressor's way out.\n if (options.gzip !== true) {\n /*\n Once per writer, not once per group. The long layout gives every group the same\n writer, so counting per group multiplied its byte total by the number of rates:\n `--stdout --layout long` on a three-rate recording handed over 32,043 bytes, was\n credited with 96,129, and failed with a disk-full error for a file that was\n complete on disk. It only showed with stdout redirected to a regular file, since\n that is the one case the audit applies to — which is the command the --layout\n documentation gives.\n */\n for (const writer of new Set(open.map((entry) => entry.writer))) {\n audit.count(writer.bytesOut);\n }\n }\n if (!open.some((entry) => entry.writer.hungUp)) audit.verify();\n }\n onHangUp?.(open.some((entry) => entry.writer.hungUp));\n return written;\n } catch (cause) {\n for (const entry of open) entry.writer.destroy();\n\n /*\n Reading and writing both fail through here, and both were reported as writing.\n\n A recording that shrinks mid-conversion — still being written by the acquisition\n software, say — raises the reader's own error, which names the record and says the\n file changed size while it was being read. That precise diagnosis was then filed under\n `Writing to \"<dir>\" failed` and given the hint about freeing disk space, which sends\n someone to look at the one part of the system that was working.\n\n The reader's message and its advice are kept; only the note about partial output is\n added, since that much is true of either failure.\n */\n // A ConversionError arrived already saying what went wrong — a callback that threw, say.\n // Wrapping it again turned \"the onProgress callback threw\" into `Writing to \"out\"\n // failed: the onProgress callback threw`, under a hint about checking the destination.\n if (cause instanceof ConversionError) throw cause;\n\n if (cause instanceof EdfError) {\n const where = outputDir === null ? 'stdout' : `\"${outputDir}\"`;\n throw new ConversionError(\n 'INPUT_UNREADABLE',\n cause.message,\n `${cause.hint ? `${cause.hint} ` : ''}What was written to ${where} before it failed is ` +\n `incomplete and should not be used.`,\n );\n }\n\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to ${outputDir === null ? 'stdout' : `\"${outputDir}\"`} failed: ${detail}`,\n writeHint(cause, outputDir === null),\n );\n }\n}\n\n/**\n * One record's samples in the long layout: `time_s,channel,value`, in time order.\n *\n * The groups are merged rather than written one after another. Every sample in a record\n * falls inside that record's span, so taking the earliest next sample across the groups\n * each time leaves the whole file sorted by `time_s` — which is the only thing that makes a\n * mixed-rate long table useful, since sorting 3 million rows afterwards is the reader's\n * problem and a large one.\n *\n * Ties go to the group with the higher rate, which is the order the groups are already in.\n * Within a sample time the channels come out in the order the file declares them.\n */\n/**\n * Whether two sample times are close enough that only rounding could separate them.\n *\n * A relative epsilon, because the gap between doubles grows with magnitude. Well below any\n * real sample interval — the finest a recording can declare is bounded by its record\n * duration and samples-per-record fields — and well above the one-ULP disagreement that two\n * exact divisions of the same instant produce.\n */\nfunction nearlyEqual(a: number, b: number): boolean {\n return Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 1e-12;\n}\n\n/** The chosen instant as it will be written, for deciding which channels share it. */\nfunction formatAt(\n open: readonly OpenGroup[],\n cursors: Int32Array,\n recordStart: number,\n earliest: number,\n): string {\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n if (recordStart + sample / entry.group.rate === earliest) {\n return entry.formatTime(recordStart, sample);\n }\n }\n return '';\n}\n\nasync function writeLongRecord(\n file: EdfFile,\n open: readonly OpenGroup[],\n batch: RecordBatch,\n recordInBatch: number,\n recordStart: number,\n range: ConversionPlan['range'],\n): Promise<void> {\n const writer = open[0]?.writer;\n if (!writer) return;\n\n const cursors = new Int32Array(open.length);\n /* Reused across sample times so a three-million-row conversion allocates one of these. */\n const due: { entry: OpenGroup; groupIndex: number; sample: number; channel: number }[] = [];\n\n for (;;) {\n let earliest = Infinity;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time < earliest) earliest = time;\n }\n if (earliest === Infinity) return;\n\n /*\n Everything at this instant, in the order the file declares its channels.\n\n Groups are ordered by rate, largest first, because that is how the wide layout names\n its files. Emitting a tie group by group therefore ordered the channels by descending\n sampling rate — so a recording declaring `slow, medium, fast` wrote `fast, medium,\n slow`, while the documentation promised file order and channels.csv listed file order.\n Signal index is the file's own order, and the only one a reader can predict.\n\n \"At this instant\" is decided on the time as written, not on the double. `s / rate` for\n two channels at one moment need not give the same double: a 0.3 s record holding 12 and\n 4 samples makes 40 Hz and 13.333… Hz, and 9/40 is 0.22500000000000000555 while 3/13.333…\n is 0.22499999999999997780. Equality missed that, so those two rows fell out in numeric\n order — `slow` before `fast`, once, in the middle of a file that was otherwise right.\n\n Two rows are at one time exactly when they carry the same `time_s`, which is the only\n definition a reader of the CSV can apply. The numeric pre-filter keeps the common case\n to one comparison; formatting happens only for candidates already within a hair.\n */\n const earliestText = formatAt(open, cursors, recordStart, earliest);\n due.length = 0;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time !== earliest) {\n // Far away in the ordinary case; only a near-tie is worth formatting.\n if (!nearlyEqual(time, earliest)) continue;\n if (entry.formatTime(recordStart, sample) !== earliestText) continue;\n }\n cursors[g] = sample + 1;\n // Same window rule as the wide layout, with the same per-rate slack.\n if (\n !sampleTimeIsInRange(earliest, range.startSeconds, range.endSeconds, toleranceFor(entry.group.rate))\n ) {\n continue;\n }\n for (let c = 0; c < entry.group.channels.length; c++) {\n due.push({ entry, groupIndex: g, sample, channel: c });\n }\n }\n if (due.length === 0) continue;\n if (due.length > 1) {\n due.sort(\n (a, b) =>\n (a.entry.group.channels[a.channel]?.signal.index ?? 0) -\n (b.entry.group.channels[b.channel]?.signal.index ?? 0),\n );\n }\n\n for (const item of due) {\n const channel = item.entry.group.channels[item.channel];\n const format = item.entry.formatters[item.channel];\n if (!channel || !format) continue;\n if (writer.hungUp) return;\n writer.pushLine(\n `${item.entry.formatTime(recordStart, item.sample)},${escapeCsvField(channel.column)},` +\n `${format(file.sampleAt(batch, recordInBatch, channel.signal, item.sample))}`,\n );\n item.entry.rows++;\n // Flushed inside the record for the same reason the wide layout is; see there.\n if (writer.full) await writer.flush();\n }\n }\n}\n\nasync function streamSignalRows(\n file: EdfFile,\n plan: ConversionPlan,\n open: OpenGroup[],\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n): Promise<WrittenFile[]> {\n const { startSeconds, endSeconds, startRecord, endRecord } = plan.range;\n const { recordDuration } = file.header;\n let recordsDone = 0;\n\n /*\n Every destination has hung up, so nothing formatted from here on could reach anyone.\n `--stdout | head -1` is the usual way to arrive here: the reader takes one line and\n closes the pipe while the conversion is still near the start of the recording.\n\n Stopping also makes the reported row count mean what it says — rows that reached the\n consumer, rather than rows the loop went on formatting into a discarded buffer.\n */\n const allHungUp = (): boolean => open.length > 0 && open.every((entry) => entry.writer.hungUp);\n\n for await (const batch of file.readRecords({ startRecord, endRecord })) {\n if (allHungUp()) break;\n for (let r = 0; r < batch.recordCount; r++) {\n if (allHungUp()) break;\n const index = batch.firstRecordIndex + r;\n const recordStart = recordStarts ? (recordStarts[index] ?? index * recordDuration) : index * recordDuration;\n\n if (plan.layout === 'long') {\n await writeLongRecord(file, open, batch, r, recordStart, plan.range);\n recordsDone++;\n continue;\n }\n\n for (const entry of open) {\n const { group, writer, formatters, formatTime } = entry;\n const { channels, rate } = group;\n // Slack that never reaches the next sample; see toleranceFor.\n const slack = toleranceFor(rate);\n\n for (let sample = 0; sample < group.samplesPerRecord; sample++) {\n const time = recordStart + sample / rate;\n if (!sampleTimeIsInRange(time, startSeconds, endSeconds, slack)) continue;\n\n let row = formatTime(recordStart, sample);\n for (let c = 0; c < channels.length; c++) {\n const channel = channels[c];\n const format = formatters[c];\n if (!channel || !format) continue;\n row += ',' + format(file.sampleAt(batch, r, channel.signal, sample));\n }\n if (writer.hungUp) break;\n writer.pushLine(row);\n entry.rows++;\n\n /*\n Flushed inside the record, not only at the end of one.\n\n The buffer was drained once per record, so the rows of a single record piled up\n with nothing emptying them — memory followed samples-per-record rather than the\n batch size the writer exists to hold to. One record of 16,000,000 samples died\n with a heap out of memory under a 256 MB cap, while the same 32 MB of samples\n split into 16,000 records converted to the same 283 MB CSV without trouble. The\n format allows either layout and says nothing about which to expect.\n\n `full` is a synchronous read of the pending size, so the twenty million rows that\n are not at a boundary cost a comparison rather than a microtask each.\n */\n if (writer.full) await writer.flush();\n }\n await writer.maybeFlush();\n }\n\n recordsDone++;\n }\n\n if (options.onProgress) {\n /*\n A caller's callback is the caller's, and its failures are not the destination's.\n\n This ran inside the same try that turns a stream failure into WRITE_FAILED, so a\n progress callback that threw came back as `Writing to \"out\" failed: caller bug`,\n advising the reader to check a destination that was working perfectly. It is the\n same misattribution the write hints had until 0.4.36, one layer up.\n\n The original is kept as `cause`, so the stack that actually matters survives, and\n the conversion still stops — the callback threw, and carrying on writing into a\n directory whose owner has just failed is not an improvement.\n */\n try {\n options.onProgress({\n recordsDone,\n recordsTotal: endRecord - startRecord,\n // Once per writer: the long layout's groups share one, so summing per group\n // reported a figure larger than the file being written.\n bytesWritten: [...new Set(open.map((entry) => entry.writer))].reduce(\n (sum, entry) => sum + entry.charsWritten,\n 0,\n ),\n });\n } catch (cause) {\n throw new ConversionError(\n 'CALLBACK_FAILED',\n `The onProgress callback threw: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'This is the caller\\'s callback, not the recording or the destination. Whatever was ' +\n 'written before it threw is incomplete and should not be used.',\n { cause },\n );\n }\n }\n }\n\n const closed = new Set<BufferedLineWriter>();\n for (const entry of open) {\n if (closed.has(entry.writer)) continue;\n closed.add(entry.writer);\n await entry.writer.end();\n // With --gzip the writer's stream is the compressor, whose end callback fires when the\n // compressor is done rather than when the file behind it is. Awaiting only that would\n // report success with the tail of the file still in flight.\n await entry.settled;\n }\n\n // A shared table is one file, and its row count is every group's rows, not the first's.\n if (plan.layout === 'long') {\n const first = open[0];\n if (!first) return [];\n return [{ name: first.group.fileName, rows: open.reduce((sum, e) => sum + e.rows, 0) }];\n }\n return open.map((entry) => ({ name: entry.group.fileName, rows: entry.rows }));\n}\n\n/**\n * The stream rows are written to, plus a promise for the data reaching its destination.\n *\n * Compression sits between the writer and the file as a transform. Failures below it — a\n * full disk, an unwritable path — surface on the file stream, where nothing is listening,\n * so they are forwarded onto the compressor: that is the stream the writer watches, and\n * routing them there keeps one error path rather than two.\n */\nfunction compressed(target: Writable, gzip: boolean): { stream: Writable; settled: Promise<void> } {\n if (!gzip) return { stream: target, settled: Promise.resolve() };\n const compressor = createGzip();\n target.on('error', (error: Error) => compressor.destroy(error));\n\n /*\n pipe() ends its destination when the source ends, and stdout must survive the\n conversion: the writer already refuses to close it, because a closed stdout breaks\n every later write in the process. Nothing waits on stdout either — it is not this\n tool's to finish.\n */\n const toStdout = target === process.stdout;\n compressor.pipe(target, { end: !toStdout });\n if (toStdout) return { stream: compressor, settled: Promise.resolve() };\n\n /*\n A failure under the compressor rejects both this promise and the writer's own end(),\n and end() is the one awaited first. Without a handler attached here that rejection\n belonged to nobody, and Node killed the process over it: `--gzip` into an unwritable\n path printed a raw EISDIR stack trace instead of the ordinary \"Writing to ... failed\"\n message and exit 1, which is what the same path does uncompressed.\n\n Attaching the handler is enough to make it handled. Awaiting `settled` downstream still\n reports the failure in the case where end() happened to succeed.\n */\n const settled = finished(target);\n settled.catch(() => {});\n return { stream: compressor, settled };\n}\n\n/**\n * Write one of the sidecar files, reporting a failure the way the signal writer does.\n *\n * These three used to call `writeFile` bare, so a failure escaped as whatever the\n * filesystem said — `EISDIR: illegal operation on a directory, open '...'` with no hint\n * and, more importantly, no mention that the signal files had already been written. The\n * conversion stopped half-done and the message gave no sign of it.\n */\nasync function writeOutputFile(\n outputDir: string,\n name: string,\n contents: string,\n gzip = false,\n bom = false,\n): Promise<void> {\n // metadata.json never gets one: JSON.parse rejects a leading U+FEFF, so a mark there\n // would break every reader of the file to help a spreadsheet that will not open it.\n if (bom) contents = UTF8_BOM + contents;\n try {\n // The sidecars are built in memory before being written, so compressing them in memory\n // costs nothing extra. Only the signal tables are large enough to need a stream.\n await writeFile(path.join(outputDir, name), gzip ? gzipSync(contents) : contents, gzip ? undefined : 'utf8');\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing \"${name}\" to \"${outputDir}\" failed: ${detail}`,\n writeHint(cause),\n );\n }\n}\n\nasync function writeChannelsCsv(\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const includedColumns = new Set(plan.groups.flatMap((g) => g.channels.map((c) => c.signal.index)));\n const fileFor = new Map<number, string>();\n for (const group of plan.groups) {\n for (const channel of group.channels) fileFor.set(channel.signal.index, group.fileName);\n }\n\n const lines = [\n csvRow([\n 'column',\n 'signal_index',\n 'label',\n 'unit',\n 'sampling_rate_hz',\n 'samples_per_record',\n 'physical_min',\n 'physical_max',\n 'digital_min',\n 'digital_max',\n 'transducer',\n 'prefiltering',\n 'output_file',\n 'converted',\n ]),\n ];\n\n for (const signal of file.header.signals) {\n if (signal.isAnnotations) continue;\n const column = plan.columnNames.get(signal.index) ?? `signal_${signal.index}`;\n lines.push(\n csvRow([\n column,\n String(signal.index),\n signal.label,\n signal.physicalDimension,\n String(signal.samplingRate),\n String(signal.samplesPerRecord),\n String(signal.physicalMin),\n String(signal.physicalMax),\n String(signal.digitalMin),\n String(signal.digitalMax),\n signal.transducer,\n signal.prefiltering,\n fileFor.get(signal.index) ?? '',\n includedColumns.has(signal.index) ? 'yes' : 'no',\n ]),\n );\n }\n\n const name = gzip ? 'channels.csv.gz' : 'channels.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: lines.length - 1 };\n}\n\n/*\n The bounds an annotation is filtered against: what the caller actually asked for, not the\n window that survived being clamped to the recording.\n\n Filtering by the resolved window meant an unbounded request still lost events. `--end 999h`\n on a three-second file clamps to 3, and `--start 0` gets its end from the recording, so\n both filtered to [0, 3) and dropped the markers at 3.0 and 3.5 that a bare invocation\n keeps — asking for more of a recording returned less of it.\n\n An end the caller did not give is unbounded, not \"the end of the data\": EDF+ lets an\n annotation sit past the last sample, which is exactly where an end-of-recording marker is.\n\n `--duration` is measured from wherever the conversion actually starts. Anchoring it at 0\n instead read the same absent `--start` two ways in two adjacent lines, and on a recording\n that does not begin at zero the two windows did not even overlap: an EDF+D file whose first\n record sits at 30 s converted its samples from [30, 35) while filtering annotations against\n (-inf, 5), so every event inside the converted window was dropped and `annotations.csv` came\n back empty. `resolveRange` has always defaulted the same missing start to the earliest\n record, which is where this now takes it from.\n*/\nfunction requestedAnnotationWindow(\n options: ConvertOptions,\n recordingStart: number,\n): { from: number; to: number } {\n const from = options.start ?? -Infinity;\n const to =\n options.end !== undefined\n ? options.end\n : options.duration !== undefined\n ? (options.start ?? recordingStart) + options.duration\n : Infinity;\n return { from, to };\n}\n\nasync function writeAnnotationsCsv(\n outputDir: string,\n annotations: readonly Annotation[],\n window: { from: number; to: number },\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const inWindow = annotations\n .filter((a) => a.onset >= window.from && a.onset < window.to)\n .sort((a, b) => a.onset - b.onset || a.recordIndex - b.recordIndex);\n\n const lines = [csvRow(['onset_s', 'duration_s', 'description', 'record_index'])];\n for (const annotation of inWindow) {\n lines.push(\n csvRow([\n String(annotation.onset),\n annotation.duration === null ? '' : String(annotation.duration),\n annotation.text,\n String(annotation.recordIndex),\n ]),\n );\n }\n\n const name = gzip ? 'annotations.csv.gz' : 'annotations.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: inWindow.length };\n}\n\n/**\n * What to try next, chosen from what actually went wrong.\n *\n * Every write failure carried the same advice — \"Free up space or choose another destination\n * with --out\" — which fits exactly one errno. A directory sitting where signals.csv belongs\n * came back telling the reader to free up disk space, and so did a read-only volume, a\n * permission denial and a path too long for the filesystem. Wrong advice is worse than none:\n * it sends someone to check `df` on a disk that is fine, and the thing that is actually\n * wrong stays unexamined.\n *\n * The errno is the one piece of the failure that names the cause, so it is what picks the\n * sentence. Anything unrecognised keeps the general form rather than guessing.\n */\nfunction writeHint(cause: unknown, toStdout = false): string {\n /*\n The stdout path writes no files, and --out is the flag its user chose not to pass.\n\n Both halves of this sentence were wrong there: \"the files written so far\" named files\n that do not exist, and \"choose another destination with --out\" is advice for a different\n command — the destination is whatever the shell redirected the stream to. Same class as\n the disk-space hint this function replaced, one flag over.\n */\n const preamble = toStdout\n ? 'What reached stdout before it failed is incomplete and should not be used. '\n : 'The files written so far are incomplete and should not be used. ';\n const elsewhere = toStdout ? 'redirect it somewhere else' : 'choose another with --out';\n const code = (cause as NodeJS.ErrnoException | null)?.code;\n switch (code) {\n case 'ENOSPC':\n return `${preamble}The destination is out of space; free some up or ${elsewhere}.`;\n case 'EDQUOT':\n return `${preamble}You are over your disk quota on this filesystem; ${elsewhere}.`;\n case 'EACCES':\n case 'EPERM':\n return `${preamble}You do not have permission to write there; ${elsewhere}.`;\n case 'EROFS':\n return `${preamble}That filesystem is mounted read-only; ${elsewhere}.`;\n case 'EISDIR':\n return `${preamble}A directory is sitting where that file belongs; remove or rename it, or ${elsewhere}.`;\n case 'ENOENT':\n return `${preamble}Part of that path no longer exists; make sure nothing is removing it while the conversion runs.`;\n case 'ENAMETOOLONG':\n return `${preamble}That path is longer than the filesystem allows; ${toStdout ? 'redirect it somewhere shorter' : 'choose a shorter destination with --out'}.`;\n case 'EMFILE':\n case 'ENFILE':\n return `${preamble}Too many files are open; a recording with many sampling rates opens one output file per rate, so --channels narrows it.`;\n case 'EPIPE':\n return `${preamble}Whatever was reading the output closed it before the conversion finished.`;\n default:\n return `${preamble}Check the destination and run the conversion again.`;\n }\n}\n\n/**\n * Confirms that everything handed to a file-backed stdout actually arrived.\n *\n * `edf2csv rec.edf --stdout > out.csv` onto a volume that the output very nearly fills lost\n * the tail in silence: 94,977 of 102,400 rows on disk, the file ending mid-row, stderr\n * announcing \"Wrote 102,400 rows to stdout.\" and the process exiting 0. The same recording\n * onto the same volume through `--out` fails correctly, which is what gives it away.\n *\n * POSIX `write` returns a short count rather than an error when the disk fills partway\n * through a single call, and only the NEXT write raises ENOSPC. `--out` always has a next\n * write — channels.csv and metadata.json come after — so it always finds out. `--stdout`\n * has nothing after it, and when fd 1 is a regular file Node's stdout is a SyncWriteStream\n * whose `_write` discards the byte count `writeSync` returns, so nothing is raised at all.\n * No error means no `#failure`, so checking that alone would not have caught this.\n *\n * What can be checked is the descriptor: how much it grew against how much it was given.\n * Only for a regular file — a pipe, a terminal or a socket has no size to compare, and on\n * those a short write cannot go unreported this way. Appending (`>>`) is fine, since the\n * starting size is taken before anything is written.\n */\nfunction auditStdout(): { count: (bytes: number) => void; verify: () => void } | null {\n let startSize: number;\n try {\n const info = fstatSync(1);\n if (!info.isFile()) return null;\n startSize = info.size;\n } catch {\n // No usable descriptor to audit; the conversion is not the place to complain about it.\n return null;\n }\n\n let expected = 0;\n return {\n count: (bytes: number): void => {\n expected += bytes;\n },\n verify: (): void => {\n let landed: number;\n try {\n landed = fstatSync(1).size - startSize;\n } catch {\n return;\n }\n if (landed >= expected) return;\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to stdout failed: ${expected - landed} of ${expected} bytes did not reach ` +\n `the destination, which stopped accepting them part way through.`,\n 'What is there ends mid-row and should not be used. The destination is almost ' +\n 'certainly out of space — a short write is how a filesystem reports filling up ' +\n 'mid-write, and nothing after it raised an error because there was nothing after it.',\n );\n },\n };\n}\n\n/** Data rows across every signal table, so \"nothing was written\" is one question. */\nfunction rowsIn(written: readonly WrittenFile[]): number {\n return written.reduce((total, file) => total + file.rows, 0);\n}\n\n\n/** Raised when the input moved while it was being read. See where it is pushed. */\nfunction inputChanged(hadChecksum: boolean): Diagnostic {\n return {\n code: 'INPUT_CHANGED',\n severity: 'warning',\n message:\n 'The input changed while it was being converted, so this output covers the file as ' +\n 'it was when the conversion started, not as it is now.',\n hint: hadChecksum\n ? 'No checksum was recorded: the bytes that were converted are no longer there to ' +\n 'hash. Convert again once the recording is finished.'\n : 'Convert again once the recording is finished to pick up the rest.',\n };\n}\n\nasync function writeMetadata(\n outputDir: string,\n inputPath: string,\n file: EdfFile,\n plan: ConversionPlan,\n written: readonly WrittenFile[],\n annotationCount: number,\n /** Hash of the bytes that were converted, or null when it could not be vouched for. */\n checksum: string | null,\n): Promise<void> {\n const { header } = file;\n\n /*\n The file as it was when it was opened, not as it is now.\n\n Both of these used to come from re-opening the path once the CSVs were written, which\n describes whatever answers to that name by then rather than what was converted. A\n recording still being written grew from 2,000 records to 3,000 mid-conversion and\n metadata.json recorded `data_records: 2000` — correct, the CSV holds 2,000 — beside the\n byte count and SHA-256 of the 3,000-record file. The two halves of one provenance record\n described two different files, and the checksum covered bytes nobody had converted.\n Replacing the file at that path did the same thing more thoroughly.\n\n `file.fileSize` is the number every record count and window in this output was derived\n from, and the hash is taken over exactly those bytes through the descriptor already open\n on them, so the record describes one file throughout.\n */\n const metadata = {\n tool: { name: 'edf2csv', version: TOOL_VERSION },\n source: {\n path: path.resolve(inputPath),\n bytes: file.fileSize,\n modified: new Date(file.modifiedAtOpenMs).toISOString(),\n sha256: checksum,\n },\n recording: {\n format: describeFormat(header),\n version: header.version,\n patient_id: header.patientId,\n recording_id: header.recordingId,\n // Zone-less on purpose: EDF records local wall-clock digits and no timezone.\n start_datetime_local: formatWallClock(header.startDateTime),\n start_date_raw: header.startDateRaw,\n start_time_raw: header.startTimeRaw,\n data_records: file.recordCount,\n data_records_declared: header.declaredRecordCount,\n record_duration_seconds: header.recordDuration,\n duration_seconds: file.durationSeconds,\n signal_count: header.signalCount,\n annotation_channels: file.annotationSignals.length,\n },\n conversion: {\n converted_at: new Date().toISOString(),\n start_seconds: plan.range.startSeconds,\n end_seconds: plan.range.endSeconds,\n whole_recording: plan.range.isWholeRecording,\n records_converted: [plan.range.startRecord, plan.range.endRecord],\n annotations_written: annotationCount,\n files: written.map((f) => ({ name: f.name, rows: f.rows })),\n rate_groups: plan.groups.map((g) => ({\n file: g.fileName,\n sampling_rate_hz: g.rate,\n channels: g.channels.map((c) => c.column),\n decimals: g.channels.map((c) => c.decimals),\n })),\n },\n notes: [...withoutFileRateWarning(file.diagnostics), ...plan.diagnostics].map((d) => ({\n code: d.code,\n severity: d.severity,\n message: d.message,\n })),\n };\n\n await writeOutputFile(outputDir, 'metadata.json', JSON.stringify(metadata, null, 2) + '\\n');\n}\n\n\n"]}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/convert/run.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executing a conversion.\n *\n * Everything is written in a single pass over the data records. All rate groups are\n * open at once and fed from the same batch of bytes, so a file is read once no\n * matter how many output tables it produces, and memory stays flat.\n */\n\nimport { createWriteStream, fstatSync } from 'node:fs';\nimport { mkdir, readdir, stat, writeFile } from 'node:fs/promises';\nimport { createHash } from 'node:crypto';\nimport { createReadStream } from 'node:fs';\nimport { finished, pipeline } from 'node:stream/promises';\nimport type { Writable } from 'node:stream';\nimport { createGzip, gzipSync } from 'node:zlib';\nimport path from 'node:path';\n\nimport type { RecordBatch } from '../edf/reader.js';\nimport { EdfFile } from '../edf/reader.js';\nimport { describeFormat, formatRates, formatWallClock } from '../edf/header.js';\nimport type { Diagnostic } from '../edf/errors.js';\nimport { EdfError } from '../edf/errors.js';\nimport type { Annotation } from '../edf/annotations.js';\nimport {\n BufferedLineWriter,\n DEFAULT_FLUSH_THRESHOLD,\n UTF8_BOM,\n csvRow,\n escapeCsvField,\n} from '../format/csv.js';\nimport { listed } from '../format/list.js';\nimport {\n fixed,\n makeSampleFormatter,\n makeTimeFormatter,\n newOffsetBudget,\n newSampleCacheBudget,\n} from '../format/number.js';\nimport type { SampleFormatter } from '../format/number.js';\nimport { buildPlan, withoutFileRateWarning } from './plan.js';\nimport type { ConversionPlan, PlanOptions, RateGroup } from './plan.js';\nimport { deriveRecordStarts } from './timing.js';\nimport { sampleTimeIsInRange, toleranceFor } from './time-range.js';\nimport { VERSION as TOOL_VERSION } from '../version.js';\n\nexport { TOOL_VERSION };\n\nexport type ConversionErrorCode =\n | 'OUTPUT_EXISTS'\n | 'OUTPUT_UNWRITABLE'\n | 'INPUT_OUTPUT_COLLISION'\n | 'INPUT_UNREADABLE'\n | 'UNSUPPORTED_REQUEST'\n | 'CALLBACK_FAILED'\n | 'WRITE_FAILED';\n\n/**\n * Codes that mean the command cannot be carried out as written, rather than that something\n * about the file or the destination went wrong.\n *\n * The distinction is the one the exit codes draw: 1 is \"the file or the destination is the\n * problem\", 2 is \"the command line is the problem\". A caller with a `--stdout` conflict is\n * being told to change the flags — the hints say exactly that — so filing it under 1 sent\n * scripts looking at the disk. Exit 2 already covers checks that need the header first,\n * such as a `--channels` term matching nothing.\n */\nexport const USAGE_ERROR_CODES: ReadonlySet<ConversionErrorCode> = new Set(['UNSUPPORTED_REQUEST']);\n\nexport class ConversionError extends Error {\n readonly code: ConversionErrorCode;\n readonly hint: string | undefined;\n constructor(code: ConversionErrorCode, message: string, hint?: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ConversionError';\n this.code = code;\n this.hint = hint;\n }\n}\n\nexport interface ConvertOptions extends PlanOptions {\n /** Destination directory. Defaults to the input file's name without its extension. */\n outputDir?: string | undefined;\n /** Overwrite an existing output directory. */\n force?: boolean | undefined;\n /** Record a SHA-256 of the input in metadata.json. Costs one extra read of the file. */\n checksum?: boolean | undefined;\n /**\n * Write the signal CSV to stdout instead of to a directory.\n *\n * Only valid when the conversion produces exactly one signal file. In the default wide\n * layout a mixed-rate recording becomes several tables, and merging them into one stream\n * would mean inventing the samples this tool exists not to invent; `layout: 'long'` gives\n * one table for any recording, so it lifts the restriction. No sidecar files are written.\n */\n toStdout?: boolean | undefined;\n onProgress?: ((progress: ConversionProgress) => void) | undefined;\n}\n\nexport interface ConversionProgress {\n recordsDone: number;\n recordsTotal: number;\n bytesWritten: number;\n}\n\nexport interface WrittenFile {\n name: string;\n rows: number;\n}\n\nexport interface ConvertResult {\n outputDir: string;\n files: WrittenFile[];\n /**\n * True when a `--stdout` reader closed the pipe before the conversion finished.\n *\n * `edf2csv rec.edf --stdout | head -1` is an ordinary thing to type and not a failure, but\n * it is also not a conversion: the row count is rows formatted before the close was\n * noticed, which is neither the recording's total nor what the reader received.\n */\n readerHungUp: boolean;\n annotationCount: number;\n diagnostics: Diagnostic[];\n plan: ConversionPlan;\n file: EdfFile;\n elapsedMs: number;\n}\n\ninterface OpenGroup {\n group: RateGroup;\n writer: BufferedLineWriter;\n formatters: SampleFormatter[];\n formatTime: (recordStart: number, sample: number) => string;\n rows: number;\n /** Resolves once a compressed stream's bytes have reached the file behind it. */\n settled: Promise<void>;\n}\n\nexport async function convert(inputPath: string, options: ConvertOptions = {}): Promise<ConvertResult> {\n const startedAt = Date.now();\n const file = await EdfFile.open(inputPath);\n\n try {\n /*\n Hashed before a record is read, and only published if the file held still.\n\n A checksum taken afterwards cannot be trusted whichever descriptor it goes through. A\n file overwritten in place keeps its inode, so the open handle sees the new bytes too,\n and the old ones are simply gone — there is nowhere left to read what was converted.\n Taking it first at least means the hash describes the file the header was read from.\n\n What makes it a guarantee rather than a hope is the check at the end: if size or\n modification time moved at any point, the hash is dropped and the run says why. So\n `sha256` present means the file demonstrably did not change while it was read, and a\n recording still being written gets a null and a warning instead of a plausible hash of\n the wrong bytes. (A change reverted within the same modification timestamp would slip\n through; nothing short of copying the input first can close that.)\n */\n const checksumAtOpen = options.checksum === true ? await file.sha256() : null;\n\n // One pass over the annotation channel supplies everything annotation-related:\n // where each record sits in time, and the full event list. It reads the whole\n // file even when a window was requested, because an annotation inside the window\n // may be stored in a record outside it.\n const annotationData =\n file.annotationSignals.length > 0\n ? await file.readAnnotations()\n : { annotations: [] as Annotation[], recordStarts: [] as (number | null)[], malformed: 0 };\n\n const timing = deriveRecordStarts(file, annotationData);\n\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 options,\n );\n plan.diagnostics.push(...timing.diagnostics);\n\n if (options.toStdout === true) {\n if (!plan.writeSignals) {\n throw new ConversionError(\n 'UNSUPPORTED_REQUEST',\n '--stdout has no signal data to write because --annotations-only was given.',\n 'Drop one of the two flags.',\n );\n }\n /*\n No table at all is its own answer, and neither layout gave it.\n\n A recording with no signal channels — one holding only EDF+ annotations — produced\n zero rate groups. The wide layout then said \"--stdout needs exactly one table, but\n this recording produces 0, one for each sampling rate its channels use ()\", with an\n empty parenthetical, advice to narrow to one of no rates, and advice to use\n `--layout long` — which wrote zero bytes to stdout, not even a header row, and exited\n 0 while warning that \"the signal files hold their headers and no data\". There were no\n files and there was no header. The one path that was right about this is\n `--annotations-only`, which refuses outright, and this is the same situation reached\n by a different route.\n */\n if (plan.groups.length === 0) {\n throw new ConversionError(\n 'UNSUPPORTED_REQUEST',\n file.dataSignals.length === 0\n ? '--stdout has no signal data to write: this recording has no signal channels, ' +\n 'only EDF+ annotations.'\n : '--stdout has no signal data to write: nothing was selected that carries samples.',\n file.dataSignals.length === 0\n ? 'Convert to a directory to get its annotations.csv, or drop --stdout.'\n : 'Check --channels and the requested window, or convert to a directory instead.',\n );\n }\n\n // The long layout is one table whatever the rates are, so it has nothing to refuse.\n if (plan.layout !== 'long' && plan.groups.length !== 1) {\n throw new ConversionError(\n 'UNSUPPORTED_REQUEST',\n // Naming the rates is the point: the hint says to narrow the selection, and this\n // is what there is to narrow it to. The parenthetical used to repeat the count\n // that had just been given — \"produces 3 (its channels use 3 different sampling\n // rates)\" — which told nobody anything they could act on.\n `--stdout needs exactly one table, but this recording produces ${plan.groups.length}, ` +\n `one for each sampling rate its channels use ` +\n `(${listed(formatRates(plan.groups.map((g) => g.rate)).map((r) => `${r} Hz`))}).`,\n 'Narrow it to one rate with --channels, write --layout long to get them all in ' +\n 'one table, or convert to a directory instead.',\n );\n }\n let readerHungUp = false;\n const written = await writeSignalFiles(file, plan, null, timing.starts, options, (hungUp) => {\n readerHungUp = hungUp;\n });\n if (await file.changedSinceOpen()) plan.diagnostics.push(inputChanged(false));\n return {\n outputDir: '-',\n files: written,\n readerHungUp,\n annotationCount: 0,\n diagnostics: [...withoutFileRateWarning(file.diagnostics), ...plan.diagnostics],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n }\n\n const outputDir = options.outputDir ?? defaultOutputDir(inputPath);\n await assertInputDoesNotOverlapOutputs(inputPath, outputDir, file, plan, options);\n await prepareOutputDir(outputDir, options.force === true);\n\n const written: WrittenFile[] = [];\n\n if (plan.writeSignals && plan.groups.length > 0) {\n const signals = await writeSignalFiles(file, plan, outputDir, timing.starts, options);\n written.push(...signals);\n } else if (plan.writeSignals) {\n /*\n Asked for signal data and given none to write.\n\n Every channel selected carries zero samples per record, so there is no table to make\n — `edf2csv rec.edf --channels unused` writes channels.csv and metadata.json and no\n signals.csv at all. The NO_SAMPLES warning explains the channel; nothing explained the\n missing file, and the documentation says signals.csv is written unless\n --annotations-only was passed. Someone looking for it should be told where it went.\n\n There are two ways to arrive with no groups, and the wording above is only true of\n one of them. A recording that holds nothing but EDF+ annotations has no channel that\n could have been selected, its channels.csv is a header row and nothing else, and no\n channel of it carries samples — so \"every channel selected\", \"channels.csv still\n describes them\" and \"which channels do carry samples\" were three false statements in\n one warning, printed under a warning that had just said the file has no signal\n channels. `--stdout` distinguishes the two cases a few lines up and `--info` prints\n one accurate line; this path was the only one that did not.\n */\n const noChannelsAtAll = file.dataSignals.length === 0;\n plan.diagnostics.push({\n code: 'NO_SAMPLES',\n severity: 'warning',\n message: noChannelsAtAll\n ? 'No signal file was written: there is no signal data in this recording to put in ' +\n 'one.'\n : 'No signal file was written: every channel selected carries zero samples per data ' +\n 'record, so there is nothing to put in one.',\n hint: noChannelsAtAll\n ? 'annotations.csv holds whatever events it carries. channels.csv lists signal ' +\n 'channels, so it has none to list.'\n : 'channels.csv still describes them. Run with --info to see which channels do carry ' +\n 'samples.',\n });\n }\n\n if (options.annotationsOnly === true && file.annotationSignals.length === 0) {\n plan.diagnostics.push({\n code: 'NO_ANNOTATIONS',\n severity: 'warning',\n message:\n '--annotations-only was requested but this recording has no annotation channel, ' +\n 'so there are no events to export.',\n hint: 'Plain EDF files carry no annotations. Convert without --annotations-only to get the signals.',\n });\n }\n\n let annotationsWritten = 0;\n if (file.annotationSignals.length > 0) {\n const result = await writeAnnotationsCsv(\n outputDir,\n annotationData.annotations,\n requestedAnnotationWindow(options, plan.range.recordingStartSeconds),\n options.gzip === true,\n options.bom === true,\n );\n written.push(result);\n annotationsWritten = result.rows;\n }\n\n written.push(\n await writeChannelsCsv(outputDir, file, plan, options.gzip === true, options.bom === true),\n );\n\n /*\n The file moved under the conversion. Said out loud, because nothing else shows it.\n\n The CSVs are still correct for the records that were read, and metadata.json still\n describes the file as it was opened — so the record is consistent with the output\n whatever happens here. What stops being true is that the checksum describes the bytes\n that were converted, since an in-place overwrite leaves nowhere to read them from. It\n is dropped rather than guessed at.\n */\n const changed = await file.changedSinceOpen();\n if (changed) plan.diagnostics.push(inputChanged(options.checksum === true));\n\n await writeMetadata(\n outputDir,\n inputPath,\n file,\n plan,\n written,\n annotationsWritten,\n changed ? null : checksumAtOpen,\n );\n\n const stale = await findStaleOutput(outputDir, written);\n\n return {\n outputDir,\n files: written,\n readerHungUp: false,\n annotationCount: annotationsWritten,\n diagnostics: [...withoutFileRateWarning(file.diagnostics), ...plan.diagnostics, ...stale],\n plan,\n file,\n elapsedMs: Date.now() - startedAt,\n };\n } finally {\n await file.close();\n }\n}\n\n/*\n Files this tool produces, used to spot leftovers from an earlier conversion.\n\n The rate part has to allow every shape a filename can now take, not just the plain ones:\n\n signals_256hz.csv an integer rate\n signals_12_5hz.csv a fractional rate, decimal point written as an underscore\n signals_1_000e-7hz.csv a rate small enough to need exponent form (0.2.2)\n signals_256hz.csv.gz any of the above, compressed (0.3.0)\n signals_0hz_2.csv a second group whose rate slug collided (0.2.1)\n\n The previous `[\\w.]+hz` matched neither of the last two — `-` and `+` are not word\n characters, and the collision suffix falls after the `hz`. Both were introduced by\n recent changes and both silently stopped being recognised as this tool's own output, so\n leftovers of exactly those kinds went unreported: the one situation the warning exists\n for. Requiring a digit after the underscore keeps a user's own `signals_notes.csv` out.\n*/\nconst OUTPUT_PATTERN =\n /^(signals(_\\d[\\w.+-]*hz(_\\d+)?)?\\.csv(\\.gz)?|annotations\\.csv(\\.gz)?|channels\\.csv(\\.gz)?|metadata\\.json)$/u;\n\n/**\n * Detect output from a previous run that this one did not replace.\n *\n * `--force` overwrites files but does not empty the directory, so converting a\n * mixed-rate recording and then a single-rate one into the same place leaves\n * `signals_256hz.csv` sitting next to a fresh `signals.csv`. Both look current.\n * Nothing is deleted here — the user is told, and decides.\n */\nasync function findStaleOutput(\n outputDir: string,\n written: readonly WrittenFile[],\n): Promise<Diagnostic[]> {\n const entries = await readdir(outputDir).catch(() => null);\n if (!entries) return [];\n\n // metadata.json is rewritten on every run but is not part of the reported file list.\n const fresh = new Set([...written.map((f) => f.name), 'metadata.json']);\n const stale = entries.filter((name) => OUTPUT_PATTERN.test(name) && !fresh.has(name)).sort();\n if (stale.length === 0) return [];\n\n return [\n {\n code: 'STALE_OUTPUT',\n severity: 'warning',\n message:\n `${stale.join(', ')} ${stale.length === 1 ? 'is' : 'are'} left over from an earlier ` +\n `conversion into this directory and ${stale.length === 1 ? 'was' : 'were'} not rewritten.`,\n hint: 'Delete them, or convert into a fresh directory, so the two runs do not get mixed up.',\n },\n ];\n}\n\nexport function defaultOutputDir(inputPath: string): string {\n const base = path.basename(inputPath, path.extname(inputPath));\n return path.join(path.dirname(inputPath), `${base}_csv`);\n}\n\n/** Never let an output target resolve to the recording being read, even with --force. */\nasync function assertInputDoesNotOverlapOutputs(\n inputPath: string,\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n options: ConvertOptions,\n): Promise<void> {\n /*\n The names this run will actually write, compressed ones included.\n\n The rate files come from the plan and already carry `.csv.gz` under --gzip; the sidecars\n were spelled out here and did not. So a compressed run checked two names it would never\n write and missed the two it would: a recording sitting at <outdir>/channels.csv.gz was\n overwritten by its own conversion, with --force, reported as a success. The same file\n named signals.csv.gz was refused, which is what gives the oversight away.\n */\n const suffix = options.gzip === true ? '.csv.gz' : '.csv';\n const names = new Set(plan.groups.map((group) => group.fileName));\n if (file.annotationSignals.length > 0) names.add(`annotations${suffix}`);\n names.add(`channels${suffix}`);\n names.add('metadata.json');\n\n const inputResolved = path.resolve(inputPath);\n const inputInfo = await stat(inputPath);\n\n for (const name of names) {\n const target = path.join(outputDir, name);\n const targetResolved = path.resolve(target);\n const targetInfo = await stat(target).catch(() => null);\n const samePath = targetResolved === inputResolved;\n const sameFile =\n targetInfo !== null && targetInfo.dev === inputInfo.dev && targetInfo.ino === inputInfo.ino;\n if (!samePath && !sameFile) continue;\n\n throw new ConversionError(\n 'INPUT_OUTPUT_COLLISION',\n `Output file \"${target}\" is the same file as the input recording.`,\n 'Choose a separate directory with --out. The input was not modified.',\n );\n }\n}\n\nasync function prepareOutputDir(dir: string, force: boolean): Promise<void> {\n /*\n Claim the directory with a single atomic mkdir rather than asking whether it exists\n and then creating it.\n\n Checking first left a window between the two: two conversions started together both\n saw \"not there\", both proceeded, and both opened write streams on the same signals.csv.\n Neither reported anything — both exited 0, having half-written one file between them.\n A non-recursive mkdir cannot do that. Exactly one caller creates the directory; every\n other one gets EEXIST from the filesystem and takes the already-exists path below.\n\n Parents are still created recursively, since --out ./a/b/c should work. Only the final\n component is the claim.\n */\n const parent = path.dirname(dir);\n if (parent && parent !== dir) {\n try {\n await mkdir(parent, { recursive: true });\n } catch (cause: unknown) {\n // A parent that is a regular file surfaces as EEXIST naming the parent, which reads\n // as though the destination already exists rather than as \"you cannot put a\n // directory inside a file\". Name the real obstacle instead of the errno.\n const info = await stat(parent).catch(() => null);\n if (info && !info.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": \"${parent}\" is a file, not a directory.`,\n 'Choose a destination whose parent directories are directories, with --out.',\n );\n }\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n }\n\n let claimed = true;\n try {\n await mkdir(dir);\n } catch (cause: unknown) {\n if ((cause as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `Cannot create \"${dir}\": ${describeFsError(cause)}.`,\n 'Check the path exists and that you have permission to write there.',\n );\n }\n claimed = false;\n }\n\n if (!claimed) {\n const existing = await stat(dir).catch(() => null);\n\n // --force means \"replace my previous output\", not \"write output files into\n // whatever this happens to be\". Pointing it at a regular file needs saying so.\n if (existing && !existing.isDirectory()) {\n throw new ConversionError(\n 'OUTPUT_UNWRITABLE',\n `\"${dir}\" is a file, but the converted data needs a directory.`,\n 'Choose a directory with --out.',\n );\n }\n if (!force) {\n throw new ConversionError(\n 'OUTPUT_EXISTS',\n `\"${dir}\" already exists.`,\n 'Pass --force to overwrite it, or --out to choose a different directory.',\n );\n }\n }\n}\n\n/** Turn a Node filesystem error into something a person can act on. */\nfunction describeFsError(cause: unknown): string {\n const code = (cause as NodeJS.ErrnoException | undefined)?.code;\n if (code === 'EACCES' || code === 'EPERM') return 'permission denied';\n if (code === 'ENOSPC') return 'the disk is full';\n if (code === 'ENOTDIR') return 'part of the path is a file, not a directory';\n if (code === 'EROFS') return 'the filesystem is read-only';\n if (code === 'ENAMETOOLONG') return 'the path is too long';\n return cause instanceof Error ? cause.message : String(cause);\n}\n\nasync function writeSignalFiles(\n file: EdfFile,\n plan: ConversionPlan,\n outputDir: string | null,\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n onHangUp?: (hungUp: boolean) => void,\n): Promise<WrittenFile[]> {\n // One budget for every table in this conversion, not one per table: see OffsetBudget.\n const offsets = newOffsetBudget();\n // Likewise one for every channel, not one per channel: see SampleCacheBudget.\n const sampleCaches = newSampleCacheBudget();\n // Only the stdout path needs this; --out finds a full disk on its own, because it always\n // has another file to write afterwards. See auditStdout.\n const audit = outputDir === null ? auditStdout() : null;\n /*\n In the long layout every group writes into one table, so they share one stream. Opening\n a stream per group on the same path is what the rate-slug collision fix in groupByRate\n was about: two writers on one file interleave rows under a header naming one of them.\n */\n let shared: {\n stream: Writable;\n settled: Promise<void>;\n target: Writable;\n writer: BufferedLineWriter;\n } | null = null;\n\n /*\n One buffer's worth of memory for the conversion, not one per table.\n\n Every group had its own writer at the default 1 MiB threshold, so pending output was\n group count × 1 MiB before anything drained. A 6.5 MB recording with 40 sampling rates\n — the header allows thousands of channels, and a research montage really does mix a\n dozen rates — died with a raw V8 heap out-of-memory and exit 134 under a 96 MB cap,\n while the site advertises 48 MB. The recording is small; it is the fan-out that is not.\n\n Split evenly with a floor, so the single-rate case, which is nearly every recording,\n keeps exactly the buffer it always had, and forty tables cost 2.5 MB rather than 40.\n The long layout shares one writer already and is unaffected either way.\n */\n const MIN_FLUSH_THRESHOLD = 8 * 1024;\n const flushThreshold = Math.max(\n MIN_FLUSH_THRESHOLD,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n /*\n The stream's own buffer is the other half of the same sum.\n\n 0.5.6 shared the line-buffer budget and left `createWriteStream` at its 64 KiB default,\n which is per stream: 200 rate groups meant 12.8 MB of stream buffer on top of 12.8 MB of\n line buffer, and an 855 KB recording still died at a 48 MB cap. Shared the same way, with\n a floor that keeps an ordinary conversion writing in useful-sized pieces.\n */\n const streamBuffer = Math.max(\n 16 * 1024,\n Math.floor(DEFAULT_FLUSH_THRESHOLD / Math.max(1, plan.groups.length)),\n );\n\n const open: OpenGroup[] = plan.groups.map((group, groupIndex) => {\n // A null directory means the single table goes to stdout. process.stdout is already a\n // writable stream, so the same buffered writer and backpressure handling apply.\n const target =\n shared?.target ??\n (outputDir === null\n ? process.stdout\n : createWriteStream(path.join(outputDir, group.fileName), {\n highWaterMark: streamBuffer,\n }));\n const { stream, settled } = shared ?? compressed(target, options.gzip === true);\n /*\n Under --gzip the writer feeds the compressor, so its byte count is the CSV before\n compression and says nothing about what reached the descriptor. The compressor's own\n output is what stdout is handed, so that is what is counted. `pipe` uses a 'data'\n listener of its own and a second one is delivered the same chunks.\n\n Once per stream, which in the long layout is once for all the groups. Attaching per\n group put N listeners on the one shared compressor, so every chunk was counted N\n times: `--stdout --layout long --gzip` on a 40-rate recording claimed 622,240 of\n 622,240 bytes where 15,556 had been written, failed with a disk-full error over a\n perfectly good file, and printed Node's MaxListenersExceededWarning to stderr on the\n way past ten. 0.5.4 fixed the same arithmetic in the uncompressed branch and left\n this one, because the uncompressed branch is where the count is a sum and this one is\n where it is a subscription.\n */\n if (audit && stream !== target && !shared) {\n stream.on('data', (chunk: Buffer) => audit.count(chunk.length));\n }\n /*\n One writer, not one per group, when the table is shared. Separate writers over one\n stream each hold their own buffer and flush on their own schedule, so the rows would\n reach the file in whatever order the buffers filled — which is not the order they\n were produced in, and the long layout's whole claim is that its rows are in time\n order.\n */\n const writer = shared?.writer ?? new BufferedLineWriter(stream, flushThreshold);\n if (plan.layout === 'long' && !shared) shared = { stream, settled, target, writer };\n\n // Only the first group writes the header of a shared table, and the mark before it.\n if (plan.layout !== 'long' || groupIndex === 0) {\n if (options.bom === true) writer.push(UTF8_BOM);\n writer.pushLine(\n plan.layout === 'long'\n ? csvRow(['time_s', 'channel', 'value'])\n : csvRow(['time_s', ...group.channels.map((c) => c.column)]),\n );\n }\n return {\n group,\n writer,\n formatters: group.channels.map((c) => makeSampleFormatter(c.signal, c.decimals, sampleCaches)),\n formatTime: makeTimeFormatter(\n group.samplesPerRecord,\n group.rate,\n group.timeDecimals,\n offsets,\n ),\n rows: 0,\n settled,\n };\n });\n\n try {\n const written = await streamSignalRows(file, plan, open, recordStarts, options);\n\n /*\n Checked once everything has been flushed and ended, and not when the reader hung up.\n\n `--stdout | head -1` closes the pipe on purpose, which is a shell idiom rather than a\n failure. A pipe is not a regular file, so auditStdout declines it anyway — the second\n guard is here because the cost of getting this one wrong is reporting a failure for a\n command that worked.\n */\n if (audit) {\n // Uncompressed, the writer hands its bytes straight to the descriptor; compressed,\n // they were counted on the compressor's way out.\n if (options.gzip !== true) {\n /*\n Once per writer, not once per group. The long layout gives every group the same\n writer, so counting per group multiplied its byte total by the number of rates:\n `--stdout --layout long` on a three-rate recording handed over 32,043 bytes, was\n credited with 96,129, and failed with a disk-full error for a file that was\n complete on disk. It only showed with stdout redirected to a regular file, since\n that is the one case the audit applies to — which is the command the --layout\n documentation gives.\n */\n for (const writer of new Set(open.map((entry) => entry.writer))) {\n audit.count(writer.bytesOut);\n }\n }\n if (!open.some((entry) => entry.writer.hungUp)) audit.verify();\n }\n onHangUp?.(open.some((entry) => entry.writer.hungUp));\n return written;\n } catch (cause) {\n for (const entry of open) entry.writer.destroy();\n\n /*\n Reading and writing both fail through here, and both were reported as writing.\n\n A recording that shrinks mid-conversion — still being written by the acquisition\n software, say — raises the reader's own error, which names the record and says the\n file changed size while it was being read. That precise diagnosis was then filed under\n `Writing to \"<dir>\" failed` and given the hint about freeing disk space, which sends\n someone to look at the one part of the system that was working.\n\n The reader's message and its advice are kept; only the note about partial output is\n added, since that much is true of either failure.\n */\n // A ConversionError arrived already saying what went wrong — a callback that threw, say.\n // Wrapping it again turned \"the onProgress callback threw\" into `Writing to \"out\"\n // failed: the onProgress callback threw`, under a hint about checking the destination.\n if (cause instanceof ConversionError) throw cause;\n\n if (cause instanceof EdfError) {\n const where = outputDir === null ? 'stdout' : `\"${outputDir}\"`;\n throw new ConversionError(\n 'INPUT_UNREADABLE',\n cause.message,\n `${cause.hint ? `${cause.hint} ` : ''}What was written to ${where} before it failed is ` +\n `incomplete and should not be used.`,\n );\n }\n\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to ${outputDir === null ? 'stdout' : `\"${outputDir}\"`} failed: ${detail}`,\n writeHint(cause, outputDir === null),\n );\n }\n}\n\n/**\n * One record's samples in the long layout: `time_s,channel,value`, in time order.\n *\n * The groups are merged rather than written one after another. Every sample in a record\n * falls inside that record's span, so taking the earliest next sample across the groups\n * each time leaves the whole file sorted by `time_s` — which is the only thing that makes a\n * mixed-rate long table useful, since sorting 3 million rows afterwards is the reader's\n * problem and a large one.\n *\n * Ties go to the group with the higher rate, which is the order the groups are already in.\n * Within a sample time the channels come out in the order the file declares them.\n */\n/**\n * Whether two sample times are close enough that only rounding could separate them.\n *\n * A relative epsilon, because the gap between doubles grows with magnitude. Well below any\n * real sample interval — the finest a recording can declare is bounded by its record\n * duration and samples-per-record fields — and well above the one-ULP disagreement that two\n * exact divisions of the same instant produce.\n */\nfunction nearlyEqual(a: number, b: number): boolean {\n return Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 1e-12;\n}\n\n/** The chosen instant as it will be written, for deciding which channels share it. */\nfunction formatAt(\n open: readonly OpenGroup[],\n cursors: Int32Array,\n recordStart: number,\n earliest: number,\n): string {\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n if (recordStart + sample / entry.group.rate === earliest) {\n return entry.formatTime(recordStart, sample);\n }\n }\n return '';\n}\n\nasync function writeLongRecord(\n file: EdfFile,\n open: readonly OpenGroup[],\n batch: RecordBatch,\n recordInBatch: number,\n recordStart: number,\n range: ConversionPlan['range'],\n): Promise<void> {\n const writer = open[0]?.writer;\n if (!writer) return;\n\n const cursors = new Int32Array(open.length);\n /* Reused across sample times so a three-million-row conversion allocates one of these. */\n const due: { entry: OpenGroup; groupIndex: number; sample: number; channel: number }[] = [];\n\n for (;;) {\n let earliest = Infinity;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time < earliest) earliest = time;\n }\n if (earliest === Infinity) return;\n\n /*\n Everything at this instant, in the order the file declares its channels.\n\n Groups are ordered by rate, largest first, because that is how the wide layout names\n its files. Emitting a tie group by group therefore ordered the channels by descending\n sampling rate — so a recording declaring `slow, medium, fast` wrote `fast, medium,\n slow`, while the documentation promised file order and channels.csv listed file order.\n Signal index is the file's own order, and the only one a reader can predict.\n\n \"At this instant\" is decided on the time as written, not on the double. `s / rate` for\n two channels at one moment need not give the same double: a 0.3 s record holding 12 and\n 4 samples makes 40 Hz and 13.333… Hz, and 9/40 is 0.22500000000000000555 while 3/13.333…\n is 0.22499999999999997780. Equality missed that, so those two rows fell out in numeric\n order — `slow` before `fast`, once, in the middle of a file that was otherwise right.\n\n Two rows are at one time exactly when they carry the same `time_s`, which is the only\n definition a reader of the CSV can apply. The numeric pre-filter keeps the common case\n to one comparison; formatting happens only for candidates already within a hair.\n */\n const earliestText = formatAt(open, cursors, recordStart, earliest);\n due.length = 0;\n for (let g = 0; g < open.length; g++) {\n const entry = open[g];\n if (!entry) continue;\n const sample = cursors[g] ?? 0;\n if (sample >= entry.group.samplesPerRecord) continue;\n const time = recordStart + sample / entry.group.rate;\n if (time !== earliest) {\n // Far away in the ordinary case; only a near-tie is worth formatting.\n if (!nearlyEqual(time, earliest)) continue;\n if (entry.formatTime(recordStart, sample) !== earliestText) continue;\n }\n cursors[g] = sample + 1;\n // Same window rule as the wide layout, with the same per-rate slack.\n if (\n !sampleTimeIsInRange(earliest, range.startSeconds, range.endSeconds, toleranceFor(entry.group.rate))\n ) {\n continue;\n }\n for (let c = 0; c < entry.group.channels.length; c++) {\n due.push({ entry, groupIndex: g, sample, channel: c });\n }\n }\n if (due.length === 0) continue;\n if (due.length > 1) {\n due.sort(\n (a, b) =>\n (a.entry.group.channels[a.channel]?.signal.index ?? 0) -\n (b.entry.group.channels[b.channel]?.signal.index ?? 0),\n );\n }\n\n for (const item of due) {\n const channel = item.entry.group.channels[item.channel];\n const format = item.entry.formatters[item.channel];\n if (!channel || !format) continue;\n if (writer.hungUp) return;\n writer.pushLine(\n `${item.entry.formatTime(recordStart, item.sample)},${escapeCsvField(channel.column)},` +\n `${format(file.sampleAt(batch, recordInBatch, channel.signal, item.sample))}`,\n );\n item.entry.rows++;\n // Flushed inside the record for the same reason the wide layout is; see there.\n if (writer.full) await writer.flush();\n }\n }\n}\n\nasync function streamSignalRows(\n file: EdfFile,\n plan: ConversionPlan,\n open: OpenGroup[],\n recordStarts: Float64Array | null,\n options: ConvertOptions,\n): Promise<WrittenFile[]> {\n const { startSeconds, endSeconds, startRecord, endRecord } = plan.range;\n const { recordDuration } = file.header;\n let recordsDone = 0;\n\n /*\n Every destination has hung up, so nothing formatted from here on could reach anyone.\n `--stdout | head -1` is the usual way to arrive here: the reader takes one line and\n closes the pipe while the conversion is still near the start of the recording.\n\n Stopping also makes the reported row count mean what it says — rows that reached the\n consumer, rather than rows the loop went on formatting into a discarded buffer.\n */\n const allHungUp = (): boolean => open.length > 0 && open.every((entry) => entry.writer.hungUp);\n\n for await (const batch of file.readRecords({ startRecord, endRecord })) {\n if (allHungUp()) break;\n for (let r = 0; r < batch.recordCount; r++) {\n if (allHungUp()) break;\n const index = batch.firstRecordIndex + r;\n const recordStart = recordStarts ? (recordStarts[index] ?? index * recordDuration) : index * recordDuration;\n\n if (plan.layout === 'long') {\n await writeLongRecord(file, open, batch, r, recordStart, plan.range);\n recordsDone++;\n continue;\n }\n\n for (const entry of open) {\n const { group, writer, formatters, formatTime } = entry;\n const { channels, rate } = group;\n // Slack that never reaches the next sample; see toleranceFor.\n const slack = toleranceFor(rate);\n\n for (let sample = 0; sample < group.samplesPerRecord; sample++) {\n const time = recordStart + sample / rate;\n if (!sampleTimeIsInRange(time, startSeconds, endSeconds, slack)) continue;\n\n let row = formatTime(recordStart, sample);\n for (let c = 0; c < channels.length; c++) {\n const channel = channels[c];\n const format = formatters[c];\n if (!channel || !format) continue;\n row += ',' + format(file.sampleAt(batch, r, channel.signal, sample));\n }\n if (writer.hungUp) break;\n writer.pushLine(row);\n entry.rows++;\n\n /*\n Flushed inside the record, not only at the end of one.\n\n The buffer was drained once per record, so the rows of a single record piled up\n with nothing emptying them — memory followed samples-per-record rather than the\n batch size the writer exists to hold to. One record of 16,000,000 samples died\n with a heap out of memory under a 256 MB cap, while the same 32 MB of samples\n split into 16,000 records converted to the same 283 MB CSV without trouble. The\n format allows either layout and says nothing about which to expect.\n\n `full` is a synchronous read of the pending size, so the twenty million rows that\n are not at a boundary cost a comparison rather than a microtask each.\n */\n if (writer.full) await writer.flush();\n }\n await writer.maybeFlush();\n }\n\n recordsDone++;\n }\n\n if (options.onProgress) {\n /*\n A caller's callback is the caller's, and its failures are not the destination's.\n\n This ran inside the same try that turns a stream failure into WRITE_FAILED, so a\n progress callback that threw came back as `Writing to \"out\" failed: caller bug`,\n advising the reader to check a destination that was working perfectly. It is the\n same misattribution the write hints had until 0.4.36, one layer up.\n\n The original is kept as `cause`, so the stack that actually matters survives, and\n the conversion still stops — the callback threw, and carrying on writing into a\n directory whose owner has just failed is not an improvement.\n */\n try {\n options.onProgress({\n recordsDone,\n recordsTotal: endRecord - startRecord,\n // Once per writer: the long layout's groups share one, so summing per group\n // reported a figure larger than the file being written.\n bytesWritten: [...new Set(open.map((entry) => entry.writer))].reduce(\n (sum, entry) => sum + entry.charsWritten,\n 0,\n ),\n });\n } catch (cause) {\n throw new ConversionError(\n 'CALLBACK_FAILED',\n `The onProgress callback threw: ${cause instanceof Error ? cause.message : String(cause)}`,\n 'This is the caller\\'s callback, not the recording or the destination. Whatever was ' +\n 'written before it threw is incomplete and should not be used.',\n { cause },\n );\n }\n }\n }\n\n const closed = new Set<BufferedLineWriter>();\n for (const entry of open) {\n if (closed.has(entry.writer)) continue;\n closed.add(entry.writer);\n await entry.writer.end();\n // With --gzip the writer's stream is the compressor, whose end callback fires when the\n // compressor is done rather than when the file behind it is. Awaiting only that would\n // report success with the tail of the file still in flight.\n await entry.settled;\n }\n\n // A shared table is one file, and its row count is every group's rows, not the first's.\n if (plan.layout === 'long') {\n const first = open[0];\n if (!first) return [];\n return [{ name: first.group.fileName, rows: open.reduce((sum, e) => sum + e.rows, 0) }];\n }\n return open.map((entry) => ({ name: entry.group.fileName, rows: entry.rows }));\n}\n\n/**\n * The stream rows are written to, plus a promise for the data reaching its destination.\n *\n * Compression sits between the writer and the file as a transform. Failures below it — a\n * full disk, an unwritable path — surface on the file stream, where nothing is listening,\n * so they are forwarded onto the compressor: that is the stream the writer watches, and\n * routing them there keeps one error path rather than two.\n */\nfunction compressed(target: Writable, gzip: boolean): { stream: Writable; settled: Promise<void> } {\n if (!gzip) return { stream: target, settled: Promise.resolve() };\n const compressor = createGzip();\n target.on('error', (error: Error) => compressor.destroy(error));\n\n /*\n pipe() ends its destination when the source ends, and stdout must survive the\n conversion: the writer already refuses to close it, because a closed stdout breaks\n every later write in the process. Nothing waits on stdout either — it is not this\n tool's to finish.\n */\n const toStdout = target === process.stdout;\n compressor.pipe(target, { end: !toStdout });\n if (toStdout) return { stream: compressor, settled: Promise.resolve() };\n\n /*\n A failure under the compressor rejects both this promise and the writer's own end(),\n and end() is the one awaited first. Without a handler attached here that rejection\n belonged to nobody, and Node killed the process over it: `--gzip` into an unwritable\n path printed a raw EISDIR stack trace instead of the ordinary \"Writing to ... failed\"\n message and exit 1, which is what the same path does uncompressed.\n\n Attaching the handler is enough to make it handled. Awaiting `settled` downstream still\n reports the failure in the case where end() happened to succeed.\n */\n const settled = finished(target);\n settled.catch(() => {});\n return { stream: compressor, settled };\n}\n\n/**\n * Write one of the sidecar files, reporting a failure the way the signal writer does.\n *\n * These three used to call `writeFile` bare, so a failure escaped as whatever the\n * filesystem said — `EISDIR: illegal operation on a directory, open '...'` with no hint\n * and, more importantly, no mention that the signal files had already been written. The\n * conversion stopped half-done and the message gave no sign of it.\n */\nasync function writeOutputFile(\n outputDir: string,\n name: string,\n contents: string,\n gzip = false,\n bom = false,\n): Promise<void> {\n // metadata.json never gets one: JSON.parse rejects a leading U+FEFF, so a mark there\n // would break every reader of the file to help a spreadsheet that will not open it.\n if (bom) contents = UTF8_BOM + contents;\n try {\n // The sidecars are built in memory before being written, so compressing them in memory\n // costs nothing extra. Only the signal tables are large enough to need a stream.\n await writeFile(path.join(outputDir, name), gzip ? gzipSync(contents) : contents, gzip ? undefined : 'utf8');\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing \"${name}\" to \"${outputDir}\" failed: ${detail}`,\n writeHint(cause),\n );\n }\n}\n\nasync function writeChannelsCsv(\n outputDir: string,\n file: EdfFile,\n plan: ConversionPlan,\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const includedColumns = new Set(plan.groups.flatMap((g) => g.channels.map((c) => c.signal.index)));\n const fileFor = new Map<number, string>();\n for (const group of plan.groups) {\n for (const channel of group.channels) fileFor.set(channel.signal.index, group.fileName);\n }\n\n const lines = [\n csvRow([\n 'column',\n 'signal_index',\n 'label',\n 'unit',\n 'sampling_rate_hz',\n 'samples_per_record',\n 'physical_min',\n 'physical_max',\n 'digital_min',\n 'digital_max',\n 'transducer',\n 'prefiltering',\n 'output_file',\n 'converted',\n ]),\n ];\n\n for (const signal of file.header.signals) {\n if (signal.isAnnotations) continue;\n const column = plan.columnNames.get(signal.index) ?? `signal_${signal.index}`;\n lines.push(\n csvRow([\n column,\n String(signal.index),\n signal.label,\n signal.physicalDimension,\n String(signal.samplingRate),\n String(signal.samplesPerRecord),\n String(signal.physicalMin),\n String(signal.physicalMax),\n String(signal.digitalMin),\n String(signal.digitalMax),\n signal.transducer,\n signal.prefiltering,\n fileFor.get(signal.index) ?? '',\n includedColumns.has(signal.index) ? 'yes' : 'no',\n ]),\n );\n }\n\n const name = gzip ? 'channels.csv.gz' : 'channels.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: lines.length - 1 };\n}\n\n/*\n The bounds an annotation is filtered against: what the caller actually asked for, not the\n window that survived being clamped to the recording.\n\n Filtering by the resolved window meant an unbounded request still lost events. `--end 999h`\n on a three-second file clamps to 3, and `--start 0` gets its end from the recording, so\n both filtered to [0, 3) and dropped the markers at 3.0 and 3.5 that a bare invocation\n keeps — asking for more of a recording returned less of it.\n\n An end the caller did not give is unbounded, not \"the end of the data\": EDF+ lets an\n annotation sit past the last sample, which is exactly where an end-of-recording marker is.\n\n `--duration` is measured from wherever the conversion actually starts. Anchoring it at 0\n instead read the same absent `--start` two ways in two adjacent lines, and on a recording\n that does not begin at zero the two windows did not even overlap: an EDF+D file whose first\n record sits at 30 s converted its samples from [30, 35) while filtering annotations against\n (-inf, 5), so every event inside the converted window was dropped and `annotations.csv` came\n back empty. `resolveRange` has always defaulted the same missing start to the earliest\n record, which is where this now takes it from.\n*/\nfunction requestedAnnotationWindow(\n options: ConvertOptions,\n recordingStart: number,\n): { from: number; to: number } {\n const from = options.start ?? -Infinity;\n const to =\n options.end !== undefined\n ? options.end\n : options.duration !== undefined\n ? (options.start ?? recordingStart) + options.duration\n : Infinity;\n return { from, to };\n}\n\nasync function writeAnnotationsCsv(\n outputDir: string,\n annotations: readonly Annotation[],\n window: { from: number; to: number },\n gzip: boolean,\n bom: boolean,\n): Promise<WrittenFile> {\n const inWindow = annotations\n .filter((a) => a.onset >= window.from && a.onset < window.to)\n .sort((a, b) => a.onset - b.onset || a.recordIndex - b.recordIndex);\n\n const lines = [csvRow(['onset_s', 'duration_s', 'description', 'record_index'])];\n for (const annotation of inWindow) {\n lines.push(\n csvRow([\n String(annotation.onset),\n annotation.duration === null ? '' : String(annotation.duration),\n annotation.text,\n String(annotation.recordIndex),\n ]),\n );\n }\n\n const name = gzip ? 'annotations.csv.gz' : 'annotations.csv';\n await writeOutputFile(outputDir, name, lines.join('\\n') + '\\n', gzip, bom);\n return { name, rows: inWindow.length };\n}\n\n/**\n * What to try next, chosen from what actually went wrong.\n *\n * Every write failure carried the same advice — \"Free up space or choose another destination\n * with --out\" — which fits exactly one errno. A directory sitting where signals.csv belongs\n * came back telling the reader to free up disk space, and so did a read-only volume, a\n * permission denial and a path too long for the filesystem. Wrong advice is worse than none:\n * it sends someone to check `df` on a disk that is fine, and the thing that is actually\n * wrong stays unexamined.\n *\n * The errno is the one piece of the failure that names the cause, so it is what picks the\n * sentence. Anything unrecognised keeps the general form rather than guessing.\n */\nfunction writeHint(cause: unknown, toStdout = false): string {\n /*\n The stdout path writes no files, and --out is the flag its user chose not to pass.\n\n Both halves of this sentence were wrong there: \"the files written so far\" named files\n that do not exist, and \"choose another destination with --out\" is advice for a different\n command — the destination is whatever the shell redirected the stream to. Same class as\n the disk-space hint this function replaced, one flag over.\n */\n const preamble = toStdout\n ? 'What reached stdout before it failed is incomplete and should not be used. '\n : 'The files written so far are incomplete and should not be used. ';\n const elsewhere = toStdout ? 'redirect it somewhere else' : 'choose another with --out';\n const code = (cause as NodeJS.ErrnoException | null)?.code;\n switch (code) {\n case 'ENOSPC':\n return `${preamble}The destination is out of space; free some up or ${elsewhere}.`;\n case 'EDQUOT':\n return `${preamble}You are over your disk quota on this filesystem; ${elsewhere}.`;\n case 'EACCES':\n case 'EPERM':\n return `${preamble}You do not have permission to write there; ${elsewhere}.`;\n case 'EROFS':\n return `${preamble}That filesystem is mounted read-only; ${elsewhere}.`;\n case 'EISDIR':\n return `${preamble}A directory is sitting where that file belongs; remove or rename it, or ${elsewhere}.`;\n case 'ENOENT':\n return `${preamble}Part of that path no longer exists; make sure nothing is removing it while the conversion runs.`;\n case 'ENAMETOOLONG':\n return `${preamble}That path is longer than the filesystem allows; ${toStdout ? 'redirect it somewhere shorter' : 'choose a shorter destination with --out'}.`;\n case 'EMFILE':\n case 'ENFILE':\n return `${preamble}Too many files are open; a recording with many sampling rates opens one output file per rate, so --channels narrows it.`;\n case 'EPIPE':\n return `${preamble}Whatever was reading the output closed it before the conversion finished.`;\n default:\n return `${preamble}Check the destination and run the conversion again.`;\n }\n}\n\n/**\n * Confirms that everything handed to a file-backed stdout actually arrived.\n *\n * `edf2csv rec.edf --stdout > out.csv` onto a volume that the output very nearly fills lost\n * the tail in silence: 94,977 of 102,400 rows on disk, the file ending mid-row, stderr\n * announcing \"Wrote 102,400 rows to stdout.\" and the process exiting 0. The same recording\n * onto the same volume through `--out` fails correctly, which is what gives it away.\n *\n * POSIX `write` returns a short count rather than an error when the disk fills partway\n * through a single call, and only the NEXT write raises ENOSPC. `--out` always has a next\n * write — channels.csv and metadata.json come after — so it always finds out. `--stdout`\n * has nothing after it, and when fd 1 is a regular file Node's stdout is a SyncWriteStream\n * whose `_write` discards the byte count `writeSync` returns, so nothing is raised at all.\n * No error means no `#failure`, so checking that alone would not have caught this.\n *\n * What can be checked is the descriptor: how much it grew against how much it was given.\n * Only for a regular file — a pipe, a terminal or a socket has no size to compare, and on\n * those a short write cannot go unreported this way. Appending (`>>`) is fine, since the\n * starting size is taken before anything is written.\n */\nfunction auditStdout(): { count: (bytes: number) => void; verify: () => void } | null {\n let startSize: number;\n try {\n const info = fstatSync(1);\n if (!info.isFile()) return null;\n startSize = info.size;\n } catch {\n // No usable descriptor to audit; the conversion is not the place to complain about it.\n return null;\n }\n\n let expected = 0;\n return {\n count: (bytes: number): void => {\n expected += bytes;\n },\n verify: (): void => {\n let landed: number;\n try {\n landed = fstatSync(1).size - startSize;\n } catch {\n return;\n }\n if (landed >= expected) return;\n throw new ConversionError(\n 'WRITE_FAILED',\n `Writing to stdout failed: ${expected - landed} of ${expected} bytes did not reach ` +\n `the destination, which stopped accepting them part way through.`,\n 'What is there ends mid-row and should not be used. The destination is almost ' +\n 'certainly out of space — a short write is how a filesystem reports filling up ' +\n 'mid-write, and nothing after it raised an error because there was nothing after it.',\n );\n },\n };\n}\n\n/** Data rows across every signal table, so \"nothing was written\" is one question. */\nfunction rowsIn(written: readonly WrittenFile[]): number {\n return written.reduce((total, file) => total + file.rows, 0);\n}\n\n\n/** Raised when the input moved while it was being read. See where it is pushed. */\nfunction inputChanged(hadChecksum: boolean): Diagnostic {\n return {\n code: 'INPUT_CHANGED',\n severity: 'warning',\n message:\n 'The input changed while it was being converted, so this output covers the file as ' +\n 'it was when the conversion started, not as it is now.',\n hint: hadChecksum\n ? 'No checksum was recorded: the bytes that were converted are no longer there to ' +\n 'hash. Convert again once the recording is finished.'\n : 'Convert again once the recording is finished to pick up the rest.',\n };\n}\n\nasync function writeMetadata(\n outputDir: string,\n inputPath: string,\n file: EdfFile,\n plan: ConversionPlan,\n written: readonly WrittenFile[],\n annotationCount: number,\n /** Hash of the bytes that were converted, or null when it could not be vouched for. */\n checksum: string | null,\n): Promise<void> {\n const { header } = file;\n\n /*\n The file as it was when it was opened, not as it is now.\n\n Both of these used to come from re-opening the path once the CSVs were written, which\n describes whatever answers to that name by then rather than what was converted. A\n recording still being written grew from 2,000 records to 3,000 mid-conversion and\n metadata.json recorded `data_records: 2000` — correct, the CSV holds 2,000 — beside the\n byte count and SHA-256 of the 3,000-record file. The two halves of one provenance record\n described two different files, and the checksum covered bytes nobody had converted.\n Replacing the file at that path did the same thing more thoroughly.\n\n `file.fileSize` is the number every record count and window in this output was derived\n from, and the hash is taken over exactly those bytes through the descriptor already open\n on them, so the record describes one file throughout.\n */\n const metadata = {\n tool: { name: 'edf2csv', version: TOOL_VERSION },\n source: {\n path: path.resolve(inputPath),\n bytes: file.fileSize,\n modified: new Date(file.modifiedAtOpenMs).toISOString(),\n sha256: checksum,\n },\n recording: {\n format: describeFormat(header),\n version: header.version,\n patient_id: header.patientId,\n recording_id: header.recordingId,\n // Zone-less on purpose: EDF records local wall-clock digits and no timezone.\n start_datetime_local: formatWallClock(header.startDateTime),\n start_date_raw: header.startDateRaw,\n start_time_raw: header.startTimeRaw,\n data_records: file.recordCount,\n data_records_declared: header.declaredRecordCount,\n record_duration_seconds: header.recordDuration,\n duration_seconds: file.durationSeconds,\n signal_count: header.signalCount,\n annotation_channels: file.annotationSignals.length,\n },\n conversion: {\n converted_at: new Date().toISOString(),\n start_seconds: plan.range.startSeconds,\n end_seconds: plan.range.endSeconds,\n whole_recording: plan.range.isWholeRecording,\n records_converted: [plan.range.startRecord, plan.range.endRecord],\n annotations_written: annotationCount,\n /*\n Which shape the signal table is in, which nothing recorded.\n\n A wide `signals.csv` and a long one are different files with different columns, and\n metadata.json described them identically — so a pipeline handed an output directory\n could not tell from the archive which it had. It matters most for `rate_groups` right\n below: in the wide layout those entries are one per file and their `channels` are that\n file's columns, and in the long layout every entry names the one shared table and its\n `channels` are values in that table's `channel` column. Same array, two readings, and\n no way to know which applied.\n */\n layout: plan.layout,\n files: written.map((f) => ({ name: f.name, rows: f.rows })),\n rate_groups: plan.groups.map((g) => ({\n file: g.fileName,\n sampling_rate_hz: g.rate,\n channels: g.channels.map((c) => c.column),\n decimals: g.channels.map((c) => c.decimals),\n })),\n },\n notes: [...withoutFileRateWarning(file.diagnostics), ...plan.diagnostics].map((d) => ({\n code: d.code,\n severity: d.severity,\n message: d.message,\n })),\n };\n\n await writeOutputFile(outputDir, 'metadata.json', JSON.stringify(metadata, null, 2) + '\\n');\n}\n\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edf2csv",
3
- "version": "0.5.67",
3
+ "version": "0.5.69",
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",