edf2csv 0.7.122 → 0.7.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -403,8 +403,15 @@ export async function main(argv) {
403
403
  if (toStdout && given) {
404
404
  // `error:` and the seven-space continuation, like every other refusal — the shape
405
405
  // 0.5.79 gave the rest and these two were not enumerated in its test.
406
- process.stderr.write(`error: --stdout and ${flag} cannot be combined: --stdout writes no\n` +
407
- detail(`files, and ${flag} has nothing to act on.`) +
406
+ //
407
+ // One sentence on the first line, which is the other half of that shape. The break was
408
+ // written into the middle of it — "cannot be combined: --stdout writes no" / "files,
409
+ // and --out has nothing to act on" — so the line a log greps carried a fragment ending
410
+ // in "no", and the line under it opened with "files,". `printableLines` leaves the
411
+ // first line whole at whatever width it runs to, deliberately, so there was nothing to
412
+ // pre-wrap it for.
413
+ process.stderr.write(`error: --stdout and ${flag} cannot be combined: --stdout writes no files, and ` +
414
+ `${flag} has nothing to act on.\n` +
408
415
  detail(`Drop ${flag}, or drop --stdout and convert to a directory.`));
409
416
  return EXIT_USAGE;
410
417
  }
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 { availableParallelism } from 'node:os';\nimport { fork } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { EdfError } from './edf/errors.js';\nimport { EdfFile } from './edf/reader.js';\nimport { buildPlan, withoutFileRateWarning } from './convert/plan.js';\nimport {\n ConversionError,\n USAGE_ERROR_CODES,\n auditStdout,\n convert,\n defaultOutputDir,\n durationDiagnostics,\n requestedAnnotationWindow,\n noAnnotations,\n noSignalFile,\n stdoutRefusal,\n} from './convert/run.js';\nimport type { ConvertOptions } from './convert/run.js';\nimport { ChannelSelectionError } from './convert/channels.js';\n// Shared with the library so a bad option is the same error whichever way it arrived.\nimport { OptionError } from './convert/options.js';\nimport { TimeRangeError, parseTimeSpec } from './convert/time-range.js';\nimport { deriveRecordStarts, withTimingPromiseKept } from './convert/timing.js';\nimport { formatDiagnostics, formatInfo, infoJson, formatSummary, printable, printableLines, summaryJson, wrap } from './cli/report.js';\nimport { counted, listed } from './format/list.js';\nimport { VERSION } from './version.js';\n\nconst USAGE = `edf2csv ${VERSION}\nConvert EDF, EDF+ and BDF recordings to CSV\n\nUsage\n edf2csv <recording.edf | folder> [more ...] [options]\n\nOptions\n -i, --info Show the recording's structure and the estimated\n output size, without converting anything\n -o, --out <dir> Output directory (default: <recording>_csv, beside\n the input)\n -c, --channels <list> Only these channels, comma-separated. Use #N to pick a\n channel by position when two share a label\n --start <time> Begin at this offset (30s, 5m, 1h30m, 00:30:00)\n --duration <time> Convert this much\n --end <time> Stop at this offset (instead of --duration)\n --annotations-only Write only the EDF+ annotations, no signal data\n --decimals <n> Fix the decimal places instead of deriving them\n per channel\n --checksum Record a SHA-256 of the input in metadata.json\n --layout <kind> wide (default): one column per channel, one file per\n sampling rate. long: one file of time_s,channel,value,\n every rate together, one row per sample\n --gzip Compress every CSV, writing .csv.gz files\n --bom Start each CSV with a UTF-8 byte order mark, so Excel\n reads accented text and units like µV correctly\n -j, --jobs <n> Convert this many recordings at once, or \"auto\"\n (default: 1)\n -f, --force Write into the output directory if it already exists\n -q, --quiet Suppress the summary; warnings and errors still print\n --json Print machine-readable JSON to stdout (works with\n --info too)\n --strict Exit 1 if the recording raised any warning\n --stdout Write the signal CSV to stdout instead of a directory\n (one table only: one sampling rate, or --layout long)\n -h, --help Show this help\n -V, --version Show the version\n\nSeveral recordings\n A folder is expanded to every .edf and .bdf inside it, at any depth, and the\n layout is kept: recordings in sub-folders come out in sub-folders.\n Pass more than one and each is converted in turn. Without --out each lands\n beside itself as usual; with --out that directory becomes the parent and each\n recording gets its own inside it. A file that cannot be read is reported and\n the rest still convert, with a non-zero exit at the end. --jobs converts\n several at once, which is worth it for a folder of them.\n\nOutput\n A directory containing signals.csv, channels.csv, metadata.json, and\n annotations.csv when the recording carries EDF+ annotations. Channels recorded\n at different sampling rates are written to separate files, never resampled.\n With --gzip each CSV becomes a .csv.gz; metadata.json stays plain text so the\n directory can still be read at a glance. With --bom each CSV starts with a\n UTF-8 byte order mark and metadata.json does not, since JSON.parse rejects\n one.\n With --layout long every channel goes into one signals.csv as time_s, channel\n and value, one row per sample, whatever rates the recording mixes — which is\n also the one arrangement --stdout can stream for a mixed-rate file.\n\nExamples\n edf2csv recording.edf\n edf2csv recording.edf --info\n edf2csv recording.edf --channels \"EEG Fpz-Cz,ECG\" --out ./converted\n edf2csv recording.edf --start 30m --duration 5m\n edf2csv recording.edf --annotations-only\n edf2csv /data/*.edf --out ./converted\n edf2csv /data/*.edf --out ./converted --jobs auto\n edf2csv /data/study --out ./converted --jobs auto\n`;\n\n/**\n * A continuation line under an `error: ` or `interrupted (SIGINT): ` head.\n *\n * These are written straight to stderr rather than raised as an error, so they never passed\n * through `printableLines` and never wrapped. Most of them interpolate a destination or a\n * recording path, which makes the line as long as the caller's directory tree is deep: the\n * `--stdout` refusal for a folder reached 268 columns on a four-deep path, saying \"Name the\n * recording itself — <path> — or convert to a directory instead.\"\n *\n * A path has no spaces to break at, so it stays whole on its own line and the sentence\n * around it wraps. That is the useful shape: the path is the part that gets copied.\n */\nconst detail = (text: string): string => `${wrap(text, ' ')}\\n`;\n\n/**\n * Every option the command line takes.\n *\n * Its own constant so `parseArgs` and the \"did you mean\" suggestion read the same list. A\n * second copy of twenty flag names is a copy that will be missing the next one.\n */\nconst OPTIONS = {\n info: { type: 'boolean', short: 'i' },\n out: { type: 'string', short: 'o' },\n channels: { type: 'string', short: 'c', multiple: true },\n start: { type: 'string' },\n duration: { type: 'string' },\n end: { type: 'string' },\n 'annotations-only': { type: 'boolean' },\n decimals: { type: 'string' },\n checksum: { type: 'boolean' },\n layout: { type: 'string' },\n gzip: { type: 'boolean' },\n bom: { type: 'boolean' },\n jobs: { type: 'string', short: 'j' },\n force: { type: 'boolean', short: 'f' },\n quiet: { type: 'boolean', short: 'q' },\n json: { type: 'boolean' },\n strict: { type: 'boolean' },\n stdout: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'V' },\n} as const;\n\nconst EXIT_OK = 0;\nconst EXIT_ERROR = 1;\nconst EXIT_USAGE = 2;\n\n/**\n * Whether this run is already shutting its children down.\n *\n * A child dying by signal is normally worth a line of its own, but not when this process is\n * the one that killed it: the interrupt handler names every abandoned directory in a single\n * message, and a per-child line underneath it would say the same thing again, once per job.\n */\nlet stopping = false;\n\n/**\n * Piping into a consumer that exits early (`| head -1`) closes our stdout, and the\n * next write raises EPIPE. That is normal in a shell pipeline, not an error worth a\n * stack trace, so it is swallowed while anything else still surfaces.\n */\nfunction ignoreBrokenPipe(stream: NodeJS.WriteStream): void {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n // A reader closing early (`edf2csv ... --info | head -5`) is not a failure, so the\n // error is swallowed rather than thrown. It deliberately does NOT set an exit code:\n // forcing 0 here would erase a real failure whenever the pipe happened to close\n // after the run had already failed, reporting success for a conversion that did\n // not happen. Node exits 0 on its own when nothing sets a code.\n return;\n }\n\n /*\n Anything else is a real write failure, and this used to rethrow it.\n\n The throw lands on a nextTick, outside whatever try/catch the conversion is running\n inside, so it became an uncaught exception: `--stdout` redirected onto a full disk\n died with a raw stack trace headed `dist/cli.js: throw error;` and lost the warning\n that the CSV it had already produced was truncated. The same failure through `--out`\n printed the ordinary message, and so did `--stdout` through the library API, which\n never registers this listener — the designed path exists and works, and this was\n preempting it.\n\n A second listener means the conversion's writer is watching this stream and will\n turn the failure into that ordinary message. Saying it here too would report one\n failure twice, so this only speaks when nothing else will.\n */\n if (stream.listenerCount('error') > 1) return;\n process.stderr.write(`error: Writing to ${stream === process.stdout ? 'stdout' : 'stderr'} failed: ${error.message}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n\nexport async function main(argv: readonly string[]): Promise<number> {\n ignoreBrokenPipe(process.stdout);\n ignoreBrokenPipe(process.stderr);\n\n let values: Record<string, unknown>;\n let positionals: string[];\n\n try {\n const parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: OPTIONS,\n });\n values = parsed.values as Record<string, unknown>;\n positionals = parsed.positionals;\n } catch (error) {\n process.stderr.write(`${usageMessage(error, argv)}\\n\\nRun edf2csv --help to see the options.\\n`);\n return EXIT_USAGE;\n }\n\n if (values['help'] === true) {\n process.stdout.write(USAGE);\n return EXIT_OK;\n }\n if (values['version'] === true) {\n process.stdout.write(`${VERSION}\\n`);\n return EXIT_OK;\n }\n\n if (positionals.length === 0) {\n process.stderr.write(`No input file given.\\n\\n${USAGE}`);\n return EXIT_USAGE;\n }\n\n /*\n An empty --out, refused here rather than by the filesystem.\n\n Every other option that takes a value refuses an empty one from the command line and says\n what it wanted: `--start is empty`, `--channels was given but lists no channel names`,\n `--layout must be \"wide\" or \"long\", got \"\"`. `--out \"\"` was the eighth of eight and the\n exception — it went the whole way to `mkdir('')` and came back as a conversion failure\n about a path:\n\n error: Cannot create \"\": part of the path does not exist.\n Check the path exists and that you have permission to write there.\n\n Exit 1 where the other seven are exit 2, advice about a path and a permission for a value\n that is neither, and in a batch it is decided once per recording rather than once for the\n run. `--out \"$DEST\"` with `DEST` unset is how it gets written by accident, which is also\n why the empty string alone is refused and the value is not trimmed first: a directory whose\n name is a space is a strange thing to ask for, but it is a thing the filesystem has and a\n path is not a keyword.\n */\n if (values['out'] === '') {\n process.stderr.write('error: --out is empty. Give a directory, for example --out ./converted.\\n');\n return EXIT_USAGE;\n }\n\n let expanded: Input[];\n let unreadable: string[];\n let namedDirectory: boolean;\n try {\n const found = await expandInputs(positionals);\n expanded = found.inputs;\n unreadable = found.unreadable;\n namedDirectory = found.namedDirectory;\n } catch (error) {\n return reportError(error);\n }\n\n // Reported before anything is converted, so it cannot be lost among the summaries, and\n // counted against the run so the exit code does not call a partial sweep a success.\n for (const entry of unreadable) {\n process.stderr.write(\n `error: ${printable(entry)}: could not be read, so any recordings inside it were skipped.\\n`,\n );\n }\n\n if (expanded.length === 0) {\n /*\n \"None here\" and \"could not look\" are different answers, and so are their exit codes.\n\n A folder the process cannot read gave the same exit 2 and the same \"No EDF or BDF\n recordings found\" as an empty one — while the line above it said the folder could not\n be read. Exit 2 is this tool's code for \"the command itself was wrong\", so a script\n was being told to fix its arguments when what needed fixing was a permission. The\n command was fine; the filesystem refused.\n\n And the sentence itself claimed a fact the run is in no position to state: nothing was\n found because nothing was looked at.\n */\n if (unreadable.length > 0) {\n process.stderr.write(\n `${wrap(\n `Nothing could be converted: ${unreadable.length === 1 ? 'that path' : 'those paths'} ` +\n `could not be read, so whether ${unreadable.length === 1 ? 'it holds' : 'they hold'} ` +\n `recordings is unknown.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n process.stderr.write(\n // Escaped, like every other path this prints. These come from the command line, which\n // makes them no safer: a shell glob expands to whatever the directory holds.\n `No EDF or BDF recordings found in ${listed(positionals.map((p) => `\"${printable(p)}\"`))}.\\n`,\n );\n return EXIT_USAGE;\n }\n const inputs = expanded.map((entry) => entry.path);\n /*\n A batch is what you asked for, not what happened to be there.\n\n Counting the recordings made the shape of the run depend on the contents of a folder.\n Under --json a study holding one night printed a pretty-printed object and the same study\n holding two printed two compact lines, so a script written against one of them broke on\n the other — and it broke the day a recording was added, not the day the script changed.\n An input going missing did it in reverse. This is the same count 0.4.20 took out of\n `--out` for the same reason.\n\n Counted from what was named, not from what survived deduplication. `edf2csv one.edf\n alias.edf --out o`, where `alias.edf` is a link to `one.edf`, is two names for one\n recording — converted once, which is right — and the count collapsing to 1 made the run\n stop being a batch: `--out` became the output directory itself, so the files landed in\n `o/` rather than `o/one/`, and `--json` printed one indented document where two\n recordings would have given JSON Lines. cli-reference says of exactly that command that\n it writes `out/one`.\n */\n const batch = positionals.length > 1 || namedDirectory;\n const quiet = values['quiet'] === true;\n const asJson = values['json'] === true;\n const strict = values['strict'] === true;\n const toStdout = values['stdout'] === true;\n\n // Both of these claim stdout. Allowing them together wrote the CSV and then the summary\n // object onto one stream, producing a document that is neither valid CSV nor valid\n // JSON — and silently, since each half looked right on its own.\n /*\n Not under --info, which writes no CSV for --json to collide with.\n\n The two claim stdout because one puts a CSV there and the other a summary. `--info` puts\n neither: it prints a description, and under --json that description *is* the JSON. So\n `--info --stdout --json` — a script asking \"would --stdout work on this recording, in a\n form I can parse\" — was refused for a conflict that cannot arise, which is the same shape\n as the destination guards 0.5.51 stopped applying to it. It is also the only way to see\n the STDOUT_UNSUPPORTED warning from a script.\n */\n if (toStdout && asJson && values['info'] !== true) {\n /*\n `error: ` and the seven-space continuation, like every other refusal.\n\n These two were written before the prefix was, and kept their own shape: every other\n usage error in this tool prints \"error: <what>\" with the advice indented under it, the\n documentation shows them that way, and stderr is what a script greps. A refusal that\n does not match `^error:` is invisible to that grep — and this pair is the one a script\n is most likely to hit, since both flags are things a script passes rather than a person.\n */\n process.stderr.write(\n 'error: --stdout and --json both write to stdout, so they cannot be combined.\\n' +\n ' Use --stdout for the CSV, or --json for the summary.\\n',\n );\n return EXIT_USAGE;\n }\n\n /*\n One stream holds one table, for the same reason it holds one recording: concatenating\n them would give a CSV whose rows come from different files with nothing marking where\n one ends. Naming the count makes it obvious a glob was the cause — except when the count\n is one, which happens for a folder holding a single recording. That read \"--stdout writes\n a single CSV, so it cannot take 1 recordings\": ungrammatical, and wrong on its face, since\n one recording is exactly what it can take. What it cannot take is a folder, whose contents\n are not known until they are walked.\n */\n /*\n Asked of the recordings to stream, not of the run's shape.\n\n These are different questions and 0.5.40 conflated them: it made `batch` count the names\n rather than the recordings, which is right for `--out` and for `--json`, and this guard\n read `batch`. So `edf2csv one.edf one.edf --stdout` — one recording, named twice,\n converted once, perfectly streamable — was refused with the message written for a folder,\n telling the reader to \"name the recording itself\" and quoting the name they had just\n given twice.\n\n A folder is still refused however few recordings it turns out to hold, for the reason\n 0.5.5 gives: what it holds is not known until it is walked.\n */\n if (toStdout && (inputs.length > 1 || namedDirectory)) {\n // Prefixed and indented like the rest; see the --json refusal above. The recording's\n // name goes through `printable` for the reason 0.5.67 gives: a path is untrusted text,\n // and this one is read straight out of a directory the caller named.\n //\n // Quoted where a shell would not hand it back unchanged, by the same rule as the\n // --stdout --gzip hint below, whose comment sets it out: this sentence names a recording\n // to type in place of the folder, and one found inside a folder is as likely to hold a\n // space as the folder is. Unquoted it was two arguments, which --stdout then refuses for\n // being two recordings — the message one step on from the one being read.\n const only = printable(inputs[0] as string);\n process.stderr.write(\n inputs.length === 1\n ? `error: --stdout writes a single CSV, and a folder is converted as a batch even ` +\n `when it holds one recording.\\n` +\n detail(\n `Name the recording itself — ${survivesBare(only) ? only : singleQuoted(only)} — ` +\n `or convert to a directory instead.`,\n )\n : `error: --stdout writes a single CSV, so it cannot take ` +\n `${counted(inputs.length, 'recording')}.\\n` +\n detail('Convert them to directories instead, or run edf2csv once per file.'),\n );\n return EXIT_USAGE;\n }\n\n /*\n Flags that --stdout has nowhere to put.\n\n Both were accepted and dropped in silence. `--out` names a directory that is never\n created, so the run looked like it had written one. `--checksum` is worse than useless:\n the hash is computed before the first record is read, which is a second full pass over\n the input, and then the only file it is ever written to — metadata.json — is not written\n at all. A recording large enough to want a checksum is large enough to notice reading it\n twice for nothing.\n\n Refusing rather than ignoring is what this tool already does for `--stdout --json` and\n `--stdout --annotations-only`.\n */\n /*\n `--force` and `--jobs` were the two left doing nothing in silence.\n\n `--force` means \"write into a directory that already exists\", and there is no directory.\n `--jobs` is deliberately not here. A job count is a property of the run rather than a\n request about the output, `--stdout` clamping it to one is documented, and a wrapper that\n passes `--jobs 4` to everything is not asking for something about this file.\n */\n for (const [flag, given] of [\n ['--out', values['out'] !== undefined],\n ['--checksum', values['checksum'] === true],\n ['--force', values['force'] === true],\n ] as const) {\n if (toStdout && given) {\n // `error:` and the seven-space continuation, like every other refusal — the shape\n // 0.5.79 gave the rest and these two were not enumerated in its test.\n process.stderr.write(\n `error: --stdout and ${flag} cannot be combined: --stdout writes no\\n` +\n detail(`files, and ${flag} has nothing to act on.`) +\n detail(`Drop ${flag}, or drop --stdout and convert to a directory.`),\n );\n return EXIT_USAGE;\n }\n }\n\n /*\n Compressed bytes at a terminal.\n\n `--stdout --gzip` is a documented pair, and every place it is documented redirects it:\n `edf2csv rec.edf --stdout --gzip > signals.csv.gz`. Without the redirect it wrote the\n deflate stream to the terminal — 46 control bytes and three ESCs out of 244 for the\n smallest fixture here, which is a terminal left in whatever state those happened to\n describe, and nothing usable on the screen either way.\n\n This tool escapes every control byte out of a header before printing it, on the reasoning\n that a file should not be able to drive the reader's terminal. Doing it to them directly,\n from output it generated itself, is the same hazard with the argument for it removed.\n\n Only when stdout is a terminal. A pipe and a regular file are both `isTTY === false`, so\n the documented command is unaffected — and that is also why this cannot be answered by\n refusing the flag pair outright, the way `--stdout --json` is.\n\n gzip itself has declined to write compressed data to a terminal for thirty years, which\n is where a reader's expectation comes from.\n */\n if (toStdout && values['gzip'] === true && process.stdout.isTTY === true) {\n /*\n `<recording>` is not a placeholder a shell leaves alone.\n\n This line is printed on its own, indented, in the shape every command this tool offers\n is printed in — and 0.7.18 and 0.7.19 went through the others making sure each one can\n be pasted. Pasted, `<recording>` is a redirect: the shell looks for a file called\n `recording` and the command never runs, or one exists and edf2csv is handed a recording\n on stdin, which it does not read. Either way the answer to \"what do I type instead\"\n does not survive being typed.\n\n The name is in the invocation being refused, and exactly one of them reaches here: a\n folder and a second recording are both turned away above. Quoted by the same rule as\n the other two hints, so a path with a space in it stays one argument.\n */\n const named = printable(inputs[0] as string);\n process.stderr.write(\n 'error: --stdout --gzip would write compressed bytes straight to the terminal.\\n' +\n detail('Redirect it to a file or a pipe:') +\n ` edf2csv ${survivesBare(named) ? named : singleQuoted(named)} ` +\n `--stdout --gzip > signals.csv.gz\\n`,\n );\n return EXIT_USAGE;\n }\n\n /*\n One recording prints the document it always printed; several print one per line.\n\n Pretty-printed objects run together are still readable by a streaming JSON parser, but\n not by anything that reads a record per line — and a batch is exactly where that is\n wanted. Emitting a batch in the single-file shape would repeat 0.2.28's mistake of\n putting two documents on one stream and leaving the caller to work out the boundary.\n\n null, not undefined: the serialisers default this argument to 2, and a default parameter\n takes effect for undefined, which silently gave a batch the indentation it was supposed\n to be dropping.\n */\n const jsonIndent = batch ? null : 2;\n\n try {\n const outOption = typeof values['out'] === 'string' ? values['out'] : undefined;\n const destinations = destinationsFor(expanded, outOption, batch);\n /*\n Where the output would land is not --info's problem to refuse over. It is still\n --info's job to mention it.\n\n 0.5.32 stopped the guards refusing `--info`, and left nothing in their place — while\n the sentence it added to cli-reference says \"`--info --out` is how you would want to\n find out about them\". So the one command documented as the way to learn about a\n collision said nothing at all about it. Reported as a warning now, using the guard's own\n message so there is one wording rather than two.\n */\n if (values['info'] === true) {\n try {\n assertDistinct(inputs, destinations);\n } catch (error) {\n if (!(error instanceof OptionError)) throw error;\n process.stderr.write(`warning: ${printableLines(error.message, ' ')}\\n`);\n }\n }\n\n /*\n Where the output would land is not --info's problem, because --info has no output.\n\n Both guards refused it: `edf2csv study --info --out yy` on a folder holding `rec.edf`\n beside `rec/inner.edf` exited 2 with \"would be converted into yy/rec/inner, which is\n inside yy/rec — where rec.edf is converted\", and printed nothing about either\n recording. Two recordings whose names collide got the overwrite refusal the same way.\n Both messages assert a conversion and an overwrite that --info does not perform, and\n the identical command without --out describes both files happily.\n\n Which makes the refused command the useful one: `--info --out` is how you ask what a\n run would produce before committing to it, and a collision is exactly the thing you\n would want it to tell you about rather than refuse to look.\n */\n if (values['info'] !== true) assertDistinct(inputs, destinations);\n\n const shared = {\n channels: splitChannels(values['channels']),\n start: optionalTime(values['start'], '--start'),\n startText: typeof values['start'] === 'string' ? values['start'] : undefined,\n duration: optionalTime(values['duration'], '--duration'),\n end: optionalTime(values['end'], '--end'),\n endText: typeof values['end'] === 'string' ? values['end'] : undefined,\n decimals: optionalDecimals(values['decimals']),\n annotationsOnly: values['annotations-only'] === true,\n gzip: values['gzip'] === true,\n bom: values['bom'] === true,\n layout: optionalLayout(values['layout']),\n };\n\n // Validated before the --info branch, not inside the conversion path: a flag that\n // cannot be honoured is a usage error whatever mode it was given in, and accepting\n // \"--jobs 0\" in silence under --info is the kind of quiet that this tool avoids.\n // Parsed before it is overridden: --stdout converts one recording however many jobs\n // were asked for, but \"--stdout --jobs 0\" is still a request that cannot be honoured,\n // and accepting it in silence is the thing 0.4.2 fixed for --info.\n const requestedJobs = parseJobs(values['jobs'], inputs.length);\n /*\n Asking for several at once, of a mode that converts one.\n\n After `parseJobs`, not before it: a malformed count is a fact about the value and gets\n the message about the value, which is what the check above this one has always done and\n what its own comment insists on — \"a request that cannot be met is a usage error rather\n than something to accept in silence\". So `--stdout --jobs 0x10` still reports the `0x10`.\n\n `--jobs 1` is not refused. It asks for precisely what happens, and a script passing it\n uniformly is not asking for anything it will not get; `auto` resolves to 1 on one\n recording. What is refused is a number greater than one, which was accepted and dropped.\n */\n const jobs = toStdout ? 1 : requestedJobs;\n\n if (values['info'] === true) {\n const failures: number[] = [];\n let warnings = 0;\n for (const [index, input] of inputs.entries()) {\n // A blank line between reports, so several tables read as one document.\n if (batch && !asJson && index > 0) process.stdout.write('\\n');\n try {\n warnings += await showInfo(\n input as string,\n shared,\n asJson,\n jsonIndent,\n batch,\n values['stdout'] === true,\n );\n } catch (error) {\n failures.push(reportError(error, batch ? (input as string) : undefined));\n }\n }\n if (unreadable.length > 0) failures.push(EXIT_ERROR);\n if (failures.length > 0) return worstOf(failures);\n return strict && warnings > 0 ? EXIT_ERROR : EXIT_OK;\n }\n\n // The meter redraws one line in place, which two conversions cannot share. Running them\n // at once replaces it with the completion count each file prints as it lands.\n const showProgress =\n !quiet && !asJson && !toStdout && jobs === 1 && process.stderr.isTTY === true;\n\n const failures: number[] = [];\n let warnings = 0;\n let converted = 0;\n\n /** Convert input `index`, sending its output wherever the caller wants it. */\n const runOne = async (index: number, emit: Emit): Promise<void> => {\n const input = inputs[index] as string;\n const destination = destinations[index] as string;\n // Which recording this is, before anything it prints. Without it a batch produces a\n // stack of summaries and errors with nothing saying which file each belongs to.\n if (batch && !quiet && !asJson) {\n emit('err', `[${index + 1}/${inputs.length}] ${printable(input)}\\n`);\n }\n try {\n warnings += await convertOne(\n input,\n destination,\n {\n ...shared,\n // With one input and no --out, convert() derives the default itself, exactly as\n // it did before batches existed.\n outputDir: outOption === undefined && !batch ? undefined : destination,\n checksum: values['checksum'] === true,\n force: values['force'] === true,\n toStdout,\n quiet,\n asJson,\n showProgress,\n jsonIndent,\n batch,\n },\n emit,\n );\n converted++;\n } catch (error) {\n /*\n A batch keeps going. One unreadable recording among five hundred is a reason to\n report that file, not to abandon the ones already converted and refuse the rest.\n The exit code still reports the run as failed, and the closing line says how many\n of them made it, so nothing about the failure is quiet.\n */\n failures.push(reportError(error, batch ? input : undefined, emit));\n }\n };\n\n if (jobs === 1) {\n for (let index = 0; index < inputs.length; index++) {\n await runOne(index, writeThrough);\n // A single recording keeps the exit code it has always had rather than the batch's.\n if (!batch && failures.length > 0) return failures[0] as number;\n }\n } else {\n /*\n Real processes, not concurrent promises.\n\n Converting is almost entirely arithmetic and string building — 1.17 s of CPU for\n 1.24 s of wall clock on a 168 MB conversion — and Node runs that on one thread. An\n in-process pool was tried first and gained 6% on eight recordings, which is the\n overlap in the file reads and nothing else. Each conversion is already a whole\n command, so each one gets its own process, which is what `xargs -P` would do by hand.\n\n Workers take the next recording as they free up rather than splitting the list into\n equal shares. Recordings in a folder differ wildly in length, and a fixed split\n leaves every worker but one idle behind the longest file.\n\n Each child's output is held until it exits, so two finishing together cannot\n interleave one's summary with the other's warnings.\n */\n const running = new Map<ChildProcess, string>();\n\n /*\n Ctrl-C in a terminal reaches every process in the group, so the children would stop\n anyway; a signal sent to this process alone does not, and that is how a batch gets\n run from a script or a CI job. Interrupting one left four conversions writing\n gigabytes into a directory their owner believed abandoned, and the last thing on\n screen was a successful \"Done in 1.6s\" from whichever recording had just landed —\n the run read as if it had finished.\n */\n const onInterrupt = (signal: NodeJS.Signals): void => {\n stopping = true;\n const abandoned = [...running.values()];\n for (const child of running.keys()) child.kill('SIGTERM');\n /*\n A destination that is not there was never written to, and saying otherwise is the\n defect the serial handler was fixed for.\n\n `convert()` opens the recording, hashes it under `--checksum` and reads the whole\n annotation channel for record start times *before* it claims the directory — so\n Ctrl-C in the first seconds of a batch of large EDF+ recordings finds nothing on\n disk for any of them. This named every destination as incomplete output regardless,\n and advised distrusting directories `ls` then reported did not exist.\n\n Two answers rather than the serial path's three: whether a directory that IS there\n was there before this run is per-child state the parent does not keep, and the\n sentence that covers both is the one already printed.\n */\n const started = abandoned.filter((destination) => existsSync(destination));\n const untouched = abandoned.filter((destination) => !existsSync(destination));\n process.stderr.write(\n `\\ninterrupted (${signal}): ${abandoned.length} conversion` +\n `${abandoned.length === 1 ? '' : 's'} stopped part way through.\\n` +\n (started.length > 0\n ? detail(`Incomplete, and should not be used: ${listed(started.map(printable))}`)\n : '') +\n (untouched.length > 0\n ? detail(\n `Nothing was written to ${listed(untouched.map(printable))}: ` +\n `${untouched.length === 1 ? 'that directory was' : 'those directories were'} ` +\n `never created.`,\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 ${counted(inputs.length, 'recording')}${failed}${unread}.\\n`,\n );\n }\n\n /*\n A parent that forked this process needs the counts, not just an exit status.\n\n An exit code cannot separate \"converted, and raised warnings\" from \"did not convert\",\n and under --strict those are the same code. The parent read it as a failure, so a\n parallel run of two recordings — one of which merely warned — reported \"Converted 1 of\n 2 recordings; 1 failed\" for a run in which both converted, while the serial path said\n \"Converted 2 of 2\". `process.send` exists only when this process was forked with a\n channel, so nothing changes for an ordinary invocation.\n */\n process.send?.({ edf2csv: { converted, warnings } });\n\n if (failures.length > 0) return worstOf(failures);\n\n /*\n --strict turns any warning into a non-zero exit, for pipelines that would rather stop\n than proceed on a recording the tool had something to say about.\n\n The output is still written. A warning describes the recording, not a failure to\n convert it — a truncated file converts correctly for the records that are there — so\n destroying that work would be the wrong response. The exit code is the signal; what\n to do about it is the caller's decision, and they still have the files to inspect.\n */\n if (strict && warnings > 0) {\n process.stderr.write(\n `\\n${wrap(\n `--strict: ${warnings} warning${warnings === 1 ? '' : 's'} raised, so this run is ` +\n `reported as a failure. The output was still written.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n return EXIT_OK;\n } catch (error) {\n return reportError(error);\n }\n}\n\n/** Print one recording's `--info`, and return how many diagnostics it raised. */\nasync function showInfo(\n input: string,\n shared: Record<string, unknown>,\n asJson: boolean,\n jsonIndent: number | null,\n batch = false,\n toStdout = false,\n): Promise<number> {\n const file = await EdfFile.open(input);\n try {\n /*\n Read the annotation channel only when the timing actually depends on it.\n\n --info is documented as a header-only summary that returns immediately whatever\n the file's size, but it called readAnnotations() on every EDF+/BDF+ file — a seek\n into every data record. deriveRecordStarts discards that data unless the file is\n EDF+D, where record start times are stored rather than arithmetic, so on a\n continuous recording the whole scan was thrown away. It cost 0.29 s on a 12 MB\n file and scaled with record count.\n\n A discontinuous file still needs the scan: without it the reported span and row\n estimate are wrong, which is a bug that has already been fixed once here.\n */\n const hasAnnotations = file.annotationSignals.length > 0;\n const needsEveryRecordStart = file.header.continuity === 'EDF+D' && hasAnnotations;\n /*\n A continuous recording needs its origin too, which is one read rather than one per\n record: 0.4.9 made its first record's timekeeping TAL the point the samples are timed\n from, and this report went on placing a requested window against zero. `--info --start 1`\n predicted 8 rows where the conversion wrote 10, on a file whose discontinuous twin —\n identical but for the reserved field — agreed with itself.\n */\n /*\n And a file with an annotation channel and no marker at all, which is the one case this\n condition used to exclude and the one that most needs it.\n\n Without `EDF+C` or `EDF+D` the origin is not applied — the samples are timed from zero —\n but the annotation channel is found by label, so its events keep the onsets the file\n gives them and the two CSVs come out on clocks the origin apart. That is what\n MISSING_EDF_PLUS_MARKER says, and it is raised from the record starts, which this\n declined to read for exactly the files that produce it. So a conversion warned that\n signals.csv and annotations.csv would be a hundred seconds apart, and `--info` — run\n first, on purpose, to find out what a conversion would say — said nothing at all.\n\n The same single read the continuous case already pays for, on a file that is anomalous\n to begin with: an EDF Annotations channel is not something a plain EDF file has.\n */\n const scan =\n !needsEveryRecordStart && file.header.continuity !== 'EDF+D' && 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: scan?.malformed ?? 0,\n // Counted rather than assumed: see EdfFile.scanOrigin.\n malformedTimekeeping: scan?.malformedTimekeeping ?? 0,\n // And how many of those took events with them, which decides whether the warning\n // beside it says nothing was lost. See EdfFile.scanOrigin.\n malformedTimekeepingWithText: scan?.malformedTimekeepingWithText ?? 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 /*\n The event count, where reading the channel has already produced it.\n\n Only a discontinuous file, where every record start has to be read out of the annotation\n channel; a continuous one is read as far as the first record that states a start time and\n no further, so its events are not all in hand. Counted through the same window predicate\n a conversion filters them by, rather than a second copy of it.\n */\n const eventWindow = requestedAnnotationWindow(shared, plan.range.recordingStartSeconds);\n const knownEvents = needsEveryRecordStart\n ? annotationData.annotations.filter(\n (a) => a.onset >= eventWindow.from && a.onset < eventWindow.to,\n ).length\n : null;\n plan.diagnostics.push(...timing.diagnostics);\n /*\n What --stdout would do with this recording, which --info did not ask.\n\n `--info --stdout` on a three-rate file predicted \"Would write 1,155 rows, roughly\n 22.2 KB\" and said the channels \"are written to one file per rate\" — for a command that\n refuses to run, writes nothing and names no file. --info exists to say what a\n conversion will do, and refusing is one of the things it does.\n\n A warning rather than a refusal, for the reason 0.5.51 gives about the destination\n guards: --info writes nothing, so a rule about the output has no business stopping it\n from describing the recording — and being told the command will not work is exactly\n what was asked. The conversion's own guard supplies the words, so there is one wording.\n */\n /*\n What the conversion would say about the durations it would write.\n\n Same window the conversion applies, so --info predicts the warnings rather than a\n different set. Raised here rather than in the parser because the counts have to be of\n the rows that reach annotations.csv — see durationDiagnostics.\n\n `eventWindow` above and not the resolved range, which is what this passed. The two are\n the same window only while the conversion asks for no more than the recording holds:\n the range is clamped to the recording, and an annotation onset is not obliged to fall\n inside it — a marker for the end of a recording sits at exactly its length, and\n annotations-at-edges.edf exists because filtering events by the range dropped those.\n So a two-second EDF+D carrying one unreadable duration inside its span and one past the\n end was described by\n\n --info 1 annotation states a duration that is not a number, so its ...\n conversion 2 annotations state a duration that is not a number, so their ...\n\n about the same file with no time option given, over an annotations.csv holding both\n rows. `knownEvents` three lines up already counts through `eventWindow`, for the reason\n its comment gives; this was the second copy that comment says not to make.\n */\n plan.diagnostics.push(...durationDiagnostics(annotationData.annotations, eventWindow));\n\n /*\n And the warning about the signal file this conversion would not write.\n\n `--info` prints one accurate line about it in the report body — \"Would write\n channels.csv and no signal data\" — and raised nothing, so the one mode whose purpose is\n to say what a conversion will do carried a shorter warning list than the conversion did.\n `--info --strict` is documented as a cheap way to screen a directory before converting\n it, and on a recording of nothing but annotations it reported one warning where the\n conversion reports two.\n */\n const missing = noSignalFile(file, plan);\n if (missing) plan.diagnostics.push(missing);\n // The same for --annotations-only on a file with none; see noAnnotations.\n const noEvents = noAnnotations(file, shared as ConvertOptions);\n if (noEvents) plan.diagnostics.push(noEvents);\n\n if (toStdout) {\n const refusal = stdoutRefusal(file, plan);\n if (refusal) {\n plan.diagnostics.push({\n code: 'STDOUT_UNSUPPORTED',\n severity: 'warning',\n message: `--stdout would refuse this recording: ${refusal.message.replace(/^--stdout /u, '')}`,\n ...(refusal.hint === undefined ? {} : { hint: refusal.hint }),\n });\n }\n }\n /*\n Checked, like a conversion's stdout is.\n\n This wrote and looked at nothing, so `--info > desc.txt` into a filesystem with no room\n produced a zero-byte file and exited 0. The same audit the `--stdout` path uses: it\n declines anything that is not a regular file, so a pipe or a terminal is unaffected, and\n `--info | head` keeps exiting 0.\n */\n const audit = auditStdout();\n const description = asJson\n ? `${infoJson(file, plan, jsonIndent)}\\n`\n : `${formatInfo(file, plan, knownEvents)}\\n`;\n process.stdout.write(description);\n audit?.count(Buffer.byteLength(description));\n audit?.verify();\n\n // Under --json the warnings travel inside the document, exactly as they do for a\n // conversion, so stderr stays empty and the whole result is one parseable thing.\n const diagnostics = [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics];\n if (!asJson && diagnostics.length > 0) {\n /*\n Named when there is more than one recording to confuse it with.\n\n The table goes to stdout and the warnings to stderr, which is the point of the split\n — but over a folder that left several warnings in a row on stderr with nothing saying\n which recording raised any of them. Two recordings, two warnings, and no way to pair\n them up short of running the tool again one file at a time. A batch conversion has\n named its recordings since 0.4.20; this is the same report from the same tool about\n the same files.\n */\n const where = batch ? `${printable(input)}: ` : '';\n process.stderr.write(\n `\\n${formatDiagnostics(diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n`,\n );\n }\n return diagnostics.length;\n } finally {\n await file.close();\n }\n}\n\n/** Convert one recording, and return how many diagnostics it raised. */\nasync function convertOne(\n input: string,\n destination: string,\n options: Record<string, unknown> & {\n quiet: boolean;\n asJson: boolean;\n showProgress: boolean;\n toStdout: boolean;\n jsonIndent: number | null;\n batch?: boolean;\n },\n emit: Emit = writeThrough,\n): Promise<number> {\n const { quiet, asJson, showProgress, toStdout } = options;\n let lastTick = 0;\n\n /*\n Interrupting a conversion leaves a CSV that stops mid-recording but is still\n perfectly well-formed, so nothing about the file itself reveals that half the\n data is missing. Saying so on the way out is the whole point of a tool that\n claims it will not go quiet when something is wrong.\n */\n /*\n Whether the destination was already there before this run touched it.\n\n Read once, up front, because at interrupt time it is the difference between three\n different true statements and there is no other way to tell them apart. A directory that\n exists now and did not exist then was created by this conversion; one that existed then\n holds files this conversion may never have reached.\n */\n const destinationExistedBefore = toStdout ? false : existsSync(destination);\n\n /*\n Take the progress meter off the line before anything else is printed on it.\n\n The meter writes `\\r converting\\u2026 47%` and leaves the cursor after the percentage, so\n whatever is written next continues that line. Success cleared it and an interrupt cleared\n it; a conversion that *failed* did not, and the error landed on the end of the meter:\n\n converting\\u2026 96%error: Expected 317440 bytes of data at record 1638 but only 0 ...\n\n Which is the one thing this output is shaped to avoid. Every release since 0.7.1 has kept\n `error: ` and `warning: ` at the start of a line so a batch's stderr can be grepped for\n them, and here the prefix sat mid-line, where `grep \\'^error:\\'` over a failed run finds\n nothing at all. Only reachable on a terminal, which is why nothing caught it: the meter is\n off whenever stderr is not a TTY, and a captured stderr never is.\n */\n const clearProgress = (): void => {\n if (showProgress) process.stderr.write('\\r\\u001b[K');\n };\n\n const onInterrupt = (signal: NodeJS.Signals): void => {\n clearProgress();\n /*\n Say which of the three happened, rather than the middle one every time.\n\n --stdout writes no directory, so there is none to warn about. Naming one anyway\n pointed at a path that was never created — the same \"files that were never written\"\n that 0.2.30 removed from this path's error message. The directory case had the same\n defect one step further in: `convert()` hashes the input under --checksum and scans\n the whole annotation channel for record start times *before* it claims the directory,\n so a Ctrl-C in the first second of a large EDF+ printed \"Files already written to\n \"oa\" are incomplete and should not be used\" about a directory that `ls` then reported\n did not exist. Nothing had been written, and the advice was to distrust nothing.\n */\n const wrote = !toStdout && existsSync(destination);\n process.stderr.write(\n `\\ninterrupted (${signal}): the conversion stopped part way through.\\n` +\n (toStdout\n ? detail('The CSV on stdout stops mid-recording and should not be used.')\n : !wrote\n ? detail(`Nothing was written: \"${printable(destination)}\" was never created.`)\n : destinationExistedBefore\n ? detail(\n `\"${printable(destination)}\" was already there, so what is in it may be ` +\n `this run's incomplete output.`,\n )\n : detail(\n `Files already written to \"${printable(destination)}\" are incomplete and ` +\n `should not be used.`,\n )),\n );\n // 128 + signal number, the conventional exit status for dying to a signal.\n process.exit(signal === 'SIGINT' ? 130 : 143);\n };\n process.once('SIGINT', onInterrupt);\n process.once('SIGTERM', onInterrupt);\n\n try {\n const result = await convert(input, {\n ...options,\n onProgress: showProgress\n ? (progress): void => {\n const now = Date.now();\n if (now - lastTick < 100) return;\n lastTick = now;\n const percent =\n progress.recordsTotal === 0\n ? 100\n : Math.floor((progress.recordsDone / progress.recordsTotal) * 100);\n process.stderr.write(`\\r converting… ${percent}%`);\n }\n : undefined,\n });\n\n clearProgress();\n\n if (result.diagnostics.length > 0 && !asJson) {\n /*\n Named when nothing else is naming it.\n\n A batch prints a `[n/m] <path>` header before each recording, and that header is what\n pairs a warning with the file it came from. `--quiet` suppresses it — it is the\n summary line it is documented to suppress, and it took the attribution with it. Two\n recordings, two warnings, and no way to tell which raised which, while `error:` lines\n in the same run stay named because 0.4.20 prefixes those separately.\n\n Same fix 0.5.34 made for `--info` over a folder, which has no header to lose.\n */\n const where = options.batch === true && quiet ? `${printable(input)}: ` : '';\n /*\n The trailing blank line separates these warnings from the summary underneath them,\n so it belongs to the summary and goes when the summary does.\n\n `--quiet` printed it anyway: one stray blank line per recording, in the mode whose\n entire purpose is to print less. On a batch of five hundred that is five hundred of\n them in the log, and on a single recording it left `--quiet --strict` showing two\n blank lines between the warning and the \"--strict:\" line where the ordinary run\n shows one.\n */\n emit(\n 'err',\n `${formatDiagnostics(result.diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n${quiet ? '' : '\\n'}`,\n );\n }\n if (asJson) {\n // One object per line, so a batch is JSON Lines — `jq` reads a record at a time\n // rather than waiting for the whole run to finish.\n emit('out', `${summaryJson(result, options.jsonIndent as number | null)}\\n`);\n } else if (!quiet) {\n // With --stdout there is no directory to summarise, and the row count is the only\n // thing worth saying — on stderr, so the CSV on stdout stays clean.\n if (toStdout) {\n const rows = result.files[0]?.rows ?? 0;\n /*\n A reader that closed the pipe did not receive a conversion, so it does not get a\n conversion's summary. `edf2csv rec.edf --stdout | head -1` announced \"Wrote 52,507\n rows to stdout\" — a number that is neither the recording's 102,400 nor the one row\n head took, but however many had been formatted before the close was noticed.\n\n Whether the *conversion* stopped early is a separate question from whether the\n reader did, and 0.5.12 answered the first with the second. A 10,000-row recording\n whose CSV outruns the pipe buffer but fits one flush is written in full and only\n then meets the closed pipe: every row formatted, every row handed over, and the\n summary said \"The recording was not converted in full.\" The estimate's row count is\n exact, so the two cases can be told apart and told apart honestly — what reached\n the reader is not knowable from this side either way.\n */\n const expected = result.plan.estimate.rows;\n // Singular at one, like every other count. A window narrow enough to select a single\n // sample is an ordinary thing to pipe, and this said \"Wrote 1 rows to stdout.\" — the\n // third message in this family, after 0.5.74 and 0.5.78, each found because the sweep\n // that checks for it was narrower than the set of modes that print a count.\n const written = `${rows.toLocaleString('en-US')} ${rows === 1 ? 'row' : 'rows'}`;\n emit(\n 'err',\n !result.readerHungUp\n ? `Wrote ${written} to stdout.\\n`\n : rows < expected\n ? `Stopped: the reader closed the pipe after ${rows.toLocaleString('en-US')} of ` +\n `${expected.toLocaleString('en-US')} ${expected === 1 ? 'row' : 'rows'} had been ` +\n `written. The recording was ` +\n `not converted in full.\\n`\n : `Wrote ${written} to stdout, but the reader closed ` +\n `the pipe before the end, so not all of them reached it.\\n`,\n );\n } else {\n emit('err', `${formatSummary(result)}\\n`);\n }\n }\n return result.diagnostics.length;\n } catch (error) {\n // The meter is still on the line when a conversion throws, and the caller's `error: `\n // is the next thing written.\n clearProgress();\n throw error;\n } finally {\n process.off('SIGINT', onInterrupt);\n process.off('SIGTERM', onInterrupt);\n }\n}\n\n/** A recording to convert, and the path its output should be named after. */\ninterface Input {\n /** Where the recording is. */\n path: string;\n /**\n * What to call its output, relative to `--out`.\n *\n * For a file named on the command line this is just its own name. For one found by\n * expanding a directory it keeps the position it had inside that directory, so a study\n * laid out as one folder per night comes out the same shape — and, more to the point, so\n * that fifty recordings all named `rec.edf` do not all claim `<out>/rec`.\n */\n name: string;\n /**\n * Whether this recording was found by expanding a directory rather than named directly.\n *\n * `--out` means two different things — the output directory itself, or a parent to put one\n * directory per recording inside — and this is what decides which. See `destinationsFor`.\n */\n fromDirectory: boolean;\n}\n\n/**\n * Recordings to convert, with directories expanded to what is inside them.\n *\n * A directory yields every `.edf` and `.bdf` beneath it, at any depth. Recordings arrive\n * organised into folders, and a shell has no tidy way to reach them — which is why the\n * recipes here carried a `find` incantation to do it. Passing the folder is the obvious\n * thing to try, and it used to fail with \"is a directory, not an EDF file\".\n */\nasync function expandInputs(\n positionals: readonly string[],\n): Promise<{ inputs: Input[]; unreadable: string[]; namedDirectory: boolean }> {\n const found: Input[] = [];\n const unreadable: string[] = [];\n /*\n Whether a directory was NAMED, which is not the same as whether one yielded anything.\n\n `fromDirectory` on the inputs answers the second question, and using it for the first put\n the 0.4.20 defect back one step along: `edf2csv study named.edf --out csv` wrote\n csv/named/ while the study held recordings and csv/signals.csv once it held none, because\n the surviving input then looked like a lone file. Whether some unrelated folder happens\n to contain anything decided where a different recording's output went.\n */\n let namedDirectory = false;\n for (const given of positionals) {\n const info = await stat(given).catch(() => null);\n if (info === null || !info.isDirectory()) {\n // Anything that is not a directory is passed through untouched, so a file that does\n // not exist still reports itself rather than vanishing from the list.\n found.push({ path: given, name: path.basename(given), fromDirectory: false });\n continue;\n }\n namedDirectory = true;\n const walked = await walk(given);\n unreadable.push(...walked.unreadable);\n for (const file of walked.files) {\n found.push({ path: file, name: path.relative(given, file), fromDirectory: true });\n }\n }\n /*\n One recording, however many ways it was named.\n\n The walk already does this for links inside a folder, and the two halves disagreed:\n `edf2csv study` converted a recording reached twice once, while `edf2csv a.edf a.edf`\n refused the whole run for the collision it would cause. A shell makes the second easy\n to produce by accident — `edf2csv *.edf recording.edf` — and there is nothing ambiguous\n about it: it is one recording, and converting it once is what was meant.\n\n A path that does not resolve keeps its own identity so that a file which is not there\n still reports itself rather than being folded into another entry.\n\n Which of the names survives is decided by the names themselves, not by the order they\n turned up in. Keeping the first arrival meant the output directory was named by argument\n order — `edf2csv data/one.edf data/alias.edf` wrote out/one and the same two swapped\n wrote out/alias — and a shell orders a glob however it likes. Inside a folder it was the\n order `readdir` returned, which differs between filesystems, so copying a study to\n another machine could rename its output. A recording that is not a link is preferred over\n a link to it, since that is the name the recording actually has; two of a kind are\n settled by the path that sorts first.\n */\n const identified: { entry: Input; identity: string; link: boolean }[] = [];\n for (const entry of found) {\n const identity = await realpath(entry.path).catch(() => `?${path.resolve(entry.path)}`);\n const own = await lstat(entry.path).catch(() => null);\n identified.push({ entry, identity, link: own?.isSymbolicLink() ?? false });\n }\n\n const byIdentity = new Map<string, Input>();\n const winners = new Map<string, (typeof identified)[number]>();\n for (const candidate of identified) {\n const held = winners.get(candidate.identity);\n if (held !== undefined && !outnames(candidate, held)) continue;\n winners.set(candidate.identity, candidate);\n byIdentity.set(candidate.identity, candidate.entry);\n }\n\n // A directory hands its entries back in whatever order the filesystem stored them.\n const unique = [...byIdentity.values()];\n unique.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n\n /*\n One unreadable path, however many ways it was named — the same rule the recordings above\n get, which this did not.\n\n Each named directory is walked separately and its findings appended, so a folder given\n twice, given relatively and absolutely, or given alongside a link to it, reported every\n path it could not read once per name. `edf2csv study study` on a study holding one locked\n sub-directory printed the error twice and closed with \"Converted 1 of 1 recordings; 2\n paths could not be read\" — one path, counted two — while the recordings beside it were\n correctly converted once. The count is the part that matters: it is what tells someone how\n much of their study was not looked at.\n\n Identity, not spelling, for the reason 0.5.64 gives: `study/locked` and `alias/locked` are\n one directory. `realpathSync` resolves it even when the directory itself cannot be opened,\n since that needs search permission on the parent rather than on the target. A path that\n cannot be resolved at all keeps its own identity, so a name that is simply not there still\n reports itself.\n */\n const seen = new Set<string>();\n const distinct: string[] = [];\n for (const entry of unreadable) {\n let identity: string;\n try {\n identity = realpathSync(entry);\n } catch {\n identity = `?${path.resolve(entry)}`;\n }\n if (seen.has(identity)) continue;\n seen.add(identity);\n // The first spelling is kept, which is the one the caller typed first.\n distinct.push(entry);\n }\n\n return { inputs: unique, unreadable: distinct, namedDirectory };\n}\n\n/**\n * Which of two names for one recording the output should be called after.\n *\n * A name the recording actually has beats a link pointing at it, and two of a kind are\n * settled by sort order. Both are properties of the names, so the answer does not move when\n * a shell expands a glob differently or a filesystem enumerates a folder in another order.\n */\nfunction outnames(\n candidate: { entry: Input; link: boolean },\n held: { entry: Input; link: boolean },\n): boolean {\n if (candidate.link !== held.link) return !candidate.link;\n /*\n Resolved, not as typed. `study/night-01/rec.edf` and `./study/night-01/rec.edf` are the\n same file spelled two ways, and comparing the raw strings made them two different names —\n so this returned here, on lexicographic order of the spelling, and never reached the depth\n rule below that exists for exactly this case.\n\n What that cost is not cosmetic. `edf2csv study study/night-01/rec.edf` converted both\n recordings and exited 0; the identical request written `edf2csv study\n ./study/night-01/rec.edf` was refused with \"would both be converted into \"out/rec\", so one\n would overwrite the other\", exit 2, nothing written. Same files, same folder, same\n intention — and a leading `./` decided whether the run happened. Without the sibling that\n collides it was quieter and no better: one spelling wrote `out/night-01/rec` and the other\n `out/rec`, so a script that started passing absolute paths moved its output.\n\n Through `realpathSync`, not `path.resolve`, because a lexical resolve is not enough: on\n macOS `$TMPDIR` sits under `/var`, which is a link to `/private/var`, so a folder walked\n from the name the caller gave and a recording named relative to the process's own\n directory come out with different absolute prefixes for the same file. Falls back to the\n lexical form for a path that cannot be resolved, which is the same answer as before.\n\n Following links here is safe: a link and its target reach this as two genuinely different\n names, and the rule above has already preferred the real one, so the only pairs left with\n one identity are two spellings or two links — and both should fall through to the depth\n rule below rather than be settled by how they were typed.\n */\n const spelled = (entry: Input): string => {\n try {\n return realpathSync(entry.path);\n } catch {\n return path.resolve(entry.path);\n }\n };\n if (spelled(candidate.entry) !== spelled(held.entry)) {\n return spelled(candidate.entry) < spelled(held.entry);\n }\n /*\n One recording, one path, two names — reached directly and through a named folder.\n\n `edf2csv study study/night-01/rec.edf` and the same two swapped are the same request, and\n they disagreed: the folder gives the recording its position inside the folder\n (`night-01/rec`), a direct mention gives it its bare name (`rec`), and whichever spelling\n the loop met first won. So argument order decided the output directory's name, and, once\n a second `study/rec.edf` was in play, decided whether the run happened at all — the bare\n name collides with it and the run is refused, exit 2, while the other order converts both.\n\n The nested name wins. It is what the folder promised — \"the layout is kept: recordings in\n sub-folders come out in sub-folders\" — and it is the one that does not collide, since\n collapsing a recording to its bare name is what puts it on top of a sibling.\n */\n const depth = (entry: Input): number => entry.name.split(path.sep).length;\n if (depth(candidate.entry) !== depth(held.entry)) return depth(candidate.entry) > depth(held.entry);\n return candidate.entry.name < held.entry.name;\n}\n\n/**\n * Every recording under a directory, following symbolic links.\n *\n * Written by hand rather than with a recursive `readdir` because that reports a symlink as\n * a symlink and never as a file, so a linked recording was skipped without a word — and the\n * closing \"converted 3 of 3\" then described the three it had noticed rather than what was in\n * the folder. Data organised by linking recordings into a working directory is ordinary, and\n * quietly converting fewer files than were asked for is the failure this tool exists to\n * avoid. Naming the same link on the command line always worked, which made the omission\n * harder to notice rather than easier.\n *\n * Following links means they can form a cycle, so directories are recorded by their resolved\n * identity and visited once. A link to a file already reached another way is likewise\n * converted once.\n */\nasync function walk(root: string): Promise<{ files: string[]; unreadable: string[] }> {\n const files: string[] = [];\n const unreadable: string[] = [];\n const seen = new Set<string>();\n /*\n A queue, taken from the front, and each directory's children entered in a settled order.\n\n This was a stack popped from the back, so which of two names for one directory was\n visited first came down to the order `readdir` happened to return them — and the loser\n was then skipped as already seen, taking its name out of the run with it. A folder\n holding `aaa-real/` beside `zzz-alias -> aaa-real` converted into `<out>/zzz-alias/`: the\n link's name, chosen by a hash order that differs between filesystems. 0.4.29 settled this\n for two names of one *file* and left the directory above it deciding by accident.\n\n Breadth first, and each directory's sub-directories entered with real names before links\n and alphabetically within each, so the name that survives is a property of the tree: the\n shallowest, then the one that is not a link, then the first in sort order.\n */\n const queue = [root];\n let next = 0;\n\n while (next < queue.length) {\n const directory = queue[next++] as string;\n const real = await realpath(directory).catch(() => directory);\n if (seen.has(real)) continue;\n seen.add(real);\n\n /*\n A directory that cannot be listed is reported, not stepped over.\n\n Skipping it in silence meant a folder holding three recordings, one of them inside a\n sub-directory without read permission, converted two and said \"Converted 2 of 2\n recordings\" — a total that agreed with itself and with nothing else. That is the same\n failure 0.4.4 fixed for symbolic links, arriving by a different route: converting fewer\n recordings than were asked for and reporting success.\n */\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n unreadable.push(directory);\n continue;\n }\n\n // A real name before a link to the same thing, then alphabetically. The dirent describes\n // the entry itself rather than its target, which is exactly the question here.\n const ordered = [...entries].sort((a, b) => {\n const link = Number(a.isSymbolicLink()) - Number(b.isSymbolicLink());\n return link !== 0 ? link : a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n\n for (const entry of ordered) {\n const full = path.join(directory, entry.name);\n // stat, not the dirent: a dirent describes the link, and what matters is its target.\n const info = await stat(full).catch(() => null);\n if (info === null) {\n /*\n Anything that cannot be inspected is reported, whatever it is called.\n\n This used to report only names ending in `.edf` or `.bdf`, on the reasoning that a\n broken link to something else is nobody's business. A directory carries no such\n name. A study kept as one folder per night, with one night linked to an external\n drive, converted the nights that were mounted and said nothing about the one that\n was not — and because losing that input left a single recording, `--out` stopped\n meaning \"a parent to fill\" and started meaning \"the directory to write\", so the\n survivor landed somewhere else as well. Whether a drive happened to be mounted\n changed both what was converted and where it went, in silence, exit 0.\n\n The walk cannot know what was behind a link it cannot follow, which is exactly the\n reason to say so rather than to guess.\n */\n unreadable.push(full);\n continue;\n }\n if (info.isDirectory()) {\n queue.push(full);\n } else if (info.isFile() && /\\.(edf|bdf)$/iu.test(entry.name)) {\n // Duplicates are collapsed once for the whole list in expandInputs; this only has\n // to find them. The `seen` set here is for directory cycles.\n files.push(full);\n }\n }\n }\n return { files, unreadable };\n}\n\n/**\n * Where each recording's output goes.\n *\n * Naming one recording, `--out` is the output directory itself, which is what it has always\n * meant. Naming a folder, or several recordings, it is a parent and each recording gets its\n * own directory inside it, because writing several recordings into one directory would have\n * them overwrite each other's `signals.csv` — the one thing a batch must not do quietly.\n *\n * With no `--out` at all, every recording converts beside itself exactly as it would have\n * done alone, so a glob behaves like the shell loop it replaces.\n *\n * What a folder means is decided by the folder, not by how much is in it. Counting the\n * recordings instead meant `edf2csv study --out csv` wrote `csv/signals.csv` while the study\n * held one night and `csv/night-01/rec/signals.csv` once it held two: adding a recording\n * moved the output of a recording that had not changed. The same count made the destination\n * depend on things no one had touched either — a night on an unmounted drive, a\n * sub-directory that could not be read — so where the data landed turned on the state of the\n * machine rather than on the command.\n */\nfunction destinationsFor(\n inputs: readonly Input[],\n out: string | undefined,\n /** True when a folder was named, or more than one recording was. See `batch` in `main`. */\n batch: boolean,\n): string[] {\n if (out === undefined) return inputs.map((input) => defaultOutputDir(input.path));\n if (!batch) return [out];\n return inputs.map((input) => path.join(out, stemOf(input.name)));\n}\n\n/** A name without its extension, keeping a leading dot: `.hidden.edf` -> `.hidden`. */\nfunction stemOf(name: string): string {\n const dot = name.lastIndexOf('.');\n const slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\\\'));\n return dot > slash + 1 ? name.slice(0, dot) : name;\n}\n\n/**\n * Refuse a batch in which two recordings would land in the same directory.\n *\n * Two files with the same name in different folders — `night-1/rec.edf` and\n * `night-2/rec.edf`, which is how recordings usually get organised — both resolve to\n * `<out>/rec`. Converting them in turn would leave one recording's data sitting under the\n * other's name with nothing to show it had happened, so the run stops before writing\n * anything at all.\n */\nfunction assertDistinct(inputs: readonly string[], destinations: readonly string[]): void {\n /*\n Two names that differ only in case are one directory on a filesystem that does not\n distinguish them, which is the default on macOS and the rule on Windows.\n\n `a/REC.edf` and `b/rec.edf` produce `<out>/REC` and `<out>/rec`. Compared exactly those\n are different, so both went through, and with --force the second conversion wrote into\n the directory the first had made: one directory holding one recording's `signals.csv`\n beside the other's `signals_256hz.csv`, under a single `metadata.json` naming only one\n of them. The run reported \"Converted 2 of 2 recordings\" and exited 0. A directory whose\n provenance file describes a recording other than the data beside it is the one outcome\n this tool exists to prevent.\n\n The comparison follows the platform rather than being applied everywhere, so a\n case-sensitive filesystem — where those really are two directories — keeps converting\n both. A case-sensitive volume on macOS is the exception it gets wrong, and it gets it\n wrong in the safe direction: a refusal that names both recordings, not a silent merge.\n */\n const foldsCase = process.platform === 'darwin' || process.platform === 'win32';\n /*\n macOS filesystems fold Unicode normalisation as well as case.\n\n HFS+ and APFS compare names in a normalised form, so `café` written as e + U+0301 and\n `café` written as U+00E9 are one directory — while remaining two different JavaScript\n strings, which is all this guard was comparing. Two recordings whose stems differ only\n that way therefore both passed the check and both converted into the same place:\n\n study/café.edf (NFC) -> csv/café/signals.csv\n study/café.bdf (NFD) -> csv/café/signals_256hz.csv, _128hz, _1hz\n\n one directory holding both, under a single metadata.json naming one of them, reported as\n \"Converted 2 of 2 recordings\" and exit 0 under --force. Without --force the second run\n happened to hit \"already exists\", which is the accidental save rather than the check\n doing its job — and it named the wrong problem.\n\n Not folded on Linux, where the two names are genuinely two directories and refusing them\n would be inventing a collision that does not exist. Windows preserves normalisation too,\n so only darwin. This is the same platform-shaped assumption the case fold above already\n makes, and it has the same limit: a network or removable volume that normalises while the\n running platform does not is not covered by either.\n */\n const identity = (destination: string): string => {\n const resolved = path.resolve(destination);\n if (!foldsCase) return resolved;\n const folded = resolved.toLowerCase();\n return process.platform === 'darwin' ? folded.normalize('NFC') : folded;\n };\n\n const claimed = new Map<string, string>();\n for (const [index, destination] of destinations.entries()) {\n const key = identity(destination);\n const first = claimed.get(key);\n if (first !== undefined) {\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" and \"${printable(first)}\" would both be converted into \"${printable(destination)}\", ` +\n `so one would overwrite the other.\\n` +\n `Convert them separately, or rename one of them.`,\n );\n }\n claimed.set(key, inputs[index] as string);\n }\n\n /*\n And refuse one destination sitting inside another.\n\n A recording named `rec.edf` beside a folder named `rec` is enough: their outputs are\n `<out>/rec` and `<out>/rec/inner`, which are not equal, so the check above let both\n through. What happened next depended on which conversion got there first. Each creates\n its own directory with a single non-recursive mkdir but its parents recursively, so\n whichever started second either claimed a directory the other had already made as a\n parent — failing with \"already exists\" — or did not. Five runs in twenty failed that way\n under --jobs 2, converting one recording of two; the same command succeeded the other\n fifteen times.\n\n Sorting first puts an ancestor next to its descendant: anything sorting between them\n shares the same prefix, and would be caught as its own adjacent pair.\n */\n /*\n Each destination is checked against its own ancestors, by name.\n\n The first version of this sorted the resolved paths and compared neighbours, on the\n reasoning that an ancestor and its descendant end up adjacent. They do not: the\n separator is not the lowest character, so any sibling whose name begins with one of the\n thirteen printable characters below '/' lands between them. With `rec.edf`, `rec!x.edf`\n and `rec/inner.edf` in one folder, '!' sorts between `out/rec` and `out/rec/inner` and\n the pair was never compared — three recordings converted, one of them inside another's\n directory, reported as \"Converted 3 of 3\".\n\n Walking up from each destination has no such gap. Output trees are shallow, so this is a\n handful of string lookups per recording.\n */\n const byDestination = new Map<string, number>();\n for (const [index, destination] of destinations.entries()) {\n byDestination.set(identity(destination), index);\n }\n\n for (const [index, destination] of destinations.entries()) {\n let ancestor = path.dirname(path.resolve(destination));\n for (let parent = ''; ancestor !== parent; ancestor = path.dirname(ancestor)) {\n parent = ancestor;\n const owner = byDestination.get(identity(ancestor));\n if (owner === undefined || owner === index) continue;\n /*\n All four paths escaped, not the two nearest the front of the sentence.\n\n A file name may hold a newline on every platform this runs on, and `printableLines`\n splits a message on newlines to indent its continuations — so the two raw paths broke\n the sentence into three lines and printed half a path on each:\n\n error: \"study/re\\x0ac/inner.edf\" would be converted into \"o/re\\x0ac/inner\",\n which is inside \"o/re\n c\" — where \"study/re\n c.edf\" is converted.\n\n Two of the four names read as one thing and two as two, in the same sentence, about\n the same pair of paths. That is 0.5.67's defect — a name holding a newline splitting\n `Wrote` across two lines — in the one message that names four paths at once.\n */\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" would be converted into \"${printable(destination)}\", which is inside ` +\n `\"${printable(destinations[owner] as string)}\" — where \"${printable(inputs[owner] as string)}\" is converted.\\n` +\n `One recording's output cannot sit inside another's. Convert them separately, or ` +\n `rename one of them.`,\n );\n }\n }\n}\n\n/**\n * The exit code for a run in which several files failed for different reasons.\n *\n * 2 means \"you invoked it wrong\" and 1 means \"something went wrong with a file\". When both\n * happened, 1 is the honest answer: the invocation cannot be the whole story once a file has\n * genuinely failed. With a single input there is only ever one code, so its exit status is\n * exactly what it always was.\n */\nfunction worstOf(codes: readonly number[]): number {\n /*\n Anything that is not \"the command line is the problem\" is a failure of the run.\n\n This tested for `EXIT_ERROR` and fell through to `EXIT_USAGE` for everything else, on the\n assumption that a child exits 1 or 2. A child killed by a signal exits 130 or 143 — its\n own interrupt handler does that — so a `--jobs` worker stopped by SIGTERM made the batch\n exit 2, which cli-reference's table defines as \"The command line is the problem\" and\n warnings-and-errors as \"The command was invoked incorrectly, or asked for something the\n recording can't provide\". The command was fine; something killed a worker. The\n out-of-memory killer and a scheduler's time limit both arrive exactly this way, and the\n serial path reports the same event as the failure it is.\n\n Asking the question the other way round is also the honest one: 2 is the narrow claim,\n so it should be the one that has to be earned by every code in the list.\n */\n return codes.some((code) => code !== EXIT_USAGE) ? EXIT_ERROR : EXIT_USAGE;\n}\n\n/**\n * Convert one recording in a separate process, and collect everything it printed.\n *\n * The arguments are rebuilt from the parsed options rather than sliced out of argv, so the\n * child receives exactly the flags this run was given and nothing that only makes sense to\n * the parent: not the other recordings, not --jobs, and not --info or --stdout, neither of\n * which reaches this path.\n */\nasync function convertInChild(\n input: string,\n destination: string,\n values: Record<string, unknown>,\n running: Map<ChildProcess, string>,\n): Promise<{\n code: number;\n out: string;\n err: string;\n report: { converted: number; warnings: number } | null;\n}> {\n /*\n The recording goes last, behind `--`.\n\n As the first argument it was parsed as an option whenever its path began with a dash,\n which `path.join` produces from a folder given as `.` — `./-lead.edf` normalises to\n `-lead.edf`. The child then failed on a file the parent had converted happily, so the\n same command converted two recordings serially and one under --jobs.\n */\n /*\n Option values go in the `--flag=value` form, never as two arguments.\n\n Split across two arguments, a value beginning with a dash is another option as far as the\n child's parser is concerned: `--out ./-nightly` reached it as `--out` followed by\n `-nightly`, and the child died on \"Option '--out' argument is ambiguous\" while the serial\n path converted the same command without complaint. A leading dash is not exotic —\n `path.join` produces one from a folder given as `.`, and directories get named after\n dates and flags often enough. Same failure 0.4.19 fixed for the recording's own path, on\n everything that carries a value rather than on the positional.\n */\n const args = [`--out=${destination}`];\n /*\n --strict is deliberately absent: it is a verdict on the whole run, and a child converting\n one recording is not the whole run. Passing it down made each child announce \"--strict: 1\n warning raised, so this run is reported as a failure\" about its own single file, and exit\n 1 for it, which the parent then counted as a conversion that had not happened. The parent\n applies it once, from the counts the children report.\n */\n for (const flag of ['annotations-only', 'checksum', 'gzip', 'bom', 'force', 'quiet', 'json']) {\n if (values[flag] === true) args.push(`--${flag}`);\n }\n for (const flag of ['start', 'duration', 'end', 'decimals', 'layout']) {\n if (typeof values[flag] === 'string') args.push(`--${flag}=${values[flag] as string}`);\n }\n // --channels is repeatable, and each term is passed as given so that a label containing a\n // comma survives: joining them back into one list would split it in the child.\n const channels = values['channels'];\n if (channels !== undefined) {\n for (const term of Array.isArray(channels) ? (channels as string[]) : [String(channels)]) {\n args.push(`--channels=${term}`);\n }\n }\n args.push('--', input);\n\n return new Promise((resolve) => {\n const child = fork(fileURLToPath(import.meta.url), args, {\n stdio: ['ignore', 'pipe', 'pipe', 'ipc'],\n });\n running.set(child, destination);\n let out = '';\n let err = '';\n let report: { converted: number; warnings: number } | null = null;\n child.on('message', (message: unknown) => {\n const payload = (message as { edf2csv?: { converted: number; warnings: number } })?.edf2csv;\n if (payload) report = payload;\n });\n child.stdout?.setEncoding('utf8').on('data', (chunk: string) => {\n out += chunk;\n });\n child.stderr?.setEncoding('utf8').on('data', (chunk: string) => {\n err += chunk;\n });\n child.on('error', (error) => {\n running.delete(child);\n /*\n Escaped, which every other place this file prints a recording's name has been since\n 0.5.67 and this one was not.\n\n A file name is untrusted text — the filesystem supplies it and a recording inside a\n scanned folder may be called anything, including a name carrying an ESC byte — and\n `named`, `reportError`, the `[n/m]` header and the interrupt handler all put it\n through `printable`. This line is the one that fires when the fork itself fails, which\n is the moment a batch of five hundred at `--jobs auto` runs out of file descriptors,\n and it is the only line in this file that prints a name straight to stderr.\n */\n resolve({\n code: EXIT_ERROR,\n out,\n err: `${err}error: ${printable(input)}: ${printableLines(error.message, ' ')}\\n`,\n report,\n });\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 // And the same question of this one child, for the same reason as the handler above.\n err +=\n `error: stopped by ${signal} before it finished.\\n` +\n detail(\n existsSync(destination)\n ? `Incomplete, and should not be used: ${printable(destination)}`\n : `Nothing was written: \"${printable(destination)}\" was never created.`,\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 /*\n Wrapped, for the same reason the hint under a warning is — and because on this file\n it is frequently the same sentence.\n\n A mixed-rate recording refused by --stdout and the same recording described by --info\n both end in \"Narrow it to one rate with --channels, write --layout long ...\". One\n arrived as a ConversionError hint and printed on a single 130-column line; the other\n arrived as a Diagnostic hint and wrapped at 80 from 0.7.1. Identical advice about an\n identical file, laid out two different ways depending on whether the tool went on to\n convert it.\n */\n /*\n Escaped, which the message above it has been since 0.5.67 and this line never was.\n\n A hint is usually a fixed sentence, which is why it looked like text nobody supplies.\n One is not: a read that fails part way through a conversion reports \"What was written\n to <outputDir> before it failed is incomplete\", and `--out` takes whatever path the\n caller gives it. A directory may be named with an ESC byte on every platform this\n runs on, so `--out $'out\\x1b[31mred'` on a recording that shrinks mid-read put a live\n colour change on stderr — under an `error:` line that had been escaped correctly, two\n lines above.\n */\n if (error.hint) emit('err', `${wrap(printable(error.hint), ' ')}\\n`);\n // A request the tool cannot carry out is the command line's problem, not the file's,\n // whatever layer noticed it. See USAGE_ERROR_CODES.\n return error instanceof ConversionError && USAGE_ERROR_CODES.has(error.code)\n ? EXIT_USAGE\n : EXIT_ERROR;\n }\n if (\n error instanceof ChannelSelectionError ||\n error instanceof TimeRangeError ||\n error instanceof OptionError\n ) {\n emit('err', `error: ${where}${printableLines(error.message, ' ')}\\n`);\n return EXIT_USAGE;\n }\n emit('err', `error: ${where}${message(error, ' ')}\\n`);\n return EXIT_ERROR;\n}\n\n/**\n * How many conversions to run at once.\n *\n * `auto` is one per core, less one, so a long batch does not take the machine over. A batch\n * is the only place this means anything: a single recording is a single conversion however\n * many jobs are asked for.\n */\nfunction parseJobs(raw: unknown, inputs: number): number {\n if (raw === undefined) return 1;\n const text = String(raw).trim();\n /*\n The cores this process may use, not the ones the machine has.\n\n `cpus().length` counts every core the kernel can see, which is the machine's answer to a\n question about this process. A container given two CPUs of a sixty-four core node, a job\n pinned by `taskset` or a scheduler, a `docker --cpuset-cpus` — in all of them `auto` asked\n for sixty-three workers, which is exactly the \"leaves the machine usable\" this setting\n exists for, inverted. `availableParallelism` is the call for the question and has been\n there since Node 18.14; this package requires 20.\n */\n if (text === 'auto') return Math.max(1, Math.min(inputs, availableParallelism() - 1));\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x10`, `1e3` and `999999999999999999999` — the\n last of which is not an integer any more, only the nearest double to one — while refusing\n `4.7`. The message says \"a whole number of 1 or more\" and the refusal of 4.7 is what makes\n a reader believe it, so the others read as the tool silently reinterpreting what they\n typed. `--jobs` is the flag 0.4.2 hardened because accepting `--jobs 0` in silence \"is the\n kind of quiet this tool avoids\".\n\n Surrounding space is trimmed above and is not one of those: ` 4 ` is four to anyone who\n reads it, and a value assembled by a shell out of a file or a `$(...)` routinely carries\n some. What matters is that the refusal quote what was typed rather than what survived the\n trim — see below.\n */\n if (!/^\\d+$/u.test(text)) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n const value = Number(text);\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n return Math.min(value, Math.max(1, inputs));\n}\n\nfunction splitChannels(raw: unknown): string[] | undefined {\n if (raw === undefined) return undefined;\n const list = Array.isArray(raw) ? (raw as string[]) : [String(raw)];\n const terms = list.flatMap((entry) => entry.split(',')).map((t) => t.trim()).filter((t) => t !== '');\n // Returning undefined here would mean \"no --channels given\" and convert everything,\n // which is the opposite of what someone passing an empty list is asking for.\n if (terms.length === 0) {\n throw new OptionError('--channels was given but lists no channel names.');\n }\n return terms;\n}\n\n/**\n * `--start`, `--duration` and `--end`, in seconds.\n *\n * The first two name a position on the recording's own clock, which a file timed from before\n * zero puts below it, so those take a sign. `--duration` is a length and does not.\n */\nfunction optionalTime(raw: unknown, option: string): number | undefined {\n if (raw === undefined) return undefined;\n return parseTimeSpec(String(raw), option, option !== '--duration');\n}\n\n/** `--layout`, which is one of two words and not a guess at what was meant. */\nfunction optionalLayout(raw: unknown): 'wide' | 'long' | undefined {\n if (raw === undefined) return undefined;\n // Trimmed, like every other option's value. This one alone refused ` long`, so a value that\n // came out of a file or a `$(...)` with a newline on it was rejected for a character nobody\n // typed and which the message could not show.\n const text = String(raw).trim();\n if (text === 'wide' || text === 'long') return text;\n throw new OptionError(`--layout must be \"wide\" or \"long\", got \"${quotedValue(raw)}\".`);\n}\n\n/**\n * An option's value as the user typed it, for a message that quotes it back.\n *\n * The point of the quotation marks is to show where the value begins and ends, which matters\n * most for the values that went wrong because of what surrounds them. `--jobs` trimmed first\n * and then quoted the remains, so `--jobs \" x\"` came back as `got \"x\"` and `--jobs \" \"` as\n * `got \"\"` — the second reading as though nothing had been given at all, when what was given\n * is the whole reason it failed. `--start` has quoted the typed value since 0.5.60 and\n * `--decimals` since 0.5.86; this is the same rule, spelled once.\n */\nfunction quotedValue(raw: unknown): string {\n return String(raw);\n}\n\nfunction optionalDecimals(raw: unknown): number | undefined {\n if (raw === undefined) return undefined;\n const text = String(raw).trim();\n // Number('') is 0, which would quietly round every physical value to a whole number.\n if (text === '') {\n throw new OptionError('--decimals needs a number, for example --decimals 3.');\n }\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x3`, `0b11`, `0o5`, `3e0` and `+3`,\n while refusing `3.5` — and the refusal of `3.5` is what makes a reader believe the message\n that says \"a whole number between 0 and 20\". So `--decimals 0o5` wrote five decimals for\n an argument that reads as five to nobody, and `--decimals 0b11` wrote three, in silence,\n exit 0, with a CSV that looks exactly as intended.\n\n The same fix `--jobs` got, and the same one `--channels '#N'` got — where the comment\n records why: \"Every one of them selected a channel and exited 0, so a slip did not fail —\n it quietly converted a different channel than the one asked for, which for this tool is\n the worst way to be wrong.\" A precision is the same kind of quiet.\n */\n const value = Number(text);\n if (!/^\\d+$/u.test(text) || !Number.isInteger(value) || value < 0 || value > 20) {\n throw new OptionError(`--decimals must be a whole number between 0 and 20, got \"${String(raw)}\".`);\n }\n return value;\n}\n\n/*\n Error text is escaped for the same reason --info's table is: a fatal header error quotes\n the channel label that caused it, and that label is free text out of the file. A recording\n declaring a negative sample count under a label containing `\\x1b[2J` cleared the reader's\n screen on the way out.\n*/\n/**\n * A `parseArgs` failure, in this tool's words where they are better than Node's.\n *\n * Node refuses `--out -nightly` with \"Option '--out' argument is ambiguous. Did you forget to\n * specify the option argument for '--out'? To specify an option argument starting with a dash\n * use '--out=-XYZ'.\" The user did not forget anything — they gave a value — and `-XYZ` is a\n * placeholder, where every other message this tool prints quotes what was actually typed and\n * says exactly what to type instead. A destination whose name starts with a dash is not exotic,\n * and neither is a negative `--start` on a recording timed from before zero.\n *\n * 0.4.34 already fixed this message where the tool produced it internally, building child argv\n * for `--jobs` — `--out ./-nightly` reached the child as two arguments and died on it, while the\n * serial path converted the same command. The half a user can hit was left as Node wrote it.\n *\n * An unknown option is reworded too, and for a plainer reason than tone. Node's sentence is\n * \"Unknown option '--chanels'. To specify a positional argument starting with a '-', place it at\n * the end of the command after '--', as in '-- \"--chanels\"' — except the closing quote is not\n * there. Node opens one before `--` and never closes it, so this tool printed an unfinished\n * sentence, without the `error:` prefix every other refusal carries, at the single most common way\n * to get a command wrong. The advice is also for a different mistake than the one almost everyone\n * makes: it explains how to pass a *file* whose name begins with a dash, where the reader has\n * mistyped one of twenty flags.\n *\n * Node's remaining two — a switch given a value, an option missing its value — say something true\n * in words a reader can act on, and replacing them with near-identical sentences would be churn.\n * They do go out with the prefix, which is the other half of what was wrong with the unknown-option\n * message above and was left behind when its wording was fixed:\n *\n * $ edf2csv recording.edf --decimals\n * Option '--decimals <value>' argument missing\n * $ edf2csv recording.edf --gzip=yes\n * Option '--gzip' does not take an argument\n *\n * against `error:` on every other refusal this tool prints, including the one for `--decimals 101`\n * one keystroke away. This repository's own stdout audit picks errors out of stderr with\n * `line.startsWith('error:')`, so by its own reckoning these two runs printed no error and exited 2.\n */\n/**\n * A token as it has to be typed for a shell to hand it over unchanged.\n *\n * Every hint in this file that prints a command has to print one that works — the comment on\n * the `--channels` advice in the header parser lists three separate times it did not. These\n * two are the same failure at the command line rather than in a channel label. `--out \"-my\n * nightly\"` was answered with `Write it as one argument instead: --out=-my nightly`, which is\n * two arguments; `--chan\"nels` was answered with `edf2csv -- \"--chan\"nels\"`, whose quotes\n * collapse into something else again.\n *\n * Left bare when a shell would read it as written, so the ordinary `--out=-nightly` reads as\n * it always has. Otherwise single-quoted, the one POSIX form with no escapes inside it: a\n * single quote in the token closes, escapes and reopens.\n */\nfunction survivesBare(text: string): boolean {\n return text !== '' && !/[^\\w@%+=:,./-]/u.test(text);\n}\n\nfunction singleQuoted(text: string): string {\n return `'${text.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction usageMessage(error: unknown, argv: readonly string[]): string {\n /** Node's own wording, carrying this tool's prefix. */\n const asError = (text: string): string => (text.startsWith('error:') ? text : `error: ${text}`);\n const raw = message(error);\n const code = (error as { code?: unknown } | null)?.code;\n if (code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') return unknownOption(raw);\n if (code !== 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE') {\n return asError(raw);\n }\n const ambiguous = /^Option '(-[^']+)' argument is ambiguous/u.exec(raw);\n if (!ambiguous) return asError(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 asError(raw);\n\n /*\n How the two shapes join, which is not the same and matters.\n\n A long option takes `--out=-nightly`. A short one does NOT take `-o=-nightly`: parseArgs\n reads the equals sign as the first character of the value and hands back \"=-nightly\", so\n that advice would have produced a directory named `=-nightly` without a word — a worse\n failure than the message it replaced, and a silent one. Short options join directly.\n */\n const joined = flag.startsWith('--') ? `${flag}=${value}` : `${flag}${value}`;\n const typeable = survivesBare(joined) ? joined : singleQuoted(joined);\n return printableLines(\n `error: ${flag} was given \"${value}\", which begins with a dash and so reads as another ` +\n `flag rather than as its value.\\n` +\n ` Write it as one argument instead: ${typeable}`,\n );\n}\n\n/** Levenshtein distance, two rows at a time. Twenty names of under twenty characters. */\nfunction editDistance(a: string, b: string): number {\n const row = Array.from({ length: b.length + 1 }, (_unused, i) => i);\n for (let i = 1; i <= a.length; i++) {\n let diagonal = row[0] as number;\n row[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const above = row[j] as number;\n row[j] = Math.min(above + 1, (row[j - 1] as number) + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));\n diagonal = above;\n }\n }\n return row[b.length] as number;\n}\n\n/**\n * The option they probably meant, or null when nothing is close enough to say.\n *\n * Long options only. A stray `-Z` carries one character of evidence, and guessing a word\n * from it would be guessing.\n *\n * Two rules, because typos come in two shapes. An unfinished name is a prefix — `--chan`,\n * `--decim` — and is only taken from three characters up, so `--c` does not become\n * `--channels` or `--checksum` by coin toss. A misspelt one is within a couple of edits,\n * scaled to the name's length so `--jobs` is not reached from three characters away.\n */\nfunction nearestOption(flag: string): string | null {\n if (!flag.startsWith('--')) return null;\n const typed = flag.slice(2).toLowerCase();\n if (typed.length < 2) return null;\n\n const names = Object.keys(OPTIONS);\n if (typed.length >= 3) {\n const prefixed = names.filter((name) => name.startsWith(typed));\n if (prefixed.length === 1) return `--${prefixed[0] as string}`;\n }\n\n let best: string | null = null;\n let score = Infinity;\n let tied = false;\n for (const name of names) {\n const d = editDistance(typed, name);\n if (d < score) {\n score = d;\n best = name;\n tied = false;\n } else if (d === score) {\n tied = true;\n }\n }\n if (best === null || tied) return null;\n return score <= Math.max(1, Math.min(3, Math.floor(best.length / 3))) ? `--${best}` : null;\n}\n\n/** An option this tool does not have, in a finished sentence, naming the likely one. */\nfunction unknownOption(raw: string): string {\n const found = /^Unknown option '([^']+)'/u.exec(raw);\n // If Node ever rewords it, its text is still true; only the polish is lost.\n if (!found) return raw;\n const flag = found[1] as string;\n const near = nearestOption(flag);\n return printableLines(\n `error: There is no ${flag} option.${near === null ? '' : ` Did you mean ${near}?`}\\n` +\n ` If it is the name of a file, pass it after -- instead:\\n` +\n ` edf2csv -- ${survivesBare(flag) ? `\"${flag}\"` : singleQuoted(flag)}`,\n );\n}\n\nfunction message(error: unknown, indent = ''): string {\n // The indent lines continuation up under an \"error: \" prefix. Usage problems print without\n // one, alongside the usage text, so they pass nothing and stay flush left.\n return printableLines(error instanceof Error ? error.message : String(error), indent);\n}\n\n// `worstOf` is exported for the test that pins the exit-code mapping. A batch's verdict is\n// one of the few things a script branches on, and the mapping is easier to check directly\n// than by racing a signal at a worker.\n// `parseJobs` joins them for the same reason: what `auto` resolves to is a number nothing\n// could see from outside, since the only observable effect is how many children run at once.\nexport { defaultOutputDir, parseJobs, worstOf };\n\n/**\n * Whether this file was executed rather than imported.\n *\n * npm installs a bin as a symlink (node_modules/.bin/edf2csv -> ../edf2csv/dist/cli.js),\n * and that is the path `npx` runs. In that case process.argv[1] is the symlink while\n * import.meta.url is already resolved to the real file, so comparing the two directly\n * reports \"imported\" and the command silently does nothing. Both sides are resolved\n * through realpath before comparing.\n */\nfunction isMainModule(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n // An unreadable or deleted entry path is not a reason to refuse to run.\n return pathToFileURL(entry).href === import.meta.url;\n }\n}\n\nconst invokedDirectly = isMainModule();\n\nif (invokedDirectly) {\n main(process.argv.slice(2))\n .then((code) => {\n /*\n A write failure already reported is not erased by a run that thought it finished.\n\n The stdout listener above prints `Writing to stdout failed: ...` and sets EXIT_ERROR,\n and assigning `code` over it put that back to 0 — so `--info > desc.txt` onto a full\n filesystem printed the error, left a zero-byte file, and exited 0. It only looked\n right because the stdout audit threw a second error on the way out, whose message was\n about a short write that had not happened.\n\n A closed pipe deliberately sets no code (see ignoreBrokenPipe), so `--info | head`\n still exits 0 through here.\n */\n process.exitCode = code === EXIT_OK ? (process.exitCode ?? EXIT_OK) : code;\n })\n .catch((error: unknown) => {\n process.stderr.write(`error: ${message(error)}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE1C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,yBAAyB,EACzB,aAAa,EACb,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,sFAAsF;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvI,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,KAAK,GAAG,WAAW,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoE/B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,OAAO,GAAG;IACd,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACrC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;IACnC,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE;IACxD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC5B,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACvB,kBAAkB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACvC,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC5B,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC7B,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACzB,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACxB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;IACpC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACtC,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACtC,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACzB,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC3B,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;CAChC,CAAC;AAEX,MAAM,OAAO,GAAG,CAAC,CAAC;AAClB,MAAM,UAAU,GAAG,CAAC,CAAC;AACrB,MAAM,UAAU,GAAG,CAAC,CAAC;AAErB;;;;;;GAMG;AACH,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,MAA0B;IAClD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAA4B,EAAE,EAAE;QAClD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC3B,mFAAmF;YACnF,oFAAoF;YACpF,gFAAgF;YAChF,gFAAgF;YAChF,gEAAgE;YAChE,OAAO;QACT,CAAC;QAED;;;;;;;;;;;;;;UAcE;QACF,IAAI,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,YAAY,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACxH,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAuB;IAChD,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjC,IAAI,MAA+B,CAAC;IACpC,IAAI,WAAqB,CAAC;IAE1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC;YACvB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;YACf,gBAAgB,EAAE,IAAI;YACtB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QACH,MAAM,GAAG,MAAM,CAAC,MAAiC,CAAC;QAClD,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,8CAA8C,CAAC,CAAC;QACjG,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;QACrC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,KAAK,EAAE,CAAC,CAAC;QACzD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;;;;;;;;;;;;;;;;;MAkBE;IACF,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2EAA2E,CAAC,CAAC;QAClG,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,QAAiB,CAAC;IACtB,IAAI,UAAoB,CAAC;IACzB,IAAI,cAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;QAC9C,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;QACxB,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QAC9B,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,uFAAuF;IACvF,oFAAoF;IACpF,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,UAAU,SAAS,CAAC,KAAK,CAAC,kEAAkE,CAC7F,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B;;;;;;;;;;;UAWE;QACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,IAAI,CACL,+BAA+B,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,GAAG;gBACrF,iCAAiC,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,GAAG;gBACtF,wBAAwB,CAC3B,IAAI,CACN,CAAC;YACF,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK;QAClB,sFAAsF;QACtF,6EAA6E;QAC7E,qCAAqC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAC9F,CAAC;QACF,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnD;;;;;;;;;;;;;;;;;MAiBE;IACF,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,cAAc,CAAC;IACvD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IAE3C,wFAAwF;IACxF,mFAAmF;IACnF,gEAAgE;IAChE;;;;;;;;;MASE;IACF,IAAI,QAAQ,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD;;;;;;;;UAQE;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gFAAgF;YAC9E,+DAA+D,CAClE,CAAC;QACF,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;;;;;;;MAQE;IACF;;;;;;;;;;;;MAYE;IACF,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,cAAc,CAAC,EAAE,CAAC;QACtD,qFAAqF;QACrF,uFAAuF;QACvF,qEAAqE;QACrE,EAAE;QACF,iFAAiF;QACjF,yFAAyF;QACzF,uFAAuF;QACvF,yFAAyF;QACzF,0EAA0E;QAC1E,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAW,CAAC,CAAC;QAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,MAAM,CAAC,MAAM,KAAK,CAAC;YACjB,CAAC,CAAC,iFAAiF;gBACjF,gCAAgC;gBAChC,MAAM,CACJ,+BAA+B,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK;oBAChF,oCAAoC,CACvC;YACH,CAAC,CAAC,yDAAyD;gBACzD,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK;gBAC3C,MAAM,CAAC,oEAAoE,CAAC,CACjF,CAAC;QACF,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;;;;;;;;;;;MAYE;IACF;;;;;;;MAOE;IACF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI;QAC1B,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;QACtC,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;QAC3C,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;KAC7B,EAAE,CAAC;QACX,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YACtB,kFAAkF;YAClF,sEAAsE;YACtE,EAAE;YACF,uFAAuF;YACvF,qFAAqF;YACrF,uFAAuF;YACvF,mFAAmF;YACnF,uFAAuF;YACvF,mBAAmB;YACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uBAAuB,IAAI,qDAAqD;gBAC9E,GAAG,IAAI,2BAA2B;gBAClC,MAAM,CAAC,QAAQ,IAAI,gDAAgD,CAAC,CACvE,CAAC;YACF,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;MAmBE;IACF,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QACzE;;;;;;;;;;;;;UAaE;QACF,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAW,CAAC,CAAC;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iFAAiF;YAC/E,MAAM,CAAC,kCAAkC,CAAC;YAC1C,kBAAkB,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG;YACtE,oCAAoC,CACvC,CAAC;QACF,OAAO,UAAU,CAAC;IACpB,CAAC;IAED;;;;;;;;;;;MAWE;IACF,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChF,MAAM,YAAY,GAAG,eAAe,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QACjE;;;;;;;;;UASE;QACF,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;oBAAE,MAAM,KAAK,CAAC;gBACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;YACnF,CAAC;QACH,CAAC;QAED;;;;;;;;;;;;;UAaE;QACF,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI;YAAE,cAAc,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAElE,MAAM,MAAM,GAAG;YACb,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC3C,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;YAC/C,SAAS,EAAE,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;YAC5E,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;YACxD,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;YACzC,OAAO,EAAE,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;YACtE,QAAQ,EAAE,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC9C,eAAe,EAAE,MAAM,CAAC,kBAAkB,CAAC,KAAK,IAAI;YACpD,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI;YAC7B,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI;YAC3B,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;SACzC,CAAC;QAEF,kFAAkF;QAClF,mFAAmF;QACnF,iFAAiF;QACjF,oFAAoF;QACpF,sFAAsF;QACtF,mEAAmE;QACnE,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/D;;;;;;;;;;;UAWE;QACF,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;QAE1C,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;YAC9B,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC9C,wEAAwE;gBACxE,IAAI,KAAK,IAAI,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC;oBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC9D,IAAI,CAAC;oBACH,QAAQ,IAAI,MAAM,QAAQ,CACxB,KAAe,EACf,MAAM,EACN,MAAM,EACN,UAAU,EACV,KAAK,EACL,MAAM,CAAC,QAAQ,CAAC,KAAK,IAAI,CAC1B,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAE,KAAgB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;YACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;YAClD,OAAO,MAAM,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC;QACvD,CAAC;QAED,wFAAwF;QACxF,8EAA8E;QAC9E,MAAM,YAAY,GAChB,CAAC,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,IAAI,IAAI,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;QAEhF,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,8EAA8E;QAC9E,MAAM,MAAM,GAAG,KAAK,EAAE,KAAa,EAAE,IAAU,EAAiB,EAAE;YAChE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAW,CAAC;YACtC,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAW,CAAC;YAClD,oFAAoF;YACpF,gFAAgF;YAChF,IAAI,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC/B,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACvE,CAAC;YACD,IAAI,CAAC;gBACH,QAAQ,IAAI,MAAM,UAAU,CAC1B,KAAK,EACL,WAAW,EACX;oBACE,GAAG,MAAM;oBACT,gFAAgF;oBAChF,iCAAiC;oBACjC,SAAS,EAAE,SAAS,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW;oBACtE,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,IAAI;oBACrC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI;oBAC/B,QAAQ;oBACR,KAAK;oBACL,MAAM;oBACN,YAAY;oBACZ,UAAU;oBACV,KAAK;iBACN,EACD,IAAI,CACL,CAAC;gBACF,SAAS,EAAE,CAAC;YACd,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf;;;;;kBAKE;gBACF,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;YACrE,CAAC;QACH,CAAC,CAAC;QAEF,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;YACf,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnD,MAAM,MAAM,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;gBAClC,oFAAoF;gBACpF,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;oBAAE,OAAO,QAAQ,CAAC,CAAC,CAAW,CAAC;YAClE,CAAC;QACH,CAAC;aAAM,CAAC;YACN;;;;;;;;;;;;;;;cAeE;YACF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;YAEhD;;;;;;;cAOE;YACF,MAAM,WAAW,GAAG,CAAC,MAAsB,EAAQ,EAAE;gBACnD,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;gBACxC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE;oBAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC1D;;;;;;;;;;;;;kBAaE;gBACF,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC3E,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;gBAC9E,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kBAAkB,MAAM,MAAM,SAAS,CAAC,MAAM,aAAa;oBACzD,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,8BAA8B;oBAClE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;wBACjB,CAAC,CAAC,MAAM,CAAC,uCAAuC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;wBACjF,CAAC,CAAC,EAAE,CAAC;oBACP,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;wBACnB,CAAC,CAAC,MAAM,CACJ,0BAA0B,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI;4BAC5D,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,wBAAwB,GAAG;4BAC9E,gBAAgB,CACnB;wBACH,CAAC,CAAC,EAAE,CAAC,CACV,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAChD,CAAC,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;YAErC,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,MAAM,MAAM,GAAG,KAAK,IAAmB,EAAE;gBACvC,KAAK,IAAI,KAAK,GAAG,IAAI,EAAE,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,EAAE,EAAE,CAAC;oBAC/D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAW,CAAC;oBACtC,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;oBACxB,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;wBACtB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC5E,CAAC;oBACD,MAAM,KAAK,GAAG,MAAM,cAAc,CAChC,KAAK,EACL,YAAY,CAAC,KAAK,CAAW,EAC7B,MAAM,EACN,OAAO,CACR,CAAC;oBACF,6EAA6E;oBAC7E,+EAA+E;oBAC/E,qCAAqC;oBACrC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;wBACjB,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;wBACpC,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;oBACpC,CAAC;yBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;wBAC5B,SAAS,EAAE,CAAC;oBACd,CAAC;oBACD,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC;wBAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAChD,4EAA4E;oBAC5E,qDAAqD;oBACrD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC9D;;;;;;;;;;;;;;sBAcE;oBACF,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;oBACjD,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,CAAC;YACH,CAAC,CAAC;YACF,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAClE,CAAC;oBAAS,CAAC;gBACT,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;gBACnC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;QAED;;;;;;;;;;;;UAYE;QACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAErD,IAAI,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAC/B,MAAM,MAAM,GACV,UAAU,CAAC,MAAM,GAAG,CAAC;gBACnB,CAAC,CAAC,KAAK,UAAU,CAAC,MAAM,QAAQ,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,oBAAoB;gBACtF,CAAC,CAAC,EAAE,CAAC;YACT,MAAM,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YACxF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,eAAe,SAAS,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,GAAG,MAAM,KAAK,CAC1F,CAAC;QACJ,CAAC;QAED;;;;;;;;;UASE;QACF,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;QAErD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;QAElD;;;;;;;;UAQE;QACF,IAAI,MAAM,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,KAAK,IAAI,CACP,aAAa,QAAQ,WAAW,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,0BAA0B;gBACjF,sDAAsD,CACzD,IAAI,CACN,CAAC;YACF,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,KAAK,UAAU,QAAQ,CACrB,KAAa,EACb,MAA+B,EAC/B,MAAe,EACf,UAAyB,EACzB,KAAK,GAAG,KAAK,EACb,QAAQ,GAAG,KAAK;IAEhB,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC;QACH;;;;;;;;;;;;UAYE;QACF,MAAM,cAAc,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC;QACzD,MAAM,qBAAqB,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,OAAO,IAAI,cAAc,CAAC;QACnF;;;;;;UAME;QACF;;;;;;;;;;;;;;UAcE;QACF,MAAM,IAAI,GACR,CAAC,qBAAqB,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,OAAO,IAAI,cAAc;YAC5E,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE;YACzB,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,cAAc,GAAG,qBAAqB;YAC1C,CAAC,CAAC,MAAM,IAAI,CAAC,eAAe,EAAE;YAC9B,CAAC,CAAC;gBACE,WAAW,EAAE,EAAE;gBACf,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;gBACvC,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,CAAC;gBAC/B,uDAAuD;gBACvD,oBAAoB,EAAE,IAAI,EAAE,oBAAoB,IAAI,CAAC;gBACrD,iFAAiF;gBACjF,2DAA2D;gBAC3D,4BAA4B,EAAE,IAAI,EAAE,4BAA4B,IAAI,CAAC;aACtE,CAAC;QACN,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,CACpB;YACE,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,cAAc;YAC1C,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,oBAAoB,EAAE,IAAI,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC;YACvD,YAAY,EAAE,MAAM,CAAC,MAAM;SAC5B,EACD,MAAM,CACP,CAAC;QACF;;;;;;;UAOE;QACF,MAAM,WAAW,GAAG,yBAAyB,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACxF,MAAM,WAAW,GAAG,qBAAqB;YACvC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,MAAM,CAC/B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,WAAW,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,GAAG,WAAW,CAAC,EAAE,CAC/D,CAAC,MAAM;YACV,CAAC,CAAC,IAAI,CAAC;QACT,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QAC7C;;;;;;;;;;;;UAYE;QACF;;;;;;;;;;;;;;;;;;;;;UAqBE;QACF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,mBAAmB,CAAC,cAAc,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;QAEvF;;;;;;;;;UASE;QACF,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,OAAO;YAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5C,0EAA0E;QAC1E,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,MAAwB,CAAC,CAAC;QAC/D,IAAI,QAAQ;YAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAE9C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC1C,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;oBACpB,IAAI,EAAE,oBAAoB;oBAC1B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,yCAAyC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,EAAE;oBAC9F,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;iBAC9D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD;;;;;;;UAOE;QACF,MAAM,KAAK,GAAG,WAAW,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,MAAM;YACxB,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI;YACzC,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;QAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAClC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;QAC7C,KAAK,EAAE,MAAM,EAAE,CAAC;QAEhB,iFAAiF;QACjF,iFAAiF;QACjF,MAAM,WAAW,GAAG,CAAC,GAAG,qBAAqB,CAAC,sBAAsB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;QACtI,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC;;;;;;;;;cASE;YACF,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,KAAK,iBAAiB,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAC/F,CAAC;QACJ,CAAC;QACD,OAAO,WAAW,CAAC,MAAM,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AAED,wEAAwE;AACxE,KAAK,UAAU,UAAU,CACvB,KAAa,EACb,WAAmB,EACnB,OAOC,EACD,OAAa,YAAY;IAEzB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAC1D,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB;;;;;MAKE;IACF;;;;;;;MAOE;IACF,MAAM,wBAAwB,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAE5E;;;;;;;;;;;;;;MAcE;IACF,MAAM,aAAa,GAAG,GAAS,EAAE;QAC/B,IAAI,YAAY;YAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,MAAsB,EAAQ,EAAE;QACnD,aAAa,EAAE,CAAC;QAChB;;;;;;;;;;;UAWE;QACF,MAAM,KAAK,GAAG,CAAC,QAAQ,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kBAAkB,MAAM,+CAA+C;YACrE,CAAC,QAAQ;gBACP,CAAC,CAAC,MAAM,CAAC,+DAA+D,CAAC;gBACzE,CAAC,CAAC,CAAC,KAAK;oBACN,CAAC,CAAC,MAAM,CAAC,yBAAyB,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC;oBAC/E,CAAC,CAAC,wBAAwB;wBACxB,CAAC,CAAC,MAAM,CACJ,IAAI,SAAS,CAAC,WAAW,CAAC,+CAA+C;4BACvE,+BAA+B,CAClC;wBACH,CAAC,CAAC,MAAM,CACJ,6BAA6B,SAAS,CAAC,WAAW,CAAC,uBAAuB;4BACxE,qBAAqB,CACxB,CAAC,CACb,CAAC;QACF,2EAA2E;QAC3E,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACpC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAErC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE;YAClC,GAAG,OAAO;YACV,UAAU,EAAE,YAAY;gBACtB,CAAC,CAAC,CAAC,QAAQ,EAAQ,EAAE;oBACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBACvB,IAAI,GAAG,GAAG,QAAQ,GAAG,GAAG;wBAAE,OAAO;oBACjC,QAAQ,GAAG,GAAG,CAAC;oBACf,MAAM,OAAO,GACX,QAAQ,CAAC,YAAY,KAAK,CAAC;wBACzB,CAAC,CAAC,GAAG;wBACL,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC;oBACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,OAAO,GAAG,CAAC,CAAC;gBACtD,CAAC;gBACH,CAAC,CAAC,SAAS;SACd,CAAC,CAAC;QAEH,aAAa,EAAE,CAAC;QAEhB,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAC7C;;;;;;;;;;cAUE;YACF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7E;;;;;;;;;cASE;YACF,IAAI,CACF,KAAK,EACL,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CACxH,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,EAAE,CAAC;YACX,gFAAgF;YAChF,mDAAmD;YACnD,IAAI,CAAC,KAAK,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,UAA2B,CAAC,IAAI,CAAC,CAAC;QAC/E,CAAC;aAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAClB,kFAAkF;YAClF,oEAAoE;YACpE,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;gBACxC;;;;;;;;;;;;;kBAaE;gBACF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC3C,qFAAqF;gBACrF,qFAAqF;gBACrF,sFAAsF;gBACtF,4EAA4E;gBAC5E,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;gBACjF,IAAI,CACF,KAAK,EACL,CAAC,MAAM,CAAC,YAAY;oBAClB,CAAC,CAAC,SAAS,OAAO,eAAe;oBACjC,CAAC,CAAC,IAAI,GAAG,QAAQ;wBACf,CAAC,CAAC,6CAA6C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM;4BAC/E,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,YAAY;4BAClF,6BAA6B;4BAC7B,0BAA0B;wBAC5B,CAAC,CAAC,SAAS,OAAO,oCAAoC;4BACpD,2DAA2D,CAClE,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,KAAK,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sFAAsF;QACtF,6BAA6B;QAC7B,aAAa,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAwBD;;;;;;;GAOG;AACH,KAAK,UAAU,YAAY,CACzB,WAA8B;IAE9B,MAAM,KAAK,GAAY,EAAE,CAAC;IAC1B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC;;;;;;;;MAQE;IACF,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YACzC,oFAAoF;YACpF,sEAAsE;YACtE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9E,SAAS;QACX,CAAC;QACD,cAAc,GAAG,IAAI,CAAC;QACtB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,UAAU,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QACtC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IACD;;;;;;;;;;;;;;;;;;;;MAoBE;IACF,MAAM,UAAU,GAAwD,EAAE,CAAC;IAC3E,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACtD,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC5C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAuC,CAAC;IAC/D,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;YAAE,SAAS;QAC/D,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC3C,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,mFAAmF;IACnF,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE;;;;;;;;;;;;;;;;;MAiBE;IACF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,SAAS;QACjC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnB,uEAAuE;QACvE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AAClE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CACf,SAA0C,EAC1C,IAAqC;IAErC,IAAI,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC;IACzD;;;;;;;;;;;;;;;;;;;;;;;;MAwBE;IACF,MAAM,OAAO,GAAG,CAAC,KAAY,EAAU,EAAE;QACvC,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CAAC;IACF,IAAI,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,OAAO,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC;IACD;;;;;;;;;;;;;MAaE;IACF,MAAM,KAAK,GAAG,CAAC,KAAY,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1E,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpG,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;AAChD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,KAAK,UAAU,IAAI,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B;;;;;;;;;;;;;MAaE;IACF,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,CAAW,CAAC;QAC1C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC9D,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAEf;;;;;;;;UAQE;QACF,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,yFAAyF;QACzF,+EAA+E;QAC/E,MAAM,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACzC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;YACrE,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;QAEH,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C,qFAAqF;YACrF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB;;;;;;;;;;;;;;kBAcE;gBACF,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9D,kFAAkF;gBAClF,6DAA6D;gBAC7D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,eAAe,CACtB,MAAwB,EACxB,GAAuB;AACvB,2FAA2F;AAC3F,KAAc;IAEd,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAClF,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,uFAAuF;AACvF,SAAS,MAAM,CAAC,IAAY;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,OAAO,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACrD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,cAAc,CAAC,MAAyB,EAAE,YAA+B;IAChF;;;;;;;;;;;;;;;;MAgBE;IACF,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IAChF;;;;;;;;;;;;;;;;;;;;;MAqBE;IACF,MAAM,QAAQ,GAAG,CAAC,WAAmB,EAAU,EAAE;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS;YAAE,OAAO,QAAQ,CAAC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QACtC,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1E,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1D,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,WAAW,CACnB,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAW,CAAC,UAAU,SAAS,CAAC,KAAK,CAAC,mCAAmC,SAAS,CAAC,WAAW,CAAC,KAAK;gBAC5H,qCAAqC;gBACrC,iDAAiD,CACpD,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAW,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;;;;;MAcE;IACF;;;;;;;;;;;;;MAaE;IACF,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;IAChD,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1D,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1D,IAAI,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;QACvD,KAAK,IAAI,MAAM,GAAG,EAAE,EAAE,QAAQ,KAAK,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7E,MAAM,GAAG,QAAQ,CAAC;YAClB,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;YACpD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;gBAAE,SAAS;YACrD;;;;;;;;;;;;;;;cAeE;YACF,MAAM,IAAI,WAAW,CACnB,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAW,CAAC,8BAA8B,SAAS,CAAC,WAAW,CAAC,qBAAqB;gBAC7G,IAAI,SAAS,CAAC,YAAY,CAAC,KAAK,CAAW,CAAC,cAAc,SAAS,CAAC,MAAM,CAAC,KAAK,CAAW,CAAC,mBAAmB;gBAC/G,kFAAkF;gBAClF,qBAAqB,CACxB,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,KAAwB;IACvC;;;;;;;;;;;;;;MAcE;IACF,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;AAC7E,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,cAAc,CAC3B,KAAa,EACb,WAAmB,EACnB,MAA+B,EAC/B,OAAkC;IAOlC;;;;;;;MAOE;IACF;;;;;;;;;;MAUE;IACF,MAAM,IAAI,GAAG,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;IACtC;;;;;;MAME;IACF,KAAK,MAAM,IAAI,IAAI,CAAC,kBAAkB,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;QAC7F,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,CAAC;QACtE,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,QAAQ;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAW,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,0FAA0F;IAC1F,+EAA+E;IAC/E,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,QAAqB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzF,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAEvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;YACvD,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC;SACzC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAChC,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,MAAM,GAAmD,IAAI,CAAC;QAClE,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAgB,EAAE,EAAE;YACvC,MAAM,OAAO,GAAI,OAAiE,EAAE,OAAO,CAAC;YAC5F,IAAI,OAAO;gBAAE,MAAM,GAAG,OAAO,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC7D,GAAG,IAAI,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC7D,GAAG,IAAI,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtB;;;;;;;;;;cAUE;YACF,OAAO,CAAC;gBACN,IAAI,EAAE,UAAU;gBAChB,GAAG;gBACH,GAAG,EAAE,GAAG,GAAG,UAAU,SAAS,CAAC,KAAK,CAAC,KAAK,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI;gBACtF,MAAM;aACP,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtB;;;;;;;;;;;;;cAaE;YACF,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACjC,qFAAqF;gBACrF,GAAG;oBACD,qBAAqB,MAAM,wBAAwB;wBACnD,MAAM,CACJ,UAAU,CAAC,WAAW,CAAC;4BACrB,CAAC,CAAC,uCAAuC,SAAS,CAAC,WAAW,CAAC,EAAE;4BACjE,CAAC,CAAC,yBAAyB,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAC1E,CAAC;YACN,CAAC;YACD,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,SAAS,KAAK,CAAC,IAAY,EAAE,KAAa,EAAE,YAAY,GAAG,KAAK;IAC9D,uFAAuF;IACvF,2FAA2F;IAC3F,4EAA4E;IAC5E,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,4BAA4B,CAAC,CAAC,CAAC,aAAa,CAAC;IAC1E,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,6FAA6F;AAC7F,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAYD,MAAM,YAAY,GAAS,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;IAC1C,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,qFAAqF;AACrF,SAAS,QAAQ;IACf,MAAM,KAAK,GAAgC,EAAE,CAAC;IAC9C,OAAO;QACL,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAClD,KAAK,EAAE,GAAG,EAAE;YACV,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,KAAK;gBAAE,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjE,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,WAAW,CAAC,KAAc,EAAE,KAAc,EAAE,OAAa,YAAY;IAC5E,MAAM,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;IACjE,IAAI,KAAK,YAAY,QAAQ,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QAClE,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC5E;;;;;;;;;;UAUE;QACF;;;;;;;;;;UAUE;QACF,IAAI,KAAK,CAAC,IAAI;YAAE,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC3E,qFAAqF;QACrF,oDAAoD;QACpD,OAAO,KAAK,YAAY,eAAe,IAAI,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;YAC1E,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,UAAU,CAAC;IACjB,CAAC;IACD,IACE,KAAK,YAAY,qBAAqB;QACtC,KAAK,YAAY,cAAc;QAC/B,KAAK,YAAY,WAAW,EAC5B,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC5E,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7D,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,GAAY,EAAE,MAAc;IAC7C,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC;;;;;;;;;MASE;IACF,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,oBAAoB,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IACtF;;;;;;;;;;;;;;MAcE;IACF,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,WAAW,CAAC,+DAA+D,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7G,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,WAAW,CAAC,+DAA+D,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7G,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,aAAa,CAAC,GAAY;IACjC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,GAAgB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACrG,oFAAoF;IACpF,6EAA6E;IAC7E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,WAAW,CAAC,kDAAkD,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,GAAY,EAAE,MAAc;IAChD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,OAAO,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC;AACrE,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,GAAY;IAClC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,4FAA4F;IAC5F,4FAA4F;IAC5F,8CAA8C;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACpD,MAAM,IAAI,WAAW,CAAC,2CAA2C,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACzF,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,GAAY;IAC/B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,qFAAqF;IACrF,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,WAAW,CAAC,sDAAsD,CAAC,CAAC;IAChF,CAAC;IACD;;;;;;;;;;;;;MAaE;IACF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QAChF,MAAM,IAAI,WAAW,CAAC,4DAA4D,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;EAKE;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH;;;;;;;;;;;;;GAaG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,KAAK,EAAE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC9C,CAAC;AAED,SAAS,YAAY,CAAC,KAAc,EAAE,IAAuB;IAC3D,uDAAuD;IACvD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IAChG,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAI,KAAmC,EAAE,IAAI,CAAC;IACxD,IAAI,IAAI,KAAK,+BAA+B;QAAE,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC;IACxE,IAAI,IAAI,KAAK,qCAAqC,EAAE,CAAC;QACnD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,SAAS,GAAG,2CAA2C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxE,IAAI,CAAC,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IAEpC,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAW,CAAC;IACpC,wFAAwF;IACxF,yFAAyF;IACzF,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACjD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;IAE7C;;;;;;;MAOE;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC;IAC9E,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACtE,OAAO,cAAc,CACnB,UAAU,IAAI,eAAe,KAAK,sDAAsD;QACtF,kCAAkC;QAClC,4CAA4C,QAAQ,EAAE,CACzD,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACxC,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,IAAI,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAW,CAAC;QAChC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAW,CAAC;YAC/B,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAY,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrG,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC,CAAC,CAAC,MAAM,CAAW,CAAC;AACjC,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAW,EAAE,CAAC;IACjE,CAAC;IAED,IAAI,IAAI,GAAkB,IAAI,CAAC;IAC/B,IAAI,KAAK,GAAG,QAAQ,CAAC;IACrB,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC;YACd,KAAK,GAAG,CAAC,CAAC;YACV,IAAI,GAAG,IAAI,CAAC;YACZ,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;aAAM,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;YACvB,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7F,CAAC;AAED,wFAAwF;AACxF,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrD,4EAA4E;IAC5E,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAC;IACvB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAW,CAAC;IAChC,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,cAAc,CACnB,sBAAsB,IAAI,WAAW,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,IAAI,GAAG,IAAI;QACpF,iEAAiE;QACjE,qBAAqB,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAC/E,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,KAAc,EAAE,MAAM,GAAG,EAAE;IAC1C,2FAA2F;IAC3F,2EAA2E;IAC3E,OAAO,cAAc,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;AACxF,CAAC;AAED,2FAA2F;AAC3F,0FAA0F;AAC1F,uCAAuC;AACvC,0FAA0F;AAC1F,6FAA6F;AAC7F,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AAEhD;;;;;;;;GAQG;AACH,SAAS,YAAY;IACnB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;IACvD,CAAC;AACH,CAAC;AAED,MAAM,eAAe,GAAG,YAAY,EAAE,CAAC;AAEvC,IAAI,eAAe,EAAE,CAAC;IACpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QACb;;;;;;;;;;;UAWE;QACF,OAAO,CAAC,QAAQ,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,CAAC,CAAC;SACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC;IAChC,CAAC,CAAC,CAAC;AACP,CAAC","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 { availableParallelism } from 'node:os';\nimport { fork } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport path from 'node:path';\nimport process from 'node:process';\n\nimport { EdfError } from './edf/errors.js';\nimport { EdfFile } from './edf/reader.js';\nimport { buildPlan, withoutFileRateWarning } from './convert/plan.js';\nimport {\n ConversionError,\n USAGE_ERROR_CODES,\n auditStdout,\n convert,\n defaultOutputDir,\n durationDiagnostics,\n requestedAnnotationWindow,\n noAnnotations,\n noSignalFile,\n stdoutRefusal,\n} from './convert/run.js';\nimport type { ConvertOptions } from './convert/run.js';\nimport { ChannelSelectionError } from './convert/channels.js';\n// Shared with the library so a bad option is the same error whichever way it arrived.\nimport { OptionError } from './convert/options.js';\nimport { TimeRangeError, parseTimeSpec } from './convert/time-range.js';\nimport { deriveRecordStarts, withTimingPromiseKept } from './convert/timing.js';\nimport { formatDiagnostics, formatInfo, infoJson, formatSummary, printable, printableLines, summaryJson, wrap } from './cli/report.js';\nimport { counted, listed } from './format/list.js';\nimport { VERSION } from './version.js';\n\nconst USAGE = `edf2csv ${VERSION}\nConvert EDF, EDF+ and BDF recordings to CSV\n\nUsage\n edf2csv <recording.edf | folder> [more ...] [options]\n\nOptions\n -i, --info Show the recording's structure and the estimated\n output size, without converting anything\n -o, --out <dir> Output directory (default: <recording>_csv, beside\n the input)\n -c, --channels <list> Only these channels, comma-separated. Use #N to pick a\n channel by position when two share a label\n --start <time> Begin at this offset (30s, 5m, 1h30m, 00:30:00)\n --duration <time> Convert this much\n --end <time> Stop at this offset (instead of --duration)\n --annotations-only Write only the EDF+ annotations, no signal data\n --decimals <n> Fix the decimal places instead of deriving them\n per channel\n --checksum Record a SHA-256 of the input in metadata.json\n --layout <kind> wide (default): one column per channel, one file per\n sampling rate. long: one file of time_s,channel,value,\n every rate together, one row per sample\n --gzip Compress every CSV, writing .csv.gz files\n --bom Start each CSV with a UTF-8 byte order mark, so Excel\n reads accented text and units like µV correctly\n -j, --jobs <n> Convert this many recordings at once, or \"auto\"\n (default: 1)\n -f, --force Write into the output directory if it already exists\n -q, --quiet Suppress the summary; warnings and errors still print\n --json Print machine-readable JSON to stdout (works with\n --info too)\n --strict Exit 1 if the recording raised any warning\n --stdout Write the signal CSV to stdout instead of a directory\n (one table only: one sampling rate, or --layout long)\n -h, --help Show this help\n -V, --version Show the version\n\nSeveral recordings\n A folder is expanded to every .edf and .bdf inside it, at any depth, and the\n layout is kept: recordings in sub-folders come out in sub-folders.\n Pass more than one and each is converted in turn. Without --out each lands\n beside itself as usual; with --out that directory becomes the parent and each\n recording gets its own inside it. A file that cannot be read is reported and\n the rest still convert, with a non-zero exit at the end. --jobs converts\n several at once, which is worth it for a folder of them.\n\nOutput\n A directory containing signals.csv, channels.csv, metadata.json, and\n annotations.csv when the recording carries EDF+ annotations. Channels recorded\n at different sampling rates are written to separate files, never resampled.\n With --gzip each CSV becomes a .csv.gz; metadata.json stays plain text so the\n directory can still be read at a glance. With --bom each CSV starts with a\n UTF-8 byte order mark and metadata.json does not, since JSON.parse rejects\n one.\n With --layout long every channel goes into one signals.csv as time_s, channel\n and value, one row per sample, whatever rates the recording mixes — which is\n also the one arrangement --stdout can stream for a mixed-rate file.\n\nExamples\n edf2csv recording.edf\n edf2csv recording.edf --info\n edf2csv recording.edf --channels \"EEG Fpz-Cz,ECG\" --out ./converted\n edf2csv recording.edf --start 30m --duration 5m\n edf2csv recording.edf --annotations-only\n edf2csv /data/*.edf --out ./converted\n edf2csv /data/*.edf --out ./converted --jobs auto\n edf2csv /data/study --out ./converted --jobs auto\n`;\n\n/**\n * A continuation line under an `error: ` or `interrupted (SIGINT): ` head.\n *\n * These are written straight to stderr rather than raised as an error, so they never passed\n * through `printableLines` and never wrapped. Most of them interpolate a destination or a\n * recording path, which makes the line as long as the caller's directory tree is deep: the\n * `--stdout` refusal for a folder reached 268 columns on a four-deep path, saying \"Name the\n * recording itself — <path> — or convert to a directory instead.\"\n *\n * A path has no spaces to break at, so it stays whole on its own line and the sentence\n * around it wraps. That is the useful shape: the path is the part that gets copied.\n */\nconst detail = (text: string): string => `${wrap(text, ' ')}\\n`;\n\n/**\n * Every option the command line takes.\n *\n * Its own constant so `parseArgs` and the \"did you mean\" suggestion read the same list. A\n * second copy of twenty flag names is a copy that will be missing the next one.\n */\nconst OPTIONS = {\n info: { type: 'boolean', short: 'i' },\n out: { type: 'string', short: 'o' },\n channels: { type: 'string', short: 'c', multiple: true },\n start: { type: 'string' },\n duration: { type: 'string' },\n end: { type: 'string' },\n 'annotations-only': { type: 'boolean' },\n decimals: { type: 'string' },\n checksum: { type: 'boolean' },\n layout: { type: 'string' },\n gzip: { type: 'boolean' },\n bom: { type: 'boolean' },\n jobs: { type: 'string', short: 'j' },\n force: { type: 'boolean', short: 'f' },\n quiet: { type: 'boolean', short: 'q' },\n json: { type: 'boolean' },\n strict: { type: 'boolean' },\n stdout: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'V' },\n} as const;\n\nconst EXIT_OK = 0;\nconst EXIT_ERROR = 1;\nconst EXIT_USAGE = 2;\n\n/**\n * Whether this run is already shutting its children down.\n *\n * A child dying by signal is normally worth a line of its own, but not when this process is\n * the one that killed it: the interrupt handler names every abandoned directory in a single\n * message, and a per-child line underneath it would say the same thing again, once per job.\n */\nlet stopping = false;\n\n/**\n * Piping into a consumer that exits early (`| head -1`) closes our stdout, and the\n * next write raises EPIPE. That is normal in a shell pipeline, not an error worth a\n * stack trace, so it is swallowed while anything else still surfaces.\n */\nfunction ignoreBrokenPipe(stream: NodeJS.WriteStream): void {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') {\n // A reader closing early (`edf2csv ... --info | head -5`) is not a failure, so the\n // error is swallowed rather than thrown. It deliberately does NOT set an exit code:\n // forcing 0 here would erase a real failure whenever the pipe happened to close\n // after the run had already failed, reporting success for a conversion that did\n // not happen. Node exits 0 on its own when nothing sets a code.\n return;\n }\n\n /*\n Anything else is a real write failure, and this used to rethrow it.\n\n The throw lands on a nextTick, outside whatever try/catch the conversion is running\n inside, so it became an uncaught exception: `--stdout` redirected onto a full disk\n died with a raw stack trace headed `dist/cli.js: throw error;` and lost the warning\n that the CSV it had already produced was truncated. The same failure through `--out`\n printed the ordinary message, and so did `--stdout` through the library API, which\n never registers this listener — the designed path exists and works, and this was\n preempting it.\n\n A second listener means the conversion's writer is watching this stream and will\n turn the failure into that ordinary message. Saying it here too would report one\n failure twice, so this only speaks when nothing else will.\n */\n if (stream.listenerCount('error') > 1) return;\n process.stderr.write(`error: Writing to ${stream === process.stdout ? 'stdout' : 'stderr'} failed: ${error.message}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n\nexport async function main(argv: readonly string[]): Promise<number> {\n ignoreBrokenPipe(process.stdout);\n ignoreBrokenPipe(process.stderr);\n\n let values: Record<string, unknown>;\n let positionals: string[];\n\n try {\n const parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n strict: true,\n options: OPTIONS,\n });\n values = parsed.values as Record<string, unknown>;\n positionals = parsed.positionals;\n } catch (error) {\n process.stderr.write(`${usageMessage(error, argv)}\\n\\nRun edf2csv --help to see the options.\\n`);\n return EXIT_USAGE;\n }\n\n if (values['help'] === true) {\n process.stdout.write(USAGE);\n return EXIT_OK;\n }\n if (values['version'] === true) {\n process.stdout.write(`${VERSION}\\n`);\n return EXIT_OK;\n }\n\n if (positionals.length === 0) {\n process.stderr.write(`No input file given.\\n\\n${USAGE}`);\n return EXIT_USAGE;\n }\n\n /*\n An empty --out, refused here rather than by the filesystem.\n\n Every other option that takes a value refuses an empty one from the command line and says\n what it wanted: `--start is empty`, `--channels was given but lists no channel names`,\n `--layout must be \"wide\" or \"long\", got \"\"`. `--out \"\"` was the eighth of eight and the\n exception — it went the whole way to `mkdir('')` and came back as a conversion failure\n about a path:\n\n error: Cannot create \"\": part of the path does not exist.\n Check the path exists and that you have permission to write there.\n\n Exit 1 where the other seven are exit 2, advice about a path and a permission for a value\n that is neither, and in a batch it is decided once per recording rather than once for the\n run. `--out \"$DEST\"` with `DEST` unset is how it gets written by accident, which is also\n why the empty string alone is refused and the value is not trimmed first: a directory whose\n name is a space is a strange thing to ask for, but it is a thing the filesystem has and a\n path is not a keyword.\n */\n if (values['out'] === '') {\n process.stderr.write('error: --out is empty. Give a directory, for example --out ./converted.\\n');\n return EXIT_USAGE;\n }\n\n let expanded: Input[];\n let unreadable: string[];\n let namedDirectory: boolean;\n try {\n const found = await expandInputs(positionals);\n expanded = found.inputs;\n unreadable = found.unreadable;\n namedDirectory = found.namedDirectory;\n } catch (error) {\n return reportError(error);\n }\n\n // Reported before anything is converted, so it cannot be lost among the summaries, and\n // counted against the run so the exit code does not call a partial sweep a success.\n for (const entry of unreadable) {\n process.stderr.write(\n `error: ${printable(entry)}: could not be read, so any recordings inside it were skipped.\\n`,\n );\n }\n\n if (expanded.length === 0) {\n /*\n \"None here\" and \"could not look\" are different answers, and so are their exit codes.\n\n A folder the process cannot read gave the same exit 2 and the same \"No EDF or BDF\n recordings found\" as an empty one — while the line above it said the folder could not\n be read. Exit 2 is this tool's code for \"the command itself was wrong\", so a script\n was being told to fix its arguments when what needed fixing was a permission. The\n command was fine; the filesystem refused.\n\n And the sentence itself claimed a fact the run is in no position to state: nothing was\n found because nothing was looked at.\n */\n if (unreadable.length > 0) {\n process.stderr.write(\n `${wrap(\n `Nothing could be converted: ${unreadable.length === 1 ? 'that path' : 'those paths'} ` +\n `could not be read, so whether ${unreadable.length === 1 ? 'it holds' : 'they hold'} ` +\n `recordings is unknown.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n process.stderr.write(\n // Escaped, like every other path this prints. These come from the command line, which\n // makes them no safer: a shell glob expands to whatever the directory holds.\n `No EDF or BDF recordings found in ${listed(positionals.map((p) => `\"${printable(p)}\"`))}.\\n`,\n );\n return EXIT_USAGE;\n }\n const inputs = expanded.map((entry) => entry.path);\n /*\n A batch is what you asked for, not what happened to be there.\n\n Counting the recordings made the shape of the run depend on the contents of a folder.\n Under --json a study holding one night printed a pretty-printed object and the same study\n holding two printed two compact lines, so a script written against one of them broke on\n the other — and it broke the day a recording was added, not the day the script changed.\n An input going missing did it in reverse. This is the same count 0.4.20 took out of\n `--out` for the same reason.\n\n Counted from what was named, not from what survived deduplication. `edf2csv one.edf\n alias.edf --out o`, where `alias.edf` is a link to `one.edf`, is two names for one\n recording — converted once, which is right — and the count collapsing to 1 made the run\n stop being a batch: `--out` became the output directory itself, so the files landed in\n `o/` rather than `o/one/`, and `--json` printed one indented document where two\n recordings would have given JSON Lines. cli-reference says of exactly that command that\n it writes `out/one`.\n */\n const batch = positionals.length > 1 || namedDirectory;\n const quiet = values['quiet'] === true;\n const asJson = values['json'] === true;\n const strict = values['strict'] === true;\n const toStdout = values['stdout'] === true;\n\n // Both of these claim stdout. Allowing them together wrote the CSV and then the summary\n // object onto one stream, producing a document that is neither valid CSV nor valid\n // JSON — and silently, since each half looked right on its own.\n /*\n Not under --info, which writes no CSV for --json to collide with.\n\n The two claim stdout because one puts a CSV there and the other a summary. `--info` puts\n neither: it prints a description, and under --json that description *is* the JSON. So\n `--info --stdout --json` — a script asking \"would --stdout work on this recording, in a\n form I can parse\" — was refused for a conflict that cannot arise, which is the same shape\n as the destination guards 0.5.51 stopped applying to it. It is also the only way to see\n the STDOUT_UNSUPPORTED warning from a script.\n */\n if (toStdout && asJson && values['info'] !== true) {\n /*\n `error: ` and the seven-space continuation, like every other refusal.\n\n These two were written before the prefix was, and kept their own shape: every other\n usage error in this tool prints \"error: <what>\" with the advice indented under it, the\n documentation shows them that way, and stderr is what a script greps. A refusal that\n does not match `^error:` is invisible to that grep — and this pair is the one a script\n is most likely to hit, since both flags are things a script passes rather than a person.\n */\n process.stderr.write(\n 'error: --stdout and --json both write to stdout, so they cannot be combined.\\n' +\n ' Use --stdout for the CSV, or --json for the summary.\\n',\n );\n return EXIT_USAGE;\n }\n\n /*\n One stream holds one table, for the same reason it holds one recording: concatenating\n them would give a CSV whose rows come from different files with nothing marking where\n one ends. Naming the count makes it obvious a glob was the cause — except when the count\n is one, which happens for a folder holding a single recording. That read \"--stdout writes\n a single CSV, so it cannot take 1 recordings\": ungrammatical, and wrong on its face, since\n one recording is exactly what it can take. What it cannot take is a folder, whose contents\n are not known until they are walked.\n */\n /*\n Asked of the recordings to stream, not of the run's shape.\n\n These are different questions and 0.5.40 conflated them: it made `batch` count the names\n rather than the recordings, which is right for `--out` and for `--json`, and this guard\n read `batch`. So `edf2csv one.edf one.edf --stdout` — one recording, named twice,\n converted once, perfectly streamable — was refused with the message written for a folder,\n telling the reader to \"name the recording itself\" and quoting the name they had just\n given twice.\n\n A folder is still refused however few recordings it turns out to hold, for the reason\n 0.5.5 gives: what it holds is not known until it is walked.\n */\n if (toStdout && (inputs.length > 1 || namedDirectory)) {\n // Prefixed and indented like the rest; see the --json refusal above. The recording's\n // name goes through `printable` for the reason 0.5.67 gives: a path is untrusted text,\n // and this one is read straight out of a directory the caller named.\n //\n // Quoted where a shell would not hand it back unchanged, by the same rule as the\n // --stdout --gzip hint below, whose comment sets it out: this sentence names a recording\n // to type in place of the folder, and one found inside a folder is as likely to hold a\n // space as the folder is. Unquoted it was two arguments, which --stdout then refuses for\n // being two recordings — the message one step on from the one being read.\n const only = printable(inputs[0] as string);\n process.stderr.write(\n inputs.length === 1\n ? `error: --stdout writes a single CSV, and a folder is converted as a batch even ` +\n `when it holds one recording.\\n` +\n detail(\n `Name the recording itself — ${survivesBare(only) ? only : singleQuoted(only)} — ` +\n `or convert to a directory instead.`,\n )\n : `error: --stdout writes a single CSV, so it cannot take ` +\n `${counted(inputs.length, 'recording')}.\\n` +\n detail('Convert them to directories instead, or run edf2csv once per file.'),\n );\n return EXIT_USAGE;\n }\n\n /*\n Flags that --stdout has nowhere to put.\n\n Both were accepted and dropped in silence. `--out` names a directory that is never\n created, so the run looked like it had written one. `--checksum` is worse than useless:\n the hash is computed before the first record is read, which is a second full pass over\n the input, and then the only file it is ever written to — metadata.json — is not written\n at all. A recording large enough to want a checksum is large enough to notice reading it\n twice for nothing.\n\n Refusing rather than ignoring is what this tool already does for `--stdout --json` and\n `--stdout --annotations-only`.\n */\n /*\n `--force` and `--jobs` were the two left doing nothing in silence.\n\n `--force` means \"write into a directory that already exists\", and there is no directory.\n `--jobs` is deliberately not here. A job count is a property of the run rather than a\n request about the output, `--stdout` clamping it to one is documented, and a wrapper that\n passes `--jobs 4` to everything is not asking for something about this file.\n */\n for (const [flag, given] of [\n ['--out', values['out'] !== undefined],\n ['--checksum', values['checksum'] === true],\n ['--force', values['force'] === true],\n ] as const) {\n if (toStdout && given) {\n // `error:` and the seven-space continuation, like every other refusal — the shape\n // 0.5.79 gave the rest and these two were not enumerated in its test.\n //\n // One sentence on the first line, which is the other half of that shape. The break was\n // written into the middle of it — \"cannot be combined: --stdout writes no\" / \"files,\n // and --out has nothing to act on\" — so the line a log greps carried a fragment ending\n // in \"no\", and the line under it opened with \"files,\". `printableLines` leaves the\n // first line whole at whatever width it runs to, deliberately, so there was nothing to\n // pre-wrap it for.\n process.stderr.write(\n `error: --stdout and ${flag} cannot be combined: --stdout writes no files, and ` +\n `${flag} has nothing to act on.\\n` +\n detail(`Drop ${flag}, or drop --stdout and convert to a directory.`),\n );\n return EXIT_USAGE;\n }\n }\n\n /*\n Compressed bytes at a terminal.\n\n `--stdout --gzip` is a documented pair, and every place it is documented redirects it:\n `edf2csv rec.edf --stdout --gzip > signals.csv.gz`. Without the redirect it wrote the\n deflate stream to the terminal — 46 control bytes and three ESCs out of 244 for the\n smallest fixture here, which is a terminal left in whatever state those happened to\n describe, and nothing usable on the screen either way.\n\n This tool escapes every control byte out of a header before printing it, on the reasoning\n that a file should not be able to drive the reader's terminal. Doing it to them directly,\n from output it generated itself, is the same hazard with the argument for it removed.\n\n Only when stdout is a terminal. A pipe and a regular file are both `isTTY === false`, so\n the documented command is unaffected — and that is also why this cannot be answered by\n refusing the flag pair outright, the way `--stdout --json` is.\n\n gzip itself has declined to write compressed data to a terminal for thirty years, which\n is where a reader's expectation comes from.\n */\n if (toStdout && values['gzip'] === true && process.stdout.isTTY === true) {\n /*\n `<recording>` is not a placeholder a shell leaves alone.\n\n This line is printed on its own, indented, in the shape every command this tool offers\n is printed in — and 0.7.18 and 0.7.19 went through the others making sure each one can\n be pasted. Pasted, `<recording>` is a redirect: the shell looks for a file called\n `recording` and the command never runs, or one exists and edf2csv is handed a recording\n on stdin, which it does not read. Either way the answer to \"what do I type instead\"\n does not survive being typed.\n\n The name is in the invocation being refused, and exactly one of them reaches here: a\n folder and a second recording are both turned away above. Quoted by the same rule as\n the other two hints, so a path with a space in it stays one argument.\n */\n const named = printable(inputs[0] as string);\n process.stderr.write(\n 'error: --stdout --gzip would write compressed bytes straight to the terminal.\\n' +\n detail('Redirect it to a file or a pipe:') +\n ` edf2csv ${survivesBare(named) ? named : singleQuoted(named)} ` +\n `--stdout --gzip > signals.csv.gz\\n`,\n );\n return EXIT_USAGE;\n }\n\n /*\n One recording prints the document it always printed; several print one per line.\n\n Pretty-printed objects run together are still readable by a streaming JSON parser, but\n not by anything that reads a record per line — and a batch is exactly where that is\n wanted. Emitting a batch in the single-file shape would repeat 0.2.28's mistake of\n putting two documents on one stream and leaving the caller to work out the boundary.\n\n null, not undefined: the serialisers default this argument to 2, and a default parameter\n takes effect for undefined, which silently gave a batch the indentation it was supposed\n to be dropping.\n */\n const jsonIndent = batch ? null : 2;\n\n try {\n const outOption = typeof values['out'] === 'string' ? values['out'] : undefined;\n const destinations = destinationsFor(expanded, outOption, batch);\n /*\n Where the output would land is not --info's problem to refuse over. It is still\n --info's job to mention it.\n\n 0.5.32 stopped the guards refusing `--info`, and left nothing in their place — while\n the sentence it added to cli-reference says \"`--info --out` is how you would want to\n find out about them\". So the one command documented as the way to learn about a\n collision said nothing at all about it. Reported as a warning now, using the guard's own\n message so there is one wording rather than two.\n */\n if (values['info'] === true) {\n try {\n assertDistinct(inputs, destinations);\n } catch (error) {\n if (!(error instanceof OptionError)) throw error;\n process.stderr.write(`warning: ${printableLines(error.message, ' ')}\\n`);\n }\n }\n\n /*\n Where the output would land is not --info's problem, because --info has no output.\n\n Both guards refused it: `edf2csv study --info --out yy` on a folder holding `rec.edf`\n beside `rec/inner.edf` exited 2 with \"would be converted into yy/rec/inner, which is\n inside yy/rec — where rec.edf is converted\", and printed nothing about either\n recording. Two recordings whose names collide got the overwrite refusal the same way.\n Both messages assert a conversion and an overwrite that --info does not perform, and\n the identical command without --out describes both files happily.\n\n Which makes the refused command the useful one: `--info --out` is how you ask what a\n run would produce before committing to it, and a collision is exactly the thing you\n would want it to tell you about rather than refuse to look.\n */\n if (values['info'] !== true) assertDistinct(inputs, destinations);\n\n const shared = {\n channels: splitChannels(values['channels']),\n start: optionalTime(values['start'], '--start'),\n startText: typeof values['start'] === 'string' ? values['start'] : undefined,\n duration: optionalTime(values['duration'], '--duration'),\n end: optionalTime(values['end'], '--end'),\n endText: typeof values['end'] === 'string' ? values['end'] : undefined,\n decimals: optionalDecimals(values['decimals']),\n annotationsOnly: values['annotations-only'] === true,\n gzip: values['gzip'] === true,\n bom: values['bom'] === true,\n layout: optionalLayout(values['layout']),\n };\n\n // Validated before the --info branch, not inside the conversion path: a flag that\n // cannot be honoured is a usage error whatever mode it was given in, and accepting\n // \"--jobs 0\" in silence under --info is the kind of quiet that this tool avoids.\n // Parsed before it is overridden: --stdout converts one recording however many jobs\n // were asked for, but \"--stdout --jobs 0\" is still a request that cannot be honoured,\n // and accepting it in silence is the thing 0.4.2 fixed for --info.\n const requestedJobs = parseJobs(values['jobs'], inputs.length);\n /*\n Asking for several at once, of a mode that converts one.\n\n After `parseJobs`, not before it: a malformed count is a fact about the value and gets\n the message about the value, which is what the check above this one has always done and\n what its own comment insists on — \"a request that cannot be met is a usage error rather\n than something to accept in silence\". So `--stdout --jobs 0x10` still reports the `0x10`.\n\n `--jobs 1` is not refused. It asks for precisely what happens, and a script passing it\n uniformly is not asking for anything it will not get; `auto` resolves to 1 on one\n recording. What is refused is a number greater than one, which was accepted and dropped.\n */\n const jobs = toStdout ? 1 : requestedJobs;\n\n if (values['info'] === true) {\n const failures: number[] = [];\n let warnings = 0;\n for (const [index, input] of inputs.entries()) {\n // A blank line between reports, so several tables read as one document.\n if (batch && !asJson && index > 0) process.stdout.write('\\n');\n try {\n warnings += await showInfo(\n input as string,\n shared,\n asJson,\n jsonIndent,\n batch,\n values['stdout'] === true,\n );\n } catch (error) {\n failures.push(reportError(error, batch ? (input as string) : undefined));\n }\n }\n if (unreadable.length > 0) failures.push(EXIT_ERROR);\n if (failures.length > 0) return worstOf(failures);\n return strict && warnings > 0 ? EXIT_ERROR : EXIT_OK;\n }\n\n // The meter redraws one line in place, which two conversions cannot share. Running them\n // at once replaces it with the completion count each file prints as it lands.\n const showProgress =\n !quiet && !asJson && !toStdout && jobs === 1 && process.stderr.isTTY === true;\n\n const failures: number[] = [];\n let warnings = 0;\n let converted = 0;\n\n /** Convert input `index`, sending its output wherever the caller wants it. */\n const runOne = async (index: number, emit: Emit): Promise<void> => {\n const input = inputs[index] as string;\n const destination = destinations[index] as string;\n // Which recording this is, before anything it prints. Without it a batch produces a\n // stack of summaries and errors with nothing saying which file each belongs to.\n if (batch && !quiet && !asJson) {\n emit('err', `[${index + 1}/${inputs.length}] ${printable(input)}\\n`);\n }\n try {\n warnings += await convertOne(\n input,\n destination,\n {\n ...shared,\n // With one input and no --out, convert() derives the default itself, exactly as\n // it did before batches existed.\n outputDir: outOption === undefined && !batch ? undefined : destination,\n checksum: values['checksum'] === true,\n force: values['force'] === true,\n toStdout,\n quiet,\n asJson,\n showProgress,\n jsonIndent,\n batch,\n },\n emit,\n );\n converted++;\n } catch (error) {\n /*\n A batch keeps going. One unreadable recording among five hundred is a reason to\n report that file, not to abandon the ones already converted and refuse the rest.\n The exit code still reports the run as failed, and the closing line says how many\n of them made it, so nothing about the failure is quiet.\n */\n failures.push(reportError(error, batch ? input : undefined, emit));\n }\n };\n\n if (jobs === 1) {\n for (let index = 0; index < inputs.length; index++) {\n await runOne(index, writeThrough);\n // A single recording keeps the exit code it has always had rather than the batch's.\n if (!batch && failures.length > 0) return failures[0] as number;\n }\n } else {\n /*\n Real processes, not concurrent promises.\n\n Converting is almost entirely arithmetic and string building — 1.17 s of CPU for\n 1.24 s of wall clock on a 168 MB conversion — and Node runs that on one thread. An\n in-process pool was tried first and gained 6% on eight recordings, which is the\n overlap in the file reads and nothing else. Each conversion is already a whole\n command, so each one gets its own process, which is what `xargs -P` would do by hand.\n\n Workers take the next recording as they free up rather than splitting the list into\n equal shares. Recordings in a folder differ wildly in length, and a fixed split\n leaves every worker but one idle behind the longest file.\n\n Each child's output is held until it exits, so two finishing together cannot\n interleave one's summary with the other's warnings.\n */\n const running = new Map<ChildProcess, string>();\n\n /*\n Ctrl-C in a terminal reaches every process in the group, so the children would stop\n anyway; a signal sent to this process alone does not, and that is how a batch gets\n run from a script or a CI job. Interrupting one left four conversions writing\n gigabytes into a directory their owner believed abandoned, and the last thing on\n screen was a successful \"Done in 1.6s\" from whichever recording had just landed —\n the run read as if it had finished.\n */\n const onInterrupt = (signal: NodeJS.Signals): void => {\n stopping = true;\n const abandoned = [...running.values()];\n for (const child of running.keys()) child.kill('SIGTERM');\n /*\n A destination that is not there was never written to, and saying otherwise is the\n defect the serial handler was fixed for.\n\n `convert()` opens the recording, hashes it under `--checksum` and reads the whole\n annotation channel for record start times *before* it claims the directory — so\n Ctrl-C in the first seconds of a batch of large EDF+ recordings finds nothing on\n disk for any of them. This named every destination as incomplete output regardless,\n and advised distrusting directories `ls` then reported did not exist.\n\n Two answers rather than the serial path's three: whether a directory that IS there\n was there before this run is per-child state the parent does not keep, and the\n sentence that covers both is the one already printed.\n */\n const started = abandoned.filter((destination) => existsSync(destination));\n const untouched = abandoned.filter((destination) => !existsSync(destination));\n process.stderr.write(\n `\\ninterrupted (${signal}): ${abandoned.length} conversion` +\n `${abandoned.length === 1 ? '' : 's'} stopped part way through.\\n` +\n (started.length > 0\n ? detail(`Incomplete, and should not be used: ${listed(started.map(printable))}`)\n : '') +\n (untouched.length > 0\n ? detail(\n `Nothing was written to ${listed(untouched.map(printable))}: ` +\n `${untouched.length === 1 ? 'that directory was' : 'those directories were'} ` +\n `never created.`,\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 ${counted(inputs.length, 'recording')}${failed}${unread}.\\n`,\n );\n }\n\n /*\n A parent that forked this process needs the counts, not just an exit status.\n\n An exit code cannot separate \"converted, and raised warnings\" from \"did not convert\",\n and under --strict those are the same code. The parent read it as a failure, so a\n parallel run of two recordings — one of which merely warned — reported \"Converted 1 of\n 2 recordings; 1 failed\" for a run in which both converted, while the serial path said\n \"Converted 2 of 2\". `process.send` exists only when this process was forked with a\n channel, so nothing changes for an ordinary invocation.\n */\n process.send?.({ edf2csv: { converted, warnings } });\n\n if (failures.length > 0) return worstOf(failures);\n\n /*\n --strict turns any warning into a non-zero exit, for pipelines that would rather stop\n than proceed on a recording the tool had something to say about.\n\n The output is still written. A warning describes the recording, not a failure to\n convert it — a truncated file converts correctly for the records that are there — so\n destroying that work would be the wrong response. The exit code is the signal; what\n to do about it is the caller's decision, and they still have the files to inspect.\n */\n if (strict && warnings > 0) {\n process.stderr.write(\n `\\n${wrap(\n `--strict: ${warnings} warning${warnings === 1 ? '' : 's'} raised, so this run is ` +\n `reported as a failure. The output was still written.`,\n )}\\n`,\n );\n return EXIT_ERROR;\n }\n return EXIT_OK;\n } catch (error) {\n return reportError(error);\n }\n}\n\n/** Print one recording's `--info`, and return how many diagnostics it raised. */\nasync function showInfo(\n input: string,\n shared: Record<string, unknown>,\n asJson: boolean,\n jsonIndent: number | null,\n batch = false,\n toStdout = false,\n): Promise<number> {\n const file = await EdfFile.open(input);\n try {\n /*\n Read the annotation channel only when the timing actually depends on it.\n\n --info is documented as a header-only summary that returns immediately whatever\n the file's size, but it called readAnnotations() on every EDF+/BDF+ file — a seek\n into every data record. deriveRecordStarts discards that data unless the file is\n EDF+D, where record start times are stored rather than arithmetic, so on a\n continuous recording the whole scan was thrown away. It cost 0.29 s on a 12 MB\n file and scaled with record count.\n\n A discontinuous file still needs the scan: without it the reported span and row\n estimate are wrong, which is a bug that has already been fixed once here.\n */\n const hasAnnotations = file.annotationSignals.length > 0;\n const needsEveryRecordStart = file.header.continuity === 'EDF+D' && hasAnnotations;\n /*\n A continuous recording needs its origin too, which is one read rather than one per\n record: 0.4.9 made its first record's timekeeping TAL the point the samples are timed\n from, and this report went on placing a requested window against zero. `--info --start 1`\n predicted 8 rows where the conversion wrote 10, on a file whose discontinuous twin —\n identical but for the reserved field — agreed with itself.\n */\n /*\n And a file with an annotation channel and no marker at all, which is the one case this\n condition used to exclude and the one that most needs it.\n\n Without `EDF+C` or `EDF+D` the origin is not applied — the samples are timed from zero —\n but the annotation channel is found by label, so its events keep the onsets the file\n gives them and the two CSVs come out on clocks the origin apart. That is what\n MISSING_EDF_PLUS_MARKER says, and it is raised from the record starts, which this\n declined to read for exactly the files that produce it. So a conversion warned that\n signals.csv and annotations.csv would be a hundred seconds apart, and `--info` — run\n first, on purpose, to find out what a conversion would say — said nothing at all.\n\n The same single read the continuous case already pays for, on a file that is anomalous\n to begin with: an EDF Annotations channel is not something a plain EDF file has.\n */\n const scan =\n !needsEveryRecordStart && file.header.continuity !== 'EDF+D' && 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: scan?.malformed ?? 0,\n // Counted rather than assumed: see EdfFile.scanOrigin.\n malformedTimekeeping: scan?.malformedTimekeeping ?? 0,\n // And how many of those took events with them, which decides whether the warning\n // beside it says nothing was lost. See EdfFile.scanOrigin.\n malformedTimekeepingWithText: scan?.malformedTimekeepingWithText ?? 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 /*\n The event count, where reading the channel has already produced it.\n\n Only a discontinuous file, where every record start has to be read out of the annotation\n channel; a continuous one is read as far as the first record that states a start time and\n no further, so its events are not all in hand. Counted through the same window predicate\n a conversion filters them by, rather than a second copy of it.\n */\n const eventWindow = requestedAnnotationWindow(shared, plan.range.recordingStartSeconds);\n const knownEvents = needsEveryRecordStart\n ? annotationData.annotations.filter(\n (a) => a.onset >= eventWindow.from && a.onset < eventWindow.to,\n ).length\n : null;\n plan.diagnostics.push(...timing.diagnostics);\n /*\n What --stdout would do with this recording, which --info did not ask.\n\n `--info --stdout` on a three-rate file predicted \"Would write 1,155 rows, roughly\n 22.2 KB\" and said the channels \"are written to one file per rate\" — for a command that\n refuses to run, writes nothing and names no file. --info exists to say what a\n conversion will do, and refusing is one of the things it does.\n\n A warning rather than a refusal, for the reason 0.5.51 gives about the destination\n guards: --info writes nothing, so a rule about the output has no business stopping it\n from describing the recording — and being told the command will not work is exactly\n what was asked. The conversion's own guard supplies the words, so there is one wording.\n */\n /*\n What the conversion would say about the durations it would write.\n\n Same window the conversion applies, so --info predicts the warnings rather than a\n different set. Raised here rather than in the parser because the counts have to be of\n the rows that reach annotations.csv — see durationDiagnostics.\n\n `eventWindow` above and not the resolved range, which is what this passed. The two are\n the same window only while the conversion asks for no more than the recording holds:\n the range is clamped to the recording, and an annotation onset is not obliged to fall\n inside it — a marker for the end of a recording sits at exactly its length, and\n annotations-at-edges.edf exists because filtering events by the range dropped those.\n So a two-second EDF+D carrying one unreadable duration inside its span and one past the\n end was described by\n\n --info 1 annotation states a duration that is not a number, so its ...\n conversion 2 annotations state a duration that is not a number, so their ...\n\n about the same file with no time option given, over an annotations.csv holding both\n rows. `knownEvents` three lines up already counts through `eventWindow`, for the reason\n its comment gives; this was the second copy that comment says not to make.\n */\n plan.diagnostics.push(...durationDiagnostics(annotationData.annotations, eventWindow));\n\n /*\n And the warning about the signal file this conversion would not write.\n\n `--info` prints one accurate line about it in the report body — \"Would write\n channels.csv and no signal data\" — and raised nothing, so the one mode whose purpose is\n to say what a conversion will do carried a shorter warning list than the conversion did.\n `--info --strict` is documented as a cheap way to screen a directory before converting\n it, and on a recording of nothing but annotations it reported one warning where the\n conversion reports two.\n */\n const missing = noSignalFile(file, plan);\n if (missing) plan.diagnostics.push(missing);\n // The same for --annotations-only on a file with none; see noAnnotations.\n const noEvents = noAnnotations(file, shared as ConvertOptions);\n if (noEvents) plan.diagnostics.push(noEvents);\n\n if (toStdout) {\n const refusal = stdoutRefusal(file, plan);\n if (refusal) {\n plan.diagnostics.push({\n code: 'STDOUT_UNSUPPORTED',\n severity: 'warning',\n message: `--stdout would refuse this recording: ${refusal.message.replace(/^--stdout /u, '')}`,\n ...(refusal.hint === undefined ? {} : { hint: refusal.hint }),\n });\n }\n }\n /*\n Checked, like a conversion's stdout is.\n\n This wrote and looked at nothing, so `--info > desc.txt` into a filesystem with no room\n produced a zero-byte file and exited 0. The same audit the `--stdout` path uses: it\n declines anything that is not a regular file, so a pipe or a terminal is unaffected, and\n `--info | head` keeps exiting 0.\n */\n const audit = auditStdout();\n const description = asJson\n ? `${infoJson(file, plan, jsonIndent)}\\n`\n : `${formatInfo(file, plan, knownEvents)}\\n`;\n process.stdout.write(description);\n audit?.count(Buffer.byteLength(description));\n audit?.verify();\n\n // Under --json the warnings travel inside the document, exactly as they do for a\n // conversion, so stderr stays empty and the whole result is one parseable thing.\n const diagnostics = [...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null), ...plan.diagnostics];\n if (!asJson && diagnostics.length > 0) {\n /*\n Named when there is more than one recording to confuse it with.\n\n The table goes to stdout and the warnings to stderr, which is the point of the split\n — but over a folder that left several warnings in a row on stderr with nothing saying\n which recording raised any of them. Two recordings, two warnings, and no way to pair\n them up short of running the tool again one file at a time. A batch conversion has\n named its recordings since 0.4.20; this is the same report from the same tool about\n the same files.\n */\n const where = batch ? `${printable(input)}: ` : '';\n process.stderr.write(\n `\\n${formatDiagnostics(diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n`,\n );\n }\n return diagnostics.length;\n } finally {\n await file.close();\n }\n}\n\n/** Convert one recording, and return how many diagnostics it raised. */\nasync function convertOne(\n input: string,\n destination: string,\n options: Record<string, unknown> & {\n quiet: boolean;\n asJson: boolean;\n showProgress: boolean;\n toStdout: boolean;\n jsonIndent: number | null;\n batch?: boolean;\n },\n emit: Emit = writeThrough,\n): Promise<number> {\n const { quiet, asJson, showProgress, toStdout } = options;\n let lastTick = 0;\n\n /*\n Interrupting a conversion leaves a CSV that stops mid-recording but is still\n perfectly well-formed, so nothing about the file itself reveals that half the\n data is missing. Saying so on the way out is the whole point of a tool that\n claims it will not go quiet when something is wrong.\n */\n /*\n Whether the destination was already there before this run touched it.\n\n Read once, up front, because at interrupt time it is the difference between three\n different true statements and there is no other way to tell them apart. A directory that\n exists now and did not exist then was created by this conversion; one that existed then\n holds files this conversion may never have reached.\n */\n const destinationExistedBefore = toStdout ? false : existsSync(destination);\n\n /*\n Take the progress meter off the line before anything else is printed on it.\n\n The meter writes `\\r converting\\u2026 47%` and leaves the cursor after the percentage, so\n whatever is written next continues that line. Success cleared it and an interrupt cleared\n it; a conversion that *failed* did not, and the error landed on the end of the meter:\n\n converting\\u2026 96%error: Expected 317440 bytes of data at record 1638 but only 0 ...\n\n Which is the one thing this output is shaped to avoid. Every release since 0.7.1 has kept\n `error: ` and `warning: ` at the start of a line so a batch's stderr can be grepped for\n them, and here the prefix sat mid-line, where `grep \\'^error:\\'` over a failed run finds\n nothing at all. Only reachable on a terminal, which is why nothing caught it: the meter is\n off whenever stderr is not a TTY, and a captured stderr never is.\n */\n const clearProgress = (): void => {\n if (showProgress) process.stderr.write('\\r\\u001b[K');\n };\n\n const onInterrupt = (signal: NodeJS.Signals): void => {\n clearProgress();\n /*\n Say which of the three happened, rather than the middle one every time.\n\n --stdout writes no directory, so there is none to warn about. Naming one anyway\n pointed at a path that was never created — the same \"files that were never written\"\n that 0.2.30 removed from this path's error message. The directory case had the same\n defect one step further in: `convert()` hashes the input under --checksum and scans\n the whole annotation channel for record start times *before* it claims the directory,\n so a Ctrl-C in the first second of a large EDF+ printed \"Files already written to\n \"oa\" are incomplete and should not be used\" about a directory that `ls` then reported\n did not exist. Nothing had been written, and the advice was to distrust nothing.\n */\n const wrote = !toStdout && existsSync(destination);\n process.stderr.write(\n `\\ninterrupted (${signal}): the conversion stopped part way through.\\n` +\n (toStdout\n ? detail('The CSV on stdout stops mid-recording and should not be used.')\n : !wrote\n ? detail(`Nothing was written: \"${printable(destination)}\" was never created.`)\n : destinationExistedBefore\n ? detail(\n `\"${printable(destination)}\" was already there, so what is in it may be ` +\n `this run's incomplete output.`,\n )\n : detail(\n `Files already written to \"${printable(destination)}\" are incomplete and ` +\n `should not be used.`,\n )),\n );\n // 128 + signal number, the conventional exit status for dying to a signal.\n process.exit(signal === 'SIGINT' ? 130 : 143);\n };\n process.once('SIGINT', onInterrupt);\n process.once('SIGTERM', onInterrupt);\n\n try {\n const result = await convert(input, {\n ...options,\n onProgress: showProgress\n ? (progress): void => {\n const now = Date.now();\n if (now - lastTick < 100) return;\n lastTick = now;\n const percent =\n progress.recordsTotal === 0\n ? 100\n : Math.floor((progress.recordsDone / progress.recordsTotal) * 100);\n process.stderr.write(`\\r converting… ${percent}%`);\n }\n : undefined,\n });\n\n clearProgress();\n\n if (result.diagnostics.length > 0 && !asJson) {\n /*\n Named when nothing else is naming it.\n\n A batch prints a `[n/m] <path>` header before each recording, and that header is what\n pairs a warning with the file it came from. `--quiet` suppresses it — it is the\n summary line it is documented to suppress, and it took the attribution with it. Two\n recordings, two warnings, and no way to tell which raised which, while `error:` lines\n in the same run stay named because 0.4.20 prefixes those separately.\n\n Same fix 0.5.34 made for `--info` over a folder, which has no header to lose.\n */\n const where = options.batch === true && quiet ? `${printable(input)}: ` : '';\n /*\n The trailing blank line separates these warnings from the summary underneath them,\n so it belongs to the summary and goes when the summary does.\n\n `--quiet` printed it anyway: one stray blank line per recording, in the mode whose\n entire purpose is to print less. On a batch of five hundred that is five hundred of\n them in the log, and on a single recording it left `--quiet --strict` showing two\n blank lines between the warning and the \"--strict:\" line where the ordinary run\n shows one.\n */\n emit(\n 'err',\n `${formatDiagnostics(result.diagnostics).replace(/^(warning|note): /gmu, (m) => `${m}${where}`)}\\n${quiet ? '' : '\\n'}`,\n );\n }\n if (asJson) {\n // One object per line, so a batch is JSON Lines — `jq` reads a record at a time\n // rather than waiting for the whole run to finish.\n emit('out', `${summaryJson(result, options.jsonIndent as number | null)}\\n`);\n } else if (!quiet) {\n // With --stdout there is no directory to summarise, and the row count is the only\n // thing worth saying — on stderr, so the CSV on stdout stays clean.\n if (toStdout) {\n const rows = result.files[0]?.rows ?? 0;\n /*\n A reader that closed the pipe did not receive a conversion, so it does not get a\n conversion's summary. `edf2csv rec.edf --stdout | head -1` announced \"Wrote 52,507\n rows to stdout\" — a number that is neither the recording's 102,400 nor the one row\n head took, but however many had been formatted before the close was noticed.\n\n Whether the *conversion* stopped early is a separate question from whether the\n reader did, and 0.5.12 answered the first with the second. A 10,000-row recording\n whose CSV outruns the pipe buffer but fits one flush is written in full and only\n then meets the closed pipe: every row formatted, every row handed over, and the\n summary said \"The recording was not converted in full.\" The estimate's row count is\n exact, so the two cases can be told apart and told apart honestly — what reached\n the reader is not knowable from this side either way.\n */\n const expected = result.plan.estimate.rows;\n // Singular at one, like every other count. A window narrow enough to select a single\n // sample is an ordinary thing to pipe, and this said \"Wrote 1 rows to stdout.\" — the\n // third message in this family, after 0.5.74 and 0.5.78, each found because the sweep\n // that checks for it was narrower than the set of modes that print a count.\n const written = `${rows.toLocaleString('en-US')} ${rows === 1 ? 'row' : 'rows'}`;\n emit(\n 'err',\n !result.readerHungUp\n ? `Wrote ${written} to stdout.\\n`\n : rows < expected\n ? `Stopped: the reader closed the pipe after ${rows.toLocaleString('en-US')} of ` +\n `${expected.toLocaleString('en-US')} ${expected === 1 ? 'row' : 'rows'} had been ` +\n `written. The recording was ` +\n `not converted in full.\\n`\n : `Wrote ${written} to stdout, but the reader closed ` +\n `the pipe before the end, so not all of them reached it.\\n`,\n );\n } else {\n emit('err', `${formatSummary(result)}\\n`);\n }\n }\n return result.diagnostics.length;\n } catch (error) {\n // The meter is still on the line when a conversion throws, and the caller's `error: `\n // is the next thing written.\n clearProgress();\n throw error;\n } finally {\n process.off('SIGINT', onInterrupt);\n process.off('SIGTERM', onInterrupt);\n }\n}\n\n/** A recording to convert, and the path its output should be named after. */\ninterface Input {\n /** Where the recording is. */\n path: string;\n /**\n * What to call its output, relative to `--out`.\n *\n * For a file named on the command line this is just its own name. For one found by\n * expanding a directory it keeps the position it had inside that directory, so a study\n * laid out as one folder per night comes out the same shape — and, more to the point, so\n * that fifty recordings all named `rec.edf` do not all claim `<out>/rec`.\n */\n name: string;\n /**\n * Whether this recording was found by expanding a directory rather than named directly.\n *\n * `--out` means two different things — the output directory itself, or a parent to put one\n * directory per recording inside — and this is what decides which. See `destinationsFor`.\n */\n fromDirectory: boolean;\n}\n\n/**\n * Recordings to convert, with directories expanded to what is inside them.\n *\n * A directory yields every `.edf` and `.bdf` beneath it, at any depth. Recordings arrive\n * organised into folders, and a shell has no tidy way to reach them — which is why the\n * recipes here carried a `find` incantation to do it. Passing the folder is the obvious\n * thing to try, and it used to fail with \"is a directory, not an EDF file\".\n */\nasync function expandInputs(\n positionals: readonly string[],\n): Promise<{ inputs: Input[]; unreadable: string[]; namedDirectory: boolean }> {\n const found: Input[] = [];\n const unreadable: string[] = [];\n /*\n Whether a directory was NAMED, which is not the same as whether one yielded anything.\n\n `fromDirectory` on the inputs answers the second question, and using it for the first put\n the 0.4.20 defect back one step along: `edf2csv study named.edf --out csv` wrote\n csv/named/ while the study held recordings and csv/signals.csv once it held none, because\n the surviving input then looked like a lone file. Whether some unrelated folder happens\n to contain anything decided where a different recording's output went.\n */\n let namedDirectory = false;\n for (const given of positionals) {\n const info = await stat(given).catch(() => null);\n if (info === null || !info.isDirectory()) {\n // Anything that is not a directory is passed through untouched, so a file that does\n // not exist still reports itself rather than vanishing from the list.\n found.push({ path: given, name: path.basename(given), fromDirectory: false });\n continue;\n }\n namedDirectory = true;\n const walked = await walk(given);\n unreadable.push(...walked.unreadable);\n for (const file of walked.files) {\n found.push({ path: file, name: path.relative(given, file), fromDirectory: true });\n }\n }\n /*\n One recording, however many ways it was named.\n\n The walk already does this for links inside a folder, and the two halves disagreed:\n `edf2csv study` converted a recording reached twice once, while `edf2csv a.edf a.edf`\n refused the whole run for the collision it would cause. A shell makes the second easy\n to produce by accident — `edf2csv *.edf recording.edf` — and there is nothing ambiguous\n about it: it is one recording, and converting it once is what was meant.\n\n A path that does not resolve keeps its own identity so that a file which is not there\n still reports itself rather than being folded into another entry.\n\n Which of the names survives is decided by the names themselves, not by the order they\n turned up in. Keeping the first arrival meant the output directory was named by argument\n order — `edf2csv data/one.edf data/alias.edf` wrote out/one and the same two swapped\n wrote out/alias — and a shell orders a glob however it likes. Inside a folder it was the\n order `readdir` returned, which differs between filesystems, so copying a study to\n another machine could rename its output. A recording that is not a link is preferred over\n a link to it, since that is the name the recording actually has; two of a kind are\n settled by the path that sorts first.\n */\n const identified: { entry: Input; identity: string; link: boolean }[] = [];\n for (const entry of found) {\n const identity = await realpath(entry.path).catch(() => `?${path.resolve(entry.path)}`);\n const own = await lstat(entry.path).catch(() => null);\n identified.push({ entry, identity, link: own?.isSymbolicLink() ?? false });\n }\n\n const byIdentity = new Map<string, Input>();\n const winners = new Map<string, (typeof identified)[number]>();\n for (const candidate of identified) {\n const held = winners.get(candidate.identity);\n if (held !== undefined && !outnames(candidate, held)) continue;\n winners.set(candidate.identity, candidate);\n byIdentity.set(candidate.identity, candidate.entry);\n }\n\n // A directory hands its entries back in whatever order the filesystem stored them.\n const unique = [...byIdentity.values()];\n unique.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n\n /*\n One unreadable path, however many ways it was named — the same rule the recordings above\n get, which this did not.\n\n Each named directory is walked separately and its findings appended, so a folder given\n twice, given relatively and absolutely, or given alongside a link to it, reported every\n path it could not read once per name. `edf2csv study study` on a study holding one locked\n sub-directory printed the error twice and closed with \"Converted 1 of 1 recordings; 2\n paths could not be read\" — one path, counted two — while the recordings beside it were\n correctly converted once. The count is the part that matters: it is what tells someone how\n much of their study was not looked at.\n\n Identity, not spelling, for the reason 0.5.64 gives: `study/locked` and `alias/locked` are\n one directory. `realpathSync` resolves it even when the directory itself cannot be opened,\n since that needs search permission on the parent rather than on the target. A path that\n cannot be resolved at all keeps its own identity, so a name that is simply not there still\n reports itself.\n */\n const seen = new Set<string>();\n const distinct: string[] = [];\n for (const entry of unreadable) {\n let identity: string;\n try {\n identity = realpathSync(entry);\n } catch {\n identity = `?${path.resolve(entry)}`;\n }\n if (seen.has(identity)) continue;\n seen.add(identity);\n // The first spelling is kept, which is the one the caller typed first.\n distinct.push(entry);\n }\n\n return { inputs: unique, unreadable: distinct, namedDirectory };\n}\n\n/**\n * Which of two names for one recording the output should be called after.\n *\n * A name the recording actually has beats a link pointing at it, and two of a kind are\n * settled by sort order. Both are properties of the names, so the answer does not move when\n * a shell expands a glob differently or a filesystem enumerates a folder in another order.\n */\nfunction outnames(\n candidate: { entry: Input; link: boolean },\n held: { entry: Input; link: boolean },\n): boolean {\n if (candidate.link !== held.link) return !candidate.link;\n /*\n Resolved, not as typed. `study/night-01/rec.edf` and `./study/night-01/rec.edf` are the\n same file spelled two ways, and comparing the raw strings made them two different names —\n so this returned here, on lexicographic order of the spelling, and never reached the depth\n rule below that exists for exactly this case.\n\n What that cost is not cosmetic. `edf2csv study study/night-01/rec.edf` converted both\n recordings and exited 0; the identical request written `edf2csv study\n ./study/night-01/rec.edf` was refused with \"would both be converted into \"out/rec\", so one\n would overwrite the other\", exit 2, nothing written. Same files, same folder, same\n intention — and a leading `./` decided whether the run happened. Without the sibling that\n collides it was quieter and no better: one spelling wrote `out/night-01/rec` and the other\n `out/rec`, so a script that started passing absolute paths moved its output.\n\n Through `realpathSync`, not `path.resolve`, because a lexical resolve is not enough: on\n macOS `$TMPDIR` sits under `/var`, which is a link to `/private/var`, so a folder walked\n from the name the caller gave and a recording named relative to the process's own\n directory come out with different absolute prefixes for the same file. Falls back to the\n lexical form for a path that cannot be resolved, which is the same answer as before.\n\n Following links here is safe: a link and its target reach this as two genuinely different\n names, and the rule above has already preferred the real one, so the only pairs left with\n one identity are two spellings or two links — and both should fall through to the depth\n rule below rather than be settled by how they were typed.\n */\n const spelled = (entry: Input): string => {\n try {\n return realpathSync(entry.path);\n } catch {\n return path.resolve(entry.path);\n }\n };\n if (spelled(candidate.entry) !== spelled(held.entry)) {\n return spelled(candidate.entry) < spelled(held.entry);\n }\n /*\n One recording, one path, two names — reached directly and through a named folder.\n\n `edf2csv study study/night-01/rec.edf` and the same two swapped are the same request, and\n they disagreed: the folder gives the recording its position inside the folder\n (`night-01/rec`), a direct mention gives it its bare name (`rec`), and whichever spelling\n the loop met first won. So argument order decided the output directory's name, and, once\n a second `study/rec.edf` was in play, decided whether the run happened at all — the bare\n name collides with it and the run is refused, exit 2, while the other order converts both.\n\n The nested name wins. It is what the folder promised — \"the layout is kept: recordings in\n sub-folders come out in sub-folders\" — and it is the one that does not collide, since\n collapsing a recording to its bare name is what puts it on top of a sibling.\n */\n const depth = (entry: Input): number => entry.name.split(path.sep).length;\n if (depth(candidate.entry) !== depth(held.entry)) return depth(candidate.entry) > depth(held.entry);\n return candidate.entry.name < held.entry.name;\n}\n\n/**\n * Every recording under a directory, following symbolic links.\n *\n * Written by hand rather than with a recursive `readdir` because that reports a symlink as\n * a symlink and never as a file, so a linked recording was skipped without a word — and the\n * closing \"converted 3 of 3\" then described the three it had noticed rather than what was in\n * the folder. Data organised by linking recordings into a working directory is ordinary, and\n * quietly converting fewer files than were asked for is the failure this tool exists to\n * avoid. Naming the same link on the command line always worked, which made the omission\n * harder to notice rather than easier.\n *\n * Following links means they can form a cycle, so directories are recorded by their resolved\n * identity and visited once. A link to a file already reached another way is likewise\n * converted once.\n */\nasync function walk(root: string): Promise<{ files: string[]; unreadable: string[] }> {\n const files: string[] = [];\n const unreadable: string[] = [];\n const seen = new Set<string>();\n /*\n A queue, taken from the front, and each directory's children entered in a settled order.\n\n This was a stack popped from the back, so which of two names for one directory was\n visited first came down to the order `readdir` happened to return them — and the loser\n was then skipped as already seen, taking its name out of the run with it. A folder\n holding `aaa-real/` beside `zzz-alias -> aaa-real` converted into `<out>/zzz-alias/`: the\n link's name, chosen by a hash order that differs between filesystems. 0.4.29 settled this\n for two names of one *file* and left the directory above it deciding by accident.\n\n Breadth first, and each directory's sub-directories entered with real names before links\n and alphabetically within each, so the name that survives is a property of the tree: the\n shallowest, then the one that is not a link, then the first in sort order.\n */\n const queue = [root];\n let next = 0;\n\n while (next < queue.length) {\n const directory = queue[next++] as string;\n const real = await realpath(directory).catch(() => directory);\n if (seen.has(real)) continue;\n seen.add(real);\n\n /*\n A directory that cannot be listed is reported, not stepped over.\n\n Skipping it in silence meant a folder holding three recordings, one of them inside a\n sub-directory without read permission, converted two and said \"Converted 2 of 2\n recordings\" — a total that agreed with itself and with nothing else. That is the same\n failure 0.4.4 fixed for symbolic links, arriving by a different route: converting fewer\n recordings than were asked for and reporting success.\n */\n let entries;\n try {\n entries = await readdir(directory, { withFileTypes: true });\n } catch {\n unreadable.push(directory);\n continue;\n }\n\n // A real name before a link to the same thing, then alphabetically. The dirent describes\n // the entry itself rather than its target, which is exactly the question here.\n const ordered = [...entries].sort((a, b) => {\n const link = Number(a.isSymbolicLink()) - Number(b.isSymbolicLink());\n return link !== 0 ? link : a.name < b.name ? -1 : a.name > b.name ? 1 : 0;\n });\n\n for (const entry of ordered) {\n const full = path.join(directory, entry.name);\n // stat, not the dirent: a dirent describes the link, and what matters is its target.\n const info = await stat(full).catch(() => null);\n if (info === null) {\n /*\n Anything that cannot be inspected is reported, whatever it is called.\n\n This used to report only names ending in `.edf` or `.bdf`, on the reasoning that a\n broken link to something else is nobody's business. A directory carries no such\n name. A study kept as one folder per night, with one night linked to an external\n drive, converted the nights that were mounted and said nothing about the one that\n was not — and because losing that input left a single recording, `--out` stopped\n meaning \"a parent to fill\" and started meaning \"the directory to write\", so the\n survivor landed somewhere else as well. Whether a drive happened to be mounted\n changed both what was converted and where it went, in silence, exit 0.\n\n The walk cannot know what was behind a link it cannot follow, which is exactly the\n reason to say so rather than to guess.\n */\n unreadable.push(full);\n continue;\n }\n if (info.isDirectory()) {\n queue.push(full);\n } else if (info.isFile() && /\\.(edf|bdf)$/iu.test(entry.name)) {\n // Duplicates are collapsed once for the whole list in expandInputs; this only has\n // to find them. The `seen` set here is for directory cycles.\n files.push(full);\n }\n }\n }\n return { files, unreadable };\n}\n\n/**\n * Where each recording's output goes.\n *\n * Naming one recording, `--out` is the output directory itself, which is what it has always\n * meant. Naming a folder, or several recordings, it is a parent and each recording gets its\n * own directory inside it, because writing several recordings into one directory would have\n * them overwrite each other's `signals.csv` — the one thing a batch must not do quietly.\n *\n * With no `--out` at all, every recording converts beside itself exactly as it would have\n * done alone, so a glob behaves like the shell loop it replaces.\n *\n * What a folder means is decided by the folder, not by how much is in it. Counting the\n * recordings instead meant `edf2csv study --out csv` wrote `csv/signals.csv` while the study\n * held one night and `csv/night-01/rec/signals.csv` once it held two: adding a recording\n * moved the output of a recording that had not changed. The same count made the destination\n * depend on things no one had touched either — a night on an unmounted drive, a\n * sub-directory that could not be read — so where the data landed turned on the state of the\n * machine rather than on the command.\n */\nfunction destinationsFor(\n inputs: readonly Input[],\n out: string | undefined,\n /** True when a folder was named, or more than one recording was. See `batch` in `main`. */\n batch: boolean,\n): string[] {\n if (out === undefined) return inputs.map((input) => defaultOutputDir(input.path));\n if (!batch) return [out];\n return inputs.map((input) => path.join(out, stemOf(input.name)));\n}\n\n/** A name without its extension, keeping a leading dot: `.hidden.edf` -> `.hidden`. */\nfunction stemOf(name: string): string {\n const dot = name.lastIndexOf('.');\n const slash = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\\\'));\n return dot > slash + 1 ? name.slice(0, dot) : name;\n}\n\n/**\n * Refuse a batch in which two recordings would land in the same directory.\n *\n * Two files with the same name in different folders — `night-1/rec.edf` and\n * `night-2/rec.edf`, which is how recordings usually get organised — both resolve to\n * `<out>/rec`. Converting them in turn would leave one recording's data sitting under the\n * other's name with nothing to show it had happened, so the run stops before writing\n * anything at all.\n */\nfunction assertDistinct(inputs: readonly string[], destinations: readonly string[]): void {\n /*\n Two names that differ only in case are one directory on a filesystem that does not\n distinguish them, which is the default on macOS and the rule on Windows.\n\n `a/REC.edf` and `b/rec.edf` produce `<out>/REC` and `<out>/rec`. Compared exactly those\n are different, so both went through, and with --force the second conversion wrote into\n the directory the first had made: one directory holding one recording's `signals.csv`\n beside the other's `signals_256hz.csv`, under a single `metadata.json` naming only one\n of them. The run reported \"Converted 2 of 2 recordings\" and exited 0. A directory whose\n provenance file describes a recording other than the data beside it is the one outcome\n this tool exists to prevent.\n\n The comparison follows the platform rather than being applied everywhere, so a\n case-sensitive filesystem — where those really are two directories — keeps converting\n both. A case-sensitive volume on macOS is the exception it gets wrong, and it gets it\n wrong in the safe direction: a refusal that names both recordings, not a silent merge.\n */\n const foldsCase = process.platform === 'darwin' || process.platform === 'win32';\n /*\n macOS filesystems fold Unicode normalisation as well as case.\n\n HFS+ and APFS compare names in a normalised form, so `café` written as e + U+0301 and\n `café` written as U+00E9 are one directory — while remaining two different JavaScript\n strings, which is all this guard was comparing. Two recordings whose stems differ only\n that way therefore both passed the check and both converted into the same place:\n\n study/café.edf (NFC) -> csv/café/signals.csv\n study/café.bdf (NFD) -> csv/café/signals_256hz.csv, _128hz, _1hz\n\n one directory holding both, under a single metadata.json naming one of them, reported as\n \"Converted 2 of 2 recordings\" and exit 0 under --force. Without --force the second run\n happened to hit \"already exists\", which is the accidental save rather than the check\n doing its job — and it named the wrong problem.\n\n Not folded on Linux, where the two names are genuinely two directories and refusing them\n would be inventing a collision that does not exist. Windows preserves normalisation too,\n so only darwin. This is the same platform-shaped assumption the case fold above already\n makes, and it has the same limit: a network or removable volume that normalises while the\n running platform does not is not covered by either.\n */\n const identity = (destination: string): string => {\n const resolved = path.resolve(destination);\n if (!foldsCase) return resolved;\n const folded = resolved.toLowerCase();\n return process.platform === 'darwin' ? folded.normalize('NFC') : folded;\n };\n\n const claimed = new Map<string, string>();\n for (const [index, destination] of destinations.entries()) {\n const key = identity(destination);\n const first = claimed.get(key);\n if (first !== undefined) {\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" and \"${printable(first)}\" would both be converted into \"${printable(destination)}\", ` +\n `so one would overwrite the other.\\n` +\n `Convert them separately, or rename one of them.`,\n );\n }\n claimed.set(key, inputs[index] as string);\n }\n\n /*\n And refuse one destination sitting inside another.\n\n A recording named `rec.edf` beside a folder named `rec` is enough: their outputs are\n `<out>/rec` and `<out>/rec/inner`, which are not equal, so the check above let both\n through. What happened next depended on which conversion got there first. Each creates\n its own directory with a single non-recursive mkdir but its parents recursively, so\n whichever started second either claimed a directory the other had already made as a\n parent — failing with \"already exists\" — or did not. Five runs in twenty failed that way\n under --jobs 2, converting one recording of two; the same command succeeded the other\n fifteen times.\n\n Sorting first puts an ancestor next to its descendant: anything sorting between them\n shares the same prefix, and would be caught as its own adjacent pair.\n */\n /*\n Each destination is checked against its own ancestors, by name.\n\n The first version of this sorted the resolved paths and compared neighbours, on the\n reasoning that an ancestor and its descendant end up adjacent. They do not: the\n separator is not the lowest character, so any sibling whose name begins with one of the\n thirteen printable characters below '/' lands between them. With `rec.edf`, `rec!x.edf`\n and `rec/inner.edf` in one folder, '!' sorts between `out/rec` and `out/rec/inner` and\n the pair was never compared — three recordings converted, one of them inside another's\n directory, reported as \"Converted 3 of 3\".\n\n Walking up from each destination has no such gap. Output trees are shallow, so this is a\n handful of string lookups per recording.\n */\n const byDestination = new Map<string, number>();\n for (const [index, destination] of destinations.entries()) {\n byDestination.set(identity(destination), index);\n }\n\n for (const [index, destination] of destinations.entries()) {\n let ancestor = path.dirname(path.resolve(destination));\n for (let parent = ''; ancestor !== parent; ancestor = path.dirname(ancestor)) {\n parent = ancestor;\n const owner = byDestination.get(identity(ancestor));\n if (owner === undefined || owner === index) continue;\n /*\n All four paths escaped, not the two nearest the front of the sentence.\n\n A file name may hold a newline on every platform this runs on, and `printableLines`\n splits a message on newlines to indent its continuations — so the two raw paths broke\n the sentence into three lines and printed half a path on each:\n\n error: \"study/re\\x0ac/inner.edf\" would be converted into \"o/re\\x0ac/inner\",\n which is inside \"o/re\n c\" — where \"study/re\n c.edf\" is converted.\n\n Two of the four names read as one thing and two as two, in the same sentence, about\n the same pair of paths. That is 0.5.67's defect — a name holding a newline splitting\n `Wrote` across two lines — in the one message that names four paths at once.\n */\n throw new OptionError(\n `\"${printable(inputs[index] as string)}\" would be converted into \"${printable(destination)}\", which is inside ` +\n `\"${printable(destinations[owner] as string)}\" — where \"${printable(inputs[owner] as string)}\" is converted.\\n` +\n `One recording's output cannot sit inside another's. Convert them separately, or ` +\n `rename one of them.`,\n );\n }\n }\n}\n\n/**\n * The exit code for a run in which several files failed for different reasons.\n *\n * 2 means \"you invoked it wrong\" and 1 means \"something went wrong with a file\". When both\n * happened, 1 is the honest answer: the invocation cannot be the whole story once a file has\n * genuinely failed. With a single input there is only ever one code, so its exit status is\n * exactly what it always was.\n */\nfunction worstOf(codes: readonly number[]): number {\n /*\n Anything that is not \"the command line is the problem\" is a failure of the run.\n\n This tested for `EXIT_ERROR` and fell through to `EXIT_USAGE` for everything else, on the\n assumption that a child exits 1 or 2. A child killed by a signal exits 130 or 143 — its\n own interrupt handler does that — so a `--jobs` worker stopped by SIGTERM made the batch\n exit 2, which cli-reference's table defines as \"The command line is the problem\" and\n warnings-and-errors as \"The command was invoked incorrectly, or asked for something the\n recording can't provide\". The command was fine; something killed a worker. The\n out-of-memory killer and a scheduler's time limit both arrive exactly this way, and the\n serial path reports the same event as the failure it is.\n\n Asking the question the other way round is also the honest one: 2 is the narrow claim,\n so it should be the one that has to be earned by every code in the list.\n */\n return codes.some((code) => code !== EXIT_USAGE) ? EXIT_ERROR : EXIT_USAGE;\n}\n\n/**\n * Convert one recording in a separate process, and collect everything it printed.\n *\n * The arguments are rebuilt from the parsed options rather than sliced out of argv, so the\n * child receives exactly the flags this run was given and nothing that only makes sense to\n * the parent: not the other recordings, not --jobs, and not --info or --stdout, neither of\n * which reaches this path.\n */\nasync function convertInChild(\n input: string,\n destination: string,\n values: Record<string, unknown>,\n running: Map<ChildProcess, string>,\n): Promise<{\n code: number;\n out: string;\n err: string;\n report: { converted: number; warnings: number } | null;\n}> {\n /*\n The recording goes last, behind `--`.\n\n As the first argument it was parsed as an option whenever its path began with a dash,\n which `path.join` produces from a folder given as `.` — `./-lead.edf` normalises to\n `-lead.edf`. The child then failed on a file the parent had converted happily, so the\n same command converted two recordings serially and one under --jobs.\n */\n /*\n Option values go in the `--flag=value` form, never as two arguments.\n\n Split across two arguments, a value beginning with a dash is another option as far as the\n child's parser is concerned: `--out ./-nightly` reached it as `--out` followed by\n `-nightly`, and the child died on \"Option '--out' argument is ambiguous\" while the serial\n path converted the same command without complaint. A leading dash is not exotic —\n `path.join` produces one from a folder given as `.`, and directories get named after\n dates and flags often enough. Same failure 0.4.19 fixed for the recording's own path, on\n everything that carries a value rather than on the positional.\n */\n const args = [`--out=${destination}`];\n /*\n --strict is deliberately absent: it is a verdict on the whole run, and a child converting\n one recording is not the whole run. Passing it down made each child announce \"--strict: 1\n warning raised, so this run is reported as a failure\" about its own single file, and exit\n 1 for it, which the parent then counted as a conversion that had not happened. The parent\n applies it once, from the counts the children report.\n */\n for (const flag of ['annotations-only', 'checksum', 'gzip', 'bom', 'force', 'quiet', 'json']) {\n if (values[flag] === true) args.push(`--${flag}`);\n }\n for (const flag of ['start', 'duration', 'end', 'decimals', 'layout']) {\n if (typeof values[flag] === 'string') args.push(`--${flag}=${values[flag] as string}`);\n }\n // --channels is repeatable, and each term is passed as given so that a label containing a\n // comma survives: joining them back into one list would split it in the child.\n const channels = values['channels'];\n if (channels !== undefined) {\n for (const term of Array.isArray(channels) ? (channels as string[]) : [String(channels)]) {\n args.push(`--channels=${term}`);\n }\n }\n args.push('--', input);\n\n return new Promise((resolve) => {\n const child = fork(fileURLToPath(import.meta.url), args, {\n stdio: ['ignore', 'pipe', 'pipe', 'ipc'],\n });\n running.set(child, destination);\n let out = '';\n let err = '';\n let report: { converted: number; warnings: number } | null = null;\n child.on('message', (message: unknown) => {\n const payload = (message as { edf2csv?: { converted: number; warnings: number } })?.edf2csv;\n if (payload) report = payload;\n });\n child.stdout?.setEncoding('utf8').on('data', (chunk: string) => {\n out += chunk;\n });\n child.stderr?.setEncoding('utf8').on('data', (chunk: string) => {\n err += chunk;\n });\n child.on('error', (error) => {\n running.delete(child);\n /*\n Escaped, which every other place this file prints a recording's name has been since\n 0.5.67 and this one was not.\n\n A file name is untrusted text — the filesystem supplies it and a recording inside a\n scanned folder may be called anything, including a name carrying an ESC byte — and\n `named`, `reportError`, the `[n/m]` header and the interrupt handler all put it\n through `printable`. This line is the one that fires when the fork itself fails, which\n is the moment a batch of five hundred at `--jobs auto` runs out of file descriptors,\n and it is the only line in this file that prints a name straight to stderr.\n */\n resolve({\n code: EXIT_ERROR,\n out,\n err: `${err}error: ${printable(input)}: ${printableLines(error.message, ' ')}\\n`,\n report,\n });\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 // And the same question of this one child, for the same reason as the handler above.\n err +=\n `error: stopped by ${signal} before it finished.\\n` +\n detail(\n existsSync(destination)\n ? `Incomplete, and should not be used: ${printable(destination)}`\n : `Nothing was written: \"${printable(destination)}\" was never created.`,\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 /*\n Wrapped, for the same reason the hint under a warning is — and because on this file\n it is frequently the same sentence.\n\n A mixed-rate recording refused by --stdout and the same recording described by --info\n both end in \"Narrow it to one rate with --channels, write --layout long ...\". One\n arrived as a ConversionError hint and printed on a single 130-column line; the other\n arrived as a Diagnostic hint and wrapped at 80 from 0.7.1. Identical advice about an\n identical file, laid out two different ways depending on whether the tool went on to\n convert it.\n */\n /*\n Escaped, which the message above it has been since 0.5.67 and this line never was.\n\n A hint is usually a fixed sentence, which is why it looked like text nobody supplies.\n One is not: a read that fails part way through a conversion reports \"What was written\n to <outputDir> before it failed is incomplete\", and `--out` takes whatever path the\n caller gives it. A directory may be named with an ESC byte on every platform this\n runs on, so `--out $'out\\x1b[31mred'` on a recording that shrinks mid-read put a live\n colour change on stderr — under an `error:` line that had been escaped correctly, two\n lines above.\n */\n if (error.hint) emit('err', `${wrap(printable(error.hint), ' ')}\\n`);\n // A request the tool cannot carry out is the command line's problem, not the file's,\n // whatever layer noticed it. See USAGE_ERROR_CODES.\n return error instanceof ConversionError && USAGE_ERROR_CODES.has(error.code)\n ? EXIT_USAGE\n : EXIT_ERROR;\n }\n if (\n error instanceof ChannelSelectionError ||\n error instanceof TimeRangeError ||\n error instanceof OptionError\n ) {\n emit('err', `error: ${where}${printableLines(error.message, ' ')}\\n`);\n return EXIT_USAGE;\n }\n emit('err', `error: ${where}${message(error, ' ')}\\n`);\n return EXIT_ERROR;\n}\n\n/**\n * How many conversions to run at once.\n *\n * `auto` is one per core, less one, so a long batch does not take the machine over. A batch\n * is the only place this means anything: a single recording is a single conversion however\n * many jobs are asked for.\n */\nfunction parseJobs(raw: unknown, inputs: number): number {\n if (raw === undefined) return 1;\n const text = String(raw).trim();\n /*\n The cores this process may use, not the ones the machine has.\n\n `cpus().length` counts every core the kernel can see, which is the machine's answer to a\n question about this process. A container given two CPUs of a sixty-four core node, a job\n pinned by `taskset` or a scheduler, a `docker --cpuset-cpus` — in all of them `auto` asked\n for sixty-three workers, which is exactly the \"leaves the machine usable\" this setting\n exists for, inverted. `availableParallelism` is the call for the question and has been\n there since Node 18.14; this package requires 20.\n */\n if (text === 'auto') return Math.max(1, Math.min(inputs, availableParallelism() - 1));\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x10`, `1e3` and `999999999999999999999` — the\n last of which is not an integer any more, only the nearest double to one — while refusing\n `4.7`. The message says \"a whole number of 1 or more\" and the refusal of 4.7 is what makes\n a reader believe it, so the others read as the tool silently reinterpreting what they\n typed. `--jobs` is the flag 0.4.2 hardened because accepting `--jobs 0` in silence \"is the\n kind of quiet this tool avoids\".\n\n Surrounding space is trimmed above and is not one of those: ` 4 ` is four to anyone who\n reads it, and a value assembled by a shell out of a file or a `$(...)` routinely carries\n some. What matters is that the refusal quote what was typed rather than what survived the\n trim — see below.\n */\n if (!/^\\d+$/u.test(text)) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n const value = Number(text);\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new OptionError(`--jobs must be a whole number of 1 or more, or \"auto\", got \"${quotedValue(raw)}\".`);\n }\n return Math.min(value, Math.max(1, inputs));\n}\n\nfunction splitChannels(raw: unknown): string[] | undefined {\n if (raw === undefined) return undefined;\n const list = Array.isArray(raw) ? (raw as string[]) : [String(raw)];\n const terms = list.flatMap((entry) => entry.split(',')).map((t) => t.trim()).filter((t) => t !== '');\n // Returning undefined here would mean \"no --channels given\" and convert everything,\n // which is the opposite of what someone passing an empty list is asking for.\n if (terms.length === 0) {\n throw new OptionError('--channels was given but lists no channel names.');\n }\n return terms;\n}\n\n/**\n * `--start`, `--duration` and `--end`, in seconds.\n *\n * The first two name a position on the recording's own clock, which a file timed from before\n * zero puts below it, so those take a sign. `--duration` is a length and does not.\n */\nfunction optionalTime(raw: unknown, option: string): number | undefined {\n if (raw === undefined) return undefined;\n return parseTimeSpec(String(raw), option, option !== '--duration');\n}\n\n/** `--layout`, which is one of two words and not a guess at what was meant. */\nfunction optionalLayout(raw: unknown): 'wide' | 'long' | undefined {\n if (raw === undefined) return undefined;\n // Trimmed, like every other option's value. This one alone refused ` long`, so a value that\n // came out of a file or a `$(...)` with a newline on it was rejected for a character nobody\n // typed and which the message could not show.\n const text = String(raw).trim();\n if (text === 'wide' || text === 'long') return text;\n throw new OptionError(`--layout must be \"wide\" or \"long\", got \"${quotedValue(raw)}\".`);\n}\n\n/**\n * An option's value as the user typed it, for a message that quotes it back.\n *\n * The point of the quotation marks is to show where the value begins and ends, which matters\n * most for the values that went wrong because of what surrounds them. `--jobs` trimmed first\n * and then quoted the remains, so `--jobs \" x\"` came back as `got \"x\"` and `--jobs \" \"` as\n * `got \"\"` — the second reading as though nothing had been given at all, when what was given\n * is the whole reason it failed. `--start` has quoted the typed value since 0.5.60 and\n * `--decimals` since 0.5.86; this is the same rule, spelled once.\n */\nfunction quotedValue(raw: unknown): string {\n return String(raw);\n}\n\nfunction optionalDecimals(raw: unknown): number | undefined {\n if (raw === undefined) return undefined;\n const text = String(raw).trim();\n // Number('') is 0, which would quietly round every physical value to a whole number.\n if (text === '') {\n throw new OptionError('--decimals needs a number, for example --decimals 3.');\n }\n /*\n A whole number as written, not as `Number()` chooses to read it.\n\n `Number.isInteger(Number(text))` accepted `0x3`, `0b11`, `0o5`, `3e0` and `+3`,\n while refusing `3.5` — and the refusal of `3.5` is what makes a reader believe the message\n that says \"a whole number between 0 and 20\". So `--decimals 0o5` wrote five decimals for\n an argument that reads as five to nobody, and `--decimals 0b11` wrote three, in silence,\n exit 0, with a CSV that looks exactly as intended.\n\n The same fix `--jobs` got, and the same one `--channels '#N'` got — where the comment\n records why: \"Every one of them selected a channel and exited 0, so a slip did not fail —\n it quietly converted a different channel than the one asked for, which for this tool is\n the worst way to be wrong.\" A precision is the same kind of quiet.\n */\n const value = Number(text);\n if (!/^\\d+$/u.test(text) || !Number.isInteger(value) || value < 0 || value > 20) {\n throw new OptionError(`--decimals must be a whole number between 0 and 20, got \"${String(raw)}\".`);\n }\n return value;\n}\n\n/*\n Error text is escaped for the same reason --info's table is: a fatal header error quotes\n the channel label that caused it, and that label is free text out of the file. A recording\n declaring a negative sample count under a label containing `\\x1b[2J` cleared the reader's\n screen on the way out.\n*/\n/**\n * A `parseArgs` failure, in this tool's words where they are better than Node's.\n *\n * Node refuses `--out -nightly` with \"Option '--out' argument is ambiguous. Did you forget to\n * specify the option argument for '--out'? To specify an option argument starting with a dash\n * use '--out=-XYZ'.\" The user did not forget anything — they gave a value — and `-XYZ` is a\n * placeholder, where every other message this tool prints quotes what was actually typed and\n * says exactly what to type instead. A destination whose name starts with a dash is not exotic,\n * and neither is a negative `--start` on a recording timed from before zero.\n *\n * 0.4.34 already fixed this message where the tool produced it internally, building child argv\n * for `--jobs` — `--out ./-nightly` reached the child as two arguments and died on it, while the\n * serial path converted the same command. The half a user can hit was left as Node wrote it.\n *\n * An unknown option is reworded too, and for a plainer reason than tone. Node's sentence is\n * \"Unknown option '--chanels'. To specify a positional argument starting with a '-', place it at\n * the end of the command after '--', as in '-- \"--chanels\"' — except the closing quote is not\n * there. Node opens one before `--` and never closes it, so this tool printed an unfinished\n * sentence, without the `error:` prefix every other refusal carries, at the single most common way\n * to get a command wrong. The advice is also for a different mistake than the one almost everyone\n * makes: it explains how to pass a *file* whose name begins with a dash, where the reader has\n * mistyped one of twenty flags.\n *\n * Node's remaining two — a switch given a value, an option missing its value — say something true\n * in words a reader can act on, and replacing them with near-identical sentences would be churn.\n * They do go out with the prefix, which is the other half of what was wrong with the unknown-option\n * message above and was left behind when its wording was fixed:\n *\n * $ edf2csv recording.edf --decimals\n * Option '--decimals <value>' argument missing\n * $ edf2csv recording.edf --gzip=yes\n * Option '--gzip' does not take an argument\n *\n * against `error:` on every other refusal this tool prints, including the one for `--decimals 101`\n * one keystroke away. This repository's own stdout audit picks errors out of stderr with\n * `line.startsWith('error:')`, so by its own reckoning these two runs printed no error and exited 2.\n */\n/**\n * A token as it has to be typed for a shell to hand it over unchanged.\n *\n * Every hint in this file that prints a command has to print one that works — the comment on\n * the `--channels` advice in the header parser lists three separate times it did not. These\n * two are the same failure at the command line rather than in a channel label. `--out \"-my\n * nightly\"` was answered with `Write it as one argument instead: --out=-my nightly`, which is\n * two arguments; `--chan\"nels` was answered with `edf2csv -- \"--chan\"nels\"`, whose quotes\n * collapse into something else again.\n *\n * Left bare when a shell would read it as written, so the ordinary `--out=-nightly` reads as\n * it always has. Otherwise single-quoted, the one POSIX form with no escapes inside it: a\n * single quote in the token closes, escapes and reopens.\n */\nfunction survivesBare(text: string): boolean {\n return text !== '' && !/[^\\w@%+=:,./-]/u.test(text);\n}\n\nfunction singleQuoted(text: string): string {\n return `'${text.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction usageMessage(error: unknown, argv: readonly string[]): string {\n /** Node's own wording, carrying this tool's prefix. */\n const asError = (text: string): string => (text.startsWith('error:') ? text : `error: ${text}`);\n const raw = message(error);\n const code = (error as { code?: unknown } | null)?.code;\n if (code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') return unknownOption(raw);\n if (code !== 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE') {\n return asError(raw);\n }\n const ambiguous = /^Option '(-[^']+)' argument is ambiguous/u.exec(raw);\n if (!ambiguous) return asError(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 asError(raw);\n\n /*\n How the two shapes join, which is not the same and matters.\n\n A long option takes `--out=-nightly`. A short one does NOT take `-o=-nightly`: parseArgs\n reads the equals sign as the first character of the value and hands back \"=-nightly\", so\n that advice would have produced a directory named `=-nightly` without a word — a worse\n failure than the message it replaced, and a silent one. Short options join directly.\n */\n const joined = flag.startsWith('--') ? `${flag}=${value}` : `${flag}${value}`;\n const typeable = survivesBare(joined) ? joined : singleQuoted(joined);\n return printableLines(\n `error: ${flag} was given \"${value}\", which begins with a dash and so reads as another ` +\n `flag rather than as its value.\\n` +\n ` Write it as one argument instead: ${typeable}`,\n );\n}\n\n/** Levenshtein distance, two rows at a time. Twenty names of under twenty characters. */\nfunction editDistance(a: string, b: string): number {\n const row = Array.from({ length: b.length + 1 }, (_unused, i) => i);\n for (let i = 1; i <= a.length; i++) {\n let diagonal = row[0] as number;\n row[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const above = row[j] as number;\n row[j] = Math.min(above + 1, (row[j - 1] as number) + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));\n diagonal = above;\n }\n }\n return row[b.length] as number;\n}\n\n/**\n * The option they probably meant, or null when nothing is close enough to say.\n *\n * Long options only. A stray `-Z` carries one character of evidence, and guessing a word\n * from it would be guessing.\n *\n * Two rules, because typos come in two shapes. An unfinished name is a prefix — `--chan`,\n * `--decim` — and is only taken from three characters up, so `--c` does not become\n * `--channels` or `--checksum` by coin toss. A misspelt one is within a couple of edits,\n * scaled to the name's length so `--jobs` is not reached from three characters away.\n */\nfunction nearestOption(flag: string): string | null {\n if (!flag.startsWith('--')) return null;\n const typed = flag.slice(2).toLowerCase();\n if (typed.length < 2) return null;\n\n const names = Object.keys(OPTIONS);\n if (typed.length >= 3) {\n const prefixed = names.filter((name) => name.startsWith(typed));\n if (prefixed.length === 1) return `--${prefixed[0] as string}`;\n }\n\n let best: string | null = null;\n let score = Infinity;\n let tied = false;\n for (const name of names) {\n const d = editDistance(typed, name);\n if (d < score) {\n score = d;\n best = name;\n tied = false;\n } else if (d === score) {\n tied = true;\n }\n }\n if (best === null || tied) return null;\n return score <= Math.max(1, Math.min(3, Math.floor(best.length / 3))) ? `--${best}` : null;\n}\n\n/** An option this tool does not have, in a finished sentence, naming the likely one. */\nfunction unknownOption(raw: string): string {\n const found = /^Unknown option '([^']+)'/u.exec(raw);\n // If Node ever rewords it, its text is still true; only the polish is lost.\n if (!found) return raw;\n const flag = found[1] as string;\n const near = nearestOption(flag);\n return printableLines(\n `error: There is no ${flag} option.${near === null ? '' : ` Did you mean ${near}?`}\\n` +\n ` If it is the name of a file, pass it after -- instead:\\n` +\n ` edf2csv -- ${survivesBare(flag) ? `\"${flag}\"` : singleQuoted(flag)}`,\n );\n}\n\nfunction message(error: unknown, indent = ''): string {\n // The indent lines continuation up under an \"error: \" prefix. Usage problems print without\n // one, alongside the usage text, so they pass nothing and stay flush left.\n return printableLines(error instanceof Error ? error.message : String(error), indent);\n}\n\n// `worstOf` is exported for the test that pins the exit-code mapping. A batch's verdict is\n// one of the few things a script branches on, and the mapping is easier to check directly\n// than by racing a signal at a worker.\n// `parseJobs` joins them for the same reason: what `auto` resolves to is a number nothing\n// could see from outside, since the only observable effect is how many children run at once.\nexport { defaultOutputDir, parseJobs, worstOf };\n\n/**\n * Whether this file was executed rather than imported.\n *\n * npm installs a bin as a symlink (node_modules/.bin/edf2csv -> ../edf2csv/dist/cli.js),\n * and that is the path `npx` runs. In that case process.argv[1] is the symlink while\n * import.meta.url is already resolved to the real file, so comparing the two directly\n * reports \"imported\" and the command silently does nothing. Both sides are resolved\n * through realpath before comparing.\n */\nfunction isMainModule(): boolean {\n const entry = process.argv[1];\n if (entry === undefined) return false;\n try {\n return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));\n } catch {\n // An unreadable or deleted entry path is not a reason to refuse to run.\n return pathToFileURL(entry).href === import.meta.url;\n }\n}\n\nconst invokedDirectly = isMainModule();\n\nif (invokedDirectly) {\n main(process.argv.slice(2))\n .then((code) => {\n /*\n A write failure already reported is not erased by a run that thought it finished.\n\n The stdout listener above prints `Writing to stdout failed: ...` and sets EXIT_ERROR,\n and assigning `code` over it put that back to 0 — so `--info > desc.txt` onto a full\n filesystem printed the error, left a zero-byte file, and exited 0. It only looked\n right because the stdout audit threw a second error on the way out, whose message was\n about a short write that had not happened.\n\n A closed pipe deliberately sets no code (see ignoreBrokenPipe), so `--info | head`\n still exits 0 through here.\n */\n process.exitCode = code === EXIT_OK ? (process.exitCode ?? EXIT_OK) : code;\n })\n .catch((error: unknown) => {\n process.stderr.write(`error: ${message(error)}\\n`);\n process.exitCode = EXIT_ERROR;\n });\n}\n"]}
@@ -717,7 +717,15 @@ export function parseHeader(buf, fileSize) {
717
717
  Two positions render identically either way, so the ordinary duplicate reads as it
718
718
  always has.
719
719
  */
720
- message: `${indices.length} signals share the label "${label}" (positions ${listed(indices.map(String))}).`,
720
+ /*
721
+ Positions written `#N`, which is how a position is written everywhere it is meant to be
722
+ typed: `--channels "#0"`, "This file has signal channels at #0, #1, #2", and this
723
+ warning's own namesake from channel selection — "(positions #0, #1); all of them were
724
+ selected". This one said "(positions 0, 1)", two paragraphs above the page that tells
725
+ the reader to "address it by position with #N". The number is the same; the form that
726
+ works is not.
727
+ */
728
+ message: `${indices.length} signals share the label "${label}" (positions ${listed(indices.map((i) => `#${i}`))}).`,
721
729
  hint: 'Their columns are suffixed with the signal number so they stay distinguishable.',
722
730
  });
723
731
  }
@@ -1 +1 @@
1
- {"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/edf/header.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * EDF / EDF+ header parsing.\n *\n * Layout (all fields are ASCII, left-justified, space-padded):\n *\n * fixed header, 256 bytes\n * 0 8 version ('0', or 255 + 'BIOSEMI' for BDF)\n * 8 80 patient identification\n * 88 80 recording identification\n * 168 8 start date dd.mm.yy\n * 176 8 start time hh.mm.ss\n * 184 8 number of bytes in the header record\n * 192 44 reserved ('EDF+C' / 'EDF+D' live here)\n * 236 8 number of data records (-1 if unknown)\n * 244 8 duration of a data record, in seconds (may be fractional)\n * 252 4 number of signals (ns)\n *\n * signal header, ns * 256 bytes, stored FIELD-major rather than signal-major:\n * all ns labels, then all ns transducer types, and so on.\n * ns * 16 label\n * ns * 80 transducer type\n * ns * 8 physical dimension\n * ns * 8 physical minimum\n * ns * 8 physical maximum\n * ns * 8 digital minimum\n * ns * 8 digital maximum\n * ns * 80 prefiltering\n * ns * 8 number of samples in each data record\n * ns * 32 reserved\n */\n\nimport { EdfError } from './errors.js';\nimport type { Diagnostic } from './errors.js';\nimport { counted, listed } from '../format/list.js';\nimport { plain } from '../format/number.js';\n// The shell-quoting rule for a label, shared rather than repeated: this hint and\n// `--channels`' own \"did you mean\" have to print the same command for the same label.\nimport { typeable } from '../convert/channels.js';\nimport { decodeLatin1 } from './bytes.js';\n\n/** Label the EDF+ spec reserves for the annotations channel. */\nexport const ANNOTATIONS_LABEL = 'EDF Annotations';\n/** BDF+ uses its own spelling for the same channel. */\nexport const BDF_ANNOTATIONS_LABEL = 'BDF Annotations';\n\nexport const FIXED_HEADER_BYTES = 256;\nexport const SIGNAL_HEADER_BYTES = 256;\n\nexport interface EdfSignal {\n /** Position in the file, 0-based. Stable even when labels collide. */\n index: number;\n label: string;\n transducer: string;\n physicalDimension: string;\n physicalMin: number;\n physicalMax: number;\n digitalMin: number;\n digitalMax: number;\n prefiltering: string;\n samplesPerRecord: number;\n reserved: string;\n /** True for the EDF+ 'EDF Annotations' channel, which carries text, not signal. */\n isAnnotations: boolean;\n /** samplesPerRecord / recordDuration, in Hz. */\n samplingRate: number;\n /** Byte offset of this signal's samples within one data record. */\n byteOffsetInRecord: number;\n}\n\nexport interface EdfHeader {\n version: string;\n patientId: string;\n recordingId: string;\n /** Raw 'dd.mm.yy' as written in the file. */\n startDateRaw: string;\n /** Raw 'hh.mm.ss' as written in the file. */\n startTimeRaw: string;\n /** Resolved start instant, or null when the file's date/time fields are unusable. */\n startDateTime: Date | null;\n /**\n * Header size in bytes, computed from the signal count rather than read from the field.\n *\n * Every data record offset is derived from this, so it has to be the one the layout\n * actually uses: 256 for the fixed header plus 256 per signal. A writer that fills the\n * field in carelessly is common enough to have its own warning, HEADER_BYTES_MISMATCH,\n * and trusting the field over the arithmetic would put every sample at the wrong offset.\n */\n headerBytes: number;\n /**\n * What the header's own length field says, which need not be the above.\n *\n * Exposed for the same reason `declaredRecordCount` is: the two disagreeing is a fact\n * about the file, and a caller checking how a recording was written should be able to see\n * what it claimed rather than only what was believed.\n */\n declaredHeaderBytes: number;\n reserved: string;\n isEdfPlus: boolean;\n /** True for BioSemi BDF/BDF+ files, whose samples are 3 bytes rather than 2. */\n isBdf: boolean;\n /** 'EDF+C' continuous, 'EDF+D' discontinuous, or null for plain EDF. */\n continuity: 'EDF+C' | 'EDF+D' | null;\n /** As declared in the header. -1 means \"unknown\", which the spec permits. */\n declaredRecordCount: number;\n recordDuration: number;\n signalCount: number;\n signals: EdfSignal[];\n bytesPerSample: number;\n recordBytes: number;\n}\n\n/** Everything derived by combining the header with the file's real size. */\nexport interface EdfHeaderInfo {\n header: EdfHeader;\n /** Record count implied by the actual file size — the one we trust for reading. */\n recordCount: number;\n /** Bytes after the last complete data record. */\n trailingBytes: number;\n diagnostics: Diagnostic[];\n}\n\n/**\n * A byte the terminal treats as an instruction rather than as text.\n *\n * C0 and C1, plus DEL. Tab is included deliberately: it is harmless to a terminal but it\n * makes a CSV column name that cannot be typed or matched reliably, which is the other half\n * of what this warning is for.\n */\n/**\n * Whether a spreadsheet reads this field as the start of a formula rather than as text.\n *\n * `=` and `@` unconditionally; every list of these characters names two more, and this had\n * `-` as an exception with the reason written out on the warnings page: \"a lone `-` is a\n * real convention for no unit ... and neither is evaluated unless what follows it parses as a\n * formula\". Which is the condition, and it was not being applied — nothing with a leading\n * minus was flagged at all. A channel labelled `-2+3` opens as a column headed `1`, and\n * `-HYPERLINK(\"http://...\",\"EEG\")` is a name the spreadsheet resolves, in silence.\n *\n * So the exception is what it says it is rather than the whole character. A lone `-` is left\n * as text by every spreadsheet and is not flagged; a field that is entirely a number reads as\n * that number, which is what the header says, and is not flagged either. Anything else after\n * the minus is arithmetic or a name.\n *\n * And `+` takes the same exception, which it did not. It is the same rule in the spreadsheet —\n * Lotus compatibility, which converts a leading `+` or `-` to a formula when what follows one\n * parses as a formula and leaves it as text when it does not — so the two signs cannot differ\n * here for a reason that comes from the sign. A channel labelled `+100` was warned about as\n * something a spreadsheet \"reads as the start of a formula rather than as text\", over a cell\n * that opens as 100, which is what the header says; `-100` beside it said nothing, and under\n * `--strict` the difference was an exit code. `+1+1` is still arithmetic and still flagged.\n */\nfunction startsFormula(text: string): boolean {\n if (/^[=@]/u.test(text)) return true;\n if (!/^[+-]./u.test(text)) return false;\n return !/^[+-](?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/u.test(text);\n}\n\nfunction isControlCharacter(character: string): boolean {\n const code = character.codePointAt(0) as number;\n return code <= 0x1f || (code >= 0x7f && code <= 0x9f);\n}\n\nconst dec = (buf: Uint8Array, start: number, len: number): string =>\n decodeLatin1(buf, start, start + len);\n\n/** EDF fields are space-padded; trailing NULs also occur in files written by sloppy tools. */\nconst trimField = (s: string): string => s.replace(/[\\0\\s]+$/u, '').replace(/^\\s+/u, '');\n\n/**\n * How many signals the fixed header says there are, read exactly as `parseHeader` will.\n *\n * `EdfFile.open` needs this before it can know how much header to read, and it used to work\n * it out with its own `Number(...)` — which was NUL-tolerant but not comma-tolerant, unlike\n * every other numeric field here. A header written with a comma decimal separator, which\n * COMMA_DECIMAL exists to accept and which the documentation lists this field among, was\n * therefore never given its signal headers at all, and the file died on a message that\n * contradicted itself: \"needs a 768-byte header, but the file is only 848 bytes\".\n *\n * Sharing the parse is what keeps the two from disagreeing again about which files are\n * readable. Null means \"not a usable count\", and the caller reads no further header — the\n * real error then comes from `parseHeader`, which is the one place that decides.\n */\nexport function peekSignalCount(fixed: Uint8Array): number | null {\n const text = normaliseNumberField(dec(fixed, 252, 4)).text;\n if (!DECIMAL_FIELD.test(text)) return null;\n const count = Number(text);\n return Number.isInteger(count) && count > 0 ? count : null;\n}\n\n/**\n * What EDF allows a numeric field to look like, which is less than `Number()` allows.\n *\n * A sign, digits, an optional fractional part, an optional exponent — the spec's own grammar,\n * plus the exponent form the 8-character physical bounds need to reach a magnetometer's range\n * and which real headers use.\n *\n * `Number()` accepts a great deal more, and every one of those forms is a header this tool\n * would have read as a number nobody wrote. A physical maximum of `0x64` came out as 100: it\n * printed as `-100 to 100` in the channel table, went into channels.csv as `physical_max,100`,\n * and set the gain every sample on that channel was scaled by — a whole calibration invented\n * from four bytes that are not a decimal number, exit 0, no diagnostic. `0b1100100` and `0o144`\n * are the same hundred, and `0x02` in the signal-count field is a two-channel recording.\n *\n * The same mistake as `#0x2` reaching channel 2 through `--channels`, `--decimals 0o5` writing\n * five places and `--jobs 0x10` running sixteen, all of which have their own comments and their\n * own fixes. Those were values somebody typed. These are the fields every number in the output\n * is computed from, and the page describing them says \"all fields are ASCII\" and gives the\n * layout digit by digit.\n */\nconst DECIMAL_FIELD = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/u;\n\n/** A numeric header field, trimmed and with a comma decimal separator turned into a dot. */\nfunction normaliseNumberField(raw: string): { text: string; sawComma: boolean } {\n const text = trimField(raw);\n // Some writers emit a comma decimal separator despite the spec requiring '.'.\n if (text.includes(',') && !text.includes('.')) {\n return { text: text.replace(',', '.'), sawComma: true };\n }\n return { text, sawComma: false };\n}\n\nfunction parseNumberField(\n raw: string,\n field: string,\n { integer = false, sawComma }: { integer?: boolean; sawComma?: { value: boolean } } = {},\n): number {\n const normalised = normaliseNumberField(raw);\n const text = normalised.text;\n if (normalised.sawComma && sawComma) sawComma.value = true;\n if (text === '') {\n throw new EdfError('BAD_HEADER_FIELD', `Header field \"${field}\" is empty.`);\n }\n const n = DECIMAL_FIELD.test(text) ? Number(text) : NaN;\n if (!Number.isFinite(n)) {\n throw new EdfError(\n 'BAD_HEADER_FIELD',\n `Header field \"${field}\" is not a number (found ${JSON.stringify(text)}).`,\n 'The file may be truncated, byte-shifted, or not an EDF file at all.',\n );\n }\n if (integer && !Number.isInteger(n)) {\n throw new EdfError(\n 'BAD_HEADER_FIELD',\n `Header field \"${field}\" must be a whole number (found ${JSON.stringify(text)}).`,\n );\n }\n return n;\n}\n\n/** Whether the start time names the sixtieth second. See LEAP_SECOND_START. */\nfunction namesLeapSecond(timeRaw: string): boolean {\n const t = /^(\\d{2})[.:\\-](\\d{2})[.:\\-](\\d{2})$/u.exec(trimField(timeRaw));\n return t !== null && Number(t[3]) === 60;\n}\n\nconst MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];\n\n/**\n * The date an EDF+ recording ID states, which is the only place the file writes a full year.\n *\n * EDF+ requires the recording identification field to begin `Startdate dd-MMM-yyyy`, and\n * requires it to agree with the header's own date field. It is a four-digit year, so it says\n * something the eight-character date field physically cannot.\n */\nfunction recordingIdStartdate(\n recordingId: string,\n): { day: number; month: number; year: number } | null {\n const stated = /^Startdate\\s+(\\d{2})-([A-Za-z]{3})-(\\d{4})(?:\\s|$)/u.exec(recordingId.trim());\n if (!stated) return null;\n const month = MONTHS.indexOf((stated[2] as string).toUpperCase()) + 1;\n if (month === 0) return null;\n return { day: Number(stated[1]), month, year: Number(stated[3]) };\n}\n\n/**\n * EDF stores a two-digit year. The spec pins the century: 85-99 mean 1985-1999\n * and 00-84 mean 2000-2084. Files outside 1985-2084 cannot express their date.\n *\n * Which is why EDF+ writes it again in full, in the recording identification field, and why\n * that is used here when it is there. The rule alone reports a recording made in 1984 as 2084\n * and one made in 2085 as 1985 — a hundred years out, on a file that states the year plainly\n * four fields earlier. Taken only where the two agree about everything the header can express:\n * the same day, the same month, and a four-digit year ending in the two digits the header\n * wrote. A recording ID that contradicts the header is a different problem and is left to the\n * spec's rule, which is at least the one the format defines.\n */\nfunction resolveStartDateTime(\n dateRaw: string,\n timeRaw: string,\n recordingId = '',\n): Date | null {\n const d = /^(\\d{2})[.\\-/](\\d{2})[.\\-/](\\d{2})$/u.exec(trimField(dateRaw));\n const t = /^(\\d{2})[.:\\-](\\d{2})[.:\\-](\\d{2})$/u.exec(trimField(timeRaw));\n if (!d || !t) return null;\n\n const dd = Number(d[1]);\n const mm = Number(d[2]);\n const yy = Number(d[3]);\n const hh = Number(t[1]);\n const mi = Number(t[2]);\n const ss = Number(t[3]);\n\n if (mm < 1 || mm > 12 || dd < 1 || dd > 31 || hh > 23 || mi > 59 || ss > 60) return null;\n\n const stated = recordingIdStartdate(recordingId);\n const year =\n stated !== null && stated.day === dd && stated.month === mm && stated.year % 100 === yy\n ? stated.year\n : yy >= 85\n ? 1900 + yy\n : 2000 + yy;\n const date = new Date(Date.UTC(year, mm - 1, dd, hh, mi, Math.min(ss, 59)));\n // Reject dates that rolled over, e.g. 31.02.\n if (date.getUTCMonth() !== mm - 1 || date.getUTCDate() !== dd) return null;\n return date;\n}\n\n/**\n * Parse the fixed 256-byte header plus the per-signal header block.\n *\n * @param buf At least FIXED_HEADER_BYTES + ns * SIGNAL_HEADER_BYTES bytes.\n * @param fileSize Total size of the file on disk, used to derive the real record count.\n */\nexport function parseHeader(buf: Uint8Array, fileSize: number): EdfHeaderInfo {\n const diagnostics: Diagnostic[] = [];\n const sawComma = { value: false };\n\n if (buf.length < FIXED_HEADER_BYTES) {\n throw new EdfError(\n 'FILE_TOO_SMALL',\n `File is ${counted(fileSize, 'byte')}; an EDF header alone needs at least ${FIXED_HEADER_BYTES}.`,\n );\n }\n\n // BDF (BioSemi) marks itself with byte 255 followed by 'BIOSEMI', and stores\n // 3-byte samples instead of 2. Everything else about the layout is identical.\n const isBdf = buf[0] === 0xff && dec(buf, 1, 7) === 'BIOSEMI';\n const version = isBdf ? 'BIOSEMI' : trimField(dec(buf, 0, 8));\n\n const patientId = trimField(dec(buf, 8, 80));\n const recordingId = trimField(dec(buf, 88, 80));\n const startDateRaw = trimField(dec(buf, 168, 8));\n const startTimeRaw = trimField(dec(buf, 176, 8));\n const headerBytes = parseNumberField(dec(buf, 184, 8), 'number of header bytes', {\n integer: true,\n sawComma,\n });\n const reserved = trimField(dec(buf, 192, 44));\n const declaredRecordCount = parseNumberField(dec(buf, 236, 8), 'number of data records', {\n integer: true,\n sawComma,\n });\n const recordDuration = parseNumberField(dec(buf, 244, 8), 'duration of a data record', {\n sawComma,\n });\n const signalCount = parseNumberField(dec(buf, 252, 4), 'number of signals', {\n integer: true,\n sawComma,\n });\n\n if (signalCount <= 0) {\n throw new EdfError(\n 'INVALID_SIGNAL_COUNT',\n `Header declares ${signalCount} signals; expected at least 1.`,\n );\n }\n if (!(recordDuration > 0)) {\n throw new EdfError(\n 'INVALID_RECORD_DURATION',\n `Header declares a data record duration of ${plain(recordDuration)}s; expected a positive number.`,\n );\n }\n\n const expectedHeaderBytes = FIXED_HEADER_BYTES + signalCount * SIGNAL_HEADER_BYTES;\n if (buf.length < expectedHeaderBytes) {\n throw new EdfError(\n 'FILE_TOO_SMALL',\n /*\n Which of the two is actually short.\n\n The file size was quoted either way, so a caller that had read too little — the\n signal count parsed one way here and another way there — produced arithmetic that\n refuted itself: \"needs a 768-byte header, but the file is only 848 bytes\". A reader\n following that looks for a truncation that is not there.\n */\n `File declares ${counted(signalCount, 'signal')}, which needs a ${expectedHeaderBytes}-byte header, ` +\n (fileSize < expectedHeaderBytes\n ? `but the file is only ${fileSize} bytes.`\n : `but only ${buf.length} bytes of it were handed to the parser.`),\n );\n }\n if (headerBytes !== expectedHeaderBytes) {\n diagnostics.push({\n code: 'HEADER_BYTES_MISMATCH',\n severity: 'warning',\n message:\n `Header says it is ${headerBytes} bytes, but ${counted(signalCount, 'signal')} ` +\n `${signalCount === 1 ? 'requires' : 'require'} ${expectedHeaderBytes} bytes. ` +\n `Using the value computed from the signal count.`,\n });\n }\n\n // Signal headers are field-major: all labels, then all transducers, and so on.\n const base = FIXED_HEADER_BYTES;\n const readField = (offsetUnits: number, width: number, i: number): string =>\n dec(buf, base + offsetUnits * signalCount + i * width, width);\n\n // EDF+ writes 'EDF+C'/'EDF+D' here; BDF+ writes 'BDF+C'/'BDF+D'. The two mean the\n // same thing, so both are normalised to a single continuity marker.\n const continuityTag = /^(?:EDF|BDF)\\+([CD])/u.exec(reserved);\n const continuity: 'EDF+C' | 'EDF+D' | null =\n continuityTag === null ? null : continuityTag[1] === 'D' ? 'EDF+D' : 'EDF+C';\n\n const signals: EdfSignal[] = [];\n let byteOffsetInRecord = 0;\n const bytesPerSample = isBdf ? 3 : 2;\n const seenLabels = new Map<string, number[]>();\n const emptyLabels: number[] = [];\n\n for (let i = 0; i < signalCount; i++) {\n const label = trimField(readField(0, 16, i));\n const transducer = trimField(readField(16, 80, i));\n const physicalDimension = trimField(readField(96, 8, i));\n const physicalMin = parseNumberField(readField(104, 8, i), `physical minimum (signal ${i})`, {\n sawComma,\n });\n const physicalMax = parseNumberField(readField(112, 8, i), `physical maximum (signal ${i})`, {\n sawComma,\n });\n const digitalMin = parseNumberField(readField(120, 8, i), `digital minimum (signal ${i})`, {\n integer: true,\n sawComma,\n });\n const digitalMax = parseNumberField(readField(128, 8, i), `digital maximum (signal ${i})`, {\n integer: true,\n sawComma,\n });\n const prefiltering = trimField(readField(136, 80, i));\n const samplesPerRecord = parseNumberField(\n readField(216, 8, i),\n `samples per record (signal ${i})`,\n { integer: true, sawComma },\n );\n const sigReserved = trimField(readField(224, 32, i));\n\n if (samplesPerRecord < 0) {\n throw new EdfError(\n 'BAD_HEADER_FIELD',\n `Signal ${i} (\"${label}\") declares ${samplesPerRecord} samples per record.`,\n );\n }\n\n const isAnnotations = label === ANNOTATIONS_LABEL || label === BDF_ANNOTATIONS_LABEL;\n\n signals.push({\n index: i,\n label,\n transducer,\n physicalDimension,\n physicalMin,\n physicalMax,\n digitalMin,\n digitalMax,\n prefiltering,\n samplesPerRecord,\n reserved: sigReserved,\n isAnnotations,\n samplingRate: samplesPerRecord / recordDuration,\n byteOffsetInRecord,\n });\n byteOffsetInRecord += samplesPerRecord * bytesPerSample;\n\n if (!isAnnotations) {\n /*\n A label is free text out of the file, and it becomes a column name in signals.csv.\n\n `--info` has escaped control bytes since it was written, because an ANSI escape in a\n header can drive the reader's terminal — `\\x1b[2J` clears the screen. The CSV had no\n such protection and needed none for correctness: quoting makes any byte safe for a\n parser, and this still passes the label through exactly as the file gives it, because\n losing what the header says is not an improvement.\n\n What was missing is the sentence saying so. A recording whose channel is labelled\n `\\x1b[2Jgone` converted with no warning at all, and `cat signals.csv` then cleared\n the terminal — while a script referencing that column by name carried an invisible\n control character in it. NONPRINTABLE_LABEL has been declared and documented as\n reserved since 0.1; this is it doing its job.\n */\n /*\n Which of the two fields carries them, because the consequences are not the same.\n\n The message said \"label or unit\", and then said the bytes \"will appear in the CSV\n column name\" and that \"the name cannot be typed\" — both of which are about the label.\n A channel labelled plainly `ECG` in a unit of `u\\x07V` got all of it: its column is\n `ECG`, `--channels ECG` selects it and exits 0, and the byte is in channels.csv's\n `unit` cell, which the warning never mentioned. Three sentences, none of them true of\n the file that raised it, on a warning whose whole purpose is to say where an invisible\n byte went.\n */\n /*\n All four free-text fields, not the two that were checked.\n\n `transducer` and `prefiltering` are free text out of the header exactly as the label\n and the unit are, and they land in channels.csv exactly as the unit does — so an ESC\n byte in a transducer field reached the CSV raw with nothing said, and `cat\n channels.csv` would drive the terminal. That is the hazard this warning exists for,\n two columns over. 0.5.71 made it name which field carries them; this is the rest of\n the fields it can name.\n */\n const fields = [\n ['label', label],\n ['unit', physicalDimension],\n ['transducer', transducer],\n ['prefiltering', prefiltering],\n ] as const;\n const affected = fields.filter(([, text]) => [...text].some(isControlCharacter));\n const control = affected.flatMap(([, text]) => [...text].filter(isControlCharacter));\n if (control.length > 0) {\n const shown = [...new Set(control)]\n .map((c) => `\\\\x${(c.codePointAt(0) as number).toString(16).padStart(2, '0')}`)\n .join(', ');\n const plural = control.length === 1 ? '' : 's';\n const inLabel = affected.some(([name]) => name === 'label');\n // \"label and unit\", not \"label, unit\" — `listed` is for long enumerations that get\n // truncated, and this is a sentence with at most four items in it.\n const names = affected.map(([name]) => name);\n const named =\n names.length === 1\n ? (names[0] as string)\n : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1] as string}`;\n // Where they land, which is the question the reader has. A label becomes a column\n // name in signals.csv; the other three are cells of channels.csv and nothing else.\n // Named down to the cell when there is one of them, because that is the answer to\n // \"where did it go\" — `channels.csv` alone leaves a reader scanning fourteen columns.\n const cells = affected.filter(([name]) => name !== 'label').map(([name]) => name);\n const where =\n cells.length === 1 ? `channels.csv's ${cells[0] as string} cell` : 'channels.csv';\n const lands =\n inLabel && cells.length > 0\n ? `which will appear in the CSV column name and in ${where}`\n : inLabel\n ? 'which will appear in the CSV column name'\n : `which will appear in ${where}`;\n diagnostics.push({\n code: 'NONPRINTABLE_LABEL',\n severity: 'warning',\n message:\n `Signal ${i}'s ${named} ${affected.length === 1 ? 'contains' : 'contain'} ` +\n `${control.length} control character${plural} (${shown}), ${lands} exactly as the ` +\n `header has ${control.length === 1 ? 'it' : 'them'}.`,\n hint:\n /*\n Every branch has to print a command that works.\n\n The middle one quoted the label back, which is right until the label is empty:\n an unlabelled channel got `--channels \"\"`, and that exits 2 with \"--channels was\n given but lists no channel names\". A hint whose command fails is worse than no\n hint, and this warning's whole job is to say how to reach a channel whose header\n text you cannot type. `EMPTY_LABEL` already says the position is the only way in\n for such a channel; so does this now.\n\n A comma is the third way. `--channels` separates names with one, and splits on\n every occurrence, so a channel labelled `EEG Fpz-Cz, ref` cannot be selected by\n name at all: the quoted-back advice printed `--channels \"EEG Fpz-Cz, ref\"`, which\n exits 2 with `No channel named \"EEG Fpz-Cz\"` — a channel the file does not have,\n named after half of one it does. Commas in labels are ordinary, since EDF labels\n are free text, and the CSV header quotes them; only this one hint claimed\n something about them that isn't so.\n */\n /*\n And a fourth way, which this branch printed straight past.\n\n A shell expands `$`, a backtick and a backslash inside double quotes, so a\n channel labelled `EEG $ref` was answered with `--channels \"EEG $ref\"` — which\n arrives as `EEG ` and exits 2 on a channel the file does not have. A backtick\n opens a command substitution and the pasted line does not even finish. That is\n the failure 0.7.18 fixed for `--channels`' own suggestion, and `typeable` is the\n rule it fixed it with: double quotes where they survive, single quotes where\n they do not, null where nothing does. Asking it settles which branch this takes\n as well, so the two cannot disagree about one label again.\n */\n (typeable(label) === null\n ? `Address the channel by position with --channels \"#${i}\" rather than by name, ` +\n `since ${\n inLabel\n ? 'the name cannot be typed'\n : label === ''\n ? 'it has no label'\n : 'a comma in the label would read as two names'\n }. `\n : `The column name is unaffected, so --channels ${typeable(label) as string} still selects it. `) +\n 'Printing the CSV to a terminal may do more than print it.',\n });\n }\n\n /*\n Header text a spreadsheet will run rather than read.\n\n The same four free-text fields, asked a different question. `=`, `+` and `@` start a\n formula in Excel, LibreOffice and Sheets no matter which file the cell came from, and\n these fields land in a CSV header row and in channels.csv verbatim — so a channel\n labelled `=1+1` opens as a column headed 2, and `=HYPERLINK(...)` opens as a link\n nobody in the reading chain wrote. SECURITY.md already calls these fields\n attacker-controlled because they reach filenames; this is where they reach a program\n that executes text.\n\n Not `-`, which the same advice usually includes. A lone `-` is a real convention for\n \"no unit\" and appears in the fixtures, a leading `-` on a montage label is ordinary,\n and neither is executed unless what follows parses as a formula — so warning on it\n would fire on files that are fine, which is how a warning gets ignored.\n\n Said, not fixed. Prefixing the cell with a quote is the usual mitigation and would mean\n writing something the header does not say, which is the one thing this tool refuses to\n do; NONPRINTABLE_LABEL answers control bytes the same way.\n */\n const formulaic = fields.filter(([, text]) => startsFormula(text));\n if (formulaic.length > 0) {\n const names = formulaic.map(([name]) => name);\n const named =\n names.length === 1\n ? (names[0] as string)\n : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1] as string}`;\n const shown = [...new Set(formulaic.map(([, text]) => text[0] as string))].join(', ');\n diagnostics.push({\n code: 'FORMULA_LABEL',\n severity: 'warning',\n message:\n `Signal ${i}'s ${named} ${formulaic.length === 1 ? 'starts' : 'start'} with ` +\n `${shown}, which Excel, LibreOffice and Google Sheets read as the start of a ` +\n `formula rather than as text.`,\n hint:\n 'The text is written exactly as the header has it, so the cell is what the ' +\n 'recording says. Open the CSV with pandas or R, or import it into the ' +\n 'spreadsheet as text, if you do not want it evaluated.',\n });\n }\n\n if (label === '') {\n // Collected, not reported here: what this channel's column ends up called depends on\n // whether some later channel is literally labelled `signal_<i>`, and inside this loop\n // the later channels do not exist yet. See the pass below.\n emptyLabels.push(i);\n } else {\n // Collected rather than reported here: a label repeated five times should\n // produce one warning naming all five, not four near-identical pairs.\n const seen = seenLabels.get(label);\n if (seen) seen.push(i);\n else seenLabels.set(label, [i]);\n }\n\n if (samplesPerRecord === 0) {\n diagnostics.push({\n code: 'NO_SAMPLES',\n severity: 'warning',\n message: `Signal ${i} (\"${label}\") carries no samples at all (0 per data record).`,\n hint: 'It is described in channels.csv but left out of the converted data.',\n });\n }\n\n /*\n Too large to represent, and too small — the second was silent.\n\n The gain is the span divided by the digital range, and a span of 2e-320 over 65,535\n codes is 3e-325: below the smallest subnormal double, so it underflows to +0. The\n scaler's flat-range branch then handed every code the same physical value, and a\n channel of 65,536 distinct readings became one repeated number with nothing raised at\n all. One power of ten away, at 1e-319, the same file raises VALUE_RESOLUTION.\n\n Both are the same fact about the header — the span cannot be turned into a mapping —\n so both get this code, and both leave the cells empty rather than filling them with a\n value the header cannot justify.\n */\n const span = physicalMax - physicalMin;\n const underflowed = span !== 0 && span / (digitalMax - digitalMin) === 0;\n if (!Number.isFinite(span) || underflowed) {\n diagnostics.push({\n code: 'UNUSABLE_PHYSICAL_RANGE',\n severity: 'warning',\n message:\n `Signal ${i} (\"${label}\") declares a physical range from ${physicalMin} to ` +\n `${physicalMax}, whose span is too ${underflowed ? 'small' : 'large'} to ` +\n `represent, so its values cannot be scaled.`,\n hint: 'Its cells are left empty rather than filled with a value the header cannot justify.',\n });\n } else if (digitalMax === digitalMin) {\n diagnostics.push({\n code: 'DEGENERATE_DIGITAL_RANGE',\n severity: 'warning',\n message:\n `Signal ${i} (\"${label}\") has digital minimum equal to digital maximum ` +\n `(${digitalMin}), so its values cannot be scaled.`,\n hint: 'Its cells are left empty rather than filled with a value the header cannot justify.',\n });\n } else if (physicalMax === physicalMin) {\n diagnostics.push({\n code: 'DEGENERATE_PHYSICAL_RANGE',\n severity: 'warning',\n message:\n `Signal ${i} (\"${label}\") has physical minimum equal to physical maximum ` +\n `(${physicalMin}), so every sample converts to the same value.`,\n });\n } else if ((physicalMax - physicalMin) * (digitalMax - digitalMin) < 0) {\n /*\n Polarity is inverted when the gain is negative, and the gain is\n (physicalMax - physicalMin) / (digitalMax - digitalMin) — so it is the sign of the\n two spans together that matters, not the physical pair alone.\n\n Testing only `physicalMax < physicalMin` was wrong in both directions. A file with\n its DIGITAL bounds reversed is just as inverted and drew no warning at all, handing\n back sign-flipped EEG with nothing to indicate it. A file with BOTH pairs reversed\n has a positive gain and is not inverted, yet was warned about — a message that was\n simply untrue of that recording.\n */\n const reversed =\n physicalMax < physicalMin\n ? `physical minimum ${physicalMin} above physical maximum ${physicalMax}`\n : `digital minimum ${digitalMin} above digital maximum ${digitalMax}`;\n diagnostics.push({\n code: 'INVERTED_PHYSICAL_RANGE',\n severity: 'warning',\n message: `Signal ${i} (\"${label}\") declares ${reversed}, which inverts its polarity.`,\n hint: 'The values are converted exactly as the header specifies, inversion included.',\n });\n }\n }\n }\n\n /*\n What an unlabelled channel is actually called, which the message used to guess.\n\n A channel with no label takes `signal_<index>` — unless another channel is literally\n labelled that, which EDF permits, since labels are free text and nothing enforces anything\n about them. Then both collide and both are suffixed. The warning said \"It will appear as\n \"signal_0\"\" while the file's header read `time_s,signal_0_ch0,signal_0_ch1`: the one\n sentence the run printed named a column that exists in neither signals.csv nor\n channels.csv.\n\n The other half was silent. The channel that genuinely carries the label `signal_0` lost\n its own column name to a collision with a synthesised one, and nothing said so —\n DUPLICATE_LABEL did not fire, because the two labels are not the same label. Both halves\n are one sentence here, because they are one event.\n\n No specific suffixed name is quoted. The suffix rule has a second pass for names that are\n still shared afterwards, and a message that hard-coded `_ch<index>` would be guessing again\n in exactly the way this is fixing.\n */\n for (const index of emptyLabels) {\n const taken = seenLabels.get(`signal_${index}`);\n diagnostics.push({\n code: 'EMPTY_LABEL',\n severity: 'warning',\n message:\n taken === undefined\n ? `Signal ${index} has no label. It will appear as \"signal_${index}\".`\n : `Signal ${index} has no label, so it takes the name \"signal_${index}\" — which ` +\n `${taken.length === 1 ? 'signal' : 'signals'} ${listed(taken.map(String))} already ` +\n `${taken.length === 1 ? 'carries' : 'carry'} as a label, so both columns are ` +\n `suffixed with their position instead.`,\n });\n }\n\n /*\n A timestamp that is not one.\n\n EDF gives the start date and time eight characters each, and nothing stops a writer\n putting `32.13.99` and `25.61.61` there. `--info` has always echoed the raw fields with\n \"(unparseable)\" beside them, but nothing was raised: the conversion exited 0, `--strict`\n passed, and metadata.json recorded `start_datetime_local: null` with no note against it.\n\n Every other unusable header field reports itself — a degenerate digital range, a physical\n span that cannot be represented, a comma decimal separator, a header whose declared size\n disagrees with its signal count. This was the one that did not, and it is the field\n output-files points at for turning `time_s` into an absolute instant.\n */\n /*\n The sixtieth second, which is a second UTC has and a calendar date does not.\n\n `resolveStartDateTime` admits `ss === 60` on purpose — a recorder synchronised to UTC\n through a leap second writes `23.59.60`, and refusing it would throw away a date that is\n otherwise perfectly good over one second. What it then does is `Math.min(ss, 59)`, because\n `Date.UTC(..., 60)` rolls over into the next minute and would move the instant the other\n way, by fifty-nine seconds more.\n\n Keeping the nearest instant is the right answer. Keeping it in silence was not: `--info`\n printed `Recorded 2020-01-01 23:59:59` and metadata.json recorded the same, for a header\n that says `23.59.60`, with `--strict` exiting 0. Every other header field this tool cannot\n represent exactly says so — a comma decimal separator, a physical span that overflows, a\n record count that disagrees with the file — and this is the field `time_s` is documented as\n being added to.\n */\n if (\n namesLeapSecond(startTimeRaw) &&\n resolveStartDateTime(startDateRaw, startTimeRaw, recordingId) !== null\n ) {\n diagnostics.push({\n code: 'LEAP_SECOND_START',\n severity: 'warning',\n message:\n `The header's start time (\"${startTimeRaw}\") names the sixtieth second of a minute, ` +\n `which no calendar date has.`,\n hint:\n 'It is recorded as the fifty-ninth second, one second earlier, since that is the ' +\n 'nearest instant a date can hold. time_s is unaffected — it counts from the start of ' +\n 'the recording either way.',\n });\n }\n\n /*\n Two dates in one header, disagreeing.\n\n EDF+ requires the recording identification field's `Startdate` to be the header's start\n date. Where it is, its four digits settle the century — see resolveStartDateTime. Where it\n is not, one of the two is wrong and there is no way to tell which, so the date field is\n used, being the one the format defines. That was done in silence, on a header that plainly\n contradicts itself:\n\n Recorded 2002-03-02 22:15:00\n Recording Startdate 05-MAR-2002 PSG-1234/2002 NN Telemetry03\n\n The same shape as a record count that disagrees with the file, or a declared header size\n that disagrees with the signal count, both of which have said so for versions.\n */\n const statedDate = recordingIdStartdate(recordingId);\n const headerDate = /^(\\d{2})[.\\-/](\\d{2})[.\\-/](\\d{2})$/u.exec(trimField(startDateRaw));\n if (statedDate !== null && headerDate !== null) {\n const day = Number(headerDate[1]);\n const month = Number(headerDate[2]);\n const yy = Number(headerDate[3]);\n if (statedDate.day !== day || statedDate.month !== month || statedDate.year % 100 !== yy) {\n diagnostics.push({\n code: 'START_DATE_MISMATCH',\n severity: 'warning',\n message:\n `The header's start date (\"${startDateRaw}\") and the date its recording ` +\n `identification states (\"${String(statedDate.day).padStart(2, '0')}-` +\n `${MONTHS[statedDate.month - 1] as string}-${statedDate.year}\") are different ` +\n `dates, which EDF+ does not permit.`,\n hint:\n 'The start date field is used, since that is the one the format defines. Which of ' +\n 'the two is right is not knowable from the file, so start_datetime_local may name ' +\n 'the wrong day.',\n });\n }\n }\n\n if (resolveStartDateTime(startDateRaw, startTimeRaw, recordingId) === null) {\n diagnostics.push({\n code: 'START_TIME_UNREADABLE',\n severity: 'warning',\n message:\n `The header's start date and time (\"${startDateRaw}\" and ` +\n `\"${startTimeRaw}\") are not a date and a time, so the recording has ` +\n `no start instant.`,\n hint:\n 'time_s is unaffected — it counts from the start of the recording either way. What ' +\n 'cannot be done is turning it into a wall-clock instant, and metadata.json records ' +\n 'start_datetime_local as null.',\n });\n }\n\n for (const [label, indices] of seenLabels) {\n if (indices.length < 2) continue;\n diagnostics.push({\n code: 'DUPLICATE_LABEL',\n severity: 'warning',\n /*\n Cut by the function that cuts every other list in a sentence here.\n\n `join` names all of them, however many there are, and a header may declare as many\n channels as it likes under one label: a 200-channel montage all labelled `T8-P8` — which\n is exactly the kind of file this warning is for, since CHB-MIT ships two of them —\n produced a single 1,100-character line of positions with the sentence that mattered at\n the front of it. `listed` shows eight and counts the rest, which is what the rate\n warning, the leftover-file warning, the channel-position lists in `--channels` and the\n `EMPTY_LABEL` message one loop up all already do. This was the last `join` of a\n file-controlled list left in a diagnostic.\n\n Two positions render identically either way, so the ordinary duplicate reads as it\n always has.\n */\n message: `${indices.length} signals share the label \"${label}\" (positions ${listed(indices.map(String))}).`,\n hint: 'Their columns are suffixed with the signal number so they stay distinguishable.',\n });\n }\n\n const recordBytes = byteOffsetInRecord;\n if (recordBytes <= 0) {\n throw new EdfError(\n 'NO_SAMPLES',\n 'No signal in this file carries any samples (every channel declares 0 samples per record).',\n );\n }\n\n if (sawComma.value) {\n diagnostics.push({\n code: 'COMMA_DECIMAL',\n severity: 'warning',\n message: 'Some header numbers use a comma decimal separator, which the EDF spec does not allow.',\n hint: 'They were read as decimal points. Check the values in the channel table.',\n });\n }\n\n const dataBytes = fileSize - expectedHeaderBytes;\n if (dataBytes < 0) {\n throw new EdfError('FILE_TOO_SMALL', `File is smaller than its own header.`);\n }\n const recordCount = Math.floor(dataBytes / recordBytes);\n const trailingBytes = dataBytes - recordCount * recordBytes;\n\n if (recordCount === 0) {\n /*\n Which of the two, and with the numbers.\n\n \"The recording was probably interrupted before any data was written\" is right about an\n empty file and wrong about the other way to get here: a header declaring records larger\n than the data present. A 606 KB file holding 589 KB of samples — 60% of one record, more\n than half a million readings — was told no data was written, and the message carried no\n figures at all, so nothing in it could be checked against the file. The declared record\n size is the thing to look at, and it was the one thing not said.\n\n Still an error either way. A record is the unit the format is addressed in, and there is\n nothing smaller to convert.\n */\n const empty = dataBytes === 0;\n throw new EdfError(\n 'NO_DATA_RECORDS',\n empty\n ? 'The file contains a header and no data at all.'\n : `The file contains ${counted(dataBytes, 'byte')} of data, which is less than the ` +\n `${recordBytes} its header says one data record takes.`,\n empty\n ? 'The recording was probably interrupted before any data was written.'\n : 'Either the recording was cut short part way through its first record, or the ' +\n 'header describes records larger than the ones actually written. Check the ' +\n 'samples-per-record fields against the file size.',\n );\n }\n\n if (declaredRecordCount === -1) {\n diagnostics.push({\n code: 'RECORD_COUNT_UNKNOWN',\n severity: 'warning',\n message:\n `The header does not say how many data records the file has (-1), which the spec allows ` +\n `for recordings still in progress. Using the ${counted(recordCount, 'record')} the file actually contains.`,\n });\n } else if (declaredRecordCount !== recordCount) {\n diagnostics.push({\n code: 'RECORD_COUNT_MISMATCH',\n severity: 'warning',\n message:\n `The header declares ${declaredRecordCount} data records but the file contains ` +\n `${recordCount}. Converting the ${counted(recordCount, 'record')} that ${recordCount === 1 ? 'is' : 'are'} present.`,\n hint:\n declaredRecordCount > recordCount\n ? 'The recording looks truncated. It may have been cut short or copied incompletely.'\n : 'The file is longer than its header claims.',\n });\n }\n\n if (trailingBytes > 0) {\n diagnostics.push({\n code: 'TRAILING_BYTES',\n severity: 'warning',\n message: `${counted(trailingBytes, 'byte')} after the last complete data record ${trailingBytes === 1 ? 'was' : 'were'} ignored.`,\n });\n }\n\n const isEdfPlus = continuity !== null;\n if (continuity === 'EDF+D') {\n diagnostics.push({\n code: 'DISCONTINUOUS',\n severity: 'warning',\n message:\n `This is a discontinuous (${isBdf ? 'BDF+D' : 'EDF+D'}) recording: its data records are ` +\n `not contiguous in time.`,\n hint: 'Each row carries its true recording time, so gaps stay visible instead of being closed.',\n });\n }\n\n const dataSignals = signals.filter((s) => !s.isAnnotations);\n if (dataSignals.length === 0) {\n diagnostics.push({\n code: 'NO_SIGNAL_CHANNELS',\n severity: 'warning',\n message: 'This file has no signal channels; it contains only EDF+ annotations.',\n });\n }\n\n // A channel declaring zero samples per record has no sampling rate to speak of — it is\n // reported separately as NO_SAMPLES and no file is written for it. Counting its nominal\n // 0 Hz as a rate made a single-rate recording warn that it used \"2 different sampling\n // rates (4 Hz, 0 Hz)\" and claim it was splitting output it never split.\n const rates = new Set(dataSignals.filter((s) => s.samplesPerRecord > 0).map((s) => s.samplingRate));\n if (rates.size > 1) {\n diagnostics.push({\n code: 'MIXED_SAMPLING_RATES',\n severity: 'warning',\n message:\n `Channels use ${rates.size} different sampling rates ` +\n `(${listed(formatRates([...rates].sort((a, b) => b - a)).map((r) => `${r} Hz`))}).`,\n hint: 'They are written to one file per rate so no channel is resampled.',\n });\n }\n\n return {\n header: {\n version,\n patientId,\n recordingId,\n startDateRaw,\n startTimeRaw,\n startDateTime: resolveStartDateTime(startDateRaw, startTimeRaw, recordingId),\n headerBytes: expectedHeaderBytes,\n declaredHeaderBytes: headerBytes,\n reserved,\n isEdfPlus,\n isBdf,\n continuity,\n declaredRecordCount,\n recordDuration,\n signalCount,\n signals,\n bytesPerSample,\n recordBytes,\n },\n recordCount,\n trailingBytes,\n diagnostics,\n };\n}\n\n/**\n * The recording start as a zone-less wall clock, \"YYYY-MM-DDTHH:MM:SS\".\n *\n * EDF stores the start time as local wall-clock digits with no timezone anywhere in\n * the format. `startDateTime` is built with Date.UTC purely so those digits survive a\n * round trip unshifted, which makes it a carrier for the wall clock rather than a\n * real instant. Serialising it with `toISOString()` would append a Z and assert UTC,\n * and any reader converting to local time would then shift the recording by their own\n * offset: 13:43:04 in the file becomes 08:43:04 in New York. The Z is omitted because\n * the file genuinely does not say which zone it meant.\n */\nexport function formatWallClock(date: Date | null): string | null {\n if (!date) return null;\n return date.toISOString().slice(0, 19);\n}\n\n/**\n * The recording's format, as `--info`, `metadata.json` and `--json` all name it.\n *\n * `\"EDF\"`, `\"BDF\"`, or one of `\"EDF+ (continuous)\"`, `\"EDF+ (discontinuous)\"`,\n * `\"BDF+ (continuous)\"`, `\"BDF+ (discontinuous)\"`. A BDF+ file reports its own spelling even\n * though `continuity` normalises the marker to the `EDF+` form.\n *\n * This said `EDF+ (EDF+D)` and `BDF+ (EDF+C)`, which are not strings it can return — the\n * parenthetical is the word, not the marker. It is a one-line doc comment on a public export,\n * so it is what a TypeScript consumer's editor shows and what `dist/edf/header.d.ts` ships,\n * and the value it describes is `recording.format` in every metadata.json this tool writes.\n * A consumer branching on the tooltip's spelling never matches. Every documentation page had\n * it right; this was the only place that did not.\n */\nexport function describeFormat(header: EdfHeader): string {\n const base = header.isBdf ? 'BDF' : 'EDF';\n if (!header.isEdfPlus) return base;\n return `${base}+ (${header.continuity === 'EDF+D' ? 'discontinuous' : 'continuous'})`;\n}\n\n/** Render a sampling rate without trailing noise: 256, 0.5, 12.5. */\nexport function formatRate(hz: number): string {\n if (Number.isInteger(hz)) return String(hz);\n const rounded = Number(hz.toFixed(6));\n // A rate below 5e-7 rounds away to \"0\", which reads as \"this channel has no sampling\n // rate\" and made the mixed-rate warning contradict itself: it announced two different\n // rates and then printed both as \"0 Hz\". Exponent form keeps a real rate legible, and\n // keeps distinct rates distinct in the channel table and in output filenames.\n if (rounded === 0) return hz.toExponential(3);\n return String(rounded);\n}\n\n/**\n * Renders a group of rates so that rates which differ read as differing.\n *\n * `formatRate` rounds to six decimals, which is what keeps an ordinary rate free of\n * float noise — 30 samples in a 0.1-second record is 299.99999999999994 as a double,\n * and belongs on screen as 300. Two rates separated by less than that round to one\n * string, so a file carrying 1e-6 Hz and 1.25e-6 Hz warned that it used \"2 different\n * sampling rates (0.000001 Hz, 0.000001 Hz)\" and named both files the same thing.\n *\n * That is the contradiction the exponent fallback above already removes for rates that\n * round away to zero; this is the same one a step further out. On a collision every rate\n * in the group switches to its shortest exact form, which is unique for distinct values,\n * rather than only the pair that collided — one column in one notation reads better than\n * two.\n */\nexport function formatRates(rates: readonly number[]): string[] {\n const rounded = rates.map(formatRate);\n const distinct = new Set(rates).size;\n return new Set(rounded).size === distinct ? rounded : rates.map((hz) => String(hz));\n}\n"]}
1
+ {"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/edf/header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,iFAAiF;AACjF,sFAAsF;AACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AACnD,uDAAuD;AACvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AA2EvC;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,CAAC,iDAAiD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,kBAAkB,CAAC,SAAiB;IAC3C,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC,CAAW,CAAC;IAChD,OAAO,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,GAAe,EAAE,KAAa,EAAE,GAAW,EAAU,EAAE,CAClE,YAAY,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC;AAExC,8FAA8F;AAC9F,MAAM,SAAS,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAEzF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,MAAM,IAAI,GAAG,oBAAoB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAEzE,4FAA4F;AAC5F,SAAS,oBAAoB,CAAC,GAAW;IACvC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5B,8EAA8E;IAC9E,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9C,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC1D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,gBAAgB,CACvB,GAAW,EACX,KAAa,EACb,EAAE,OAAO,GAAG,KAAK,EAAE,QAAQ,KAA2D,EAAE;IAExF,MAAM,UAAU,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC;IAC7B,IAAI,UAAU,CAAC,QAAQ,IAAI,QAAQ;QAAE,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC;IAC3D,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,QAAQ,CAAC,kBAAkB,EAAE,iBAAiB,KAAK,aAAa,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,iBAAiB,KAAK,4BAA4B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAC1E,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,iBAAiB,KAAK,mCAAmC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAClF,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,+EAA+E;AAC/E,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,CAAC,GAAG,sCAAsC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,OAAO,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAEpG;;;;;;GAMG;AACH,SAAS,oBAAoB,CAC3B,WAAmB;IAEnB,MAAM,MAAM,GAAG,qDAAqD,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9F,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAE,MAAM,CAAC,CAAC,CAAY,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;IACtE,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,oBAAoB,CAC3B,OAAe,EACf,OAAe,EACf,WAAW,GAAG,EAAE;IAEhB,MAAM,CAAC,GAAG,sCAAsC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,MAAM,CAAC,GAAG,sCAAsC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1E,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE1B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAExB,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IAEzF,MAAM,MAAM,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,IAAI,GACR,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,IAAI,MAAM,CAAC,IAAI,GAAG,GAAG,KAAK,EAAE;QACrF,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,EAAE,IAAI,EAAE;YACR,CAAC,CAAC,IAAI,GAAG,EAAE;YACX,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;IAClB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5E,6CAA6C;IAC7C,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3E,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAe,EAAE,QAAgB;IAC3D,MAAM,WAAW,GAAiB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAElC,IAAI,GAAG,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;QACpC,MAAM,IAAI,QAAQ,CAChB,gBAAgB,EAChB,WAAW,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,wCAAwC,kBAAkB,GAAG,CAClG,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;IAC9D,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAE9D,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC7C,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,wBAAwB,EAAE;QAC/E,OAAO,EAAE,IAAI;QACb,QAAQ;KACT,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;IAC9C,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,wBAAwB,EAAE;QACvF,OAAO,EAAE,IAAI;QACb,QAAQ;KACT,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,2BAA2B,EAAE;QACrF,QAAQ;KACT,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,mBAAmB,EAAE;QAC1E,OAAO,EAAE,IAAI;QACb,QAAQ;KACT,CAAC,CAAC;IAEH,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,QAAQ,CAChB,sBAAsB,EACtB,mBAAmB,WAAW,gCAAgC,CAC/D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,cAAc,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAChB,yBAAyB,EACzB,6CAA6C,KAAK,CAAC,cAAc,CAAC,gCAAgC,CACnG,CAAC;IACJ,CAAC;IAED,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,WAAW,GAAG,mBAAmB,CAAC;IACnF,IAAI,GAAG,CAAC,MAAM,GAAG,mBAAmB,EAAE,CAAC;QACrC,MAAM,IAAI,QAAQ,CAChB,gBAAgB;QAChB;;;;;;;UAOE;QACF,iBAAiB,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,mBAAmB,mBAAmB,gBAAgB;YACnG,CAAC,QAAQ,GAAG,mBAAmB;gBAC7B,CAAC,CAAC,wBAAwB,QAAQ,SAAS;gBAC3C,CAAC,CAAC,YAAY,GAAG,CAAC,MAAM,yCAAyC,CAAC,CACvE,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,KAAK,mBAAmB,EAAE,CAAC;QACxC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,uBAAuB;YAC7B,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,qBAAqB,WAAW,eAAe,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,GAAG;gBAChF,GAAG,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,IAAI,mBAAmB,UAAU;gBAC9E,iDAAiD;SACpD,CAAC,CAAC;IACL,CAAC;IAED,+EAA+E;IAC/E,MAAM,IAAI,GAAG,kBAAkB,CAAC;IAChC,MAAM,SAAS,GAAG,CAAC,WAAmB,EAAE,KAAa,EAAE,CAAS,EAAU,EAAE,CAC1E,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC;IAEhE,kFAAkF;IAClF,oEAAoE;IACpE,MAAM,aAAa,GAAG,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7D,MAAM,UAAU,GACd,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;IAE/E,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,MAAM,cAAc,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC/C,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAG,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,MAAM,iBAAiB,GAAG,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,4BAA4B,CAAC,GAAG,EAAE;YAC3F,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,4BAA4B,CAAC,GAAG,EAAE;YAC3F,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,2BAA2B,CAAC,GAAG,EAAE;YACzF,OAAO,EAAE,IAAI;YACb,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,2BAA2B,CAAC,GAAG,EAAE;YACzF,OAAO,EAAE,IAAI;YACb,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACtD,MAAM,gBAAgB,GAAG,gBAAgB,CACvC,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,EACpB,8BAA8B,CAAC,GAAG,EAClC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAC5B,CAAC;QACF,MAAM,WAAW,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QAErD,IAAI,gBAAgB,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,QAAQ,CAChB,kBAAkB,EAClB,UAAU,CAAC,MAAM,KAAK,eAAe,gBAAgB,sBAAsB,CAC5E,CAAC;QACJ,CAAC;QAED,MAAM,aAAa,GAAG,KAAK,KAAK,iBAAiB,IAAI,KAAK,KAAK,qBAAqB,CAAC;QAErF,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,CAAC;YACR,KAAK;YACL,UAAU;YACV,iBAAiB;YACjB,WAAW;YACX,WAAW;YACX,UAAU;YACV,UAAU;YACV,YAAY;YACZ,gBAAgB;YAChB,QAAQ,EAAE,WAAW;YACrB,aAAa;YACb,YAAY,EAAE,gBAAgB,GAAG,cAAc;YAC/C,kBAAkB;SACnB,CAAC,CAAC;QACH,kBAAkB,IAAI,gBAAgB,GAAG,cAAc,CAAC;QAExD,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB;;;;;;;;;;;;;;cAcE;YACF;;;;;;;;;;cAUE;YACF;;;;;;;;;cASE;YACF,MAAM,MAAM,GAAG;gBACb,CAAC,OAAO,EAAE,KAAK,CAAC;gBAChB,CAAC,MAAM,EAAE,iBAAiB,CAAC;gBAC3B,CAAC,YAAY,EAAE,UAAU,CAAC;gBAC1B,CAAC,cAAc,EAAE,YAAY,CAAC;aACtB,CAAC;YACX,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACjF,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC;YACrF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;qBAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAO,CAAC,CAAC,WAAW,CAAC,CAAC,CAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;qBAC9E,IAAI,CAAC,IAAI,CAAC,CAAC;gBACd,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;gBAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;gBAC5D,mFAAmF;gBACnF,mEAAmE;gBACnE,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;gBAC7C,MAAM,KAAK,GACT,KAAK,CAAC,MAAM,KAAK,CAAC;oBAChB,CAAC,CAAE,KAAK,CAAC,CAAC,CAAY;oBACtB,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAW,EAAE,CAAC;gBAClF,kFAAkF;gBAClF,mFAAmF;gBACnF,kFAAkF;gBAClF,sFAAsF;gBACtF,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;gBAClF,MAAM,KAAK,GACT,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,kBAAkB,KAAK,CAAC,CAAC,CAAW,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;gBACpF,MAAM,KAAK,GACT,OAAO,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;oBACzB,CAAC,CAAC,mDAAmD,KAAK,EAAE;oBAC5D,CAAC,CAAC,OAAO;wBACP,CAAC,CAAC,0CAA0C;wBAC5C,CAAC,CAAC,wBAAwB,KAAK,EAAE,CAAC;gBACxC,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,oBAAoB;oBAC1B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EACL,UAAU,CAAC,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,GAAG;wBAC3E,GAAG,OAAO,CAAC,MAAM,qBAAqB,MAAM,KAAK,KAAK,MAAM,KAAK,kBAAkB;wBACnF,cAAc,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG;oBACvD,IAAI;oBACF;;;;;;;;;;;;;;;;;sBAiBE;oBACF;;;;;;;;;;;sBAWE;oBACF,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI;wBACvB,CAAC,CAAC,qDAAqD,CAAC,yBAAyB;4BAC/E,SACE,OAAO;gCACL,CAAC,CAAC,0BAA0B;gCAC5B,CAAC,CAAC,KAAK,KAAK,EAAE;oCACZ,CAAC,CAAC,iBAAiB;oCACnB,CAAC,CAAC,8CACR,IAAI;wBACN,CAAC,CAAC,gDAAgD,QAAQ,CAAC,KAAK,CAAW,qBAAqB,CAAC;wBACnG,2DAA2D;iBAC9D,CAAC,CAAC;YACL,CAAC;YAED;;;;;;;;;;;;;;;;;;;cAmBE;YACF,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;YACnE,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;gBAC9C,MAAM,KAAK,GACT,KAAK,CAAC,MAAM,KAAK,CAAC;oBAChB,CAAC,CAAE,KAAK,CAAC,CAAC,CAAY;oBACtB,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAW,EAAE,CAAC;gBAClF,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACtF,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,eAAe;oBACrB,QAAQ,EAAE,SAAS;oBACnB,OAAO,EACL,UAAU,CAAC,MAAM,KAAK,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,QAAQ;wBAC7E,GAAG,KAAK,sEAAsE;wBAC9E,8BAA8B;oBAChC,IAAI,EACF,4EAA4E;wBAC5E,uEAAuE;wBACvE,uDAAuD;iBAC1D,CAAC,CAAC;YACL,CAAC;YAED,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACjB,qFAAqF;gBACrF,sFAAsF;gBACtF,2DAA2D;gBAC3D,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,0EAA0E;gBAC1E,sEAAsE;gBACtE,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACnC,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;oBAClB,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,CAAC;YAED,IAAI,gBAAgB,KAAK,CAAC,EAAE,CAAC;gBAC3B,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,YAAY;oBAClB,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,UAAU,CAAC,MAAM,KAAK,mDAAmD;oBAClF,IAAI,EAAE,qEAAqE;iBAC5E,CAAC,CAAC;YACL,CAAC;YAED;;;;;;;;;;;;cAYE;YACF,MAAM,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC;YACvC,MAAM,WAAW,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YACzE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC;gBAC1C,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,yBAAyB;oBAC/B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EACL,UAAU,CAAC,MAAM,KAAK,qCAAqC,WAAW,MAAM;wBAC5E,GAAG,WAAW,uBAAuB,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,MAAM;wBAC1E,4CAA4C;oBAC9C,IAAI,EAAE,qFAAqF;iBAC5F,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;gBACrC,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,0BAA0B;oBAChC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EACL,UAAU,CAAC,MAAM,KAAK,kDAAkD;wBACxE,IAAI,UAAU,oCAAoC;oBACpD,IAAI,EAAE,qFAAqF;iBAC5F,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,WAAW,KAAK,WAAW,EAAE,CAAC;gBACvC,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,2BAA2B;oBACjC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EACL,UAAU,CAAC,MAAM,KAAK,oDAAoD;wBAC1E,IAAI,WAAW,gDAAgD;iBAClE,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvE;;;;;;;;;;kBAUE;gBACF,MAAM,QAAQ,GACZ,WAAW,GAAG,WAAW;oBACvB,CAAC,CAAC,oBAAoB,WAAW,2BAA2B,WAAW,EAAE;oBACzE,CAAC,CAAC,mBAAmB,UAAU,0BAA0B,UAAU,EAAE,CAAC;gBAC1E,WAAW,CAAC,IAAI,CAAC;oBACf,IAAI,EAAE,yBAAyB;oBAC/B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,UAAU,CAAC,MAAM,KAAK,eAAe,QAAQ,+BAA+B;oBACrF,IAAI,EAAE,+EAA+E;iBACtF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;MAkBE;IACF,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC;QAChD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,KAAK,KAAK,SAAS;gBACjB,CAAC,CAAC,UAAU,KAAK,4CAA4C,KAAK,IAAI;gBACtE,CAAC,CAAC,UAAU,KAAK,+CAA+C,KAAK,YAAY;oBAC/E,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,WAAW;oBACpF,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,mCAAmC;oBAC9E,uCAAuC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;MAYE;IACF;;;;;;;;;;;;;;;MAeE;IACF,IACE,eAAe,CAAC,YAAY,CAAC;QAC7B,oBAAoB,CAAC,YAAY,EAAE,YAAY,EAAE,WAAW,CAAC,KAAK,IAAI,EACtE,CAAC;QACD,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,mBAAmB;YACzB,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,6BAA6B,YAAY,4CAA4C;gBACrF,6BAA6B;YAC/B,IAAI,EACF,kFAAkF;gBAClF,sFAAsF;gBACtF,2BAA2B;SAC9B,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;MAcE;IACF,MAAM,UAAU,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,sCAAsC,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;IACxF,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,UAAU,CAAC,GAAG,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK,IAAI,UAAU,CAAC,IAAI,GAAG,GAAG,KAAK,EAAE,EAAE,CAAC;YACzF,WAAW,CAAC,IAAI,CAAC;gBACf,IAAI,EAAE,qBAAqB;gBAC3B,QAAQ,EAAE,SAAS;gBACnB,OAAO,EACL,6BAA6B,YAAY,gCAAgC;oBACzE,2BAA2B,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG;oBACrE,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,GAAG,CAAC,CAAW,IAAI,UAAU,CAAC,IAAI,mBAAmB;oBAC/E,oCAAoC;gBACtC,IAAI,EACF,mFAAmF;oBACnF,mFAAmF;oBACnF,gBAAgB;aACnB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,oBAAoB,CAAC,YAAY,EAAE,YAAY,EAAE,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3E,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,uBAAuB;YAC7B,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,sCAAsC,YAAY,QAAQ;gBAC1D,IAAI,YAAY,qDAAqD;gBACrE,mBAAmB;YACrB,IAAI,EACF,oFAAoF;gBACpF,oFAAoF;gBACpF,+BAA+B;SAClC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,UAAU,EAAE,CAAC;QAC1C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QACjC,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,iBAAiB;YACvB,QAAQ,EAAE,SAAS;YACnB;;;;;;;;;;;;;;cAcE;YACF;;;;;;;cAOE;YACF,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,6BAA6B,KAAK,gBAAgB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI;YACnH,IAAI,EAAE,iFAAiF;SACxF,CAAC,CAAC;IACL,CAAC;IAED,MAAM,WAAW,GAAG,kBAAkB,CAAC;IACvC,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,QAAQ,CAChB,YAAY,EACZ,2FAA2F,CAC5F,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,uFAAuF;YAChG,IAAI,EAAE,0EAA0E;SACjF,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,GAAG,mBAAmB,CAAC;IACjD,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,QAAQ,CAAC,gBAAgB,EAAE,sCAAsC,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,WAAW,CAAC,CAAC;IACxD,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;IAE5D,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACtB;;;;;;;;;;;;UAYE;QACF,MAAM,KAAK,GAAG,SAAS,KAAK,CAAC,CAAC;QAC9B,MAAM,IAAI,QAAQ,CAChB,iBAAiB,EACjB,KAAK;YACH,CAAC,CAAC,gDAAgD;YAClD,CAAC,CAAC,qBAAqB,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,mCAAmC;gBAClF,GAAG,WAAW,yCAAyC,EAC3D,KAAK;YACH,CAAC,CAAC,qEAAqE;YACvE,CAAC,CAAC,+EAA+E;gBAC/E,4EAA4E;gBAC5E,kDAAkD,CACvD,CAAC;IACJ,CAAC;IAED,IAAI,mBAAmB,KAAK,CAAC,CAAC,EAAE,CAAC;QAC/B,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,yFAAyF;gBACzF,+CAA+C,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,8BAA8B;SAC9G,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,mBAAmB,KAAK,WAAW,EAAE,CAAC;QAC/C,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,uBAAuB;YAC7B,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,uBAAuB,mBAAmB,sCAAsC;gBAChF,GAAG,WAAW,oBAAoB,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,SAAS,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW;YACtH,IAAI,EACF,mBAAmB,GAAG,WAAW;gBAC/B,CAAC,CAAC,mFAAmF;gBACrF,CAAC,CAAC,4CAA4C;SACnD,CAAC,CAAC;IACL,CAAC;IAED,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACtB,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,gBAAgB;YACtB,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,GAAG,OAAO,CAAC,aAAa,EAAE,MAAM,CAAC,wCAAwC,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,WAAW;SAClI,CAAC,CAAC;IACL,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,KAAK,IAAI,CAAC;IACtC,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,4BAA4B,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,oCAAoC;gBACzF,yBAAyB;YAC3B,IAAI,EAAE,yFAAyF;SAChG,CAAC,CAAC;IACL,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAC5D,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,oBAAoB;YAC1B,QAAQ,EAAE,SAAS;YACnB,OAAO,EAAE,sEAAsE;SAChF,CAAC,CAAC;IACL,CAAC;IAED,uFAAuF;IACvF,wFAAwF;IACxF,sFAAsF;IACtF,wEAAwE;IACxE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IACpG,IAAI,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACnB,WAAW,CAAC,IAAI,CAAC;YACf,IAAI,EAAE,sBAAsB;YAC5B,QAAQ,EAAE,SAAS;YACnB,OAAO,EACL,gBAAgB,KAAK,CAAC,IAAI,4BAA4B;gBACtD,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI;YACrF,IAAI,EAAE,mEAAmE;SAC1E,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,MAAM,EAAE;YACN,OAAO;YACP,SAAS;YACT,WAAW;YACX,YAAY;YACZ,YAAY;YACZ,aAAa,EAAE,oBAAoB,CAAC,YAAY,EAAE,YAAY,EAAE,WAAW,CAAC;YAC5E,WAAW,EAAE,mBAAmB;YAChC,mBAAmB,EAAE,WAAW;YAChC,QAAQ;YACR,SAAS;YACT,KAAK;YACL,UAAU;YACV,mBAAmB;YACnB,cAAc;YACd,WAAW;YACX,OAAO;YACP,cAAc;YACd,WAAW;SACZ;QACD,WAAW;QACX,aAAa;QACb,WAAW;KACZ,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB;IAC/C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,MAAiB;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,GAAG,IAAI,MAAM,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC;AACxF,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAAE,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,qFAAqF;IACrF,sFAAsF;IACtF,sFAAsF;IACtF,8EAA8E;IAC9E,IAAI,OAAO,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,WAAW,CAAC,KAAwB;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;IACrC,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AACtF,CAAC","sourcesContent":["/**\n * EDF / EDF+ header parsing.\n *\n * Layout (all fields are ASCII, left-justified, space-padded):\n *\n * fixed header, 256 bytes\n * 0 8 version ('0', or 255 + 'BIOSEMI' for BDF)\n * 8 80 patient identification\n * 88 80 recording identification\n * 168 8 start date dd.mm.yy\n * 176 8 start time hh.mm.ss\n * 184 8 number of bytes in the header record\n * 192 44 reserved ('EDF+C' / 'EDF+D' live here)\n * 236 8 number of data records (-1 if unknown)\n * 244 8 duration of a data record, in seconds (may be fractional)\n * 252 4 number of signals (ns)\n *\n * signal header, ns * 256 bytes, stored FIELD-major rather than signal-major:\n * all ns labels, then all ns transducer types, and so on.\n * ns * 16 label\n * ns * 80 transducer type\n * ns * 8 physical dimension\n * ns * 8 physical minimum\n * ns * 8 physical maximum\n * ns * 8 digital minimum\n * ns * 8 digital maximum\n * ns * 80 prefiltering\n * ns * 8 number of samples in each data record\n * ns * 32 reserved\n */\n\nimport { EdfError } from './errors.js';\nimport type { Diagnostic } from './errors.js';\nimport { counted, listed } from '../format/list.js';\nimport { plain } from '../format/number.js';\n// The shell-quoting rule for a label, shared rather than repeated: this hint and\n// `--channels`' own \"did you mean\" have to print the same command for the same label.\nimport { typeable } from '../convert/channels.js';\nimport { decodeLatin1 } from './bytes.js';\n\n/** Label the EDF+ spec reserves for the annotations channel. */\nexport const ANNOTATIONS_LABEL = 'EDF Annotations';\n/** BDF+ uses its own spelling for the same channel. */\nexport const BDF_ANNOTATIONS_LABEL = 'BDF Annotations';\n\nexport const FIXED_HEADER_BYTES = 256;\nexport const SIGNAL_HEADER_BYTES = 256;\n\nexport interface EdfSignal {\n /** Position in the file, 0-based. Stable even when labels collide. */\n index: number;\n label: string;\n transducer: string;\n physicalDimension: string;\n physicalMin: number;\n physicalMax: number;\n digitalMin: number;\n digitalMax: number;\n prefiltering: string;\n samplesPerRecord: number;\n reserved: string;\n /** True for the EDF+ 'EDF Annotations' channel, which carries text, not signal. */\n isAnnotations: boolean;\n /** samplesPerRecord / recordDuration, in Hz. */\n samplingRate: number;\n /** Byte offset of this signal's samples within one data record. */\n byteOffsetInRecord: number;\n}\n\nexport interface EdfHeader {\n version: string;\n patientId: string;\n recordingId: string;\n /** Raw 'dd.mm.yy' as written in the file. */\n startDateRaw: string;\n /** Raw 'hh.mm.ss' as written in the file. */\n startTimeRaw: string;\n /** Resolved start instant, or null when the file's date/time fields are unusable. */\n startDateTime: Date | null;\n /**\n * Header size in bytes, computed from the signal count rather than read from the field.\n *\n * Every data record offset is derived from this, so it has to be the one the layout\n * actually uses: 256 for the fixed header plus 256 per signal. A writer that fills the\n * field in carelessly is common enough to have its own warning, HEADER_BYTES_MISMATCH,\n * and trusting the field over the arithmetic would put every sample at the wrong offset.\n */\n headerBytes: number;\n /**\n * What the header's own length field says, which need not be the above.\n *\n * Exposed for the same reason `declaredRecordCount` is: the two disagreeing is a fact\n * about the file, and a caller checking how a recording was written should be able to see\n * what it claimed rather than only what was believed.\n */\n declaredHeaderBytes: number;\n reserved: string;\n isEdfPlus: boolean;\n /** True for BioSemi BDF/BDF+ files, whose samples are 3 bytes rather than 2. */\n isBdf: boolean;\n /** 'EDF+C' continuous, 'EDF+D' discontinuous, or null for plain EDF. */\n continuity: 'EDF+C' | 'EDF+D' | null;\n /** As declared in the header. -1 means \"unknown\", which the spec permits. */\n declaredRecordCount: number;\n recordDuration: number;\n signalCount: number;\n signals: EdfSignal[];\n bytesPerSample: number;\n recordBytes: number;\n}\n\n/** Everything derived by combining the header with the file's real size. */\nexport interface EdfHeaderInfo {\n header: EdfHeader;\n /** Record count implied by the actual file size — the one we trust for reading. */\n recordCount: number;\n /** Bytes after the last complete data record. */\n trailingBytes: number;\n diagnostics: Diagnostic[];\n}\n\n/**\n * A byte the terminal treats as an instruction rather than as text.\n *\n * C0 and C1, plus DEL. Tab is included deliberately: it is harmless to a terminal but it\n * makes a CSV column name that cannot be typed or matched reliably, which is the other half\n * of what this warning is for.\n */\n/**\n * Whether a spreadsheet reads this field as the start of a formula rather than as text.\n *\n * `=` and `@` unconditionally; every list of these characters names two more, and this had\n * `-` as an exception with the reason written out on the warnings page: \"a lone `-` is a\n * real convention for no unit ... and neither is evaluated unless what follows it parses as a\n * formula\". Which is the condition, and it was not being applied — nothing with a leading\n * minus was flagged at all. A channel labelled `-2+3` opens as a column headed `1`, and\n * `-HYPERLINK(\"http://...\",\"EEG\")` is a name the spreadsheet resolves, in silence.\n *\n * So the exception is what it says it is rather than the whole character. A lone `-` is left\n * as text by every spreadsheet and is not flagged; a field that is entirely a number reads as\n * that number, which is what the header says, and is not flagged either. Anything else after\n * the minus is arithmetic or a name.\n *\n * And `+` takes the same exception, which it did not. It is the same rule in the spreadsheet —\n * Lotus compatibility, which converts a leading `+` or `-` to a formula when what follows one\n * parses as a formula and leaves it as text when it does not — so the two signs cannot differ\n * here for a reason that comes from the sign. A channel labelled `+100` was warned about as\n * something a spreadsheet \"reads as the start of a formula rather than as text\", over a cell\n * that opens as 100, which is what the header says; `-100` beside it said nothing, and under\n * `--strict` the difference was an exit code. `+1+1` is still arithmetic and still flagged.\n */\nfunction startsFormula(text: string): boolean {\n if (/^[=@]/u.test(text)) return true;\n if (!/^[+-]./u.test(text)) return false;\n return !/^[+-](?:\\d+(?:\\.\\d+)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/u.test(text);\n}\n\nfunction isControlCharacter(character: string): boolean {\n const code = character.codePointAt(0) as number;\n return code <= 0x1f || (code >= 0x7f && code <= 0x9f);\n}\n\nconst dec = (buf: Uint8Array, start: number, len: number): string =>\n decodeLatin1(buf, start, start + len);\n\n/** EDF fields are space-padded; trailing NULs also occur in files written by sloppy tools. */\nconst trimField = (s: string): string => s.replace(/[\\0\\s]+$/u, '').replace(/^\\s+/u, '');\n\n/**\n * How many signals the fixed header says there are, read exactly as `parseHeader` will.\n *\n * `EdfFile.open` needs this before it can know how much header to read, and it used to work\n * it out with its own `Number(...)` — which was NUL-tolerant but not comma-tolerant, unlike\n * every other numeric field here. A header written with a comma decimal separator, which\n * COMMA_DECIMAL exists to accept and which the documentation lists this field among, was\n * therefore never given its signal headers at all, and the file died on a message that\n * contradicted itself: \"needs a 768-byte header, but the file is only 848 bytes\".\n *\n * Sharing the parse is what keeps the two from disagreeing again about which files are\n * readable. Null means \"not a usable count\", and the caller reads no further header — the\n * real error then comes from `parseHeader`, which is the one place that decides.\n */\nexport function peekSignalCount(fixed: Uint8Array): number | null {\n const text = normaliseNumberField(dec(fixed, 252, 4)).text;\n if (!DECIMAL_FIELD.test(text)) return null;\n const count = Number(text);\n return Number.isInteger(count) && count > 0 ? count : null;\n}\n\n/**\n * What EDF allows a numeric field to look like, which is less than `Number()` allows.\n *\n * A sign, digits, an optional fractional part, an optional exponent — the spec's own grammar,\n * plus the exponent form the 8-character physical bounds need to reach a magnetometer's range\n * and which real headers use.\n *\n * `Number()` accepts a great deal more, and every one of those forms is a header this tool\n * would have read as a number nobody wrote. A physical maximum of `0x64` came out as 100: it\n * printed as `-100 to 100` in the channel table, went into channels.csv as `physical_max,100`,\n * and set the gain every sample on that channel was scaled by — a whole calibration invented\n * from four bytes that are not a decimal number, exit 0, no diagnostic. `0b1100100` and `0o144`\n * are the same hundred, and `0x02` in the signal-count field is a two-channel recording.\n *\n * The same mistake as `#0x2` reaching channel 2 through `--channels`, `--decimals 0o5` writing\n * five places and `--jobs 0x10` running sixteen, all of which have their own comments and their\n * own fixes. Those were values somebody typed. These are the fields every number in the output\n * is computed from, and the page describing them says \"all fields are ASCII\" and gives the\n * layout digit by digit.\n */\nconst DECIMAL_FIELD = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/u;\n\n/** A numeric header field, trimmed and with a comma decimal separator turned into a dot. */\nfunction normaliseNumberField(raw: string): { text: string; sawComma: boolean } {\n const text = trimField(raw);\n // Some writers emit a comma decimal separator despite the spec requiring '.'.\n if (text.includes(',') && !text.includes('.')) {\n return { text: text.replace(',', '.'), sawComma: true };\n }\n return { text, sawComma: false };\n}\n\nfunction parseNumberField(\n raw: string,\n field: string,\n { integer = false, sawComma }: { integer?: boolean; sawComma?: { value: boolean } } = {},\n): number {\n const normalised = normaliseNumberField(raw);\n const text = normalised.text;\n if (normalised.sawComma && sawComma) sawComma.value = true;\n if (text === '') {\n throw new EdfError('BAD_HEADER_FIELD', `Header field \"${field}\" is empty.`);\n }\n const n = DECIMAL_FIELD.test(text) ? Number(text) : NaN;\n if (!Number.isFinite(n)) {\n throw new EdfError(\n 'BAD_HEADER_FIELD',\n `Header field \"${field}\" is not a number (found ${JSON.stringify(text)}).`,\n 'The file may be truncated, byte-shifted, or not an EDF file at all.',\n );\n }\n if (integer && !Number.isInteger(n)) {\n throw new EdfError(\n 'BAD_HEADER_FIELD',\n `Header field \"${field}\" must be a whole number (found ${JSON.stringify(text)}).`,\n );\n }\n return n;\n}\n\n/** Whether the start time names the sixtieth second. See LEAP_SECOND_START. */\nfunction namesLeapSecond(timeRaw: string): boolean {\n const t = /^(\\d{2})[.:\\-](\\d{2})[.:\\-](\\d{2})$/u.exec(trimField(timeRaw));\n return t !== null && Number(t[3]) === 60;\n}\n\nconst MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];\n\n/**\n * The date an EDF+ recording ID states, which is the only place the file writes a full year.\n *\n * EDF+ requires the recording identification field to begin `Startdate dd-MMM-yyyy`, and\n * requires it to agree with the header's own date field. It is a four-digit year, so it says\n * something the eight-character date field physically cannot.\n */\nfunction recordingIdStartdate(\n recordingId: string,\n): { day: number; month: number; year: number } | null {\n const stated = /^Startdate\\s+(\\d{2})-([A-Za-z]{3})-(\\d{4})(?:\\s|$)/u.exec(recordingId.trim());\n if (!stated) return null;\n const month = MONTHS.indexOf((stated[2] as string).toUpperCase()) + 1;\n if (month === 0) return null;\n return { day: Number(stated[1]), month, year: Number(stated[3]) };\n}\n\n/**\n * EDF stores a two-digit year. The spec pins the century: 85-99 mean 1985-1999\n * and 00-84 mean 2000-2084. Files outside 1985-2084 cannot express their date.\n *\n * Which is why EDF+ writes it again in full, in the recording identification field, and why\n * that is used here when it is there. The rule alone reports a recording made in 1984 as 2084\n * and one made in 2085 as 1985 — a hundred years out, on a file that states the year plainly\n * four fields earlier. Taken only where the two agree about everything the header can express:\n * the same day, the same month, and a four-digit year ending in the two digits the header\n * wrote. A recording ID that contradicts the header is a different problem and is left to the\n * spec's rule, which is at least the one the format defines.\n */\nfunction resolveStartDateTime(\n dateRaw: string,\n timeRaw: string,\n recordingId = '',\n): Date | null {\n const d = /^(\\d{2})[.\\-/](\\d{2})[.\\-/](\\d{2})$/u.exec(trimField(dateRaw));\n const t = /^(\\d{2})[.:\\-](\\d{2})[.:\\-](\\d{2})$/u.exec(trimField(timeRaw));\n if (!d || !t) return null;\n\n const dd = Number(d[1]);\n const mm = Number(d[2]);\n const yy = Number(d[3]);\n const hh = Number(t[1]);\n const mi = Number(t[2]);\n const ss = Number(t[3]);\n\n if (mm < 1 || mm > 12 || dd < 1 || dd > 31 || hh > 23 || mi > 59 || ss > 60) return null;\n\n const stated = recordingIdStartdate(recordingId);\n const year =\n stated !== null && stated.day === dd && stated.month === mm && stated.year % 100 === yy\n ? stated.year\n : yy >= 85\n ? 1900 + yy\n : 2000 + yy;\n const date = new Date(Date.UTC(year, mm - 1, dd, hh, mi, Math.min(ss, 59)));\n // Reject dates that rolled over, e.g. 31.02.\n if (date.getUTCMonth() !== mm - 1 || date.getUTCDate() !== dd) return null;\n return date;\n}\n\n/**\n * Parse the fixed 256-byte header plus the per-signal header block.\n *\n * @param buf At least FIXED_HEADER_BYTES + ns * SIGNAL_HEADER_BYTES bytes.\n * @param fileSize Total size of the file on disk, used to derive the real record count.\n */\nexport function parseHeader(buf: Uint8Array, fileSize: number): EdfHeaderInfo {\n const diagnostics: Diagnostic[] = [];\n const sawComma = { value: false };\n\n if (buf.length < FIXED_HEADER_BYTES) {\n throw new EdfError(\n 'FILE_TOO_SMALL',\n `File is ${counted(fileSize, 'byte')}; an EDF header alone needs at least ${FIXED_HEADER_BYTES}.`,\n );\n }\n\n // BDF (BioSemi) marks itself with byte 255 followed by 'BIOSEMI', and stores\n // 3-byte samples instead of 2. Everything else about the layout is identical.\n const isBdf = buf[0] === 0xff && dec(buf, 1, 7) === 'BIOSEMI';\n const version = isBdf ? 'BIOSEMI' : trimField(dec(buf, 0, 8));\n\n const patientId = trimField(dec(buf, 8, 80));\n const recordingId = trimField(dec(buf, 88, 80));\n const startDateRaw = trimField(dec(buf, 168, 8));\n const startTimeRaw = trimField(dec(buf, 176, 8));\n const headerBytes = parseNumberField(dec(buf, 184, 8), 'number of header bytes', {\n integer: true,\n sawComma,\n });\n const reserved = trimField(dec(buf, 192, 44));\n const declaredRecordCount = parseNumberField(dec(buf, 236, 8), 'number of data records', {\n integer: true,\n sawComma,\n });\n const recordDuration = parseNumberField(dec(buf, 244, 8), 'duration of a data record', {\n sawComma,\n });\n const signalCount = parseNumberField(dec(buf, 252, 4), 'number of signals', {\n integer: true,\n sawComma,\n });\n\n if (signalCount <= 0) {\n throw new EdfError(\n 'INVALID_SIGNAL_COUNT',\n `Header declares ${signalCount} signals; expected at least 1.`,\n );\n }\n if (!(recordDuration > 0)) {\n throw new EdfError(\n 'INVALID_RECORD_DURATION',\n `Header declares a data record duration of ${plain(recordDuration)}s; expected a positive number.`,\n );\n }\n\n const expectedHeaderBytes = FIXED_HEADER_BYTES + signalCount * SIGNAL_HEADER_BYTES;\n if (buf.length < expectedHeaderBytes) {\n throw new EdfError(\n 'FILE_TOO_SMALL',\n /*\n Which of the two is actually short.\n\n The file size was quoted either way, so a caller that had read too little — the\n signal count parsed one way here and another way there — produced arithmetic that\n refuted itself: \"needs a 768-byte header, but the file is only 848 bytes\". A reader\n following that looks for a truncation that is not there.\n */\n `File declares ${counted(signalCount, 'signal')}, which needs a ${expectedHeaderBytes}-byte header, ` +\n (fileSize < expectedHeaderBytes\n ? `but the file is only ${fileSize} bytes.`\n : `but only ${buf.length} bytes of it were handed to the parser.`),\n );\n }\n if (headerBytes !== expectedHeaderBytes) {\n diagnostics.push({\n code: 'HEADER_BYTES_MISMATCH',\n severity: 'warning',\n message:\n `Header says it is ${headerBytes} bytes, but ${counted(signalCount, 'signal')} ` +\n `${signalCount === 1 ? 'requires' : 'require'} ${expectedHeaderBytes} bytes. ` +\n `Using the value computed from the signal count.`,\n });\n }\n\n // Signal headers are field-major: all labels, then all transducers, and so on.\n const base = FIXED_HEADER_BYTES;\n const readField = (offsetUnits: number, width: number, i: number): string =>\n dec(buf, base + offsetUnits * signalCount + i * width, width);\n\n // EDF+ writes 'EDF+C'/'EDF+D' here; BDF+ writes 'BDF+C'/'BDF+D'. The two mean the\n // same thing, so both are normalised to a single continuity marker.\n const continuityTag = /^(?:EDF|BDF)\\+([CD])/u.exec(reserved);\n const continuity: 'EDF+C' | 'EDF+D' | null =\n continuityTag === null ? null : continuityTag[1] === 'D' ? 'EDF+D' : 'EDF+C';\n\n const signals: EdfSignal[] = [];\n let byteOffsetInRecord = 0;\n const bytesPerSample = isBdf ? 3 : 2;\n const seenLabels = new Map<string, number[]>();\n const emptyLabels: number[] = [];\n\n for (let i = 0; i < signalCount; i++) {\n const label = trimField(readField(0, 16, i));\n const transducer = trimField(readField(16, 80, i));\n const physicalDimension = trimField(readField(96, 8, i));\n const physicalMin = parseNumberField(readField(104, 8, i), `physical minimum (signal ${i})`, {\n sawComma,\n });\n const physicalMax = parseNumberField(readField(112, 8, i), `physical maximum (signal ${i})`, {\n sawComma,\n });\n const digitalMin = parseNumberField(readField(120, 8, i), `digital minimum (signal ${i})`, {\n integer: true,\n sawComma,\n });\n const digitalMax = parseNumberField(readField(128, 8, i), `digital maximum (signal ${i})`, {\n integer: true,\n sawComma,\n });\n const prefiltering = trimField(readField(136, 80, i));\n const samplesPerRecord = parseNumberField(\n readField(216, 8, i),\n `samples per record (signal ${i})`,\n { integer: true, sawComma },\n );\n const sigReserved = trimField(readField(224, 32, i));\n\n if (samplesPerRecord < 0) {\n throw new EdfError(\n 'BAD_HEADER_FIELD',\n `Signal ${i} (\"${label}\") declares ${samplesPerRecord} samples per record.`,\n );\n }\n\n const isAnnotations = label === ANNOTATIONS_LABEL || label === BDF_ANNOTATIONS_LABEL;\n\n signals.push({\n index: i,\n label,\n transducer,\n physicalDimension,\n physicalMin,\n physicalMax,\n digitalMin,\n digitalMax,\n prefiltering,\n samplesPerRecord,\n reserved: sigReserved,\n isAnnotations,\n samplingRate: samplesPerRecord / recordDuration,\n byteOffsetInRecord,\n });\n byteOffsetInRecord += samplesPerRecord * bytesPerSample;\n\n if (!isAnnotations) {\n /*\n A label is free text out of the file, and it becomes a column name in signals.csv.\n\n `--info` has escaped control bytes since it was written, because an ANSI escape in a\n header can drive the reader's terminal — `\\x1b[2J` clears the screen. The CSV had no\n such protection and needed none for correctness: quoting makes any byte safe for a\n parser, and this still passes the label through exactly as the file gives it, because\n losing what the header says is not an improvement.\n\n What was missing is the sentence saying so. A recording whose channel is labelled\n `\\x1b[2Jgone` converted with no warning at all, and `cat signals.csv` then cleared\n the terminal — while a script referencing that column by name carried an invisible\n control character in it. NONPRINTABLE_LABEL has been declared and documented as\n reserved since 0.1; this is it doing its job.\n */\n /*\n Which of the two fields carries them, because the consequences are not the same.\n\n The message said \"label or unit\", and then said the bytes \"will appear in the CSV\n column name\" and that \"the name cannot be typed\" — both of which are about the label.\n A channel labelled plainly `ECG` in a unit of `u\\x07V` got all of it: its column is\n `ECG`, `--channels ECG` selects it and exits 0, and the byte is in channels.csv's\n `unit` cell, which the warning never mentioned. Three sentences, none of them true of\n the file that raised it, on a warning whose whole purpose is to say where an invisible\n byte went.\n */\n /*\n All four free-text fields, not the two that were checked.\n\n `transducer` and `prefiltering` are free text out of the header exactly as the label\n and the unit are, and they land in channels.csv exactly as the unit does — so an ESC\n byte in a transducer field reached the CSV raw with nothing said, and `cat\n channels.csv` would drive the terminal. That is the hazard this warning exists for,\n two columns over. 0.5.71 made it name which field carries them; this is the rest of\n the fields it can name.\n */\n const fields = [\n ['label', label],\n ['unit', physicalDimension],\n ['transducer', transducer],\n ['prefiltering', prefiltering],\n ] as const;\n const affected = fields.filter(([, text]) => [...text].some(isControlCharacter));\n const control = affected.flatMap(([, text]) => [...text].filter(isControlCharacter));\n if (control.length > 0) {\n const shown = [...new Set(control)]\n .map((c) => `\\\\x${(c.codePointAt(0) as number).toString(16).padStart(2, '0')}`)\n .join(', ');\n const plural = control.length === 1 ? '' : 's';\n const inLabel = affected.some(([name]) => name === 'label');\n // \"label and unit\", not \"label, unit\" — `listed` is for long enumerations that get\n // truncated, and this is a sentence with at most four items in it.\n const names = affected.map(([name]) => name);\n const named =\n names.length === 1\n ? (names[0] as string)\n : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1] as string}`;\n // Where they land, which is the question the reader has. A label becomes a column\n // name in signals.csv; the other three are cells of channels.csv and nothing else.\n // Named down to the cell when there is one of them, because that is the answer to\n // \"where did it go\" — `channels.csv` alone leaves a reader scanning fourteen columns.\n const cells = affected.filter(([name]) => name !== 'label').map(([name]) => name);\n const where =\n cells.length === 1 ? `channels.csv's ${cells[0] as string} cell` : 'channels.csv';\n const lands =\n inLabel && cells.length > 0\n ? `which will appear in the CSV column name and in ${where}`\n : inLabel\n ? 'which will appear in the CSV column name'\n : `which will appear in ${where}`;\n diagnostics.push({\n code: 'NONPRINTABLE_LABEL',\n severity: 'warning',\n message:\n `Signal ${i}'s ${named} ${affected.length === 1 ? 'contains' : 'contain'} ` +\n `${control.length} control character${plural} (${shown}), ${lands} exactly as the ` +\n `header has ${control.length === 1 ? 'it' : 'them'}.`,\n hint:\n /*\n Every branch has to print a command that works.\n\n The middle one quoted the label back, which is right until the label is empty:\n an unlabelled channel got `--channels \"\"`, and that exits 2 with \"--channels was\n given but lists no channel names\". A hint whose command fails is worse than no\n hint, and this warning's whole job is to say how to reach a channel whose header\n text you cannot type. `EMPTY_LABEL` already says the position is the only way in\n for such a channel; so does this now.\n\n A comma is the third way. `--channels` separates names with one, and splits on\n every occurrence, so a channel labelled `EEG Fpz-Cz, ref` cannot be selected by\n name at all: the quoted-back advice printed `--channels \"EEG Fpz-Cz, ref\"`, which\n exits 2 with `No channel named \"EEG Fpz-Cz\"` — a channel the file does not have,\n named after half of one it does. Commas in labels are ordinary, since EDF labels\n are free text, and the CSV header quotes them; only this one hint claimed\n something about them that isn't so.\n */\n /*\n And a fourth way, which this branch printed straight past.\n\n A shell expands `$`, a backtick and a backslash inside double quotes, so a\n channel labelled `EEG $ref` was answered with `--channels \"EEG $ref\"` — which\n arrives as `EEG ` and exits 2 on a channel the file does not have. A backtick\n opens a command substitution and the pasted line does not even finish. That is\n the failure 0.7.18 fixed for `--channels`' own suggestion, and `typeable` is the\n rule it fixed it with: double quotes where they survive, single quotes where\n they do not, null where nothing does. Asking it settles which branch this takes\n as well, so the two cannot disagree about one label again.\n */\n (typeable(label) === null\n ? `Address the channel by position with --channels \"#${i}\" rather than by name, ` +\n `since ${\n inLabel\n ? 'the name cannot be typed'\n : label === ''\n ? 'it has no label'\n : 'a comma in the label would read as two names'\n }. `\n : `The column name is unaffected, so --channels ${typeable(label) as string} still selects it. `) +\n 'Printing the CSV to a terminal may do more than print it.',\n });\n }\n\n /*\n Header text a spreadsheet will run rather than read.\n\n The same four free-text fields, asked a different question. `=`, `+` and `@` start a\n formula in Excel, LibreOffice and Sheets no matter which file the cell came from, and\n these fields land in a CSV header row and in channels.csv verbatim — so a channel\n labelled `=1+1` opens as a column headed 2, and `=HYPERLINK(...)` opens as a link\n nobody in the reading chain wrote. SECURITY.md already calls these fields\n attacker-controlled because they reach filenames; this is where they reach a program\n that executes text.\n\n Not `-`, which the same advice usually includes. A lone `-` is a real convention for\n \"no unit\" and appears in the fixtures, a leading `-` on a montage label is ordinary,\n and neither is executed unless what follows parses as a formula — so warning on it\n would fire on files that are fine, which is how a warning gets ignored.\n\n Said, not fixed. Prefixing the cell with a quote is the usual mitigation and would mean\n writing something the header does not say, which is the one thing this tool refuses to\n do; NONPRINTABLE_LABEL answers control bytes the same way.\n */\n const formulaic = fields.filter(([, text]) => startsFormula(text));\n if (formulaic.length > 0) {\n const names = formulaic.map(([name]) => name);\n const named =\n names.length === 1\n ? (names[0] as string)\n : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1] as string}`;\n const shown = [...new Set(formulaic.map(([, text]) => text[0] as string))].join(', ');\n diagnostics.push({\n code: 'FORMULA_LABEL',\n severity: 'warning',\n message:\n `Signal ${i}'s ${named} ${formulaic.length === 1 ? 'starts' : 'start'} with ` +\n `${shown}, which Excel, LibreOffice and Google Sheets read as the start of a ` +\n `formula rather than as text.`,\n hint:\n 'The text is written exactly as the header has it, so the cell is what the ' +\n 'recording says. Open the CSV with pandas or R, or import it into the ' +\n 'spreadsheet as text, if you do not want it evaluated.',\n });\n }\n\n if (label === '') {\n // Collected, not reported here: what this channel's column ends up called depends on\n // whether some later channel is literally labelled `signal_<i>`, and inside this loop\n // the later channels do not exist yet. See the pass below.\n emptyLabels.push(i);\n } else {\n // Collected rather than reported here: a label repeated five times should\n // produce one warning naming all five, not four near-identical pairs.\n const seen = seenLabels.get(label);\n if (seen) seen.push(i);\n else seenLabels.set(label, [i]);\n }\n\n if (samplesPerRecord === 0) {\n diagnostics.push({\n code: 'NO_SAMPLES',\n severity: 'warning',\n message: `Signal ${i} (\"${label}\") carries no samples at all (0 per data record).`,\n hint: 'It is described in channels.csv but left out of the converted data.',\n });\n }\n\n /*\n Too large to represent, and too small — the second was silent.\n\n The gain is the span divided by the digital range, and a span of 2e-320 over 65,535\n codes is 3e-325: below the smallest subnormal double, so it underflows to +0. The\n scaler's flat-range branch then handed every code the same physical value, and a\n channel of 65,536 distinct readings became one repeated number with nothing raised at\n all. One power of ten away, at 1e-319, the same file raises VALUE_RESOLUTION.\n\n Both are the same fact about the header — the span cannot be turned into a mapping —\n so both get this code, and both leave the cells empty rather than filling them with a\n value the header cannot justify.\n */\n const span = physicalMax - physicalMin;\n const underflowed = span !== 0 && span / (digitalMax - digitalMin) === 0;\n if (!Number.isFinite(span) || underflowed) {\n diagnostics.push({\n code: 'UNUSABLE_PHYSICAL_RANGE',\n severity: 'warning',\n message:\n `Signal ${i} (\"${label}\") declares a physical range from ${physicalMin} to ` +\n `${physicalMax}, whose span is too ${underflowed ? 'small' : 'large'} to ` +\n `represent, so its values cannot be scaled.`,\n hint: 'Its cells are left empty rather than filled with a value the header cannot justify.',\n });\n } else if (digitalMax === digitalMin) {\n diagnostics.push({\n code: 'DEGENERATE_DIGITAL_RANGE',\n severity: 'warning',\n message:\n `Signal ${i} (\"${label}\") has digital minimum equal to digital maximum ` +\n `(${digitalMin}), so its values cannot be scaled.`,\n hint: 'Its cells are left empty rather than filled with a value the header cannot justify.',\n });\n } else if (physicalMax === physicalMin) {\n diagnostics.push({\n code: 'DEGENERATE_PHYSICAL_RANGE',\n severity: 'warning',\n message:\n `Signal ${i} (\"${label}\") has physical minimum equal to physical maximum ` +\n `(${physicalMin}), so every sample converts to the same value.`,\n });\n } else if ((physicalMax - physicalMin) * (digitalMax - digitalMin) < 0) {\n /*\n Polarity is inverted when the gain is negative, and the gain is\n (physicalMax - physicalMin) / (digitalMax - digitalMin) — so it is the sign of the\n two spans together that matters, not the physical pair alone.\n\n Testing only `physicalMax < physicalMin` was wrong in both directions. A file with\n its DIGITAL bounds reversed is just as inverted and drew no warning at all, handing\n back sign-flipped EEG with nothing to indicate it. A file with BOTH pairs reversed\n has a positive gain and is not inverted, yet was warned about — a message that was\n simply untrue of that recording.\n */\n const reversed =\n physicalMax < physicalMin\n ? `physical minimum ${physicalMin} above physical maximum ${physicalMax}`\n : `digital minimum ${digitalMin} above digital maximum ${digitalMax}`;\n diagnostics.push({\n code: 'INVERTED_PHYSICAL_RANGE',\n severity: 'warning',\n message: `Signal ${i} (\"${label}\") declares ${reversed}, which inverts its polarity.`,\n hint: 'The values are converted exactly as the header specifies, inversion included.',\n });\n }\n }\n }\n\n /*\n What an unlabelled channel is actually called, which the message used to guess.\n\n A channel with no label takes `signal_<index>` — unless another channel is literally\n labelled that, which EDF permits, since labels are free text and nothing enforces anything\n about them. Then both collide and both are suffixed. The warning said \"It will appear as\n \"signal_0\"\" while the file's header read `time_s,signal_0_ch0,signal_0_ch1`: the one\n sentence the run printed named a column that exists in neither signals.csv nor\n channels.csv.\n\n The other half was silent. The channel that genuinely carries the label `signal_0` lost\n its own column name to a collision with a synthesised one, and nothing said so —\n DUPLICATE_LABEL did not fire, because the two labels are not the same label. Both halves\n are one sentence here, because they are one event.\n\n No specific suffixed name is quoted. The suffix rule has a second pass for names that are\n still shared afterwards, and a message that hard-coded `_ch<index>` would be guessing again\n in exactly the way this is fixing.\n */\n for (const index of emptyLabels) {\n const taken = seenLabels.get(`signal_${index}`);\n diagnostics.push({\n code: 'EMPTY_LABEL',\n severity: 'warning',\n message:\n taken === undefined\n ? `Signal ${index} has no label. It will appear as \"signal_${index}\".`\n : `Signal ${index} has no label, so it takes the name \"signal_${index}\" — which ` +\n `${taken.length === 1 ? 'signal' : 'signals'} ${listed(taken.map(String))} already ` +\n `${taken.length === 1 ? 'carries' : 'carry'} as a label, so both columns are ` +\n `suffixed with their position instead.`,\n });\n }\n\n /*\n A timestamp that is not one.\n\n EDF gives the start date and time eight characters each, and nothing stops a writer\n putting `32.13.99` and `25.61.61` there. `--info` has always echoed the raw fields with\n \"(unparseable)\" beside them, but nothing was raised: the conversion exited 0, `--strict`\n passed, and metadata.json recorded `start_datetime_local: null` with no note against it.\n\n Every other unusable header field reports itself — a degenerate digital range, a physical\n span that cannot be represented, a comma decimal separator, a header whose declared size\n disagrees with its signal count. This was the one that did not, and it is the field\n output-files points at for turning `time_s` into an absolute instant.\n */\n /*\n The sixtieth second, which is a second UTC has and a calendar date does not.\n\n `resolveStartDateTime` admits `ss === 60` on purpose — a recorder synchronised to UTC\n through a leap second writes `23.59.60`, and refusing it would throw away a date that is\n otherwise perfectly good over one second. What it then does is `Math.min(ss, 59)`, because\n `Date.UTC(..., 60)` rolls over into the next minute and would move the instant the other\n way, by fifty-nine seconds more.\n\n Keeping the nearest instant is the right answer. Keeping it in silence was not: `--info`\n printed `Recorded 2020-01-01 23:59:59` and metadata.json recorded the same, for a header\n that says `23.59.60`, with `--strict` exiting 0. Every other header field this tool cannot\n represent exactly says so — a comma decimal separator, a physical span that overflows, a\n record count that disagrees with the file — and this is the field `time_s` is documented as\n being added to.\n */\n if (\n namesLeapSecond(startTimeRaw) &&\n resolveStartDateTime(startDateRaw, startTimeRaw, recordingId) !== null\n ) {\n diagnostics.push({\n code: 'LEAP_SECOND_START',\n severity: 'warning',\n message:\n `The header's start time (\"${startTimeRaw}\") names the sixtieth second of a minute, ` +\n `which no calendar date has.`,\n hint:\n 'It is recorded as the fifty-ninth second, one second earlier, since that is the ' +\n 'nearest instant a date can hold. time_s is unaffected — it counts from the start of ' +\n 'the recording either way.',\n });\n }\n\n /*\n Two dates in one header, disagreeing.\n\n EDF+ requires the recording identification field's `Startdate` to be the header's start\n date. Where it is, its four digits settle the century — see resolveStartDateTime. Where it\n is not, one of the two is wrong and there is no way to tell which, so the date field is\n used, being the one the format defines. That was done in silence, on a header that plainly\n contradicts itself:\n\n Recorded 2002-03-02 22:15:00\n Recording Startdate 05-MAR-2002 PSG-1234/2002 NN Telemetry03\n\n The same shape as a record count that disagrees with the file, or a declared header size\n that disagrees with the signal count, both of which have said so for versions.\n */\n const statedDate = recordingIdStartdate(recordingId);\n const headerDate = /^(\\d{2})[.\\-/](\\d{2})[.\\-/](\\d{2})$/u.exec(trimField(startDateRaw));\n if (statedDate !== null && headerDate !== null) {\n const day = Number(headerDate[1]);\n const month = Number(headerDate[2]);\n const yy = Number(headerDate[3]);\n if (statedDate.day !== day || statedDate.month !== month || statedDate.year % 100 !== yy) {\n diagnostics.push({\n code: 'START_DATE_MISMATCH',\n severity: 'warning',\n message:\n `The header's start date (\"${startDateRaw}\") and the date its recording ` +\n `identification states (\"${String(statedDate.day).padStart(2, '0')}-` +\n `${MONTHS[statedDate.month - 1] as string}-${statedDate.year}\") are different ` +\n `dates, which EDF+ does not permit.`,\n hint:\n 'The start date field is used, since that is the one the format defines. Which of ' +\n 'the two is right is not knowable from the file, so start_datetime_local may name ' +\n 'the wrong day.',\n });\n }\n }\n\n if (resolveStartDateTime(startDateRaw, startTimeRaw, recordingId) === null) {\n diagnostics.push({\n code: 'START_TIME_UNREADABLE',\n severity: 'warning',\n message:\n `The header's start date and time (\"${startDateRaw}\" and ` +\n `\"${startTimeRaw}\") are not a date and a time, so the recording has ` +\n `no start instant.`,\n hint:\n 'time_s is unaffected — it counts from the start of the recording either way. What ' +\n 'cannot be done is turning it into a wall-clock instant, and metadata.json records ' +\n 'start_datetime_local as null.',\n });\n }\n\n for (const [label, indices] of seenLabels) {\n if (indices.length < 2) continue;\n diagnostics.push({\n code: 'DUPLICATE_LABEL',\n severity: 'warning',\n /*\n Cut by the function that cuts every other list in a sentence here.\n\n `join` names all of them, however many there are, and a header may declare as many\n channels as it likes under one label: a 200-channel montage all labelled `T8-P8` — which\n is exactly the kind of file this warning is for, since CHB-MIT ships two of them —\n produced a single 1,100-character line of positions with the sentence that mattered at\n the front of it. `listed` shows eight and counts the rest, which is what the rate\n warning, the leftover-file warning, the channel-position lists in `--channels` and the\n `EMPTY_LABEL` message one loop up all already do. This was the last `join` of a\n file-controlled list left in a diagnostic.\n\n Two positions render identically either way, so the ordinary duplicate reads as it\n always has.\n */\n /*\n Positions written `#N`, which is how a position is written everywhere it is meant to be\n typed: `--channels \"#0\"`, \"This file has signal channels at #0, #1, #2\", and this\n warning's own namesake from channel selection — \"(positions #0, #1); all of them were\n selected\". This one said \"(positions 0, 1)\", two paragraphs above the page that tells\n the reader to \"address it by position with #N\". The number is the same; the form that\n works is not.\n */\n message: `${indices.length} signals share the label \"${label}\" (positions ${listed(indices.map((i) => `#${i}`))}).`,\n hint: 'Their columns are suffixed with the signal number so they stay distinguishable.',\n });\n }\n\n const recordBytes = byteOffsetInRecord;\n if (recordBytes <= 0) {\n throw new EdfError(\n 'NO_SAMPLES',\n 'No signal in this file carries any samples (every channel declares 0 samples per record).',\n );\n }\n\n if (sawComma.value) {\n diagnostics.push({\n code: 'COMMA_DECIMAL',\n severity: 'warning',\n message: 'Some header numbers use a comma decimal separator, which the EDF spec does not allow.',\n hint: 'They were read as decimal points. Check the values in the channel table.',\n });\n }\n\n const dataBytes = fileSize - expectedHeaderBytes;\n if (dataBytes < 0) {\n throw new EdfError('FILE_TOO_SMALL', `File is smaller than its own header.`);\n }\n const recordCount = Math.floor(dataBytes / recordBytes);\n const trailingBytes = dataBytes - recordCount * recordBytes;\n\n if (recordCount === 0) {\n /*\n Which of the two, and with the numbers.\n\n \"The recording was probably interrupted before any data was written\" is right about an\n empty file and wrong about the other way to get here: a header declaring records larger\n than the data present. A 606 KB file holding 589 KB of samples — 60% of one record, more\n than half a million readings — was told no data was written, and the message carried no\n figures at all, so nothing in it could be checked against the file. The declared record\n size is the thing to look at, and it was the one thing not said.\n\n Still an error either way. A record is the unit the format is addressed in, and there is\n nothing smaller to convert.\n */\n const empty = dataBytes === 0;\n throw new EdfError(\n 'NO_DATA_RECORDS',\n empty\n ? 'The file contains a header and no data at all.'\n : `The file contains ${counted(dataBytes, 'byte')} of data, which is less than the ` +\n `${recordBytes} its header says one data record takes.`,\n empty\n ? 'The recording was probably interrupted before any data was written.'\n : 'Either the recording was cut short part way through its first record, or the ' +\n 'header describes records larger than the ones actually written. Check the ' +\n 'samples-per-record fields against the file size.',\n );\n }\n\n if (declaredRecordCount === -1) {\n diagnostics.push({\n code: 'RECORD_COUNT_UNKNOWN',\n severity: 'warning',\n message:\n `The header does not say how many data records the file has (-1), which the spec allows ` +\n `for recordings still in progress. Using the ${counted(recordCount, 'record')} the file actually contains.`,\n });\n } else if (declaredRecordCount !== recordCount) {\n diagnostics.push({\n code: 'RECORD_COUNT_MISMATCH',\n severity: 'warning',\n message:\n `The header declares ${declaredRecordCount} data records but the file contains ` +\n `${recordCount}. Converting the ${counted(recordCount, 'record')} that ${recordCount === 1 ? 'is' : 'are'} present.`,\n hint:\n declaredRecordCount > recordCount\n ? 'The recording looks truncated. It may have been cut short or copied incompletely.'\n : 'The file is longer than its header claims.',\n });\n }\n\n if (trailingBytes > 0) {\n diagnostics.push({\n code: 'TRAILING_BYTES',\n severity: 'warning',\n message: `${counted(trailingBytes, 'byte')} after the last complete data record ${trailingBytes === 1 ? 'was' : 'were'} ignored.`,\n });\n }\n\n const isEdfPlus = continuity !== null;\n if (continuity === 'EDF+D') {\n diagnostics.push({\n code: 'DISCONTINUOUS',\n severity: 'warning',\n message:\n `This is a discontinuous (${isBdf ? 'BDF+D' : 'EDF+D'}) recording: its data records are ` +\n `not contiguous in time.`,\n hint: 'Each row carries its true recording time, so gaps stay visible instead of being closed.',\n });\n }\n\n const dataSignals = signals.filter((s) => !s.isAnnotations);\n if (dataSignals.length === 0) {\n diagnostics.push({\n code: 'NO_SIGNAL_CHANNELS',\n severity: 'warning',\n message: 'This file has no signal channels; it contains only EDF+ annotations.',\n });\n }\n\n // A channel declaring zero samples per record has no sampling rate to speak of — it is\n // reported separately as NO_SAMPLES and no file is written for it. Counting its nominal\n // 0 Hz as a rate made a single-rate recording warn that it used \"2 different sampling\n // rates (4 Hz, 0 Hz)\" and claim it was splitting output it never split.\n const rates = new Set(dataSignals.filter((s) => s.samplesPerRecord > 0).map((s) => s.samplingRate));\n if (rates.size > 1) {\n diagnostics.push({\n code: 'MIXED_SAMPLING_RATES',\n severity: 'warning',\n message:\n `Channels use ${rates.size} different sampling rates ` +\n `(${listed(formatRates([...rates].sort((a, b) => b - a)).map((r) => `${r} Hz`))}).`,\n hint: 'They are written to one file per rate so no channel is resampled.',\n });\n }\n\n return {\n header: {\n version,\n patientId,\n recordingId,\n startDateRaw,\n startTimeRaw,\n startDateTime: resolveStartDateTime(startDateRaw, startTimeRaw, recordingId),\n headerBytes: expectedHeaderBytes,\n declaredHeaderBytes: headerBytes,\n reserved,\n isEdfPlus,\n isBdf,\n continuity,\n declaredRecordCount,\n recordDuration,\n signalCount,\n signals,\n bytesPerSample,\n recordBytes,\n },\n recordCount,\n trailingBytes,\n diagnostics,\n };\n}\n\n/**\n * The recording start as a zone-less wall clock, \"YYYY-MM-DDTHH:MM:SS\".\n *\n * EDF stores the start time as local wall-clock digits with no timezone anywhere in\n * the format. `startDateTime` is built with Date.UTC purely so those digits survive a\n * round trip unshifted, which makes it a carrier for the wall clock rather than a\n * real instant. Serialising it with `toISOString()` would append a Z and assert UTC,\n * and any reader converting to local time would then shift the recording by their own\n * offset: 13:43:04 in the file becomes 08:43:04 in New York. The Z is omitted because\n * the file genuinely does not say which zone it meant.\n */\nexport function formatWallClock(date: Date | null): string | null {\n if (!date) return null;\n return date.toISOString().slice(0, 19);\n}\n\n/**\n * The recording's format, as `--info`, `metadata.json` and `--json` all name it.\n *\n * `\"EDF\"`, `\"BDF\"`, or one of `\"EDF+ (continuous)\"`, `\"EDF+ (discontinuous)\"`,\n * `\"BDF+ (continuous)\"`, `\"BDF+ (discontinuous)\"`. A BDF+ file reports its own spelling even\n * though `continuity` normalises the marker to the `EDF+` form.\n *\n * This said `EDF+ (EDF+D)` and `BDF+ (EDF+C)`, which are not strings it can return — the\n * parenthetical is the word, not the marker. It is a one-line doc comment on a public export,\n * so it is what a TypeScript consumer's editor shows and what `dist/edf/header.d.ts` ships,\n * and the value it describes is `recording.format` in every metadata.json this tool writes.\n * A consumer branching on the tooltip's spelling never matches. Every documentation page had\n * it right; this was the only place that did not.\n */\nexport function describeFormat(header: EdfHeader): string {\n const base = header.isBdf ? 'BDF' : 'EDF';\n if (!header.isEdfPlus) return base;\n return `${base}+ (${header.continuity === 'EDF+D' ? 'discontinuous' : 'continuous'})`;\n}\n\n/** Render a sampling rate without trailing noise: 256, 0.5, 12.5. */\nexport function formatRate(hz: number): string {\n if (Number.isInteger(hz)) return String(hz);\n const rounded = Number(hz.toFixed(6));\n // A rate below 5e-7 rounds away to \"0\", which reads as \"this channel has no sampling\n // rate\" and made the mixed-rate warning contradict itself: it announced two different\n // rates and then printed both as \"0 Hz\". Exponent form keeps a real rate legible, and\n // keeps distinct rates distinct in the channel table and in output filenames.\n if (rounded === 0) return hz.toExponential(3);\n return String(rounded);\n}\n\n/**\n * Renders a group of rates so that rates which differ read as differing.\n *\n * `formatRate` rounds to six decimals, which is what keeps an ordinary rate free of\n * float noise — 30 samples in a 0.1-second record is 299.99999999999994 as a double,\n * and belongs on screen as 300. Two rates separated by less than that round to one\n * string, so a file carrying 1e-6 Hz and 1.25e-6 Hz warned that it used \"2 different\n * sampling rates (0.000001 Hz, 0.000001 Hz)\" and named both files the same thing.\n *\n * That is the contradiction the exponent fallback above already removes for rates that\n * round away to zero; this is the same one a step further out. On a collision every rate\n * in the group switches to its shortest exact form, which is unique for distinct values,\n * rather than only the pair that collided — one column in one notation reads better than\n * two.\n */\nexport function formatRates(rates: readonly number[]): string[] {\n const rounded = rates.map(formatRate);\n const distinct = new Set(rates).size;\n return new Set(rounded).size === distinct ? rounded : rates.map((hz) => String(hz));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edf2csv",
3
- "version": "0.7.122",
3
+ "version": "0.7.124",
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",