Skip to content

Commit

Permalink
feat: Add header option to toCSV method. (#370)
Browse files Browse the repository at this point in the history
* feat: Add toCSV header option, ensure trailing newline.

* test: Update toCSV tests, test header option.

* docs: Add toCSV header option docs.
  • Loading branch information
jheer authored Sep 24, 2024
1 parent 6271161 commit a07c34f
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ This method performs parsing only. To both load and parse a CSV file, use [loadC
* *options*: A CSV format options object:
* *delimiter*: A single-character delimiter string between column values (default `','`).
* *decimal*: A single-character numeric decimal separator (default `'.'`).
* *header*: Boolean flag (default `true`) to specify the presence of a header row. If `true`, indicates the CSV contains a header row with column names. If `false`, indicates the CSV does not contain a header row and the columns are given the names `'col1'`, `'col2'`, etc unless the *names* option is specified.
* *header*: Boolean flag (default `true`) to specify the presence of a header row. If `true`, indicates the CSV contains a header row with column names. If `false`, indicates the CSV does not contain a header row and the columns are given the names `'col1'`, `'col2'`, etc unless the *names* option is specified.
* *names*: An array of column names to use for header-less CSV files. This option is ignored if the *header* option is `true`.
* *skip*: The number of lines to skip (default `0`) before reading data.
* *comment*: A string used to identify comment lines. Any lines that start with the comment pattern are skipped.
Expand Down
1 change: 1 addition & 0 deletions docs/api/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ Format this table as a comma-separated values (CSV) string. Other delimiters, su

* *options*: A formatting options object:
* *delimiter*: The delimiter between values (default `","`).
* *header*: Boolean flag (default `true`) to specify the presence of a header row. If `true`, includes a header row with column names. If `false`, the header is omitted.
* *limit*: The maximum number of rows to print (default `Infinity`).
* *offset*: The row offset indicating how many initial rows to skip (default `0`).
* *columns*: Ordered list of column names to include. If function-valued, the function should accept a table as input and return an array of column name strings. Otherwise, should be an array of name strings.
Expand Down
14 changes: 9 additions & 5 deletions src/format/to-csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import isDate from '../util/is-date.js';
* Options for CSV formatting.
* @typedef {object} CSVFormatOptions
* @property {string} [delimiter=','] The delimiter between values.
* @property {boolean} [header=true] Flag to specify presence of header row.
* If true, includes a header row with column names.
* If false, the header is omitted.
* @property {number} [limit=Infinity] The maximum number of rows to print.
* @property {number} [offset=0] The row offset indicating how many initial rows to skip.
* @property {import('./util.js').ColumnSelectOptions} [columns] Ordered list
Expand All @@ -29,6 +32,7 @@ export default function(table, options = {}) {
const names = columns(table, options.columns);
const format = options.format || {};
const delim = options.delimiter || ',';
const header = options.header ?? true;
const reFormat = new RegExp(`["${delim}\n\r]`);

const formatValue = value => value == null ? ''
Expand All @@ -37,16 +41,16 @@ export default function(table, options = {}) {
: value;

const vals = names.map(formatValue);
let text = '';
let text = header ? (vals.join(delim) + '\n') : '';

scan(table, names, options.limit || Infinity, options.offset, {
row() {
text += vals.join(delim) + '\n';
},
cell(value, name, index) {
vals[index] = formatValue(format[name] ? format[name](value) : value);
},
end() {
text += vals.join(delim) + '\n';
}
});

return text + vals.join(delim);
return text;
}
10 changes: 7 additions & 3 deletions src/format/to-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,22 @@ export default function(table, options = {}) {
+ tag('tbody');

scan(table, names, options.limit, options.offset, {
row(row) {
start(row) {
r = row;
text += (++idx ? '</tr>' : '') + tag('tr');
++idx;
text += tag('tr');
},
cell(value, name) {
text += tag('td', name, 1)
+ formatter(value, format[name])
+ '</td>';
},
end() {
text += '</tr>';
}
});

return text + '</tr></tbody></table>';
return text + '</tbody></table>';
}

function styles(options) {
Expand Down
11 changes: 7 additions & 4 deletions src/format/to-markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,19 @@ export default function(table, options = {}) {
+ names.map(escape).join('|')
+ '|\n|'
+ names.map(name => alignValue(align[name])).join('|')
+ '|';
+ '|\n';

scan(table, names, options.limit, options.offset, {
row() {
text += '\n|';
start() {
text += '|';
},
cell(value, name) {
text += escape(formatValue(value, format[name])) + '|';
},
end() {
text += '\n';
}
});

return text + '\n';
return text;
}
7 changes: 5 additions & 2 deletions src/format/util.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inferFormat from './infer.js';
import isFunction from '../util/is-function.js';
import identity from '../util/identity.js';

/**
* Column selection function.
Expand Down Expand Up @@ -59,13 +60,15 @@ function values(table, columnName) {
}

export function scan(table, names, limit = 100, offset, ctx) {
const { start = identity, cell, end = identity } = ctx;
const data = table.data();
const n = names.length;
table.scan(row => {
ctx.row(row);
start(row);
for (let i = 0; i < n; ++i) {
const name = names[i];
ctx.cell(data[names[i]].at(row), name, i);
cell(data[name].at(row), name, i);
}
end(row);
}, true, limit, offset);
}
28 changes: 22 additions & 6 deletions test/format/to-csv-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ const tabText = text.map(t => t.split(',').join('\t'));
describe('toCSV', () => {
it('formats delimited text', () => {
const dt = new ColumnTable(data());
assert.equal(toCSV(dt), text.join('\n'), 'csv text');
assert.equal(toCSV(dt), text.join('\n') + '\n', 'csv text');
assert.equal(
toCSV(dt, { limit: 2, columns: ['str', 'int'] }),
text.slice(0, 3)
.map(s => s.split(',').slice(0, 2).join(','))
.join('\n'),
.join('\n') + '\n',
'csv text with limit'
);
});
Expand All @@ -37,24 +37,40 @@ describe('toCSV', () => {
const dt = new ColumnTable(data());
assert.equal(
toCSV(dt, { delimiter: '\t' }),
tabText.join('\n'),
tabText.join('\n') + '\n',
'csv text with delimiter'
);
assert.equal(
toCSV(dt, { limit: 2, delimiter: '\t', columns: ['str', 'int'] }),
text.slice(0, 3)
.map(s => s.split(',').slice(0, 2).join('\t'))
.join('\n'),
.join('\n') + '\n',
'csv text with delimiter and limit'
);
});

it('formats delimited text with header option', () => {
const dt = new ColumnTable(data());
assert.equal(
toCSV(dt, { header: false }),
text.slice(1).join('\n') + '\n',
'csv text without header'
);
assert.equal(
toCSV(dt, { header: false, limit: 2, columns: ['str', 'int'] }),
text.slice(1, 3)
.map(s => s.split(',').slice(0, 2).join(','))
.join('\n') + '\n',
'csv text without header and with limit'
);
});

it('formats delimited text for filtered table', () => {
const bs = new BitSet(3).not(); bs.clear(1);
const dt = new ColumnTable(data(), null, bs);
assert.equal(
toCSV(dt),
[ ...text.slice(0, 2), ...text.slice(3) ].join('\n'),
[ ...text.slice(0, 2), ...text.slice(3) ].join('\n') + '\n',
'csv text with limit'
);
});
Expand All @@ -63,7 +79,7 @@ describe('toCSV', () => {
const dt = new ColumnTable(data());
assert.equal(
toCSV(dt, { limit: 2, columns: ['str'], format: { str: d => d + '!' } }),
['str', 'a!', 'b!'].join('\n'),
['str', 'a!', 'b!'].join('\n') + '\n',
'csv text with custom format'
);
});
Expand Down

0 comments on commit a07c34f

Please sign in to comment.