Skip to content

Commit 68d61c7

Browse files
nam-hlenhle-mgmtp
andauthored
refactor: normalize newlines (#166)
Co-authored-by: Nam Hoang Le <[email protected]>
1 parent 8148dde commit 68d61c7

8 files changed

+121
-69
lines changed

Diff for: src/drawTable.ts

+3-19
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,9 @@ import drawRow from './drawRow';
66
import type {
77
TableConfig, Row,
88
} from './types/internal';
9-
10-
/**
11-
* Group the array into sub-arrays by sizes.
12-
*
13-
* @example
14-
* groupBySizes(['a', 'b', 'c', 'd', 'e'], [2, 1, 2]) = [ ['a', 'b'], ['c'], ['d', 'e'] ]
15-
*/
16-
17-
const groupBySizes = <T>(array: T[], sizes: number[]): T[][] => {
18-
let startIndex = 0;
19-
20-
return sizes.map((size) => {
21-
const group = array.slice(startIndex, startIndex + size);
22-
23-
startIndex += size;
24-
25-
return group;
26-
});
27-
};
9+
import {
10+
groupBySizes,
11+
} from './utils';
2812

2913
export default (rows: Row[], columnWidths: number[], rowHeights: number[], config: TableConfig): string => {
3014
const {

Diff for: src/makeConfig.ts

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
import cloneDeep from 'lodash.clonedeep';
22
import calculateColumnWidths from './calculateColumnWidths';
3-
import getBorderCharacters from './getBorderCharacters';
43
import type {
54
ColumnUserConfig, Indexable,
6-
BorderUserConfig, BorderConfig, TableUserConfig,
5+
TableUserConfig,
76
} from './types/api';
87
import type {
98
ColumnConfig, Row, TableConfig,
109
} from './types/internal';
10+
import {
11+
makeBorder,
12+
} from './utils';
1113
import validateConfig from './validateConfig';
1214

13-
/**
14-
* Merges user provided border characters with the default border ("honeywell") characters.
15-
*/
16-
const makeBorder = (border: BorderUserConfig | undefined): BorderConfig => {
17-
return {
18-
...getBorderCharacters('honeywell'),
19-
...border,
20-
};
21-
};
22-
2315
/**
2416
* Creates a configuration for every column using default
2517
* values for the missing configuration properties.

Diff for: src/makeStreamConfig.ts

+3-14
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
11
import cloneDeep from 'lodash.clonedeep';
2-
import getBorderCharacters from './getBorderCharacters';
32
import type {
43
ColumnUserConfig,
54
Indexable,
65
StreamUserConfig,
7-
BorderUserConfig,
8-
BorderConfig,
96
} from './types/api';
107
import type {
118
ColumnConfig,
129
StreamConfig,
1310
} from './types/internal';
11+
import {
12+
makeBorder,
13+
} from './utils';
1414
import validateConfig from './validateConfig';
1515

16-
/**
17-
* Merges user provided border characters with the default border ("honeywell") characters.
18-
*
19-
*/
20-
const makeBorder = (border?: BorderUserConfig): BorderConfig => {
21-
return {
22-
...getBorderCharacters('honeywell'),
23-
...border,
24-
};
25-
};
26-
2716
/**
2817
* Creates a configuration for every column using default
2918
* values for the missing configuration properties.

Diff for: src/stringifyTableData.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import type {
22
Row,
33
} from './types/internal';
4+
import {
5+
normalizeString,
6+
} from './utils';
47

58
export default (rows: unknown[][]): Row[] => {
69
return rows.map((cells) => {
7-
return cells.map(String);
10+
return cells.map((cell) => {
11+
return normalizeString(String(cell));
12+
});
813
});
914
};

Diff for: src/utils.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import slice from 'slice-ansi';
2+
import stripAnsi from 'strip-ansi';
3+
import getBorderCharacters from './getBorderCharacters';
4+
import type {
5+
BorderConfig, BorderUserConfig,
6+
} from './types/api';
7+
8+
/**
9+
* Converts Windows-style newline to Unix-style
10+
*
11+
* @internal
12+
*/
13+
export const normalizeString = (input: string): string => {
14+
return input.replace(/\r\n/g, '\n');
15+
};
16+
17+
/**
18+
* Splits ansi string by newlines
19+
*
20+
* @internal
21+
*/
22+
export const splitAnsi = (input: string): string[] => {
23+
const lengths = stripAnsi(input).split('\n').map(({length}) => {
24+
return length;
25+
});
26+
27+
const result: string[] = [];
28+
let startIndex = 0;
29+
30+
lengths.forEach((length) => {
31+
result.push(length === 0 ? '' : slice(input, startIndex, startIndex + length));
32+
33+
// Plus 1 for the newline character itself
34+
startIndex += length + 1;
35+
});
36+
37+
return result;
38+
};
39+
40+
/**
41+
* Merges user provided border characters with the default border ("honeywell") characters.
42+
*
43+
* @internal
44+
*/
45+
export const makeBorder = (border: BorderUserConfig | undefined): BorderConfig => {
46+
return {
47+
...getBorderCharacters('honeywell'),
48+
...border,
49+
};
50+
};
51+
52+
/**
53+
* Groups the array into sub-arrays by sizes.
54+
*
55+
* @internal
56+
* @example
57+
* groupBySizes(['a', 'b', 'c', 'd', 'e'], [2, 1, 2]) = [ ['a', 'b'], ['c'], ['d', 'e'] ]
58+
*/
59+
60+
export const groupBySizes = <T>(array: T[], sizes: number[]): T[][] => {
61+
let startIndex = 0;
62+
63+
return sizes.map((size) => {
64+
const group = array.slice(startIndex, startIndex + size);
65+
66+
startIndex += size;
67+
68+
return group;
69+
});
70+
};

Diff for: src/validateTableData.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
normalizeString,
3+
} from './utils';
4+
15
export default (rows: unknown[][]): void => {
26
if (!Array.isArray(rows)) {
37
throw new TypeError('Table data must be an array.');
@@ -24,7 +28,7 @@ export default (rows: unknown[][]): void => {
2428

2529
for (const cell of row) {
2630
// eslint-disable-next-line no-control-regex
27-
if (/[\u0001-\u0006\u0008\u0009\u000B-\u001A]/.test(cell)) {
31+
if (/[\u0001-\u0006\u0008\u0009\u000B-\u001A]/.test(normalizeString(String(cell)))) {
2832
throw new Error('Table data must not contain control characters.');
2933
}
3034
}

Diff for: src/wrapCell.ts

+3-20
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
1-
import slice from 'slice-ansi';
2-
import stripAnsi from 'strip-ansi';
1+
import {
2+
splitAnsi,
3+
} from './utils';
34
import wrapString from './wrapString';
45
import wrapWord from './wrapWord';
56

6-
const splitAnsi = (input: string) => {
7-
const lengths = stripAnsi(input).split('\n').map(({length}) => {
8-
return length;
9-
});
10-
11-
const result: string[] = [];
12-
let startIndex = 0;
13-
14-
lengths.forEach((length) => {
15-
result.push(length === 0 ? '' : slice(input, startIndex, startIndex + length));
16-
17-
// Plus 1 for the newline character itself
18-
startIndex += length + 1;
19-
});
20-
21-
return result;
22-
};
23-
247
/**
258
* Wrap a single cell value into a list of lines
269
*

Diff for: test/validateTableData.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import {
44
expect,
55
} from 'chai';
6+
import {
7+
table,
8+
} from '../src';
69
import validateTableData from '../src/validateTableData';
710

811
describe('validateTableData', () => {
@@ -52,7 +55,27 @@ describe('validateTableData', () => {
5255

5356
context('cell data contains newlines', () => {
5457
it('does not throw', () => {
55-
validateTableData([['ab\nc']]);
58+
expect(() => {
59+
validateTableData([['ab\nc']]);
60+
}).to.not.throw();
61+
});
62+
});
63+
64+
context('cell data contains Windows-style newlines', () => {
65+
it('does not throw and replaces by Unix-style newline', () => {
66+
expect(() => {
67+
validateTableData([['ab\r\nc']]);
68+
}).to.not.throw();
69+
70+
expect(table([['ab\r\nc']])).to.equal('╔════╗\n║ ab ║\n║ c ║\n╚════╝\n');
71+
});
72+
});
73+
74+
context('cell data contains carriage return only', () => {
75+
it('throws an error', () => {
76+
expect(() => {
77+
validateTableData([['ab\rc']]);
78+
}).to.throw(Error, 'Table data must not contain control characters.');
5679
});
5780
});
5881

@@ -79,7 +102,9 @@ describe('validateTableData', () => {
79102
].join('');
80103

81104
it('does not throw', () => {
82-
validateTableData([[link]]);
105+
expect(() => {
106+
validateTableData([[link]]);
107+
}).to.not.throw();
83108
});
84109
});
85110

0 commit comments

Comments
 (0)