@thi.ng/csv 2.3.46 → 2.3.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-03T12:13:31Z
3
+ - **Last updated**: 2023-12-11T10:07:09Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/api.js CHANGED
@@ -1 +0,0 @@
1
- export {};
package/format.js CHANGED
@@ -6,62 +6,55 @@ import { iterator } from "@thi.ng/transducers/iterator";
6
6
  import { isReduced } from "@thi.ng/transducers/reduced";
7
7
  import { str } from "@thi.ng/transducers/str";
8
8
  import { transduce } from "@thi.ng/transducers/transduce";
9
- export function formatCSV(opts, src) {
10
- return isIterable(src)
11
- ? iterator(formatCSV(opts), src)
12
- : (rfn) => {
13
- let { header, cols, delim, quote } = {
14
- delim: ",",
15
- quote: `"`,
16
- cols: [],
17
- ...opts,
18
- };
19
- let colTx;
20
- const reQuote = new RegExp(quote, "g");
21
- const reduce = rfn[2];
22
- let headerDone = false;
23
- return compR(rfn, (acc, row) => {
24
- if (!headerDone) {
25
- if (!header && !isArray(row)) {
26
- header = Object.keys(row);
27
- }
28
- colTx = isArray(cols)
29
- ? cols
30
- : header
31
- ? header.map((id) => cols[id])
32
- : [];
33
- }
34
- const $row = isArray(row)
35
- ? row
36
- : header.map((k) => row[k]);
37
- const line = (header || $row)
38
- .map((_, i) => {
39
- const val = $row[i];
40
- const cell = val != null
41
- ? colTx[i]
42
- ? colTx[i](val)
43
- : String(val)
44
- : "";
45
- return cell.indexOf(quote) !== -1
46
- ? wrap(quote)(cell.replace(reQuote, `${quote}${quote}`))
47
- : cell;
48
- })
49
- .join(delim);
50
- if (!headerDone) {
51
- if (header) {
52
- acc = reduce(acc, header.join(delim));
53
- }
54
- else {
55
- header = $row;
56
- }
57
- headerDone = true;
58
- !isReduced(acc) && (acc = reduce(acc, line));
59
- return acc;
60
- }
61
- else {
62
- return reduce(acc, line);
63
- }
64
- });
65
- };
9
+ function formatCSV(opts, src) {
10
+ return isIterable(src) ? iterator(formatCSV(opts), src) : (rfn) => {
11
+ let { header, cols, delim, quote } = {
12
+ delim: ",",
13
+ quote: `"`,
14
+ cols: [],
15
+ ...opts
16
+ };
17
+ let colTx;
18
+ const reQuote = new RegExp(quote, "g");
19
+ const reduce = rfn[2];
20
+ let headerDone = false;
21
+ return compR(rfn, (acc, row) => {
22
+ if (!headerDone) {
23
+ if (!header && !isArray(row)) {
24
+ header = Object.keys(row);
25
+ }
26
+ colTx = isArray(cols) ? cols : header ? header.map(
27
+ (id) => cols[id]
28
+ ) : [];
29
+ }
30
+ const $row = isArray(row) ? row : header.map((k) => row[k]);
31
+ const line = (header || $row).map((_, i) => {
32
+ const val = $row[i];
33
+ const cell = val != null ? colTx[i] ? colTx[i](val) : String(val) : "";
34
+ return cell.indexOf(quote) !== -1 ? wrap(quote)(
35
+ cell.replace(
36
+ reQuote,
37
+ `${quote}${quote}`
38
+ )
39
+ ) : cell;
40
+ }).join(delim);
41
+ if (!headerDone) {
42
+ if (header) {
43
+ acc = reduce(acc, header.join(delim));
44
+ } else {
45
+ header = $row;
46
+ }
47
+ headerDone = true;
48
+ !isReduced(acc) && (acc = reduce(acc, line));
49
+ return acc;
50
+ } else {
51
+ return reduce(acc, line);
52
+ }
53
+ });
54
+ };
66
55
  }
67
- export const formatCSVString = (opts = {}, src) => transduce(formatCSV(opts), str(opts.rowDelim || "\n"), src);
56
+ const formatCSVString = (opts = {}, src) => transduce(formatCSV(opts), str(opts.rowDelim || "\n"), src);
57
+ export {
58
+ formatCSV,
59
+ formatCSVString
60
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/csv",
3
- "version": "2.3.46",
3
+ "version": "2.3.48",
4
4
  "description": "Customizable, transducer-based CSV parser/object mapper and transformer",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,14 +35,14 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.10",
37
- "@thi.ng/checks": "^3.4.10",
38
- "@thi.ng/strings": "^3.7.1",
39
- "@thi.ng/transducers": "^8.8.13"
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/checks": "^3.4.12",
40
+ "@thi.ng/strings": "^3.7.3",
41
+ "@thi.ng/transducers": "^8.8.15"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@microsoft/api-extractor": "^7.38.3",
43
- "@thi.ng/testament": "^0.4.3",
45
+ "esbuild": "^0.19.8",
44
46
  "rimraf": "^5.0.5",
45
47
  "tools": "^0.0.1",
46
48
  "typedoc": "^0.25.4",
@@ -84,5 +86,5 @@
84
86
  "thi.ng": {
85
87
  "year": 2014
86
88
  },
87
- "gitHead": "04d1de79f256d7a53c6b5fd157b37f49bc88e11d\n"
89
+ "gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
88
90
  }
package/parse.js CHANGED
@@ -5,204 +5,179 @@ import { ESCAPES } from "@thi.ng/strings/escape";
5
5
  import { split } from "@thi.ng/strings/split";
6
6
  import { compR } from "@thi.ng/transducers/compr";
7
7
  import { iterator1 } from "@thi.ng/transducers/iterator";
8
- /**
9
- * Default parser options.
10
- *
11
- * @internal
12
- */
13
8
  const DEFAULT_OPTS = {
14
- delim: ",",
15
- quote: '"',
16
- comment: "#",
17
- trim: false,
9
+ delim: ",",
10
+ quote: '"',
11
+ comment: "#",
12
+ trim: false
18
13
  };
19
- export function parseCSV(opts, src) {
20
- return isIterable(src)
21
- ? iterator1(parseCSV(opts), src)
22
- : (rfn) => {
23
- const { all, cols, delim, quote, comment, trim, header } = {
24
- all: true,
25
- ...DEFAULT_OPTS,
26
- ...opts,
27
- };
28
- const reduce = rfn[2];
29
- let index;
30
- let revIndex;
31
- let first = true;
32
- let isQuoted = false;
33
- let record = [];
34
- const init = (header) => {
35
- cols && (index = initIndex(header, cols));
36
- all && (revIndex = initRevIndex(header));
37
- first = false;
38
- };
39
- const collectAll = (row) => record.reduce((acc, x, i) => ((acc[revIndex[i]] = trim ? x.trim() : x), acc), row);
40
- const collectIndexed = (row) => Object.entries(index).reduce((acc, [id, { i, spec }]) => {
41
- let val = record[i];
42
- if (val !== undefined) {
43
- trim && (val = val.trim());
44
- all && spec.alias && delete acc[id];
45
- acc[spec.alias || id] = spec.tx
46
- ? spec.tx(val, acc)
47
- : val;
48
- }
49
- return acc;
50
- }, row);
51
- header && init(header);
52
- return compR(rfn, (acc, line) => {
53
- if ((!line.length || line.startsWith(comment)) &&
54
- !isQuoted)
55
- return acc;
56
- if (!first) {
57
- isQuoted = parseLine(line, record, isQuoted, delim, quote);
58
- if (isQuoted)
59
- return acc;
60
- const row = {};
61
- all && collectAll(row);
62
- index && collectIndexed(row);
63
- record = [];
64
- return reduce(acc, row);
65
- }
66
- else {
67
- isQuoted = parseLine(line, record, isQuoted, delim, quote);
68
- if (!isQuoted) {
69
- init(record);
70
- record = [];
71
- }
72
- return acc;
73
- }
74
- });
75
- };
14
+ function parseCSV(opts, src) {
15
+ return isIterable(src) ? iterator1(parseCSV(opts), src) : (rfn) => {
16
+ const { all, cols, delim, quote, comment, trim, header } = {
17
+ all: true,
18
+ ...DEFAULT_OPTS,
19
+ ...opts
20
+ };
21
+ const reduce = rfn[2];
22
+ let index;
23
+ let revIndex;
24
+ let first = true;
25
+ let isQuoted = false;
26
+ let record = [];
27
+ const init = (header2) => {
28
+ cols && (index = initIndex(header2, cols));
29
+ all && (revIndex = initRevIndex(header2));
30
+ first = false;
31
+ };
32
+ const collectAll = (row) => record.reduce(
33
+ (acc, x, i) => (acc[revIndex[i]] = trim ? x.trim() : x, acc),
34
+ row
35
+ );
36
+ const collectIndexed = (row) => Object.entries(index).reduce((acc, [id, { i, spec }]) => {
37
+ let val = record[i];
38
+ if (val !== void 0) {
39
+ trim && (val = val.trim());
40
+ all && spec.alias && delete acc[id];
41
+ acc[spec.alias || id] = spec.tx ? spec.tx(val, acc) : val;
42
+ }
43
+ return acc;
44
+ }, row);
45
+ header && init(header);
46
+ return compR(rfn, (acc, line) => {
47
+ if ((!line.length || line.startsWith(comment)) && !isQuoted)
48
+ return acc;
49
+ if (!first) {
50
+ isQuoted = parseLine(
51
+ line,
52
+ record,
53
+ isQuoted,
54
+ delim,
55
+ quote
56
+ );
57
+ if (isQuoted)
58
+ return acc;
59
+ const row = {};
60
+ all && collectAll(row);
61
+ index && collectIndexed(row);
62
+ record = [];
63
+ return reduce(acc, row);
64
+ } else {
65
+ isQuoted = parseLine(
66
+ line,
67
+ record,
68
+ isQuoted,
69
+ delim,
70
+ quote
71
+ );
72
+ if (!isQuoted) {
73
+ init(record);
74
+ record = [];
75
+ }
76
+ return acc;
77
+ }
78
+ });
79
+ };
76
80
  }
77
- export function parseCSVSimple(opts, src) {
78
- return isIterable(src)
79
- ? iterator1(parseCSVSimple(opts), src)
80
- : (rfn) => {
81
- const { cols, delim, quote, comment, trim, header } = {
82
- header: true,
83
- ...DEFAULT_OPTS,
84
- ...opts,
85
- };
86
- const reduce = rfn[2];
87
- let first = header;
88
- let isQuoted = false;
89
- let record = [];
90
- const collect = () => cols.reduce((acc, col, i) => {
91
- if (col) {
92
- let val = record[i];
93
- if (val !== undefined) {
94
- trim && (val = val.trim());
95
- acc.push(isFunction(col) ? col(val, acc) : val);
96
- }
97
- }
98
- return acc;
99
- }, []);
100
- return compR(rfn, (acc, line) => {
101
- if ((!line.length || line.startsWith(comment)) &&
102
- !isQuoted)
103
- return acc;
104
- if (!first) {
105
- isQuoted = parseLine(line, record, isQuoted, delim, quote);
106
- if (isQuoted)
107
- return acc;
108
- const row = cols ? collect() : record;
109
- record = [];
110
- return reduce(acc, row);
111
- }
112
- else {
113
- isQuoted = parseLine(line, record, isQuoted, delim, quote);
114
- first = false;
115
- record = [];
116
- return acc;
117
- }
118
- });
119
- };
81
+ function parseCSVSimple(opts, src) {
82
+ return isIterable(src) ? iterator1(parseCSVSimple(opts), src) : (rfn) => {
83
+ const { cols, delim, quote, comment, trim, header } = {
84
+ header: true,
85
+ ...DEFAULT_OPTS,
86
+ ...opts
87
+ };
88
+ const reduce = rfn[2];
89
+ let first = header;
90
+ let isQuoted = false;
91
+ let record = [];
92
+ const collect = () => cols.reduce((acc, col, i) => {
93
+ if (col) {
94
+ let val = record[i];
95
+ if (val !== void 0) {
96
+ trim && (val = val.trim());
97
+ acc.push(isFunction(col) ? col(val, acc) : val);
98
+ }
99
+ }
100
+ return acc;
101
+ }, []);
102
+ return compR(rfn, (acc, line) => {
103
+ if ((!line.length || line.startsWith(comment)) && !isQuoted)
104
+ return acc;
105
+ if (!first) {
106
+ isQuoted = parseLine(
107
+ line,
108
+ record,
109
+ isQuoted,
110
+ delim,
111
+ quote
112
+ );
113
+ if (isQuoted)
114
+ return acc;
115
+ const row = cols ? collect() : record;
116
+ record = [];
117
+ return reduce(acc, row);
118
+ } else {
119
+ isQuoted = parseLine(
120
+ line,
121
+ record,
122
+ isQuoted,
123
+ delim,
124
+ quote
125
+ );
126
+ first = false;
127
+ record = [];
128
+ return acc;
129
+ }
130
+ });
131
+ };
120
132
  }
121
- /**
122
- * Syntax sugar for iterator version of {@link parseCSV}, efficiently splitting
123
- * given source string into a line based input using
124
- * [`split()`](https://docs.thi.ng/umbrella/strings/functions/split.html).
125
- *
126
- * @param opts -
127
- * @param src -
128
- */
129
- export const parseCSVFromString = (opts, src) => parseCSV(opts, split(src));
130
- /**
131
- * Syntax sugar for iterator version of {@link parseCSVSimple}, efficiently
132
- * splitting given source string into a line based input using
133
- * [`split()`](https://docs.thi.ng/umbrella/strings/functions/split.html).
134
- *
135
- * @param opts -
136
- * @param src -
137
- */
138
- export const parseCSVSimpleFromString = (opts, src) => parseCSVSimple(opts, split(src));
139
- /**
140
- * Parses line into `acc`, taking quoted cell values and linebreaks into
141
- * account.
142
- *
143
- * @remarks
144
- * If `isQuoted` is true, the previous line ended with a quoted cell value,
145
- * which might only end in the new or a future line. If that's the case, then
146
- * the current line's contents will be added to the current last value of `acc`
147
- * until the quoted cell is complete.
148
- *
149
- * Function returns current state of `isQuoted` (i.e. if line terminated in a
150
- * quoted cell) and should be (re)called with new lines until it returns false.
151
- *
152
- * @param line -
153
- * @param acc -
154
- * @param isQuoted -
155
- * @param delim -
156
- * @param quote -
157
- */
133
+ const parseCSVFromString = (opts, src) => parseCSV(opts, split(src));
134
+ const parseCSVSimpleFromString = (opts, src) => parseCSVSimple(opts, split(src));
158
135
  const parseLine = (line, acc, isQuoted, delim, quote) => {
159
- let curr = "";
160
- let p = "";
161
- let openQuote = isQuoted;
162
- for (let i = 0, n = line.length; i < n; i++) {
163
- const c = line[i];
164
- // escaped char
165
- if (p === "\\") {
166
- curr += ESCAPES[c] || c;
167
- }
168
- // quote open/close & CSV escape pair (aka `""`)
169
- else if (c === quote) {
170
- if (!isQuoted) {
171
- p = "";
172
- isQuoted = true;
173
- continue;
174
- }
175
- else if (p === quote) {
176
- curr += quote;
177
- p = "";
178
- continue;
179
- }
180
- else if (line[i + 1] !== quote)
181
- isQuoted = false;
182
- }
183
- // field delimiter
184
- else if (!isQuoted && c === delim) {
185
- collectCell(acc, curr, openQuote);
186
- openQuote = false;
187
- curr = "";
188
- }
189
- // record unless escape seq start
190
- else if (c !== "\\") {
191
- curr += c;
192
- }
193
- p = c;
136
+ let curr = "";
137
+ let p = "";
138
+ let openQuote = isQuoted;
139
+ for (let i = 0, n = line.length; i < n; i++) {
140
+ const c = line[i];
141
+ if (p === "\\") {
142
+ curr += ESCAPES[c] || c;
143
+ } else if (c === quote) {
144
+ if (!isQuoted) {
145
+ p = "";
146
+ isQuoted = true;
147
+ continue;
148
+ } else if (p === quote) {
149
+ curr += quote;
150
+ p = "";
151
+ continue;
152
+ } else if (line[i + 1] !== quote)
153
+ isQuoted = false;
154
+ } else if (!isQuoted && c === delim) {
155
+ collectCell(acc, curr, openQuote);
156
+ openQuote = false;
157
+ curr = "";
158
+ } else if (c !== "\\") {
159
+ curr += c;
194
160
  }
195
- curr !== "" && collectCell(acc, curr, openQuote);
196
- return isQuoted;
161
+ p = c;
162
+ }
163
+ curr !== "" && collectCell(acc, curr, openQuote);
164
+ return isQuoted;
165
+ };
166
+ const collectCell = (acc, curr, openQuote) => openQuote ? acc[acc.length - 1] += "\n" + curr : acc.push(curr);
167
+ const initIndex = (line, cols) => isArray(cols) ? cols.reduce((acc, spec, i) => {
168
+ if (spec) {
169
+ const alias = spec.alias || line[i] || String(i);
170
+ acc[alias] = { i, spec: { alias, ...spec } };
171
+ }
172
+ return acc;
173
+ }, {}) : line.reduce(
174
+ (acc, id, i) => cols[id] ? (acc[id] = { i, spec: cols[id] }, acc) : acc,
175
+ {}
176
+ );
177
+ const initRevIndex = (line) => line.reduce((acc, x, i) => (acc[i] = x, acc), {});
178
+ export {
179
+ parseCSV,
180
+ parseCSVFromString,
181
+ parseCSVSimple,
182
+ parseCSVSimpleFromString
197
183
  };
198
- const collectCell = (acc, curr, openQuote) => openQuote ? (acc[acc.length - 1] += "\n" + curr) : acc.push(curr);
199
- const initIndex = (line, cols) => isArray(cols)
200
- ? cols.reduce((acc, spec, i) => {
201
- if (spec) {
202
- const alias = spec.alias || line[i] || String(i);
203
- acc[alias] = { i, spec: { alias, ...spec } };
204
- }
205
- return acc;
206
- }, {})
207
- : line.reduce((acc, id, i) => cols[id] ? ((acc[id] = { i, spec: cols[id] }), acc) : acc, {});
208
- const initRevIndex = (line) => line.reduce((acc, x, i) => ((acc[i] = x), acc), {});
package/transforms.js CHANGED
@@ -1,85 +1,41 @@
1
1
  import { padLeft } from "@thi.ng/strings/pad-left";
2
2
  import { maybeParseFloat, maybeParseInt } from "@thi.ng/strings/parse";
3
3
  import { percent as $percent } from "@thi.ng/strings/percent";
4
- /**
5
- * Cell parse value transform. Returns uppercased version of given input.
6
- *
7
- * @param x -
8
- */
9
- export const upper = (x) => x.toUpperCase();
10
- /**
11
- * Cell parse value transform. Returns lowercased version of given input.
12
- *
13
- * @param x -
14
- */
15
- export const lower = (x) => x.toLowerCase();
16
- /**
17
- * Higher-order cell parse value transform. Attempts to parse cell values as
18
- * floating point number or returns `defaultVal` if not possible.
19
- *
20
- * @param defaultVal -
21
- */
22
- export const float = (defaultVal = 0) => (x) => maybeParseFloat(x, defaultVal);
23
- /**
24
- * Higher-order cell parse value transform. Attempts to parse cell values as
25
- * integer or returns `defaultVal` if not possible.
26
- *
27
- * @param defaultVal -
28
- */
29
- export const int = (defaultVal = 0) => (x) => maybeParseInt(x, defaultVal, 10);
30
- /**
31
- * Higher-order cell parse value transform. Attempts to parse cell values as
32
- * hexadecimal integer or returns `defaultVal` if not possible.
33
- *
34
- * @param defaultVal -
35
- */
36
- export const hex = (defaultVal = 0) => (x) => maybeParseInt(x, defaultVal, 16);
37
- export const percent = (x) => maybeParseFloat(x) * 0.01;
38
- /**
39
- * Higher-order cell parse value transform. Attempts to parse cell values as
40
- * Unix epoch/timestamp or returns `defaultVal` if not possible.
41
- *
42
- * @param defaultVal -
43
- */
44
- export const epoch = (defaultVal = 0) => (x) => {
45
- const res = Date.parse(x);
46
- return isNaN(res) ? defaultVal : res;
4
+ const upper = (x) => x.toUpperCase();
5
+ const lower = (x) => x.toLowerCase();
6
+ const float = (defaultVal = 0) => (x) => maybeParseFloat(x, defaultVal);
7
+ const int = (defaultVal = 0) => (x) => maybeParseInt(x, defaultVal, 10);
8
+ const hex = (defaultVal = 0) => (x) => maybeParseInt(x, defaultVal, 16);
9
+ const percent = (x) => maybeParseFloat(x) * 0.01;
10
+ const epoch = (defaultVal = 0) => (x) => {
11
+ const res = Date.parse(x);
12
+ return isNaN(res) ? defaultVal : res;
47
13
  };
48
- /**
49
- * Higher-order cell parse value transform. Attempts to parse cell values as JS
50
- * `Date` instance or returns `defaultVal` if not possible.
51
- *
52
- * @param defaultVal -
53
- */
54
- export const date = (defaultVal) => (x) => {
55
- const epoch = Date.parse(x);
56
- if (isNaN(epoch))
57
- return defaultVal;
58
- const res = new Date();
59
- res.setTime(epoch);
60
- return res;
14
+ const date = (defaultVal) => (x) => {
15
+ const epoch2 = Date.parse(x);
16
+ if (isNaN(epoch2))
17
+ return defaultVal;
18
+ const res = /* @__PURE__ */ new Date();
19
+ res.setTime(epoch2);
20
+ return res;
21
+ };
22
+ const url = (x) => new URL(x);
23
+ const oneOf = (mappings, defaultVal) => (x) => mappings[x] ?? defaultVal;
24
+ const zeroPad = (digits) => padLeft(digits, "0");
25
+ const formatFloat = (prec = 2) => (x) => x.toFixed(prec);
26
+ const formatPercent = $percent;
27
+ export {
28
+ date,
29
+ epoch,
30
+ float,
31
+ formatFloat,
32
+ formatPercent,
33
+ hex,
34
+ int,
35
+ lower,
36
+ oneOf,
37
+ percent,
38
+ upper,
39
+ url,
40
+ zeroPad
61
41
  };
62
- /**
63
- * Cell parse value transform. Attempts to parse cell values as JS `URL`
64
- * instances.
65
- *
66
- * @param x -
67
- */
68
- export const url = (x) => new URL(x);
69
- /**
70
- * Cell parse value transform. Accepts an object of mappings with original cell
71
- * values as keys which are then mapped to arbitrary new values in an enum like
72
- * fashion.
73
- *
74
- * @param mappings
75
- * @param defaultVal
76
- */
77
- export const oneOf = (mappings, defaultVal) => (x) => mappings[x] ?? defaultVal;
78
- // formatters
79
- export const zeroPad = (digits) => padLeft(digits, "0");
80
- export const formatFloat = (prec = 2) => (x) => x.toFixed(prec);
81
- /**
82
- * Higher order cell value formatter. Takes normalized values and formats them
83
- * as percentage with given `precision` (number of fractional digits).
84
- */
85
- export const formatPercent = $percent;