feat(bgm): allow $variants to take multiple csv sources
Expand $variants to accept an array of csv paths, concatenating their rows. Detect a path by a .csv suffix on the first line instead of a newline, so inline csv and paths are self-documenting and the rule applies uniformly to single values and array elements.
This commit is contained in:
@@ -9,8 +9,8 @@
|
||||
* - Rows are validated against a schema derived from the type row.
|
||||
* - A cell for an array/tuple type uses `;` as the element separator
|
||||
* (`[0;0;5;2]`), because `,` is the CSV delimiter.
|
||||
* - `$variants` can be a file/URL path *or* an inline CSV string: a value
|
||||
* containing a newline is inline CSV, otherwise it is a path.
|
||||
* - `$variants` can be a single source or an array of them. A source is a
|
||||
* file/URL path if its first line ends in `.csv`, otherwise inline CSV.
|
||||
*
|
||||
* Parsing is delegated to `typed-csv`'s `parseCsv`, which implements exactly
|
||||
* this header/schema/data layout and validates each row against a schema
|
||||
@@ -74,7 +74,8 @@ export function parseCsvByName(
|
||||
/**
|
||||
* Expand a `$variants` value into rows.
|
||||
*
|
||||
* @param value the `$variants` value: a path or inline CSV
|
||||
* @param value the `$variants` value: a path or inline CSV string, or an
|
||||
* array of them
|
||||
* @param baseName the path-style name of the referencing def file; a path
|
||||
* value resolves relative to its directory
|
||||
* @param defs the virtual def map, for resolving the path
|
||||
@@ -86,14 +87,41 @@ export function expandVariants(
|
||||
defs: Map<string, DefFile[]>,
|
||||
source: string,
|
||||
): Record<string, unknown>[] {
|
||||
if (typeof value !== 'string') {
|
||||
throw new BgmError('`$variants` must be a path or inline CSV string', source);
|
||||
const sources = Array.isArray(value) ? value : [value];
|
||||
if (sources.length === 0) {
|
||||
throw new BgmError('`$variants` array must not be empty', source);
|
||||
}
|
||||
|
||||
if (value.includes('\n')) {
|
||||
return parseCsvData(value, source).rows;
|
||||
const rows: Record<string, unknown>[] = [];
|
||||
for (const item of sources) {
|
||||
if (typeof item !== 'string') {
|
||||
throw new BgmError(
|
||||
'`$variants` must be a path or inline CSV string, or an array of them',
|
||||
source,
|
||||
);
|
||||
}
|
||||
rows.push(...expandVariantsOne(item, baseName, defs, source));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
const name = path.posix.join(path.posix.dirname(baseName), value);
|
||||
return parseCsvByName(name, defs, source).rows;
|
||||
/**
|
||||
* Expand a single `$variants` source: a path or inline CSV.
|
||||
*
|
||||
* A source is a path when its first line ends in `.csv`; otherwise it is
|
||||
* inline CSV. This keeps the two forms self-documenting and applies the same
|
||||
* rule to single values and array elements alike.
|
||||
*/
|
||||
function expandVariantsOne(
|
||||
value: string,
|
||||
baseName: string,
|
||||
defs: Map<string, DefFile[]>,
|
||||
source: string,
|
||||
): Record<string, unknown>[] {
|
||||
const firstLine = value.split('\n', 1)[0] ?? value;
|
||||
if (/[.]csv$/i.test(firstLine)) {
|
||||
const name = path.posix.join(path.posix.dirname(baseName), value);
|
||||
return parseCsvByName(name, defs, source).rows;
|
||||
}
|
||||
return parseCsvData(value, source).rows;
|
||||
}
|
||||
Reference in New Issue
Block a user