@fontoxml/fontoxml-development-tools
Version:
Development tools for Fonto.
194 lines (171 loc) • 4.85 kB
JavaScript
import fs from 'fs';
import os from 'os';
import path from 'path';
function escapeCellForTabSeperatedOutput(cell) {
if (cell === undefined || cell === null) {
return '';
}
const cellAsString = `${cell}`;
if (cellAsString.match(/("|\r|\n|\t)/)) {
return `"${cellAsString.replace(/"/g, '""')}"`;
}
return cell;
}
function tabSeperated(tableData) {
return (
[tableData.columns.map((col) => col.label)]
.concat(tableData.rows)
.map((row) => {
return row.map(escapeCellForTabSeperatedOutput).join('\t');
})
.join(os.EOL) + os.EOL
);
}
function escapeCellForCommaSeperatedOutput(cell) {
if (cell === undefined || cell === null) {
return '""';
}
const cellAsString = `${cell}`;
return `"${cellAsString.replace(/"/g, '""')}"`;
}
function commaSeperated(tableData) {
return `${
[tableData.columns.map((col) => col.label)]
.concat(tableData.rows)
.map((row) => {
return row.map(escapeCellForCommaSeperatedOutput).join(',');
})
.join(os.EOL)
// Use \r\n rather than os.EOL as per RFC 4180
}\r\n`;
}
function jsonFormatted(tableData) {
return (
JSON.stringify(
tableData.rows.map((row) =>
tableData.columns.reduce((obj, col, i) => {
obj[col.name] = row[i];
return obj;
}, {})
),
null,
'\t'
) + os.EOL
);
}
function getSortIndexForInput(sortColumnIndex, visibleColumnDefinitions) {
return isNaN(parseInt(sortColumnIndex, 10))
? Math.max(
visibleColumnDefinitions.findIndex(
(column) => column.name === sortColumnIndex
),
0
)
: parseInt(sortColumnIndex, 10);
}
function getColumnDefinitionsForInput(columns, allColumns) {
return columns
.map((columnName) =>
allColumns.find((column) => column.name === columnName)
)
.filter((column) => !!column);
}
function getData(visibleColumnDefinitions, data, sortIndex) {
return {
columns: visibleColumnDefinitions.map((column, i) =>
Object.assign(column, { isSorted: i === sortIndex })
),
rows: data
.map((operation) =>
visibleColumnDefinitions.map((column) =>
column.value(operation)
)
)
.sort((a, b) =>
a[sortIndex] === b[sortIndex]
? 0
: a[sortIndex] < b[sortIndex]
? -1
: 1
),
};
}
const exportTransformers = {
csv: commaSeperated,
xls: tabSeperated,
json: jsonFormatted,
};
export default class FdtTable {
constructor(moduleRegistration, columns) {
this.columns = columns;
this.sortOption = new moduleRegistration.Option('sort')
.setShort('S')
.setDescription(
'Column name or number to sort by (defaults to 0, first column).'
)
.setDefault('0', true);
const columnNames = columns
.map((col) => col.name + (col.default ? '*' : ''))
.join('|');
this.columnsOption = new moduleRegistration.MultiOption('columns')
.setDescription(
`One or more space-separated column names to output (${columnNames}), only works when not exporting.`
)
.setShort('C')
.setDefault(
columns.filter((col) => col.default).map((col) => col.name),
true
);
const exportFormats = Object.keys(exportTransformers)
.map((col, i) => col + (!i ? '*' : ''))
.join('|');
this.exportOption = new moduleRegistration.Option('export')
.setDescription(
`Export table to a file; the export type is determined by the file extension (${exportFormats}), and ignores the columns option.`
)
.setShort('E');
}
print(res, columnsInput, data, sortInput, exportLocation) {
const visibleColumnDefinitions = exportLocation
? this.columns
: getColumnDefinitionsForInput(columnsInput, this.columns);
const tableData = getData(
visibleColumnDefinitions,
data,
getSortIndexForInput(sortInput, visibleColumnDefinitions)
);
if (!exportLocation) {
res.table(
tableData.columns.map(
(col) => col.label + (col.isSorted ? '*' : '')
),
tableData.rows.map((row) => row.map((cell) => cell || '-'))
);
res.break();
const columnLabels = visibleColumnDefinitions
.map((col, _i) => col.label + (col.isSorted ? '*' : ''))
.join(', ')
.toLowerCase();
res.success(
`Printed ${columnLabels} for ${tableData.rows.length} results`
);
return;
}
const ext = path
.extname(path.basename(exportLocation))
.replace('.', '');
if (!exportTransformers[ext]) {
throw new res.InputError(
`Unknown export type "${ext}".`,
`You can export a table by using the "export" option to specify a file with one of the following extensions: ${Object.keys(
exportTransformers
).join('|')}.`
);
}
res.debug(`Exporting to "${exportLocation}"`);
const exported = exportTransformers[ext](tableData);
// Notice this favours process.cwd() over app.processPath, which is not necessarily the same
fs.writeFileSync(exportLocation, exported);
res.debug(`Exported file: ${exported.length} characters`);
}
}