• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

C2FO / fast-csv / 12025003900

26 Nov 2024 06:20AM CUT coverage: 95.889%. Remained the same
12025003900

push

github

web-flow
chore(deps): update dependency @types/node to v22.10.0 (#1067)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

321 of 349 branches covered (91.98%)

Branch coverage included in aggregate %.

752 of 770 relevant lines covered (97.66%)

2179022.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.36
/packages/format/src/formatter/RowFormatter.ts
1
import isFunction from 'lodash.isfunction';
60✔
2
import isEqual from 'lodash.isequal';
60✔
3
import { FormatterOptions } from '../FormatterOptions';
4
import { FieldFormatter } from './FieldFormatter';
60✔
5
import { isSyncTransform, Row, RowArray, RowHashArray, RowTransformCallback, RowTransformFunction } from '../types';
60✔
6

7
type RowFormatterTransform<I extends Row, O extends Row> = (row: I, cb: RowTransformCallback<O>) => void;
8

9
type RowFormatterCallback = (error: Error | null, data?: RowArray) => void;
10

11
export class RowFormatter<I extends Row, O extends Row> {
60✔
12
    private static isRowHashArray(row: Row): row is RowHashArray {
13
        if (Array.isArray(row)) {
2,196✔
14
            return Array.isArray(row[0]) && row[0].length === 2;
1,842✔
15
        }
16
        return false;
354✔
17
    }
18

19
    private static isRowArray(row: Row): row is RowArray {
20
        return Array.isArray(row) && !this.isRowHashArray(row);
480✔
21
    }
22

23
    // get headers from a row item
24
    private static gatherHeaders(row: Row): string[] {
25
        if (RowFormatter.isRowHashArray(row)) {
816✔
26
            // lets assume a multi-dimesional array with item 0 being the header
27
            return row.map((it): string => {
222✔
28
                return it[0];
444✔
29
            });
30
        }
31
        if (Array.isArray(row)) {
594✔
32
            return row;
240✔
33
        }
34
        return Object.keys(row);
354✔
35
    }
36

37
    // eslint-disable-next-line @typescript-eslint/no-shadow
38
    private static createTransform<I extends Row, O extends Row>(
39
        transformFunction: RowTransformFunction<I, O>,
40
    ): RowFormatterTransform<I, O> {
41
        if (isSyncTransform(transformFunction)) {
222✔
42
            return (row: I, cb: RowTransformCallback<O>): void => {
162✔
43
                let transformedRow = null;
354✔
44
                try {
354✔
45
                    transformedRow = transformFunction(row);
354✔
46
                } catch (e) {
47
                    return cb(e);
×
48
                }
49
                return cb(null, transformedRow);
354✔
50
            };
51
        }
52
        return (row: I, cb: RowTransformCallback<O>): void => {
60✔
53
            transformFunction(row, cb);
60✔
54
        };
55
    }
56

57
    private readonly formatterOptions: FormatterOptions<I, O>;
58

59
    private readonly fieldFormatter: FieldFormatter<I, O>;
60

61
    private readonly shouldWriteHeaders: boolean;
62

63
    private _rowTransform?: RowFormatterTransform<I, O>;
64

65
    private headers: string[] | null;
66

67
    private hasWrittenHeaders: boolean;
68

69
    private rowCount = 0;
1,008✔
70

71
    public constructor(formatterOptions: FormatterOptions<I, O>) {
72
        this.formatterOptions = formatterOptions;
1,008✔
73
        this.fieldFormatter = new FieldFormatter(formatterOptions);
1,008✔
74

75
        this.headers = formatterOptions.headers;
1,008✔
76
        this.shouldWriteHeaders = formatterOptions.shouldWriteHeaders;
1,008✔
77
        this.hasWrittenHeaders = false;
1,008✔
78
        if (this.headers !== null) {
1,008✔
79
            this.fieldFormatter.headers = this.headers;
120✔
80
        }
81
        if (formatterOptions.transform) {
1,008✔
82
            this.rowTransform = formatterOptions.transform;
198✔
83
        }
84
    }
85

86
    public set rowTransform(transformFunction: RowTransformFunction<I, O>) {
87
        if (!isFunction(transformFunction)) {
228✔
88
            throw new TypeError('The transform should be a function');
6✔
89
        }
90
        this._rowTransform = RowFormatter.createTransform(transformFunction);
222✔
91
    }
92

93
    public format(row: I, cb: RowFormatterCallback): void {
94
        this.callTransformer(row, (err, transformedRow?: Row): void => {
1,848✔
95
            if (err) {
1,824✔
96
                return cb(err);
18✔
97
            }
98
            if (!row) {
1,806!
99
                return cb(null);
×
100
            }
101
            const rows = [];
1,806✔
102
            if (transformedRow) {
1,806✔
103
                const { shouldFormatColumns, headers } = this.checkHeaders(transformedRow);
1,806✔
104
                if (this.shouldWriteHeaders && headers && !this.hasWrittenHeaders) {
1,806✔
105
                    rows.push(this.formatColumns(headers, true));
798✔
106
                    this.hasWrittenHeaders = true;
798✔
107
                }
108
                if (shouldFormatColumns) {
1,806✔
109
                    const columns = this.gatherColumns(transformedRow);
1,608✔
110
                    rows.push(this.formatColumns(columns, false));
1,608✔
111
                }
112
            }
113
            return cb(null, rows);
1,806✔
114
        });
115
    }
116

117
    public finish(cb: RowFormatterCallback): void {
118
        const rows = [];
756✔
119
        // check if we should write headers and we didnt get any rows
120
        if (this.formatterOptions.alwaysWriteHeaders && this.rowCount === 0) {
756✔
121
            if (!this.headers) {
30✔
122
                return cb(new Error('`alwaysWriteHeaders` option is set to true but `headers` option not provided.'));
12✔
123
            }
124
            rows.push(this.formatColumns(this.headers, true));
18✔
125
        }
126
        if (this.formatterOptions.includeEndRowDelimiter) {
744✔
127
            rows.push(this.formatterOptions.rowDelimiter);
54✔
128
        }
129
        return cb(null, rows);
744✔
130
    }
131

132
    // check if we need to write header return true if we should also write a row
133
    // could be false if headers is true and the header row(first item) is passed in
134
    private checkHeaders(row: Row): { headers?: string[] | null; shouldFormatColumns: boolean } {
135
        if (this.headers) {
1,806✔
136
            // either the headers were provided by the user or we have already gathered them.
137
            return { shouldFormatColumns: true, headers: this.headers };
990✔
138
        }
139
        const headers = RowFormatter.gatherHeaders(row);
816✔
140
        this.headers = headers;
816✔
141
        this.fieldFormatter.headers = headers;
816✔
142
        if (!this.shouldWriteHeaders) {
816✔
143
            // if we are not supposed to write the headers then
144
            // always format the columns
145
            return { shouldFormatColumns: true, headers: null };
108✔
146
        }
147
        // if the row is equal to headers dont format
148
        return { shouldFormatColumns: !isEqual(headers, row), headers };
708✔
149
    }
150

151
    // todo change this method to unknown[]
152
    private gatherColumns(row: Row): string[] {
153
        if (this.headers === null) {
1,608!
154
            throw new Error('Headers is currently null');
×
155
        }
156
        if (!Array.isArray(row)) {
1,608✔
157
            return this.headers.map((header): string => {
708✔
158
                return row[header] as string;
1,440✔
159
            });
160
        }
161
        if (RowFormatter.isRowHashArray(row)) {
900✔
162
            return this.headers.map((header, i): string => {
420✔
163
                const col = row[i] as unknown as string;
840✔
164
                if (col) {
840✔
165
                    return col[1];
834✔
166
                }
167
                return '';
6✔
168
            });
169
        }
170
        // if its a one dimensional array and headers were not provided
171
        // then just return the row
172
        if (RowFormatter.isRowArray(row) && !this.shouldWriteHeaders) {
480✔
173
            return row;
84✔
174
        }
175
        return this.headers.map((header, i): string => {
396✔
176
            return row[i];
810✔
177
        });
178
    }
179

180
    private callTransformer(row: I, cb: RowTransformCallback<O>): void {
181
        if (!this._rowTransform) {
1,848✔
182
            return cb(null, row as unknown as O);
1,434✔
183
        }
184
        return this._rowTransform(row, cb);
414✔
185
    }
186

187
    private formatColumns(columns: string[], isHeadersRow: boolean): string {
188
        const formattedCols = columns
2,424✔
189
            .map((field, i): string => {
190
                return this.fieldFormatter.format(field, i, isHeadersRow);
4,872✔
191
            })
192
            .join(this.formatterOptions.delimiter);
193
        const { rowCount } = this;
2,424✔
194
        this.rowCount += 1;
2,424✔
195
        if (rowCount) {
2,424✔
196
            return [this.formatterOptions.rowDelimiter, formattedCols].join('');
1,488✔
197
        }
198
        return formattedCols;
936✔
199
    }
200
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc