@borgar/fx 4.3.1 → 4.5.0

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/lib/translate.js CHANGED
@@ -34,18 +34,19 @@ const settings = {
34
34
  * @param {string} anchorCell A simple string reference to an A1 cell ID (`AF123` or`$C$5`).
35
35
  * @param {Object} [options={}] The options
36
36
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
37
+ * @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
37
38
  * @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
38
39
  */
39
- export function translateToR1C1 (fx, anchorCell, { xlsx = false } = {}) {
40
+ export function translateToR1C1 (fx, anchorCell, { xlsx = false, allowTernary = true } = {}) {
40
41
  const { top, left } = fromA1(anchorCell);
41
42
  const isString = typeof fx === 'string';
42
43
 
43
44
  const tokens = isString
44
- ? tokenize(fx, { ...settings, xlsx })
45
+ ? tokenize(fx, { ...settings, xlsx, allowTernary })
45
46
  : fx;
46
47
 
47
48
  let offsetSkew = 0;
48
- const refOpts = { xlsx, allowTernary: true };
49
+ const refOpts = { xlsx, allowTernary };
49
50
  tokens.forEach(token => {
50
51
  if (isRange(token)) {
51
52
  const tokenValue = token.value;
@@ -108,6 +109,7 @@ function toFixed (val, abs, base, max, wrapEdges = true) {
108
109
  const defaultOptions = {
109
110
  wrapEdges: true,
110
111
  mergeRefs: true,
112
+ allowTernary: true,
111
113
  xlsx: false
112
114
  };
113
115
 
@@ -148,6 +150,7 @@ const defaultOptions = {
148
150
  * @param {boolean} [options.wrapEdges=true] Wrap out-of-bounds ranges around sheet edges rather than turning them to #REF! errors
149
151
  * @param {boolean} [options.mergeRefs=true] Should ranges be treated as whole references (`Sheet1!A1:B2`) or as separate tokens for each part: (`Sheet1`,`!`,`A1`,`:`,`B2`).
150
152
  * @param {boolean} [options.xlsx=false] Switches to the `[1]Sheet1!A1` or `[1]!name` prefix syntax form for external workbooks. See: [Prefixes.md](./Prefixes.md)
153
+ * @param {boolean} [options.allowTernary=true] Enables the recognition of ternary ranges in the style of `A1:A` or `A1:1`. These are supported by Google Sheets but not Excel. See: References.md.
151
154
  * @return {(string | Array<Object>)} A formula string or token list (depending on which was input)
152
155
  */
153
156
  export function translateToA1 (formula, anchorCell, options = defaultOptions) {
@@ -160,13 +163,13 @@ export function translateToA1 (formula, anchorCell, options = defaultOptions) {
160
163
  withLocation: false,
161
164
  mergeRefs: opts.mergeRefs,
162
165
  xlsx: opts.xlsx,
163
- allowTernary: true,
166
+ allowTernary: opts.allowTernary,
164
167
  r1c1: true
165
168
  })
166
169
  : formula;
167
170
 
168
171
  let offsetSkew = 0;
169
- const refOpts = { xlsx: opts.xlsx, allowTernary: true };
172
+ const refOpts = { xlsx: opts.xlsx, allowTernary: opts.allowTernary };
170
173
  tokens.forEach(token => {
171
174
  if (isRange(token)) {
172
175
  const tokenValue = token.value;
@@ -0,0 +1,21 @@
1
+ import { test, Test } from 'tape';
2
+ import { translateToR1C1, translateToA1 } from './translate.js';
3
+
4
+ Test.prototype.okayRoundTrip = function roundTrip (expr, anchor, options) {
5
+ const rc = translateToR1C1(expr, anchor, options);
6
+ const a1 = translateToA1(rc, anchor, options);
7
+ this.is(a1, expr, 'Round trip: ' + expr);
8
+ };
9
+
10
+ test('translate absolute cells from A1 to RC', t => {
11
+ t.okayRoundTrip('=Sheet1!$1:$1048576', 'A1');
12
+ t.okayRoundTrip('=D$1:$BJ$1048576', 'A1');
13
+ t.okayRoundTrip('=VLOOKUP(C7,Röðun,4,0)', 'A1');
14
+ t.okayRoundTrip('=COUNTIF(B$1442:B$1048576,$G1442)', 'A1');
15
+ t.okayRoundTrip('=IF(p2m<=D5,10,0)*scene_spend', 'A1');
16
+ t.okayRoundTrip('=(kwh_used_daily*kwhbtu*co2btu)/1000000', 'A1');
17
+ t.okayRoundTrip('=NOPLATT1+g1_+ROIC1+WACC+G1+g1_+G130+ROIC2+WACC+g2_+WACC+N', 'A1');
18
+ // FIXME: translate needs to be be able to specify allowTernary=false
19
+ t.okayRoundTrip('=foo:C3:D4', 'A1', { allowTernary: false });
20
+ t.end();
21
+ });
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@borgar/fx",
3
- "version": "4.3.1",
3
+ "version": "4.5.0",
4
4
  "description": "Utilities for working with Excel formulas",
5
5
  "main": "dist/fx.js",
6
+ "types": "dist/fx.d.ts",
6
7
  "module": "lib/index.js",
7
8
  "exports": {
8
9
  ".": {
@@ -15,7 +16,8 @@
15
16
  "version": "npm run build",
16
17
  "lint": "eslint lib/*.js",
17
18
  "test": "tape lib/*.spec.js | tap-min",
18
- "build:docs": "echo '# _Fx_ API\n'>docs/API.md; jsdoc -c .jsdoc/config.json -d console lib>>docs/API.md",
19
+ "build:types": "jsdoc -c tsd.json lib>dist/fx.d.ts",
20
+ "build:docs": "echo '# _Fx_ API\n'>docs/API.md; jsdoc -t node_modules/@borgar/jsdoc-tsmd -d console lib>>docs/API.md",
19
21
  "build": "NODE_ENV=production rollup -c"
20
22
  },
21
23
  "repository": {
@@ -39,17 +41,19 @@
39
41
  "author": "Borgar Þorsteinsson <borgar@borgar.net> (http://borgar.net/)",
40
42
  "license": "MIT",
41
43
  "devDependencies": {
42
- "@babel/core": "~7.21.0",
43
- "@babel/eslint-parser": "~7.19.1",
44
- "@babel/preset-env": "~7.20.2",
45
- "@borgar/eslint-config": "~3.0.0",
44
+ "@babel/core": "~7.23.0",
45
+ "@babel/eslint-parser": "~7.22.15",
46
+ "@babel/preset-env": "~7.22.20",
47
+ "@borgar/eslint-config": "~3.1.0",
48
+ "@borgar/jsdoc-tsmd": "~0.1.0",
46
49
  "@rollup/plugin-babel": "~6.0.3",
47
50
  "babel-eslint": "~10.1.0",
48
- "eslint": "~8.34.0",
51
+ "eslint": "~8.50.0",
49
52
  "jsdoc": "~4.0.2",
50
- "rollup": "~3.17.2",
53
+ "rollup": "~3.29.4",
51
54
  "rollup-plugin-minification": "~0.2.0",
52
- "tap-min": "~2.0.0",
53
- "tape": "~5.6.3"
55
+ "tap-min": "~3.0.0",
56
+ "typescript": "~5.2.2",
57
+ "tape": "~5.7.0"
54
58
  }
55
59
  }
package/tsd.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "source": {
3
+ "includePattern": ".+\\.js(doc|x)?$",
4
+ "excludePattern": "((^|\\/|\\\\)_|spec\\.js$)"
5
+ },
6
+ "opts": {
7
+ "template": "node_modules/@borgar/jsdoc-tsmd",
8
+ "destination": "console",
9
+ "output": "typescript",
10
+ "validate": true
11
+ }
12
+ }
@@ -1,17 +0,0 @@
1
- {
2
- "source": {
3
- "includePattern": ".+\\.js(doc|x)?$",
4
- "excludePattern": "((^|\\/|\\\\)_|spec\\.js$)"
5
- },
6
- "opts": {
7
- "template": "./.jsdoc",
8
- "encoding": "utf8",
9
- "destination": "./docs/"
10
- },
11
- "templates": {
12
- "default": {
13
- "includeDate": false,
14
- "outputSourceFiles": false
15
- }
16
- }
17
- }
package/.jsdoc/publish.js DELETED
@@ -1,217 +0,0 @@
1
- /* eslint-disable no-undefined, no-console */
2
-
3
- function formatType (d, inTable = false) {
4
- return d.names?.map(n => `\`${n}\``).join(inTable ? ' \\| ' : ' | ') || '';
5
- }
6
-
7
- function formatArguments (args, showDefaults = true) {
8
- let inOpt = false;
9
- let r = args.reduce((a, d, i) => {
10
- if (d.name.includes('.')) {
11
- return a;
12
- }
13
- if (inOpt && !d.optional) {
14
- a += ']_';
15
- inOpt = false;
16
- }
17
- if (i) {
18
- a += ', ';
19
- }
20
- if (!inOpt && d.optional) {
21
- a += '_[';
22
- inOpt = true;
23
- }
24
- a += d.name;
25
- if (showDefaults && d.defaultvalue) {
26
- a += ' = `' + d.defaultvalue + '`';
27
- }
28
- return a;
29
- }, '');
30
- if (inOpt) {
31
- r += ']_';
32
- }
33
- return r;
34
- }
35
-
36
- function repValue (v) {
37
- if (v === undefined) {
38
- return '';
39
- }
40
- if (v === "''") {
41
- // FIXME: should transform all '' strings to "" style
42
- v = '""';
43
- }
44
- return '`' + v + '`';
45
- }
46
-
47
- function repArgName (name, optional = false) {
48
- const names = name.split('.');
49
- const nPath = names.length > 1 ? '.' + names.slice(1).join('.') : '';
50
- return (optional ? '_[' : '') + names[0] + (optional ? ']_' : '') + nPath;
51
- }
52
-
53
- function listToTable (args) {
54
- const useDefaults = args.some(d => !!d.defaultvalue);
55
- const table = useDefaults
56
- ? [
57
- '| Name | Type | Default | Description |',
58
- '| ---- | ---- | ------- | ----------- |' ]
59
- : [
60
- '| Name | Type | Description |',
61
- '| ---- | ---- | ----------- |' ];
62
- args.forEach(d => {
63
- const row = [ repArgName(d.name, d.optional) ];
64
- row.push(formatType(d.type, true));
65
- useDefaults && row.push(repValue(d.defaultvalue));
66
- row.push(transformLinks(d.description));
67
- table.push('| ' + row.join(' | ') + ' |');
68
- });
69
-
70
- return table.join('\n');
71
- }
72
-
73
- function adjustLineBreaks (md) {
74
- return md.trim().replace(
75
- /(?:```([^\0]*?)```|(\S)\n(?!\n))/g,
76
- (a, b, c) => {
77
- return (c ? (c + ' ') : a);
78
- }
79
- );
80
- }
81
-
82
- function formatSee (see) {
83
- if (see) {
84
- if (/^[a-z][a-z0-9]*$/i.test(see)) {
85
- see = `{@link ${see}}`;
86
- }
87
- return `\n**See also:** ${transformLinks(see)}.\n`;
88
- }
89
- return '';
90
- }
91
-
92
- const re_links = /(?:\[([^\]]+)\])?\{@link(?:code|plain)? ([^} ]+)([^}]+)?\}/g;
93
- function transformLinks (md) {
94
- return md.replace(re_links, (a, preLabel, href, postLabel) => {
95
- const hash = /^[a-z][a-z0-9]*$/i.test(href) ? '#' : '';
96
- return `[${preLabel || postLabel || href}](${hash}${href})`;
97
- });
98
- }
99
-
100
- function formatHeading (text, level = 2) {
101
- return '#'.repeat(level || 1) + ' ' + text;
102
- }
103
-
104
- function formatIndexLink (name, text) {
105
- return `[${text.replace(/([[\]])/g, '\\$1')}](#${name})`;
106
- }
107
-
108
- const formatIndex = {
109
- function: d => {
110
- return '- ' + formatIndexLink(d.name,
111
- `${d.name}( ${formatArguments(d.params, false)} )`
112
- );
113
- },
114
- constant: d => {
115
- return '- ' + formatIndexLink(d.name,
116
- `${d.name}`
117
- );
118
- }
119
- }
120
-
121
- const format = {
122
- function: d => {
123
- return [
124
- formatHeading(`<a name="${d.name}" href="#${d.name}">#</a> ${d.name}( ${formatArguments(d.params)} ) ⇒ ${formatType(d.returns[0].type)}`, 3),
125
- adjustLineBreaks(transformLinks(d.description)),
126
- formatSee(d.see),
127
- formatHeading('Parameters', 4),
128
- listToTable(d.params),
129
- formatHeading('Returns', 4),
130
- formatType(d.returns[0].type) + ' – ' + d.returns[0].description
131
- ];
132
- },
133
- constant: d => {
134
- return [
135
- formatHeading(`<a name="${d.name}" href="#${d.name}">#</a> ${d.name} ⇒ ${formatType(d.type)}`, 3),
136
- adjustLineBreaks(transformLinks(d.description)),
137
- formatSee(d.see),
138
- formatHeading('Properties', 4),
139
- listToTable(d.properties)
140
- ];
141
- }
142
- };
143
-
144
- const categoryHeadings = {
145
- package: null,
146
- function: 'Functions',
147
- constant: 'Constants'
148
- };
149
-
150
- exports.publish = (data, { destination }) => {
151
- data({ undocumented: true }).remove();
152
- const docs = data().get();
153
-
154
- const test = '';
155
- if (test) {
156
- const s = docs.find(d => d.name === test);
157
- const o = format[s.kind](s);
158
- console.log(o.join('\n\n'));
159
- return;
160
- }
161
-
162
- const categories = {};
163
- docs.forEach(d => {
164
- if (!categories[d.kind]) {
165
- categories[d.kind] = [];
166
- }
167
- categories[d.kind].push(d);
168
- });
169
-
170
- let output = [];
171
- const index = [];
172
-
173
- Object.keys(categories)
174
- .sort()
175
- .forEach(kind => {
176
- const heading = categoryHeadings[kind];
177
- if (heading == null) { return; }
178
- // emit category heading
179
- if (index.length) { index.push(''); }
180
- index.push('**' + heading + '**', '');
181
- output.push(formatHeading(heading, 2));
182
- // emit all members belonging to that category
183
- docs
184
- .filter(d => d.kind === kind)
185
- .filter(d => d.access !== 'private')
186
- .sort((a, b) => {
187
- if (a.name < b.name) { return -1; }
188
- if (a.name > b.name) { return 1; }
189
- return NaN;
190
- })
191
- .forEach((d, i) => {
192
- if (i) {
193
- output.push('---');
194
- }
195
- if (!format[d.kind]) {
196
- console.error('Unsupported member type ' + d.kind);
197
- process.exit(1);
198
- }
199
- index.push(formatIndex[d.kind](d));
200
- output.push(...format[d.kind](d));
201
- });
202
- });
203
-
204
- if (destination === 'console') {
205
- const outText = (
206
- index.map(d => d.trim()).join('\n') +
207
- '\n\n' +
208
- output.map(d => d.trim()).filter(Boolean).join('\n\n') +
209
- '\n'
210
- );
211
- console.log(outText);
212
- }
213
- else {
214
- console.error('This template only supports output to the console. Use the option "-d console" when you run JSDoc.');
215
- process.exit(1);
216
- }
217
- };