UNPKG

4.85 kBJavaScriptView Raw
1import fs from 'fs';
2import os from 'os';
3import path from 'path';
4
5function escapeCellForTabSeperatedOutput(cell) {
6 if (cell === undefined || cell === null) {
7 return '';
8 }
9 const cellAsString = `${cell}`;
10 if (cellAsString.match(/("|\r|\n|\t)/)) {
11 return `"${cellAsString.replace(/"/g, '""')}"`;
12 }
13 return cell;
14}
15
16function tabSeperated(tableData) {
17 return (
18 [tableData.columns.map((col) => col.label)]
19 .concat(tableData.rows)
20 .map((row) => {
21 return row.map(escapeCellForTabSeperatedOutput).join('\t');
22 })
23 .join(os.EOL) + os.EOL
24 );
25}
26
27function escapeCellForCommaSeperatedOutput(cell) {
28 if (cell === undefined || cell === null) {
29 return '""';
30 }
31 const cellAsString = `${cell}`;
32 return `"${cellAsString.replace(/"/g, '""')}"`;
33}
34
35function commaSeperated(tableData) {
36 return `${
37 [tableData.columns.map((col) => col.label)]
38 .concat(tableData.rows)
39 .map((row) => {
40 return row.map(escapeCellForCommaSeperatedOutput).join(',');
41 })
42 .join(os.EOL)
43 // Use \r\n rather than os.EOL as per RFC 4180
44 }\r\n`;
45}
46
47function jsonFormatted(tableData) {
48 return (
49 JSON.stringify(
50 tableData.rows.map((row) =>
51 tableData.columns.reduce((obj, col, i) => {
52 obj[col.name] = row[i];
53 return obj;
54 }, {})
55 ),
56 null,
57 '\t'
58 ) + os.EOL
59 );
60}
61
62function getSortIndexForInput(sortColumnIndex, visibleColumnDefinitions) {
63 return isNaN(parseInt(sortColumnIndex, 10))
64 ? Math.max(
65 visibleColumnDefinitions.findIndex(
66 (column) => column.name === sortColumnIndex
67 ),
68 0
69 )
70 : parseInt(sortColumnIndex, 10);
71}
72
73function getColumnDefinitionsForInput(columns, allColumns) {
74 return columns
75 .map((columnName) =>
76 allColumns.find((column) => column.name === columnName)
77 )
78 .filter((column) => !!column);
79}
80
81function getData(visibleColumnDefinitions, data, sortIndex) {
82 return {
83 columns: visibleColumnDefinitions.map((column, i) =>
84 Object.assign(column, { isSorted: i === sortIndex })
85 ),
86 rows: data
87 .map((operation) =>
88 visibleColumnDefinitions.map((column) =>
89 column.value(operation)
90 )
91 )
92 .sort((a, b) =>
93 a[sortIndex] === b[sortIndex]
94 ? 0
95 : a[sortIndex] < b[sortIndex]
96 ? -1
97 : 1
98 ),
99 };
100}
101
102const exportTransformers = {
103 csv: commaSeperated,
104 xls: tabSeperated,
105 json: jsonFormatted,
106};
107
108export default class FdtTable {
109 constructor(moduleRegistration, columns) {
110 this.columns = columns;
111
112 this.sortOption = new moduleRegistration.Option('sort')
113 .setShort('S')
114 .setDescription(
115 'Column name or number to sort by (defaults to 0, first column).'
116 )
117 .setDefault('0', true);
118
119 const columnNames = columns
120 .map((col) => col.name + (col.default ? '*' : ''))
121 .join('|');
122 this.columnsOption = new moduleRegistration.MultiOption('columns')
123 .setDescription(
124 `One or more space-separated column names to output (${columnNames}), only works when not exporting.`
125 )
126 .setShort('C')
127 .setDefault(
128 columns.filter((col) => col.default).map((col) => col.name),
129 true
130 );
131
132 const exportFormats = Object.keys(exportTransformers)
133 .map((col, i) => col + (!i ? '*' : ''))
134 .join('|');
135 this.exportOption = new moduleRegistration.Option('export')
136 .setDescription(
137 `Export table to a file; the export type is determined by the file extension (${exportFormats}), and ignores the columns option.`
138 )
139 .setShort('E');
140 }
141
142 print(res, columnsInput, data, sortInput, exportLocation) {
143 const visibleColumnDefinitions = exportLocation
144 ? this.columns
145 : getColumnDefinitionsForInput(columnsInput, this.columns);
146 const tableData = getData(
147 visibleColumnDefinitions,
148 data,
149 getSortIndexForInput(sortInput, visibleColumnDefinitions)
150 );
151
152 if (!exportLocation) {
153 res.table(
154 tableData.columns.map(
155 (col) => col.label + (col.isSorted ? '*' : '')
156 ),
157 tableData.rows.map((row) => row.map((cell) => cell || '-'))
158 );
159
160 res.break();
161 const columnLabels = visibleColumnDefinitions
162 .map((col, _i) => col.label + (col.isSorted ? '*' : ''))
163 .join(', ')
164 .toLowerCase();
165 res.success(
166 `Printed ${columnLabels} for ${tableData.rows.length} results`
167 );
168
169 return;
170 }
171
172 const ext = path
173 .extname(path.basename(exportLocation))
174 .replace('.', '');
175
176 if (!exportTransformers[ext]) {
177 throw new res.InputError(
178 `Unknown export type "${ext}".`,
179 `You can export a table by using the "export" option to specify a file with one of the following extensions: ${Object.keys(
180 exportTransformers
181 ).join('|')}.`
182 );
183 }
184
185 res.debug(`Exporting to "${exportLocation}"`);
186 const exported = exportTransformers[ext](tableData);
187
188 // Notice this favours process.cwd() over app.processPath, which is not necessarily the same
189 fs.writeFileSync(exportLocation, exported);
190
191 res.debug(`Exported file: ${exported.length} characters`);
192 }
193}