@attrx/role-morphic 0.1.0 → 0.2.1
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/README.md +151 -84
- package/dist/cast.d.mts +678 -0
- package/dist/cast.d.ts +678 -0
- package/dist/cast.js +3569 -0
- package/dist/cast.js.map +1 -0
- package/dist/cast.mjs +3536 -0
- package/dist/cast.mjs.map +1 -0
- package/dist/constants-BZdBwuvJ.d.mts +168 -0
- package/dist/constants-BZdBwuvJ.d.ts +168 -0
- package/dist/convert.d.mts +1157 -0
- package/dist/convert.d.ts +1157 -0
- package/dist/convert.js +2244 -0
- package/dist/convert.js.map +1 -0
- package/dist/convert.mjs +2148 -0
- package/dist/convert.mjs.map +1 -0
- package/dist/format-Be15LzfS.d.ts +668 -0
- package/dist/format-o4Y3jPH-.d.mts +668 -0
- package/dist/format.d.mts +3 -0
- package/dist/format.d.ts +3 -0
- package/dist/format.js +2303 -0
- package/dist/format.js.map +1 -0
- package/dist/format.mjs +2286 -0
- package/dist/format.mjs.map +1 -0
- package/dist/index.d.mts +200 -601
- package/dist/index.d.ts +200 -601
- package/dist/index.js +5536 -2493
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5349 -2494
- package/dist/index.mjs.map +1 -1
- package/dist/types-mbeS1e-k.d.mts +312 -0
- package/dist/types-mbeS1e-k.d.ts +312 -0
- package/dist/validate.d.mts +884 -0
- package/dist/validate.d.ts +884 -0
- package/dist/validate.js +713 -0
- package/dist/validate.js.map +1 -0
- package/dist/validate.mjs +669 -0
- package/dist/validate.mjs.map +1 -0
- package/package.json +21 -1
package/dist/cast.js
ADDED
|
@@ -0,0 +1,3569 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/roles/area/constants.ts
|
|
4
|
+
var AREA_UNITS = {
|
|
5
|
+
// SI Units (todos exatos)
|
|
6
|
+
square_kilometer: {
|
|
7
|
+
factor: 1e6,
|
|
8
|
+
symbol: "km\xB2",
|
|
9
|
+
singular: "square kilometer",
|
|
10
|
+
plural: "square kilometers"
|
|
11
|
+
},
|
|
12
|
+
hectare: {
|
|
13
|
+
factor: 1e4,
|
|
14
|
+
symbol: "ha",
|
|
15
|
+
singular: "hectare",
|
|
16
|
+
plural: "hectares"
|
|
17
|
+
},
|
|
18
|
+
are: {
|
|
19
|
+
factor: 100,
|
|
20
|
+
symbol: "a",
|
|
21
|
+
singular: "are",
|
|
22
|
+
plural: "ares"
|
|
23
|
+
},
|
|
24
|
+
square_meter: {
|
|
25
|
+
factor: 1,
|
|
26
|
+
symbol: "m\xB2",
|
|
27
|
+
singular: "square meter",
|
|
28
|
+
plural: "square meters"
|
|
29
|
+
},
|
|
30
|
+
square_decimeter: {
|
|
31
|
+
factor: 0.01,
|
|
32
|
+
symbol: "dm\xB2",
|
|
33
|
+
singular: "square decimeter",
|
|
34
|
+
plural: "square decimeters"
|
|
35
|
+
},
|
|
36
|
+
square_centimeter: {
|
|
37
|
+
factor: 1e-4,
|
|
38
|
+
symbol: "cm\xB2",
|
|
39
|
+
singular: "square centimeter",
|
|
40
|
+
plural: "square centimeters"
|
|
41
|
+
},
|
|
42
|
+
square_millimeter: {
|
|
43
|
+
factor: 1e-6,
|
|
44
|
+
symbol: "mm\xB2",
|
|
45
|
+
singular: "square millimeter",
|
|
46
|
+
plural: "square millimeters"
|
|
47
|
+
},
|
|
48
|
+
// Imperial/US (todos exatos, derivados de comprimento² desde 1959)
|
|
49
|
+
square_mile: {
|
|
50
|
+
factor: 2589988110336e-6,
|
|
51
|
+
symbol: "mi\xB2",
|
|
52
|
+
singular: "square mile",
|
|
53
|
+
plural: "square miles"
|
|
54
|
+
},
|
|
55
|
+
acre: {
|
|
56
|
+
factor: 4046.8564224,
|
|
57
|
+
symbol: "ac",
|
|
58
|
+
singular: "acre",
|
|
59
|
+
plural: "acres"
|
|
60
|
+
},
|
|
61
|
+
square_yard: {
|
|
62
|
+
factor: 0.83612736,
|
|
63
|
+
symbol: "yd\xB2",
|
|
64
|
+
singular: "square yard",
|
|
65
|
+
plural: "square yards"
|
|
66
|
+
},
|
|
67
|
+
square_foot: {
|
|
68
|
+
factor: 0.09290304,
|
|
69
|
+
symbol: "ft\xB2",
|
|
70
|
+
singular: "square foot",
|
|
71
|
+
plural: "square feet"
|
|
72
|
+
},
|
|
73
|
+
square_inch: {
|
|
74
|
+
factor: 64516e-8,
|
|
75
|
+
symbol: "in\xB2",
|
|
76
|
+
singular: "square inch",
|
|
77
|
+
plural: "square inches"
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var AREA_ALIASES = {
|
|
81
|
+
// square_kilometer
|
|
82
|
+
"km\xB2": "square_kilometer",
|
|
83
|
+
km2: "square_kilometer",
|
|
84
|
+
// hectare
|
|
85
|
+
ha: "hectare",
|
|
86
|
+
// are
|
|
87
|
+
a: "are",
|
|
88
|
+
// square_meter
|
|
89
|
+
"m\xB2": "square_meter",
|
|
90
|
+
m2: "square_meter",
|
|
91
|
+
// square_decimeter
|
|
92
|
+
"dm\xB2": "square_decimeter",
|
|
93
|
+
dm2: "square_decimeter",
|
|
94
|
+
// square_centimeter
|
|
95
|
+
"cm\xB2": "square_centimeter",
|
|
96
|
+
cm2: "square_centimeter",
|
|
97
|
+
// square_millimeter
|
|
98
|
+
"mm\xB2": "square_millimeter",
|
|
99
|
+
mm2: "square_millimeter",
|
|
100
|
+
// square_mile
|
|
101
|
+
"mi\xB2": "square_mile",
|
|
102
|
+
mi2: "square_mile",
|
|
103
|
+
// acre
|
|
104
|
+
ac: "acre",
|
|
105
|
+
// square_yard
|
|
106
|
+
"yd\xB2": "square_yard",
|
|
107
|
+
yd2: "square_yard",
|
|
108
|
+
// square_foot
|
|
109
|
+
"ft\xB2": "square_foot",
|
|
110
|
+
ft2: "square_foot",
|
|
111
|
+
// square_inch
|
|
112
|
+
"in\xB2": "square_inch",
|
|
113
|
+
in2: "square_inch"
|
|
114
|
+
};
|
|
115
|
+
Object.fromEntries(
|
|
116
|
+
Object.entries(AREA_UNITS).map(([unit, config]) => [
|
|
117
|
+
unit,
|
|
118
|
+
config.factor ?? 1
|
|
119
|
+
])
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// src/roles/area/convert.ts
|
|
123
|
+
function toBaseArea(variant, value) {
|
|
124
|
+
const unit = AREA_UNITS[variant];
|
|
125
|
+
if (!unit) {
|
|
126
|
+
throw new Error(`Unknown area variant: ${variant}`);
|
|
127
|
+
}
|
|
128
|
+
return value * (unit.factor ?? 1);
|
|
129
|
+
}
|
|
130
|
+
function fromBaseArea(variant, baseValue) {
|
|
131
|
+
const unit = AREA_UNITS[variant];
|
|
132
|
+
if (!unit) {
|
|
133
|
+
throw new Error(`Unknown area variant: ${variant}`);
|
|
134
|
+
}
|
|
135
|
+
return baseValue / (unit.factor ?? 1);
|
|
136
|
+
}
|
|
137
|
+
function convertArea(from, to, value) {
|
|
138
|
+
if (from === to) {
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
const baseValue = toBaseArea(from, value);
|
|
142
|
+
return fromBaseArea(to, baseValue);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/roles/area/cast.ts
|
|
146
|
+
function castArea(input, targetVariant = "square_meter") {
|
|
147
|
+
if (typeof input === "number") {
|
|
148
|
+
return Number.isFinite(input) ? input : null;
|
|
149
|
+
}
|
|
150
|
+
if (typeof input === "string") {
|
|
151
|
+
return parseAreaString(input, targetVariant);
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function tryCastArea(input, targetVariant = "square_meter") {
|
|
156
|
+
const result = castArea(input, targetVariant);
|
|
157
|
+
if (result === null) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
error: `Cannot cast "${String(input)}" to area:${targetVariant}`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return { ok: true, value: result };
|
|
164
|
+
}
|
|
165
|
+
function parseAreaString(input, targetVariant) {
|
|
166
|
+
const trimmed = input.trim().toLowerCase();
|
|
167
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
168
|
+
if (!match) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const [, numStr, unitStr] = match;
|
|
172
|
+
const numValue = parseNumber(numStr);
|
|
173
|
+
if (numValue === null) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const cleanUnitStr = unitStr.trim();
|
|
177
|
+
if (cleanUnitStr) {
|
|
178
|
+
const detectedUnit = detectUnit(cleanUnitStr);
|
|
179
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
180
|
+
return convertArea(detectedUnit, targetVariant, numValue);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return numValue;
|
|
184
|
+
}
|
|
185
|
+
function parseNumber(numStr) {
|
|
186
|
+
numStr = numStr.replace(/\s/g, "");
|
|
187
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
188
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
189
|
+
const hasComma = lastComma !== -1;
|
|
190
|
+
const hasDot = lastDot !== -1;
|
|
191
|
+
if (hasComma && hasDot) {
|
|
192
|
+
if (lastComma > lastDot) {
|
|
193
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
194
|
+
} else {
|
|
195
|
+
numStr = numStr.replace(/,/g, "");
|
|
196
|
+
}
|
|
197
|
+
} else if (hasComma && !hasDot) {
|
|
198
|
+
const afterComma = numStr.split(",").slice(1);
|
|
199
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
200
|
+
if (isThousandSep) {
|
|
201
|
+
numStr = numStr.replace(/,/g, "");
|
|
202
|
+
} else {
|
|
203
|
+
numStr = numStr.replace(",", ".");
|
|
204
|
+
}
|
|
205
|
+
} else if (!hasComma && hasDot) {
|
|
206
|
+
const afterDot = numStr.split(".").slice(1);
|
|
207
|
+
if (afterDot.length > 1) {
|
|
208
|
+
numStr = numStr.replace(/\./g, "");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const value = parseFloat(numStr);
|
|
212
|
+
return isNaN(value) ? null : value;
|
|
213
|
+
}
|
|
214
|
+
function detectUnit(unitStr) {
|
|
215
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
216
|
+
if (normalized in AREA_ALIASES) {
|
|
217
|
+
return AREA_ALIASES[normalized];
|
|
218
|
+
}
|
|
219
|
+
for (const [variant, config] of Object.entries(AREA_UNITS)) {
|
|
220
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
221
|
+
return variant;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/roles/length/constants.ts
|
|
228
|
+
var LENGTH_UNITS = {
|
|
229
|
+
// SI Units (todos exatos)
|
|
230
|
+
kilometer: {
|
|
231
|
+
factor: 1e3,
|
|
232
|
+
symbol: "km",
|
|
233
|
+
singular: "kilometer",
|
|
234
|
+
plural: "kilometers"
|
|
235
|
+
},
|
|
236
|
+
hectometer: {
|
|
237
|
+
factor: 100,
|
|
238
|
+
symbol: "hm",
|
|
239
|
+
singular: "hectometer",
|
|
240
|
+
plural: "hectometers"
|
|
241
|
+
},
|
|
242
|
+
decameter: {
|
|
243
|
+
factor: 10,
|
|
244
|
+
symbol: "dam",
|
|
245
|
+
singular: "decameter",
|
|
246
|
+
plural: "decameters"
|
|
247
|
+
},
|
|
248
|
+
meter: {
|
|
249
|
+
factor: 1,
|
|
250
|
+
symbol: "m",
|
|
251
|
+
singular: "meter",
|
|
252
|
+
plural: "meters"
|
|
253
|
+
},
|
|
254
|
+
decimeter: {
|
|
255
|
+
factor: 0.1,
|
|
256
|
+
symbol: "dm",
|
|
257
|
+
singular: "decimeter",
|
|
258
|
+
plural: "decimeters"
|
|
259
|
+
},
|
|
260
|
+
centimeter: {
|
|
261
|
+
factor: 0.01,
|
|
262
|
+
symbol: "cm",
|
|
263
|
+
singular: "centimeter",
|
|
264
|
+
plural: "centimeters"
|
|
265
|
+
},
|
|
266
|
+
millimeter: {
|
|
267
|
+
factor: 1e-3,
|
|
268
|
+
symbol: "mm",
|
|
269
|
+
singular: "millimeter",
|
|
270
|
+
plural: "millimeters"
|
|
271
|
+
},
|
|
272
|
+
micrometer: {
|
|
273
|
+
factor: 1e-6,
|
|
274
|
+
symbol: "\u03BCm",
|
|
275
|
+
singular: "micrometer",
|
|
276
|
+
plural: "micrometers"
|
|
277
|
+
},
|
|
278
|
+
nanometer: {
|
|
279
|
+
factor: 1e-9,
|
|
280
|
+
symbol: "nm",
|
|
281
|
+
singular: "nanometer",
|
|
282
|
+
plural: "nanometers"
|
|
283
|
+
},
|
|
284
|
+
// Imperial/US (exatos desde 1959)
|
|
285
|
+
inch: {
|
|
286
|
+
factor: 0.0254,
|
|
287
|
+
symbol: "in",
|
|
288
|
+
singular: "inch",
|
|
289
|
+
plural: "inches"
|
|
290
|
+
},
|
|
291
|
+
foot: {
|
|
292
|
+
factor: 0.3048,
|
|
293
|
+
symbol: "ft",
|
|
294
|
+
singular: "foot",
|
|
295
|
+
plural: "feet"
|
|
296
|
+
},
|
|
297
|
+
yard: {
|
|
298
|
+
factor: 0.9144,
|
|
299
|
+
symbol: "yd",
|
|
300
|
+
singular: "yard",
|
|
301
|
+
plural: "yards"
|
|
302
|
+
},
|
|
303
|
+
mile: {
|
|
304
|
+
factor: 1609.344,
|
|
305
|
+
symbol: "mi",
|
|
306
|
+
singular: "mile",
|
|
307
|
+
plural: "miles"
|
|
308
|
+
},
|
|
309
|
+
// Nautical (exato por definição internacional)
|
|
310
|
+
nautical_mile: {
|
|
311
|
+
factor: 1852,
|
|
312
|
+
symbol: "nmi",
|
|
313
|
+
singular: "nautical mile",
|
|
314
|
+
plural: "nautical miles"
|
|
315
|
+
},
|
|
316
|
+
// Other (derivados, portanto exatos)
|
|
317
|
+
fathom: {
|
|
318
|
+
factor: 1.8288,
|
|
319
|
+
// 6 feet
|
|
320
|
+
symbol: "ftm",
|
|
321
|
+
singular: "fathom",
|
|
322
|
+
plural: "fathoms"
|
|
323
|
+
},
|
|
324
|
+
furlong: {
|
|
325
|
+
factor: 201.168,
|
|
326
|
+
// 660 feet = 1/8 mile
|
|
327
|
+
symbol: "fur",
|
|
328
|
+
singular: "furlong",
|
|
329
|
+
plural: "furlongs"
|
|
330
|
+
},
|
|
331
|
+
league: {
|
|
332
|
+
factor: 4828.032,
|
|
333
|
+
// 3 miles
|
|
334
|
+
symbol: "lea",
|
|
335
|
+
singular: "league",
|
|
336
|
+
plural: "leagues"
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
var LENGTH_ALIASES = {
|
|
340
|
+
// kilometer
|
|
341
|
+
km: "kilometer",
|
|
342
|
+
// hectometer
|
|
343
|
+
hm: "hectometer",
|
|
344
|
+
// decameter
|
|
345
|
+
dam: "decameter",
|
|
346
|
+
// meter
|
|
347
|
+
m: "meter",
|
|
348
|
+
// decimeter
|
|
349
|
+
dm: "decimeter",
|
|
350
|
+
// centimeter
|
|
351
|
+
cm: "centimeter",
|
|
352
|
+
// millimeter
|
|
353
|
+
mm: "millimeter",
|
|
354
|
+
// micrometer
|
|
355
|
+
"\u03BCm": "micrometer",
|
|
356
|
+
um: "micrometer",
|
|
357
|
+
// nanometer
|
|
358
|
+
nm: "nanometer",
|
|
359
|
+
// inch
|
|
360
|
+
in: "inch",
|
|
361
|
+
'"': "inch",
|
|
362
|
+
// foot
|
|
363
|
+
ft: "foot",
|
|
364
|
+
"'": "foot",
|
|
365
|
+
// yard
|
|
366
|
+
yd: "yard",
|
|
367
|
+
// mile
|
|
368
|
+
mi: "mile",
|
|
369
|
+
// nautical_mile
|
|
370
|
+
nmi: "nautical_mile",
|
|
371
|
+
// fathom
|
|
372
|
+
ftm: "fathom",
|
|
373
|
+
// furlong
|
|
374
|
+
fur: "furlong",
|
|
375
|
+
// league
|
|
376
|
+
lea: "league"
|
|
377
|
+
};
|
|
378
|
+
Object.fromEntries(
|
|
379
|
+
Object.entries(LENGTH_UNITS).map(([unit, config]) => [
|
|
380
|
+
unit,
|
|
381
|
+
config.factor ?? 1
|
|
382
|
+
])
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// src/roles/length/convert.ts
|
|
386
|
+
function toBaseLength(variant, value) {
|
|
387
|
+
const unit = LENGTH_UNITS[variant];
|
|
388
|
+
if (!unit) {
|
|
389
|
+
throw new Error(`Unknown length variant: ${variant}`);
|
|
390
|
+
}
|
|
391
|
+
return value * (unit.factor ?? 1);
|
|
392
|
+
}
|
|
393
|
+
function fromBaseLength(variant, baseValue) {
|
|
394
|
+
const unit = LENGTH_UNITS[variant];
|
|
395
|
+
if (!unit) {
|
|
396
|
+
throw new Error(`Unknown length variant: ${variant}`);
|
|
397
|
+
}
|
|
398
|
+
return baseValue / (unit.factor ?? 1);
|
|
399
|
+
}
|
|
400
|
+
function convertLength(from, to, value) {
|
|
401
|
+
if (from === to) {
|
|
402
|
+
return value;
|
|
403
|
+
}
|
|
404
|
+
const baseValue = toBaseLength(from, value);
|
|
405
|
+
return fromBaseLength(to, baseValue);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/roles/length/cast.ts
|
|
409
|
+
function castLength(input, targetVariant = "meter") {
|
|
410
|
+
if (typeof input === "number") {
|
|
411
|
+
return Number.isFinite(input) ? input : null;
|
|
412
|
+
}
|
|
413
|
+
if (typeof input === "string") {
|
|
414
|
+
return parseLengthString(input, targetVariant);
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
function tryCastLength(input, targetVariant = "meter") {
|
|
419
|
+
const result = castLength(input, targetVariant);
|
|
420
|
+
if (result === null) {
|
|
421
|
+
return {
|
|
422
|
+
ok: false,
|
|
423
|
+
error: `Cannot cast "${String(input)}" to length:${targetVariant}`
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
return { ok: true, value: result };
|
|
427
|
+
}
|
|
428
|
+
function parseLengthString(input, targetVariant) {
|
|
429
|
+
const trimmed = input.trim().toLowerCase();
|
|
430
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
431
|
+
if (!match) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
const [, numStr, unitStr] = match;
|
|
435
|
+
const numValue = parseNumber2(numStr);
|
|
436
|
+
if (numValue === null) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const cleanUnitStr = unitStr.trim();
|
|
440
|
+
if (cleanUnitStr) {
|
|
441
|
+
const detectedUnit = detectUnit2(cleanUnitStr);
|
|
442
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
443
|
+
return convertLength(detectedUnit, targetVariant, numValue);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return numValue;
|
|
447
|
+
}
|
|
448
|
+
function parseNumber2(numStr) {
|
|
449
|
+
numStr = numStr.replace(/\s/g, "");
|
|
450
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
451
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
452
|
+
const hasComma = lastComma !== -1;
|
|
453
|
+
const hasDot = lastDot !== -1;
|
|
454
|
+
if (hasComma && hasDot) {
|
|
455
|
+
if (lastComma > lastDot) {
|
|
456
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
457
|
+
} else {
|
|
458
|
+
numStr = numStr.replace(/,/g, "");
|
|
459
|
+
}
|
|
460
|
+
} else if (hasComma && !hasDot) {
|
|
461
|
+
const afterComma = numStr.split(",").slice(1);
|
|
462
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
463
|
+
if (isThousandSep) {
|
|
464
|
+
numStr = numStr.replace(/,/g, "");
|
|
465
|
+
} else {
|
|
466
|
+
numStr = numStr.replace(",", ".");
|
|
467
|
+
}
|
|
468
|
+
} else if (!hasComma && hasDot) {
|
|
469
|
+
const afterDot = numStr.split(".").slice(1);
|
|
470
|
+
if (afterDot.length > 1) {
|
|
471
|
+
numStr = numStr.replace(/\./g, "");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const value = parseFloat(numStr);
|
|
475
|
+
return isNaN(value) ? null : value;
|
|
476
|
+
}
|
|
477
|
+
function detectUnit2(unitStr) {
|
|
478
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
479
|
+
if (normalized in LENGTH_ALIASES) {
|
|
480
|
+
return LENGTH_ALIASES[normalized];
|
|
481
|
+
}
|
|
482
|
+
for (const [variant, config] of Object.entries(LENGTH_UNITS)) {
|
|
483
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
484
|
+
return variant;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/roles/mass/constants.ts
|
|
491
|
+
var MASS_UNITS = {
|
|
492
|
+
// ===========================================================================
|
|
493
|
+
// SI / Metric
|
|
494
|
+
// ===========================================================================
|
|
495
|
+
metric_ton: {
|
|
496
|
+
factor: 1e3,
|
|
497
|
+
symbol: "t",
|
|
498
|
+
singular: "metric ton",
|
|
499
|
+
plural: "metric tons"
|
|
500
|
+
},
|
|
501
|
+
kilogram: {
|
|
502
|
+
factor: 1,
|
|
503
|
+
symbol: "kg",
|
|
504
|
+
singular: "kilogram",
|
|
505
|
+
plural: "kilograms"
|
|
506
|
+
},
|
|
507
|
+
hectogram: {
|
|
508
|
+
factor: 0.1,
|
|
509
|
+
symbol: "hg",
|
|
510
|
+
singular: "hectogram",
|
|
511
|
+
plural: "hectograms"
|
|
512
|
+
},
|
|
513
|
+
decagram: {
|
|
514
|
+
factor: 0.01,
|
|
515
|
+
symbol: "dag",
|
|
516
|
+
singular: "decagram",
|
|
517
|
+
plural: "decagrams"
|
|
518
|
+
},
|
|
519
|
+
gram: {
|
|
520
|
+
factor: 1e-3,
|
|
521
|
+
symbol: "g",
|
|
522
|
+
singular: "gram",
|
|
523
|
+
plural: "grams"
|
|
524
|
+
},
|
|
525
|
+
decigram: {
|
|
526
|
+
factor: 1e-4,
|
|
527
|
+
symbol: "dg",
|
|
528
|
+
singular: "decigram",
|
|
529
|
+
plural: "decigrams"
|
|
530
|
+
},
|
|
531
|
+
centigram: {
|
|
532
|
+
factor: 1e-5,
|
|
533
|
+
symbol: "cg",
|
|
534
|
+
singular: "centigram",
|
|
535
|
+
plural: "centigrams"
|
|
536
|
+
},
|
|
537
|
+
milligram: {
|
|
538
|
+
factor: 1e-6,
|
|
539
|
+
symbol: "mg",
|
|
540
|
+
singular: "milligram",
|
|
541
|
+
plural: "milligrams"
|
|
542
|
+
},
|
|
543
|
+
microgram: {
|
|
544
|
+
factor: 1e-9,
|
|
545
|
+
symbol: "\u03BCg",
|
|
546
|
+
singular: "microgram",
|
|
547
|
+
plural: "micrograms"
|
|
548
|
+
},
|
|
549
|
+
// ===========================================================================
|
|
550
|
+
// Avoirdupois (US/UK) - Sistema padrão para peso comum
|
|
551
|
+
// ===========================================================================
|
|
552
|
+
long_ton: {
|
|
553
|
+
factor: 1016.0469088,
|
|
554
|
+
// 2240 lb (exato)
|
|
555
|
+
symbol: "long tn",
|
|
556
|
+
singular: "long ton",
|
|
557
|
+
plural: "long tons"
|
|
558
|
+
},
|
|
559
|
+
short_ton: {
|
|
560
|
+
factor: 907.18474,
|
|
561
|
+
// 2000 lb (exato)
|
|
562
|
+
symbol: "sh tn",
|
|
563
|
+
singular: "short ton",
|
|
564
|
+
plural: "short tons"
|
|
565
|
+
},
|
|
566
|
+
stone: {
|
|
567
|
+
factor: 6.35029318,
|
|
568
|
+
// 14 lb (exato)
|
|
569
|
+
symbol: "st",
|
|
570
|
+
singular: "stone",
|
|
571
|
+
plural: "stone"
|
|
572
|
+
// stone não muda no plural em inglês
|
|
573
|
+
},
|
|
574
|
+
pound: {
|
|
575
|
+
factor: 0.45359237,
|
|
576
|
+
// Exato desde 1959
|
|
577
|
+
symbol: "lb",
|
|
578
|
+
singular: "pound",
|
|
579
|
+
plural: "pounds"
|
|
580
|
+
},
|
|
581
|
+
ounce: {
|
|
582
|
+
factor: 0.028349523125,
|
|
583
|
+
// 1/16 lb (exato)
|
|
584
|
+
symbol: "oz",
|
|
585
|
+
singular: "ounce",
|
|
586
|
+
plural: "ounces"
|
|
587
|
+
},
|
|
588
|
+
dram: {
|
|
589
|
+
factor: 0.0017718451953125,
|
|
590
|
+
// 1/16 oz (exato)
|
|
591
|
+
symbol: "dr",
|
|
592
|
+
singular: "dram",
|
|
593
|
+
plural: "drams"
|
|
594
|
+
},
|
|
595
|
+
grain: {
|
|
596
|
+
factor: 6479891e-11,
|
|
597
|
+
// 1/7000 lb (exato)
|
|
598
|
+
symbol: "gr",
|
|
599
|
+
singular: "grain",
|
|
600
|
+
plural: "grains"
|
|
601
|
+
},
|
|
602
|
+
// ===========================================================================
|
|
603
|
+
// Troy (metais preciosos)
|
|
604
|
+
// ===========================================================================
|
|
605
|
+
troy_pound: {
|
|
606
|
+
factor: 0.3732417216,
|
|
607
|
+
// 12 troy oz (exato)
|
|
608
|
+
symbol: "lb t",
|
|
609
|
+
singular: "troy pound",
|
|
610
|
+
plural: "troy pounds"
|
|
611
|
+
},
|
|
612
|
+
troy_ounce: {
|
|
613
|
+
factor: 0.0311034768,
|
|
614
|
+
// 480 grains (exato)
|
|
615
|
+
symbol: "oz t",
|
|
616
|
+
singular: "troy ounce",
|
|
617
|
+
plural: "troy ounces"
|
|
618
|
+
},
|
|
619
|
+
pennyweight: {
|
|
620
|
+
factor: 0.00155517384,
|
|
621
|
+
// 1/20 troy oz (exato)
|
|
622
|
+
symbol: "dwt",
|
|
623
|
+
singular: "pennyweight",
|
|
624
|
+
plural: "pennyweights"
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
var MASS_ALIASES = {
|
|
628
|
+
// metric_ton
|
|
629
|
+
t: "metric_ton",
|
|
630
|
+
// kilogram
|
|
631
|
+
kg: "kilogram",
|
|
632
|
+
// hectogram
|
|
633
|
+
hg: "hectogram",
|
|
634
|
+
// decagram
|
|
635
|
+
dag: "decagram",
|
|
636
|
+
// gram
|
|
637
|
+
g: "gram",
|
|
638
|
+
// decigram
|
|
639
|
+
dg: "decigram",
|
|
640
|
+
// centigram
|
|
641
|
+
cg: "centigram",
|
|
642
|
+
// milligram
|
|
643
|
+
mg: "milligram",
|
|
644
|
+
// microgram
|
|
645
|
+
"\u03BCg": "microgram",
|
|
646
|
+
ug: "microgram",
|
|
647
|
+
mcg: "microgram",
|
|
648
|
+
// long_ton
|
|
649
|
+
"long tn": "long_ton",
|
|
650
|
+
// short_ton
|
|
651
|
+
"sh tn": "short_ton",
|
|
652
|
+
// stone
|
|
653
|
+
st: "stone",
|
|
654
|
+
// pound
|
|
655
|
+
lb: "pound",
|
|
656
|
+
lbs: "pound",
|
|
657
|
+
"#": "pound",
|
|
658
|
+
// ounce
|
|
659
|
+
oz: "ounce",
|
|
660
|
+
// dram
|
|
661
|
+
dr: "dram",
|
|
662
|
+
// grain
|
|
663
|
+
gr: "grain",
|
|
664
|
+
// troy_pound
|
|
665
|
+
"lb t": "troy_pound",
|
|
666
|
+
// troy_ounce
|
|
667
|
+
"oz t": "troy_ounce",
|
|
668
|
+
ozt: "troy_ounce",
|
|
669
|
+
// pennyweight
|
|
670
|
+
dwt: "pennyweight"
|
|
671
|
+
};
|
|
672
|
+
Object.fromEntries(
|
|
673
|
+
Object.entries(MASS_UNITS).map(([unit, config]) => [
|
|
674
|
+
unit,
|
|
675
|
+
config.factor ?? 1
|
|
676
|
+
])
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
// src/roles/mass/convert.ts
|
|
680
|
+
function toBaseMass(variant, value) {
|
|
681
|
+
const unit = MASS_UNITS[variant];
|
|
682
|
+
if (!unit) {
|
|
683
|
+
throw new Error(`Unknown mass variant: ${variant}`);
|
|
684
|
+
}
|
|
685
|
+
return value * (unit.factor ?? 1);
|
|
686
|
+
}
|
|
687
|
+
function fromBaseMass(variant, baseValue) {
|
|
688
|
+
const unit = MASS_UNITS[variant];
|
|
689
|
+
if (!unit) {
|
|
690
|
+
throw new Error(`Unknown mass variant: ${variant}`);
|
|
691
|
+
}
|
|
692
|
+
return baseValue / (unit.factor ?? 1);
|
|
693
|
+
}
|
|
694
|
+
function convertMass(from, to, value) {
|
|
695
|
+
if (from === to) {
|
|
696
|
+
return value;
|
|
697
|
+
}
|
|
698
|
+
const baseValue = toBaseMass(from, value);
|
|
699
|
+
return fromBaseMass(to, baseValue);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/roles/mass/cast.ts
|
|
703
|
+
function castMass(input, targetVariant = "kilogram") {
|
|
704
|
+
if (typeof input === "number") {
|
|
705
|
+
return Number.isFinite(input) ? input : null;
|
|
706
|
+
}
|
|
707
|
+
if (typeof input === "string") {
|
|
708
|
+
return parseMassString(input, targetVariant);
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
function tryCastMass(input, targetVariant = "kilogram") {
|
|
713
|
+
const result = castMass(input, targetVariant);
|
|
714
|
+
if (result === null) {
|
|
715
|
+
return {
|
|
716
|
+
ok: false,
|
|
717
|
+
error: `Cannot cast "${String(input)}" to mass:${targetVariant}`
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
return { ok: true, value: result };
|
|
721
|
+
}
|
|
722
|
+
function parseMassString(input, targetVariant) {
|
|
723
|
+
const trimmed = input.trim().toLowerCase();
|
|
724
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
725
|
+
if (!match) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
const [, numStr, unitStr] = match;
|
|
729
|
+
const numValue = parseNumber3(numStr);
|
|
730
|
+
if (numValue === null) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
const cleanUnitStr = unitStr.trim();
|
|
734
|
+
if (cleanUnitStr) {
|
|
735
|
+
const detectedUnit = detectUnit3(cleanUnitStr);
|
|
736
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
737
|
+
return convertMass(detectedUnit, targetVariant, numValue);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return numValue;
|
|
741
|
+
}
|
|
742
|
+
function parseNumber3(numStr) {
|
|
743
|
+
numStr = numStr.replace(/\s/g, "");
|
|
744
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
745
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
746
|
+
const hasComma = lastComma !== -1;
|
|
747
|
+
const hasDot = lastDot !== -1;
|
|
748
|
+
if (hasComma && hasDot) {
|
|
749
|
+
if (lastComma > lastDot) {
|
|
750
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
751
|
+
} else {
|
|
752
|
+
numStr = numStr.replace(/,/g, "");
|
|
753
|
+
}
|
|
754
|
+
} else if (hasComma && !hasDot) {
|
|
755
|
+
const afterComma = numStr.split(",").slice(1);
|
|
756
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
757
|
+
if (isThousandSep) {
|
|
758
|
+
numStr = numStr.replace(/,/g, "");
|
|
759
|
+
} else {
|
|
760
|
+
numStr = numStr.replace(",", ".");
|
|
761
|
+
}
|
|
762
|
+
} else if (!hasComma && hasDot) {
|
|
763
|
+
const afterDot = numStr.split(".").slice(1);
|
|
764
|
+
if (afterDot.length > 1) {
|
|
765
|
+
numStr = numStr.replace(/\./g, "");
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const value = parseFloat(numStr);
|
|
769
|
+
return isNaN(value) ? null : value;
|
|
770
|
+
}
|
|
771
|
+
function detectUnit3(unitStr) {
|
|
772
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
773
|
+
if (normalized in MASS_ALIASES) {
|
|
774
|
+
return MASS_ALIASES[normalized];
|
|
775
|
+
}
|
|
776
|
+
for (const [variant, config] of Object.entries(MASS_UNITS)) {
|
|
777
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
778
|
+
return variant;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/roles/temperature/constants.ts
|
|
785
|
+
var TEMPERATURE_UNITS = {
|
|
786
|
+
celsius: {
|
|
787
|
+
symbol: "\xB0C",
|
|
788
|
+
singular: "degree Celsius",
|
|
789
|
+
plural: "degrees Celsius",
|
|
790
|
+
toBase: (c) => c,
|
|
791
|
+
fromBase: (c) => c,
|
|
792
|
+
noSpace: true
|
|
793
|
+
// 25°C não 25 °C
|
|
794
|
+
},
|
|
795
|
+
fahrenheit: {
|
|
796
|
+
symbol: "\xB0F",
|
|
797
|
+
singular: "degree Fahrenheit",
|
|
798
|
+
plural: "degrees Fahrenheit",
|
|
799
|
+
toBase: (f) => (f - 32) * (5 / 9),
|
|
800
|
+
fromBase: (c) => c * (9 / 5) + 32,
|
|
801
|
+
noSpace: true
|
|
802
|
+
// 77°F não 77 °F
|
|
803
|
+
},
|
|
804
|
+
kelvin: {
|
|
805
|
+
symbol: "K",
|
|
806
|
+
singular: "kelvin",
|
|
807
|
+
plural: "kelvins",
|
|
808
|
+
toBase: (k) => k - 273.15,
|
|
809
|
+
fromBase: (c) => c + 273.15
|
|
810
|
+
// Kelvin usa espaço: "273 K" (convenção SI)
|
|
811
|
+
},
|
|
812
|
+
rankine: {
|
|
813
|
+
symbol: "\xB0R",
|
|
814
|
+
singular: "degree Rankine",
|
|
815
|
+
plural: "degrees Rankine",
|
|
816
|
+
toBase: (r) => (r - 491.67) * (5 / 9),
|
|
817
|
+
fromBase: (c) => (c + 273.15) * (9 / 5),
|
|
818
|
+
noSpace: true
|
|
819
|
+
// 500°R não 500 °R
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
var TEMPERATURE_ALIASES = {
|
|
823
|
+
// celsius
|
|
824
|
+
c: "celsius",
|
|
825
|
+
"\xB0c": "celsius",
|
|
826
|
+
// fahrenheit
|
|
827
|
+
f: "fahrenheit",
|
|
828
|
+
"\xB0f": "fahrenheit",
|
|
829
|
+
// kelvin
|
|
830
|
+
k: "kelvin",
|
|
831
|
+
// rankine
|
|
832
|
+
r: "rankine",
|
|
833
|
+
"\xB0r": "rankine"
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
// src/roles/temperature/convert.ts
|
|
837
|
+
function toBaseTemperature(variant, value) {
|
|
838
|
+
const unit = TEMPERATURE_UNITS[variant];
|
|
839
|
+
if (!unit) {
|
|
840
|
+
throw new Error(`Unknown temperature variant: ${variant}`);
|
|
841
|
+
}
|
|
842
|
+
if (unit.toBase) {
|
|
843
|
+
return unit.toBase(value);
|
|
844
|
+
}
|
|
845
|
+
return value * (unit.factor ?? 1);
|
|
846
|
+
}
|
|
847
|
+
function fromBaseTemperature(variant, baseValue) {
|
|
848
|
+
const unit = TEMPERATURE_UNITS[variant];
|
|
849
|
+
if (!unit) {
|
|
850
|
+
throw new Error(`Unknown temperature variant: ${variant}`);
|
|
851
|
+
}
|
|
852
|
+
if (unit.fromBase) {
|
|
853
|
+
return unit.fromBase(baseValue);
|
|
854
|
+
}
|
|
855
|
+
return baseValue / (unit.factor ?? 1);
|
|
856
|
+
}
|
|
857
|
+
function convertTemperature(from, to, value) {
|
|
858
|
+
if (from === to) {
|
|
859
|
+
return value;
|
|
860
|
+
}
|
|
861
|
+
const baseValue = toBaseTemperature(from, value);
|
|
862
|
+
return fromBaseTemperature(to, baseValue);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/roles/temperature/cast.ts
|
|
866
|
+
function castTemperature(input, targetVariant = "celsius") {
|
|
867
|
+
if (typeof input === "number") {
|
|
868
|
+
return Number.isFinite(input) ? input : null;
|
|
869
|
+
}
|
|
870
|
+
if (typeof input === "string") {
|
|
871
|
+
return parseTemperatureString(input, targetVariant);
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
function tryCastTemperature(input, targetVariant = "celsius") {
|
|
876
|
+
const result = castTemperature(input, targetVariant);
|
|
877
|
+
if (result === null) {
|
|
878
|
+
return {
|
|
879
|
+
ok: false,
|
|
880
|
+
error: `Cannot cast "${String(input)}" to temperature:${targetVariant}`
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
return { ok: true, value: result };
|
|
884
|
+
}
|
|
885
|
+
function parseTemperatureString(input, targetVariant) {
|
|
886
|
+
const trimmed = input.trim().toLowerCase();
|
|
887
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
888
|
+
if (!match) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
const [, numStr, unitStr] = match;
|
|
892
|
+
const numValue = parseNumber4(numStr);
|
|
893
|
+
if (numValue === null) {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
const cleanUnitStr = unitStr.trim();
|
|
897
|
+
if (cleanUnitStr) {
|
|
898
|
+
const detectedUnit = detectUnit4(cleanUnitStr);
|
|
899
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
900
|
+
return convertTemperature(detectedUnit, targetVariant, numValue);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return numValue;
|
|
904
|
+
}
|
|
905
|
+
function parseNumber4(numStr) {
|
|
906
|
+
numStr = numStr.replace(/\s/g, "");
|
|
907
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
908
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
909
|
+
const hasComma = lastComma !== -1;
|
|
910
|
+
const hasDot = lastDot !== -1;
|
|
911
|
+
if (hasComma && hasDot) {
|
|
912
|
+
if (lastComma > lastDot) {
|
|
913
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
914
|
+
} else {
|
|
915
|
+
numStr = numStr.replace(/,/g, "");
|
|
916
|
+
}
|
|
917
|
+
} else if (hasComma && !hasDot) {
|
|
918
|
+
const afterComma = numStr.split(",").slice(1);
|
|
919
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
920
|
+
if (isThousandSep) {
|
|
921
|
+
numStr = numStr.replace(/,/g, "");
|
|
922
|
+
} else {
|
|
923
|
+
numStr = numStr.replace(",", ".");
|
|
924
|
+
}
|
|
925
|
+
} else if (!hasComma && hasDot) {
|
|
926
|
+
const afterDot = numStr.split(".").slice(1);
|
|
927
|
+
if (afterDot.length > 1) {
|
|
928
|
+
numStr = numStr.replace(/\./g, "");
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
const value = parseFloat(numStr);
|
|
932
|
+
return isNaN(value) ? null : value;
|
|
933
|
+
}
|
|
934
|
+
function detectUnit4(unitStr) {
|
|
935
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
936
|
+
if (normalized in TEMPERATURE_ALIASES) {
|
|
937
|
+
return TEMPERATURE_ALIASES[normalized];
|
|
938
|
+
}
|
|
939
|
+
for (const [variant, config] of Object.entries(TEMPERATURE_UNITS)) {
|
|
940
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
941
|
+
return variant;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/roles/volume/constants.ts
|
|
948
|
+
var VOLUME_UNITS = {
|
|
949
|
+
// SI / Métrico
|
|
950
|
+
cubic_meter: {
|
|
951
|
+
factor: 1e3,
|
|
952
|
+
symbol: "m\xB3",
|
|
953
|
+
singular: "cubic meter",
|
|
954
|
+
plural: "cubic meters"
|
|
955
|
+
},
|
|
956
|
+
cubic_decimeter: {
|
|
957
|
+
factor: 1,
|
|
958
|
+
symbol: "dm\xB3",
|
|
959
|
+
singular: "cubic decimeter",
|
|
960
|
+
plural: "cubic decimeters"
|
|
961
|
+
},
|
|
962
|
+
cubic_centimeter: {
|
|
963
|
+
factor: 1e-3,
|
|
964
|
+
symbol: "cm\xB3",
|
|
965
|
+
singular: "cubic centimeter",
|
|
966
|
+
plural: "cubic centimeters"
|
|
967
|
+
},
|
|
968
|
+
cubic_millimeter: {
|
|
969
|
+
factor: 1e-6,
|
|
970
|
+
symbol: "mm\xB3",
|
|
971
|
+
singular: "cubic millimeter",
|
|
972
|
+
plural: "cubic millimeters"
|
|
973
|
+
},
|
|
974
|
+
hectoliter: {
|
|
975
|
+
factor: 100,
|
|
976
|
+
symbol: "hL",
|
|
977
|
+
singular: "hectoliter",
|
|
978
|
+
plural: "hectoliters"
|
|
979
|
+
},
|
|
980
|
+
decaliter: {
|
|
981
|
+
factor: 10,
|
|
982
|
+
symbol: "daL",
|
|
983
|
+
singular: "decaliter",
|
|
984
|
+
plural: "decaliters"
|
|
985
|
+
},
|
|
986
|
+
liter: {
|
|
987
|
+
factor: 1,
|
|
988
|
+
symbol: "L",
|
|
989
|
+
singular: "liter",
|
|
990
|
+
plural: "liters"
|
|
991
|
+
},
|
|
992
|
+
deciliter: {
|
|
993
|
+
factor: 0.1,
|
|
994
|
+
symbol: "dL",
|
|
995
|
+
singular: "deciliter",
|
|
996
|
+
plural: "deciliters"
|
|
997
|
+
},
|
|
998
|
+
centiliter: {
|
|
999
|
+
factor: 0.01,
|
|
1000
|
+
symbol: "cL",
|
|
1001
|
+
singular: "centiliter",
|
|
1002
|
+
plural: "centiliters"
|
|
1003
|
+
},
|
|
1004
|
+
milliliter: {
|
|
1005
|
+
factor: 1e-3,
|
|
1006
|
+
symbol: "mL",
|
|
1007
|
+
singular: "milliliter",
|
|
1008
|
+
plural: "milliliters"
|
|
1009
|
+
},
|
|
1010
|
+
microliter: {
|
|
1011
|
+
factor: 1e-6,
|
|
1012
|
+
symbol: "\u03BCL",
|
|
1013
|
+
singular: "microliter",
|
|
1014
|
+
plural: "microliters"
|
|
1015
|
+
},
|
|
1016
|
+
// US Customary (baseado em 1 gallon = 231 in³ = 3.785411784 L)
|
|
1017
|
+
gallon_us: {
|
|
1018
|
+
factor: 3.785411784,
|
|
1019
|
+
symbol: "gal",
|
|
1020
|
+
singular: "gallon",
|
|
1021
|
+
plural: "gallons"
|
|
1022
|
+
},
|
|
1023
|
+
quart_us: {
|
|
1024
|
+
factor: 0.946352946,
|
|
1025
|
+
// gallon/4
|
|
1026
|
+
symbol: "qt",
|
|
1027
|
+
singular: "quart",
|
|
1028
|
+
plural: "quarts"
|
|
1029
|
+
},
|
|
1030
|
+
pint_us: {
|
|
1031
|
+
factor: 0.473176473,
|
|
1032
|
+
// gallon/8
|
|
1033
|
+
symbol: "pt",
|
|
1034
|
+
singular: "pint",
|
|
1035
|
+
plural: "pints"
|
|
1036
|
+
},
|
|
1037
|
+
cup_us: {
|
|
1038
|
+
factor: 0.2365882365,
|
|
1039
|
+
// gallon/16
|
|
1040
|
+
symbol: "cup",
|
|
1041
|
+
singular: "cup",
|
|
1042
|
+
plural: "cups"
|
|
1043
|
+
},
|
|
1044
|
+
fluid_ounce_us: {
|
|
1045
|
+
factor: 0.0295735295625,
|
|
1046
|
+
// gallon/128
|
|
1047
|
+
symbol: "fl oz",
|
|
1048
|
+
singular: "fluid ounce",
|
|
1049
|
+
plural: "fluid ounces"
|
|
1050
|
+
},
|
|
1051
|
+
tablespoon_us: {
|
|
1052
|
+
factor: 0.01478676478125,
|
|
1053
|
+
// fl oz/2
|
|
1054
|
+
symbol: "tbsp",
|
|
1055
|
+
singular: "tablespoon",
|
|
1056
|
+
plural: "tablespoons"
|
|
1057
|
+
},
|
|
1058
|
+
teaspoon_us: {
|
|
1059
|
+
factor: 0.00492892159375,
|
|
1060
|
+
// tbsp/3
|
|
1061
|
+
symbol: "tsp",
|
|
1062
|
+
singular: "teaspoon",
|
|
1063
|
+
plural: "teaspoons"
|
|
1064
|
+
},
|
|
1065
|
+
// Imperial UK (1 gallon UK = 4.54609 L exato)
|
|
1066
|
+
gallon_uk: {
|
|
1067
|
+
factor: 4.54609,
|
|
1068
|
+
symbol: "gal (UK)",
|
|
1069
|
+
singular: "gallon (UK)",
|
|
1070
|
+
plural: "gallons (UK)"
|
|
1071
|
+
},
|
|
1072
|
+
quart_uk: {
|
|
1073
|
+
factor: 1.1365225,
|
|
1074
|
+
// gallon/4
|
|
1075
|
+
symbol: "qt (UK)",
|
|
1076
|
+
singular: "quart (UK)",
|
|
1077
|
+
plural: "quarts (UK)"
|
|
1078
|
+
},
|
|
1079
|
+
pint_uk: {
|
|
1080
|
+
factor: 0.56826125,
|
|
1081
|
+
// gallon/8
|
|
1082
|
+
symbol: "pt (UK)",
|
|
1083
|
+
singular: "pint (UK)",
|
|
1084
|
+
plural: "pints (UK)"
|
|
1085
|
+
},
|
|
1086
|
+
fluid_ounce_uk: {
|
|
1087
|
+
factor: 0.0284130625,
|
|
1088
|
+
// gallon/160
|
|
1089
|
+
symbol: "fl oz (UK)",
|
|
1090
|
+
singular: "fluid ounce (UK)",
|
|
1091
|
+
plural: "fluid ounces (UK)"
|
|
1092
|
+
},
|
|
1093
|
+
// Other
|
|
1094
|
+
barrel_oil: {
|
|
1095
|
+
factor: 158.987294928,
|
|
1096
|
+
// 42 US gallons (petroleum)
|
|
1097
|
+
symbol: "bbl",
|
|
1098
|
+
singular: "barrel",
|
|
1099
|
+
plural: "barrels"
|
|
1100
|
+
},
|
|
1101
|
+
cubic_inch: {
|
|
1102
|
+
factor: 0.016387064,
|
|
1103
|
+
// (0.0254)³ × 1000
|
|
1104
|
+
symbol: "in\xB3",
|
|
1105
|
+
singular: "cubic inch",
|
|
1106
|
+
plural: "cubic inches"
|
|
1107
|
+
},
|
|
1108
|
+
cubic_foot: {
|
|
1109
|
+
factor: 28.316846592,
|
|
1110
|
+
// (0.3048)³ × 1000
|
|
1111
|
+
symbol: "ft\xB3",
|
|
1112
|
+
singular: "cubic foot",
|
|
1113
|
+
plural: "cubic feet"
|
|
1114
|
+
},
|
|
1115
|
+
cubic_yard: {
|
|
1116
|
+
factor: 764.554857984,
|
|
1117
|
+
// (0.9144)³ × 1000
|
|
1118
|
+
symbol: "yd\xB3",
|
|
1119
|
+
singular: "cubic yard",
|
|
1120
|
+
plural: "cubic yards"
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
var VOLUME_ALIASES = {
|
|
1124
|
+
// cubic_meter
|
|
1125
|
+
"m\xB3": "cubic_meter",
|
|
1126
|
+
m3: "cubic_meter",
|
|
1127
|
+
// cubic_decimeter
|
|
1128
|
+
"dm\xB3": "cubic_decimeter",
|
|
1129
|
+
dm3: "cubic_decimeter",
|
|
1130
|
+
// cubic_centimeter
|
|
1131
|
+
"cm\xB3": "cubic_centimeter",
|
|
1132
|
+
cm3: "cubic_centimeter",
|
|
1133
|
+
cc: "cubic_centimeter",
|
|
1134
|
+
// cubic_millimeter
|
|
1135
|
+
"mm\xB3": "cubic_millimeter",
|
|
1136
|
+
mm3: "cubic_millimeter",
|
|
1137
|
+
// hectoliter
|
|
1138
|
+
hL: "hectoliter",
|
|
1139
|
+
hl: "hectoliter",
|
|
1140
|
+
// decaliter
|
|
1141
|
+
daL: "decaliter",
|
|
1142
|
+
dal: "decaliter",
|
|
1143
|
+
// liter
|
|
1144
|
+
L: "liter",
|
|
1145
|
+
l: "liter",
|
|
1146
|
+
// deciliter
|
|
1147
|
+
dL: "deciliter",
|
|
1148
|
+
dl: "deciliter",
|
|
1149
|
+
// centiliter
|
|
1150
|
+
cL: "centiliter",
|
|
1151
|
+
cl: "centiliter",
|
|
1152
|
+
// milliliter
|
|
1153
|
+
mL: "milliliter",
|
|
1154
|
+
ml: "milliliter",
|
|
1155
|
+
// microliter
|
|
1156
|
+
"\u03BCL": "microliter",
|
|
1157
|
+
uL: "microliter",
|
|
1158
|
+
ul: "microliter",
|
|
1159
|
+
// gallon_us
|
|
1160
|
+
gal: "gallon_us",
|
|
1161
|
+
// quart_us
|
|
1162
|
+
qt: "quart_us",
|
|
1163
|
+
// pint_us
|
|
1164
|
+
pt: "pint_us",
|
|
1165
|
+
// cup_us
|
|
1166
|
+
cup: "cup_us",
|
|
1167
|
+
// fluid_ounce_us
|
|
1168
|
+
"fl oz": "fluid_ounce_us",
|
|
1169
|
+
floz: "fluid_ounce_us",
|
|
1170
|
+
// tablespoon_us
|
|
1171
|
+
tbsp: "tablespoon_us",
|
|
1172
|
+
// teaspoon_us
|
|
1173
|
+
tsp: "teaspoon_us",
|
|
1174
|
+
// gallon_uk
|
|
1175
|
+
"gal uk": "gallon_uk",
|
|
1176
|
+
// quart_uk
|
|
1177
|
+
"qt uk": "quart_uk",
|
|
1178
|
+
// pint_uk
|
|
1179
|
+
"pt uk": "pint_uk",
|
|
1180
|
+
// fluid_ounce_uk
|
|
1181
|
+
"fl oz uk": "fluid_ounce_uk",
|
|
1182
|
+
// barrel_oil
|
|
1183
|
+
bbl: "barrel_oil",
|
|
1184
|
+
// cubic_inch
|
|
1185
|
+
"in\xB3": "cubic_inch",
|
|
1186
|
+
in3: "cubic_inch",
|
|
1187
|
+
// cubic_foot
|
|
1188
|
+
"ft\xB3": "cubic_foot",
|
|
1189
|
+
ft3: "cubic_foot",
|
|
1190
|
+
// cubic_yard
|
|
1191
|
+
"yd\xB3": "cubic_yard",
|
|
1192
|
+
yd3: "cubic_yard"
|
|
1193
|
+
};
|
|
1194
|
+
Object.fromEntries(
|
|
1195
|
+
Object.entries(VOLUME_UNITS).map(([unit, config]) => [
|
|
1196
|
+
unit,
|
|
1197
|
+
config.factor ?? 1
|
|
1198
|
+
])
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
// src/roles/volume/convert.ts
|
|
1202
|
+
function toBaseVolume(variant, value) {
|
|
1203
|
+
const unit = VOLUME_UNITS[variant];
|
|
1204
|
+
if (!unit) {
|
|
1205
|
+
throw new Error(`Unknown volume variant: ${variant}`);
|
|
1206
|
+
}
|
|
1207
|
+
return value * (unit.factor ?? 1);
|
|
1208
|
+
}
|
|
1209
|
+
function fromBaseVolume(variant, baseValue) {
|
|
1210
|
+
const unit = VOLUME_UNITS[variant];
|
|
1211
|
+
if (!unit) {
|
|
1212
|
+
throw new Error(`Unknown volume variant: ${variant}`);
|
|
1213
|
+
}
|
|
1214
|
+
return baseValue / (unit.factor ?? 1);
|
|
1215
|
+
}
|
|
1216
|
+
function convertVolume(from, to, value) {
|
|
1217
|
+
if (from === to) {
|
|
1218
|
+
return value;
|
|
1219
|
+
}
|
|
1220
|
+
const baseValue = toBaseVolume(from, value);
|
|
1221
|
+
return fromBaseVolume(to, baseValue);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/roles/volume/cast.ts
|
|
1225
|
+
function castVolume(input, targetVariant = "liter") {
|
|
1226
|
+
if (typeof input === "number") {
|
|
1227
|
+
return Number.isFinite(input) ? input : null;
|
|
1228
|
+
}
|
|
1229
|
+
if (typeof input === "string") {
|
|
1230
|
+
return parseVolumeString(input, targetVariant);
|
|
1231
|
+
}
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
function tryCastVolume(input, targetVariant = "liter") {
|
|
1235
|
+
const result = castVolume(input, targetVariant);
|
|
1236
|
+
if (result === null) {
|
|
1237
|
+
return {
|
|
1238
|
+
ok: false,
|
|
1239
|
+
error: `Cannot cast "${String(input)}" to volume:${targetVariant}`
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
return { ok: true, value: result };
|
|
1243
|
+
}
|
|
1244
|
+
function parseVolumeString(input, targetVariant) {
|
|
1245
|
+
const trimmed = input.trim().toLowerCase();
|
|
1246
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
1247
|
+
if (!match) {
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
const [, numStr, unitStr] = match;
|
|
1251
|
+
const numValue = parseNumber5(numStr);
|
|
1252
|
+
if (numValue === null) {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
const cleanUnitStr = unitStr.trim();
|
|
1256
|
+
if (cleanUnitStr) {
|
|
1257
|
+
const detectedUnit = detectUnit5(cleanUnitStr);
|
|
1258
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
1259
|
+
return convertVolume(detectedUnit, targetVariant, numValue);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return numValue;
|
|
1263
|
+
}
|
|
1264
|
+
function parseNumber5(numStr) {
|
|
1265
|
+
numStr = numStr.replace(/\s/g, "");
|
|
1266
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
1267
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
1268
|
+
const hasComma = lastComma !== -1;
|
|
1269
|
+
const hasDot = lastDot !== -1;
|
|
1270
|
+
if (hasComma && hasDot) {
|
|
1271
|
+
if (lastComma > lastDot) {
|
|
1272
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
1273
|
+
} else {
|
|
1274
|
+
numStr = numStr.replace(/,/g, "");
|
|
1275
|
+
}
|
|
1276
|
+
} else if (hasComma && !hasDot) {
|
|
1277
|
+
const afterComma = numStr.split(",").slice(1);
|
|
1278
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
1279
|
+
if (isThousandSep) {
|
|
1280
|
+
numStr = numStr.replace(/,/g, "");
|
|
1281
|
+
} else {
|
|
1282
|
+
numStr = numStr.replace(",", ".");
|
|
1283
|
+
}
|
|
1284
|
+
} else if (!hasComma && hasDot) {
|
|
1285
|
+
const afterDot = numStr.split(".").slice(1);
|
|
1286
|
+
if (afterDot.length > 1) {
|
|
1287
|
+
numStr = numStr.replace(/\./g, "");
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
const value = parseFloat(numStr);
|
|
1291
|
+
return isNaN(value) ? null : value;
|
|
1292
|
+
}
|
|
1293
|
+
function detectUnit5(unitStr) {
|
|
1294
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
1295
|
+
if (normalized in VOLUME_ALIASES) {
|
|
1296
|
+
return VOLUME_ALIASES[normalized];
|
|
1297
|
+
}
|
|
1298
|
+
for (const [variant, config] of Object.entries(VOLUME_UNITS)) {
|
|
1299
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
1300
|
+
return variant;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/roles/speed/constants.ts
|
|
1307
|
+
var SPEED_UNITS = {
|
|
1308
|
+
// SI Units
|
|
1309
|
+
meter_per_second: {
|
|
1310
|
+
factor: 1,
|
|
1311
|
+
symbol: "m/s",
|
|
1312
|
+
singular: "meter per second",
|
|
1313
|
+
plural: "meters per second"
|
|
1314
|
+
},
|
|
1315
|
+
kilometer_per_hour: {
|
|
1316
|
+
factor: 1e3 / 3600,
|
|
1317
|
+
// 0.277777...
|
|
1318
|
+
symbol: "km/h",
|
|
1319
|
+
singular: "kilometer per hour",
|
|
1320
|
+
plural: "kilometers per hour"
|
|
1321
|
+
},
|
|
1322
|
+
// Imperial/US (exatos desde 1959)
|
|
1323
|
+
mile_per_hour: {
|
|
1324
|
+
factor: 0.44704,
|
|
1325
|
+
// 1609.344 / 3600 (exato)
|
|
1326
|
+
symbol: "mph",
|
|
1327
|
+
singular: "mile per hour",
|
|
1328
|
+
plural: "miles per hour"
|
|
1329
|
+
},
|
|
1330
|
+
foot_per_second: {
|
|
1331
|
+
factor: 0.3048,
|
|
1332
|
+
// exato
|
|
1333
|
+
symbol: "ft/s",
|
|
1334
|
+
singular: "foot per second",
|
|
1335
|
+
plural: "feet per second"
|
|
1336
|
+
},
|
|
1337
|
+
// Nautical (exato)
|
|
1338
|
+
knot: {
|
|
1339
|
+
factor: 1852 / 3600,
|
|
1340
|
+
// 0.514444...
|
|
1341
|
+
symbol: "kn",
|
|
1342
|
+
singular: "knot",
|
|
1343
|
+
plural: "knots"
|
|
1344
|
+
},
|
|
1345
|
+
// Scientific
|
|
1346
|
+
mach: {
|
|
1347
|
+
factor: 340.29,
|
|
1348
|
+
// velocidade do som ao nível do mar, 15°C (aproximado)
|
|
1349
|
+
symbol: "Ma",
|
|
1350
|
+
singular: "mach",
|
|
1351
|
+
plural: "mach"
|
|
1352
|
+
},
|
|
1353
|
+
speed_of_light: {
|
|
1354
|
+
factor: 299792458,
|
|
1355
|
+
// exato por definição SI
|
|
1356
|
+
symbol: "c",
|
|
1357
|
+
singular: "speed of light",
|
|
1358
|
+
plural: "speed of light"
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
var SPEED_ALIASES = {
|
|
1362
|
+
// meter_per_second
|
|
1363
|
+
"m/s": "meter_per_second",
|
|
1364
|
+
mps: "meter_per_second",
|
|
1365
|
+
// kilometer_per_hour
|
|
1366
|
+
"km/h": "kilometer_per_hour",
|
|
1367
|
+
kmh: "kilometer_per_hour",
|
|
1368
|
+
kph: "kilometer_per_hour",
|
|
1369
|
+
// mile_per_hour
|
|
1370
|
+
mph: "mile_per_hour",
|
|
1371
|
+
"mi/h": "mile_per_hour",
|
|
1372
|
+
// foot_per_second
|
|
1373
|
+
"ft/s": "foot_per_second",
|
|
1374
|
+
fps: "foot_per_second",
|
|
1375
|
+
// knot
|
|
1376
|
+
kn: "knot",
|
|
1377
|
+
kt: "knot",
|
|
1378
|
+
kts: "knot",
|
|
1379
|
+
// mach
|
|
1380
|
+
ma: "mach",
|
|
1381
|
+
// speed_of_light
|
|
1382
|
+
c: "speed_of_light"
|
|
1383
|
+
};
|
|
1384
|
+
Object.fromEntries(
|
|
1385
|
+
Object.entries(SPEED_UNITS).map(([unit, config]) => [
|
|
1386
|
+
unit,
|
|
1387
|
+
config.factor ?? 1
|
|
1388
|
+
])
|
|
1389
|
+
);
|
|
1390
|
+
|
|
1391
|
+
// src/roles/speed/convert.ts
|
|
1392
|
+
function toBaseSpeed(variant, value) {
|
|
1393
|
+
const unit = SPEED_UNITS[variant];
|
|
1394
|
+
if (!unit) {
|
|
1395
|
+
throw new Error(`Unknown speed variant: ${variant}`);
|
|
1396
|
+
}
|
|
1397
|
+
return value * (unit.factor ?? 1);
|
|
1398
|
+
}
|
|
1399
|
+
function fromBaseSpeed(variant, baseValue) {
|
|
1400
|
+
const unit = SPEED_UNITS[variant];
|
|
1401
|
+
if (!unit) {
|
|
1402
|
+
throw new Error(`Unknown speed variant: ${variant}`);
|
|
1403
|
+
}
|
|
1404
|
+
return baseValue / (unit.factor ?? 1);
|
|
1405
|
+
}
|
|
1406
|
+
function convertSpeed(from, to, value) {
|
|
1407
|
+
if (from === to) {
|
|
1408
|
+
return value;
|
|
1409
|
+
}
|
|
1410
|
+
const baseValue = toBaseSpeed(from, value);
|
|
1411
|
+
return fromBaseSpeed(to, baseValue);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/roles/speed/cast.ts
|
|
1415
|
+
function castSpeed(input, targetVariant = "meter_per_second") {
|
|
1416
|
+
if (typeof input === "number") {
|
|
1417
|
+
return Number.isFinite(input) ? input : null;
|
|
1418
|
+
}
|
|
1419
|
+
if (typeof input === "string") {
|
|
1420
|
+
return parseSpeedString(input, targetVariant);
|
|
1421
|
+
}
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
function tryCastSpeed(input, targetVariant = "meter_per_second") {
|
|
1425
|
+
const result = castSpeed(input, targetVariant);
|
|
1426
|
+
if (result === null) {
|
|
1427
|
+
return {
|
|
1428
|
+
ok: false,
|
|
1429
|
+
error: `Cannot cast "${String(input)}" to speed:${targetVariant}`
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
return { ok: true, value: result };
|
|
1433
|
+
}
|
|
1434
|
+
function parseSpeedString(input, targetVariant) {
|
|
1435
|
+
const trimmed = input.trim().toLowerCase();
|
|
1436
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
1437
|
+
if (!match) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const [, numStr, unitStr] = match;
|
|
1441
|
+
const numValue = parseNumber6(numStr);
|
|
1442
|
+
if (numValue === null) {
|
|
1443
|
+
return null;
|
|
1444
|
+
}
|
|
1445
|
+
const cleanUnitStr = unitStr.trim();
|
|
1446
|
+
if (cleanUnitStr) {
|
|
1447
|
+
const detectedUnit = detectUnit6(cleanUnitStr);
|
|
1448
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
1449
|
+
return convertSpeed(detectedUnit, targetVariant, numValue);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return numValue;
|
|
1453
|
+
}
|
|
1454
|
+
function parseNumber6(numStr) {
|
|
1455
|
+
numStr = numStr.replace(/\s/g, "");
|
|
1456
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
1457
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
1458
|
+
const hasComma = lastComma !== -1;
|
|
1459
|
+
const hasDot = lastDot !== -1;
|
|
1460
|
+
if (hasComma && hasDot) {
|
|
1461
|
+
if (lastComma > lastDot) {
|
|
1462
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
1463
|
+
} else {
|
|
1464
|
+
numStr = numStr.replace(/,/g, "");
|
|
1465
|
+
}
|
|
1466
|
+
} else if (hasComma && !hasDot) {
|
|
1467
|
+
const afterComma = numStr.split(",").slice(1);
|
|
1468
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
1469
|
+
if (isThousandSep) {
|
|
1470
|
+
numStr = numStr.replace(/,/g, "");
|
|
1471
|
+
} else {
|
|
1472
|
+
numStr = numStr.replace(",", ".");
|
|
1473
|
+
}
|
|
1474
|
+
} else if (!hasComma && hasDot) {
|
|
1475
|
+
const afterDot = numStr.split(".").slice(1);
|
|
1476
|
+
if (afterDot.length > 1) {
|
|
1477
|
+
numStr = numStr.replace(/\./g, "");
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
const value = parseFloat(numStr);
|
|
1481
|
+
return isNaN(value) ? null : value;
|
|
1482
|
+
}
|
|
1483
|
+
function detectUnit6(unitStr) {
|
|
1484
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
1485
|
+
if (normalized in SPEED_ALIASES) {
|
|
1486
|
+
return SPEED_ALIASES[normalized];
|
|
1487
|
+
}
|
|
1488
|
+
for (const [variant, config] of Object.entries(SPEED_UNITS)) {
|
|
1489
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
1490
|
+
return variant;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return null;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// src/roles/energy/constants.ts
|
|
1497
|
+
var ENERGY_UNITS = {
|
|
1498
|
+
// SI Units (todos exatos)
|
|
1499
|
+
gigajoule: {
|
|
1500
|
+
factor: 1e9,
|
|
1501
|
+
symbol: "GJ",
|
|
1502
|
+
singular: "gigajoule",
|
|
1503
|
+
plural: "gigajoules"
|
|
1504
|
+
},
|
|
1505
|
+
megajoule: {
|
|
1506
|
+
factor: 1e6,
|
|
1507
|
+
symbol: "MJ",
|
|
1508
|
+
singular: "megajoule",
|
|
1509
|
+
plural: "megajoules"
|
|
1510
|
+
},
|
|
1511
|
+
kilojoule: {
|
|
1512
|
+
factor: 1e3,
|
|
1513
|
+
symbol: "kJ",
|
|
1514
|
+
singular: "kilojoule",
|
|
1515
|
+
plural: "kilojoules"
|
|
1516
|
+
},
|
|
1517
|
+
joule: {
|
|
1518
|
+
factor: 1,
|
|
1519
|
+
symbol: "J",
|
|
1520
|
+
singular: "joule",
|
|
1521
|
+
plural: "joules"
|
|
1522
|
+
},
|
|
1523
|
+
millijoule: {
|
|
1524
|
+
factor: 1e-3,
|
|
1525
|
+
symbol: "mJ",
|
|
1526
|
+
singular: "millijoule",
|
|
1527
|
+
plural: "millijoules"
|
|
1528
|
+
},
|
|
1529
|
+
// Calories (International Table - IT)
|
|
1530
|
+
// 1 cal (IT) = 4.1868 J (exato por definição)
|
|
1531
|
+
calorie: {
|
|
1532
|
+
factor: 4.1868,
|
|
1533
|
+
symbol: "cal",
|
|
1534
|
+
singular: "calorie",
|
|
1535
|
+
plural: "calories"
|
|
1536
|
+
},
|
|
1537
|
+
// 1 kcal = 1000 cal = 4186.8 J (= 1 "food Calorie")
|
|
1538
|
+
kilocalorie: {
|
|
1539
|
+
factor: 4186.8,
|
|
1540
|
+
symbol: "kcal",
|
|
1541
|
+
singular: "kilocalorie",
|
|
1542
|
+
plural: "kilocalories"
|
|
1543
|
+
},
|
|
1544
|
+
// Watt-hour (exatos)
|
|
1545
|
+
// 1 Wh = 1 W × 3600 s = 3600 J
|
|
1546
|
+
watt_hour: {
|
|
1547
|
+
factor: 3600,
|
|
1548
|
+
symbol: "Wh",
|
|
1549
|
+
singular: "watt-hour",
|
|
1550
|
+
plural: "watt-hours"
|
|
1551
|
+
},
|
|
1552
|
+
kilowatt_hour: {
|
|
1553
|
+
factor: 36e5,
|
|
1554
|
+
symbol: "kWh",
|
|
1555
|
+
singular: "kilowatt-hour",
|
|
1556
|
+
plural: "kilowatt-hours"
|
|
1557
|
+
},
|
|
1558
|
+
megawatt_hour: {
|
|
1559
|
+
factor: 36e8,
|
|
1560
|
+
symbol: "MWh",
|
|
1561
|
+
singular: "megawatt-hour",
|
|
1562
|
+
plural: "megawatt-hours"
|
|
1563
|
+
},
|
|
1564
|
+
gigawatt_hour: {
|
|
1565
|
+
factor: 36e11,
|
|
1566
|
+
symbol: "GWh",
|
|
1567
|
+
singular: "gigawatt-hour",
|
|
1568
|
+
plural: "gigawatt-hours"
|
|
1569
|
+
},
|
|
1570
|
+
// BTU (International Table)
|
|
1571
|
+
// 1 BTU (IT) = 1055.05585262 J (definição)
|
|
1572
|
+
btu: {
|
|
1573
|
+
factor: 1055.05585262,
|
|
1574
|
+
symbol: "BTU",
|
|
1575
|
+
singular: "BTU",
|
|
1576
|
+
plural: "BTUs"
|
|
1577
|
+
},
|
|
1578
|
+
// 1 therm = 100,000 BTU (IT)
|
|
1579
|
+
therm: {
|
|
1580
|
+
factor: 105505585262e-3,
|
|
1581
|
+
symbol: "thm",
|
|
1582
|
+
singular: "therm",
|
|
1583
|
+
plural: "therms"
|
|
1584
|
+
},
|
|
1585
|
+
// Scientific
|
|
1586
|
+
// 1 eV = 1.602176634e-19 J (SI 2019, exato por definição)
|
|
1587
|
+
electronvolt: {
|
|
1588
|
+
factor: 1602176634e-28,
|
|
1589
|
+
symbol: "eV",
|
|
1590
|
+
singular: "electronvolt",
|
|
1591
|
+
plural: "electronvolts"
|
|
1592
|
+
},
|
|
1593
|
+
kiloelectronvolt: {
|
|
1594
|
+
factor: 1602176634e-25,
|
|
1595
|
+
symbol: "keV",
|
|
1596
|
+
singular: "kiloelectronvolt",
|
|
1597
|
+
plural: "kiloelectronvolts"
|
|
1598
|
+
},
|
|
1599
|
+
megaelectronvolt: {
|
|
1600
|
+
factor: 1602176634e-22,
|
|
1601
|
+
symbol: "MeV",
|
|
1602
|
+
singular: "megaelectronvolt",
|
|
1603
|
+
plural: "megaelectronvolts"
|
|
1604
|
+
},
|
|
1605
|
+
// 1 erg = 1e-7 J (CGS, exato)
|
|
1606
|
+
erg: {
|
|
1607
|
+
factor: 1e-7,
|
|
1608
|
+
symbol: "erg",
|
|
1609
|
+
singular: "erg",
|
|
1610
|
+
plural: "ergs"
|
|
1611
|
+
},
|
|
1612
|
+
// Mechanical
|
|
1613
|
+
// 1 ft⋅lbf = 1.3558179483314004 J (derivado de foot e pound-force)
|
|
1614
|
+
foot_pound: {
|
|
1615
|
+
factor: 1.3558179483314003,
|
|
1616
|
+
symbol: "ft\u22C5lbf",
|
|
1617
|
+
singular: "foot-pound",
|
|
1618
|
+
plural: "foot-pounds"
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
var ENERGY_ALIASES = {
|
|
1622
|
+
// gigajoule
|
|
1623
|
+
gj: "gigajoule",
|
|
1624
|
+
// megajoule
|
|
1625
|
+
mj: "megajoule",
|
|
1626
|
+
// kilojoule
|
|
1627
|
+
kj: "kilojoule",
|
|
1628
|
+
// joule
|
|
1629
|
+
j: "joule",
|
|
1630
|
+
// calorie
|
|
1631
|
+
cal: "calorie",
|
|
1632
|
+
// kilocalorie
|
|
1633
|
+
kcal: "kilocalorie",
|
|
1634
|
+
Cal: "kilocalorie",
|
|
1635
|
+
// watt_hour
|
|
1636
|
+
wh: "watt_hour",
|
|
1637
|
+
// kilowatt_hour
|
|
1638
|
+
kwh: "kilowatt_hour",
|
|
1639
|
+
// megawatt_hour
|
|
1640
|
+
mwh: "megawatt_hour",
|
|
1641
|
+
// gigawatt_hour
|
|
1642
|
+
gwh: "gigawatt_hour",
|
|
1643
|
+
// btu
|
|
1644
|
+
btu: "btu",
|
|
1645
|
+
// therm
|
|
1646
|
+
thm: "therm",
|
|
1647
|
+
// electronvolt
|
|
1648
|
+
ev: "electronvolt",
|
|
1649
|
+
// kiloelectronvolt
|
|
1650
|
+
kev: "kiloelectronvolt",
|
|
1651
|
+
// megaelectronvolt
|
|
1652
|
+
mev: "megaelectronvolt",
|
|
1653
|
+
// erg
|
|
1654
|
+
erg: "erg",
|
|
1655
|
+
// foot_pound
|
|
1656
|
+
"ft-lb": "foot_pound",
|
|
1657
|
+
"ft-lbf": "foot_pound"
|
|
1658
|
+
};
|
|
1659
|
+
Object.fromEntries(
|
|
1660
|
+
Object.entries(ENERGY_UNITS).map(([unit, config]) => [
|
|
1661
|
+
unit,
|
|
1662
|
+
config.factor ?? 1
|
|
1663
|
+
])
|
|
1664
|
+
);
|
|
1665
|
+
|
|
1666
|
+
// src/roles/energy/convert.ts
|
|
1667
|
+
function toBaseEnergy(variant, value) {
|
|
1668
|
+
const unit = ENERGY_UNITS[variant];
|
|
1669
|
+
if (!unit) {
|
|
1670
|
+
throw new Error(`Unknown energy variant: ${variant}`);
|
|
1671
|
+
}
|
|
1672
|
+
return value * (unit.factor ?? 1);
|
|
1673
|
+
}
|
|
1674
|
+
function fromBaseEnergy(variant, baseValue) {
|
|
1675
|
+
const unit = ENERGY_UNITS[variant];
|
|
1676
|
+
if (!unit) {
|
|
1677
|
+
throw new Error(`Unknown energy variant: ${variant}`);
|
|
1678
|
+
}
|
|
1679
|
+
return baseValue / (unit.factor ?? 1);
|
|
1680
|
+
}
|
|
1681
|
+
function convertEnergy(from, to, value) {
|
|
1682
|
+
if (from === to) {
|
|
1683
|
+
return value;
|
|
1684
|
+
}
|
|
1685
|
+
const baseValue = toBaseEnergy(from, value);
|
|
1686
|
+
return fromBaseEnergy(to, baseValue);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// src/roles/energy/cast.ts
|
|
1690
|
+
function castEnergy(input, targetVariant = "joule") {
|
|
1691
|
+
if (typeof input === "number") {
|
|
1692
|
+
return Number.isFinite(input) ? input : null;
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof input === "string") {
|
|
1695
|
+
return parseEnergyString(input, targetVariant);
|
|
1696
|
+
}
|
|
1697
|
+
return null;
|
|
1698
|
+
}
|
|
1699
|
+
function tryCastEnergy(input, targetVariant = "joule") {
|
|
1700
|
+
const result = castEnergy(input, targetVariant);
|
|
1701
|
+
if (result === null) {
|
|
1702
|
+
return {
|
|
1703
|
+
ok: false,
|
|
1704
|
+
error: `Cannot cast "${String(input)}" to energy:${targetVariant}`
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
return { ok: true, value: result };
|
|
1708
|
+
}
|
|
1709
|
+
function parseEnergyString(input, targetVariant) {
|
|
1710
|
+
const trimmed = input.trim().toLowerCase();
|
|
1711
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
1712
|
+
if (!match) {
|
|
1713
|
+
return null;
|
|
1714
|
+
}
|
|
1715
|
+
const [, numStr, unitStr] = match;
|
|
1716
|
+
const numValue = parseNumber7(numStr);
|
|
1717
|
+
if (numValue === null) {
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1720
|
+
const cleanUnitStr = unitStr.trim();
|
|
1721
|
+
if (cleanUnitStr) {
|
|
1722
|
+
const detectedUnit = detectUnit7(cleanUnitStr);
|
|
1723
|
+
if (detectedUnit && detectedUnit !== targetVariant) {
|
|
1724
|
+
return convertEnergy(detectedUnit, targetVariant, numValue);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return numValue;
|
|
1728
|
+
}
|
|
1729
|
+
function parseNumber7(numStr) {
|
|
1730
|
+
numStr = numStr.replace(/\s/g, "");
|
|
1731
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
1732
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
1733
|
+
const hasComma = lastComma !== -1;
|
|
1734
|
+
const hasDot = lastDot !== -1;
|
|
1735
|
+
if (hasComma && hasDot) {
|
|
1736
|
+
if (lastComma > lastDot) {
|
|
1737
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
1738
|
+
} else {
|
|
1739
|
+
numStr = numStr.replace(/,/g, "");
|
|
1740
|
+
}
|
|
1741
|
+
} else if (hasComma && !hasDot) {
|
|
1742
|
+
const afterComma = numStr.split(",").slice(1);
|
|
1743
|
+
const isThousandSep = afterComma.every((part) => part.length === 3);
|
|
1744
|
+
if (isThousandSep) {
|
|
1745
|
+
numStr = numStr.replace(/,/g, "");
|
|
1746
|
+
} else {
|
|
1747
|
+
numStr = numStr.replace(",", ".");
|
|
1748
|
+
}
|
|
1749
|
+
} else if (!hasComma && hasDot) {
|
|
1750
|
+
const afterDot = numStr.split(".").slice(1);
|
|
1751
|
+
if (afterDot.length > 1) {
|
|
1752
|
+
numStr = numStr.replace(/\./g, "");
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
const value = parseFloat(numStr);
|
|
1756
|
+
return isNaN(value) ? null : value;
|
|
1757
|
+
}
|
|
1758
|
+
function detectUnit7(unitStr) {
|
|
1759
|
+
const normalized = unitStr.toLowerCase().trim();
|
|
1760
|
+
if (normalized in ENERGY_ALIASES) {
|
|
1761
|
+
return ENERGY_ALIASES[normalized];
|
|
1762
|
+
}
|
|
1763
|
+
for (const [variant, config] of Object.entries(ENERGY_UNITS)) {
|
|
1764
|
+
if (config.symbol.toLowerCase() === normalized || config.singular?.toLowerCase() === normalized || config.plural?.toLowerCase() === normalized || variant.toLowerCase() === normalized) {
|
|
1765
|
+
return variant;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return null;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// src/roles/power/constants.ts
|
|
1772
|
+
var POWER_UNITS = {
|
|
1773
|
+
// SI Units (todos exatos)
|
|
1774
|
+
gigawatt: {
|
|
1775
|
+
factor: 1e9,
|
|
1776
|
+
symbol: "GW",
|
|
1777
|
+
singular: "gigawatt",
|
|
1778
|
+
plural: "gigawatts"
|
|
1779
|
+
},
|
|
1780
|
+
megawatt: {
|
|
1781
|
+
factor: 1e6,
|
|
1782
|
+
symbol: "MW",
|
|
1783
|
+
singular: "megawatt",
|
|
1784
|
+
plural: "megawatts"
|
|
1785
|
+
},
|
|
1786
|
+
kilowatt: {
|
|
1787
|
+
factor: 1e3,
|
|
1788
|
+
symbol: "kW",
|
|
1789
|
+
singular: "kilowatt",
|
|
1790
|
+
plural: "kilowatts"
|
|
1791
|
+
},
|
|
1792
|
+
watt: {
|
|
1793
|
+
factor: 1,
|
|
1794
|
+
symbol: "W",
|
|
1795
|
+
singular: "watt",
|
|
1796
|
+
plural: "watts"
|
|
1797
|
+
},
|
|
1798
|
+
milliwatt: {
|
|
1799
|
+
factor: 1e-3,
|
|
1800
|
+
symbol: "mW",
|
|
1801
|
+
singular: "milliwatt",
|
|
1802
|
+
plural: "milliwatts"
|
|
1803
|
+
},
|
|
1804
|
+
microwatt: {
|
|
1805
|
+
factor: 1e-6,
|
|
1806
|
+
symbol: "\u03BCW",
|
|
1807
|
+
singular: "microwatt",
|
|
1808
|
+
plural: "microwatts"
|
|
1809
|
+
},
|
|
1810
|
+
// Horsepower variants
|
|
1811
|
+
// Mechanical (imperial): 550 ft⋅lbf/s
|
|
1812
|
+
// = 550 × 0.3048 m × 4.4482216152605 N / s = 745.69987158227 W
|
|
1813
|
+
horsepower_mechanical: {
|
|
1814
|
+
factor: 745.69987158227,
|
|
1815
|
+
symbol: "hp",
|
|
1816
|
+
singular: "horsepower",
|
|
1817
|
+
plural: "horsepower"
|
|
1818
|
+
},
|
|
1819
|
+
// Metric (PS, CV, pk): 75 kgf⋅m/s = 75 × 9.80665 W = 735.49875 W (exato)
|
|
1820
|
+
horsepower_metric: {
|
|
1821
|
+
factor: 735.49875,
|
|
1822
|
+
symbol: "PS",
|
|
1823
|
+
singular: "metric horsepower",
|
|
1824
|
+
plural: "metric horsepower"
|
|
1825
|
+
},
|
|
1826
|
+
// Electrical: 746 W (exato por definição)
|
|
1827
|
+
horsepower_electric: {
|
|
1828
|
+
factor: 746,
|
|
1829
|
+
symbol: "hp(E)",
|
|
1830
|
+
singular: "electric horsepower",
|
|
1831
|
+
plural: "electric horsepower"
|
|
1832
|
+
},
|
|
1833
|
+
// Boiler: 33,475 BTU/h = 9809.5 W
|
|
1834
|
+
horsepower_boiler: {
|
|
1835
|
+
factor: 9809.5,
|
|
1836
|
+
symbol: "hp(S)",
|
|
1837
|
+
singular: "boiler horsepower",
|
|
1838
|
+
plural: "boiler horsepower"
|
|
1839
|
+
},
|
|
1840
|
+
// BTU-based
|
|
1841
|
+
// 1 BTU/h = 1055.05585262 J / 3600 s = 0.29307107017222 W
|
|
1842
|
+
btu_per_hour: {
|
|
1843
|
+
factor: 0.29307107017222,
|
|
1844
|
+
symbol: "BTU/h",
|
|
1845
|
+
singular: "BTU per hour",
|
|
1846
|
+
plural: "BTUs per hour"
|
|
1847
|
+
},
|
|
1848
|
+
// 1 BTU/s = 1055.05585262 W
|
|
1849
|
+
btu_per_second: {
|
|
1850
|
+
factor: 1055.05585262,
|
|
1851
|
+
symbol: "BTU/s",
|
|
1852
|
+
singular: "BTU per second",
|
|
1853
|
+
plural: "BTUs per second"
|
|
1854
|
+
},
|
|
1855
|
+
// Other
|
|
1856
|
+
// 1 ton of refrigeration = 12000 BTU/h = 3516.8528420667 W
|
|
1857
|
+
ton_of_refrigeration: {
|
|
1858
|
+
factor: 3516.8528420667,
|
|
1859
|
+
symbol: "TR",
|
|
1860
|
+
singular: "ton of refrigeration",
|
|
1861
|
+
plural: "tons of refrigeration"
|
|
1862
|
+
},
|
|
1863
|
+
// 1 ft⋅lbf/s = 1.3558179483314004 W
|
|
1864
|
+
foot_pound_per_second: {
|
|
1865
|
+
factor: 1.3558179483314003,
|
|
1866
|
+
symbol: "ft\u22C5lbf/s",
|
|
1867
|
+
singular: "foot-pound per second",
|
|
1868
|
+
plural: "foot-pounds per second"
|
|
1869
|
+
},
|
|
1870
|
+
// 1 cal/s = 4.1868 W
|
|
1871
|
+
calorie_per_second: {
|
|
1872
|
+
factor: 4.1868,
|
|
1873
|
+
symbol: "cal/s",
|
|
1874
|
+
singular: "calorie per second",
|
|
1875
|
+
plural: "calories per second"
|
|
1876
|
+
},
|
|
1877
|
+
// 1 kcal/h = 4186.8 / 3600 = 1.163 W
|
|
1878
|
+
kilocalorie_per_hour: {
|
|
1879
|
+
factor: 1.163,
|
|
1880
|
+
symbol: "kcal/h",
|
|
1881
|
+
singular: "kilocalorie per hour",
|
|
1882
|
+
plural: "kilocalories per hour"
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
var POWER_ALIASES = {
|
|
1886
|
+
// gigawatt
|
|
1887
|
+
gw: "gigawatt",
|
|
1888
|
+
// megawatt
|
|
1889
|
+
mw: "megawatt",
|
|
1890
|
+
// kilowatt
|
|
1891
|
+
kw: "kilowatt",
|
|
1892
|
+
// watt
|
|
1893
|
+
w: "watt",
|
|
1894
|
+
// microwatt
|
|
1895
|
+
"\u03BCw": "microwatt",
|
|
1896
|
+
uw: "microwatt",
|
|
1897
|
+
// horsepower_mechanical
|
|
1898
|
+
hp: "horsepower_mechanical",
|
|
1899
|
+
// horsepower_metric
|
|
1900
|
+
ps: "horsepower_metric",
|
|
1901
|
+
cv: "horsepower_metric",
|
|
1902
|
+
// horsepower_electric
|
|
1903
|
+
"hp(e)": "horsepower_electric",
|
|
1904
|
+
// horsepower_boiler
|
|
1905
|
+
"hp(s)": "horsepower_boiler",
|
|
1906
|
+
bhp: "horsepower_boiler",
|
|
1907
|
+
// btu_per_hour
|
|
1908
|
+
"btu/h": "btu_per_hour",
|
|
1909
|
+
"btu/hr": "btu_per_hour",
|
|
1910
|
+
// btu_per_second
|
|
1911
|
+
"btu/s": "btu_per_second",
|
|
1912
|
+
// ton_of_refrigeration
|
|
1913
|
+
tr: "ton_of_refrigeration",
|
|
1914
|
+
rt: "ton_of_refrigeration",
|
|
1915
|
+
// foot_pound_per_second
|
|
1916
|
+
"ft-lb/s": "foot_pound_per_second",
|
|
1917
|
+
"ft-lbf/s": "foot_pound_per_second",
|
|
1918
|
+
// calorie_per_second
|
|
1919
|
+
"cal/s": "calorie_per_second",
|
|
1920
|
+
// kilocalorie_per_hour
|
|
1921
|
+
"kcal/h": "kilocalorie_per_hour",
|
|
1922
|
+
"kcal/hr": "kilocalorie_per_hour"
|
|
1923
|
+
};
|
|
1924
|
+
Object.fromEntries(
|
|
1925
|
+
Object.entries(POWER_UNITS).map(([unit, config]) => [
|
|
1926
|
+
unit,
|
|
1927
|
+
config.factor ?? 1
|
|
1928
|
+
])
|
|
1929
|
+
);
|
|
1930
|
+
|
|
1931
|
+
// src/roles/power/convert.ts
|
|
1932
|
+
var POWER_BASE = "watt";
|
|
1933
|
+
function toBasePower(variant, value) {
|
|
1934
|
+
const config = POWER_UNITS[variant];
|
|
1935
|
+
if (!config) {
|
|
1936
|
+
throw new Error(`Unknown power unit: ${variant}`);
|
|
1937
|
+
}
|
|
1938
|
+
return value * (config.factor ?? 1);
|
|
1939
|
+
}
|
|
1940
|
+
function fromBasePower(variant, baseValue) {
|
|
1941
|
+
const config = POWER_UNITS[variant];
|
|
1942
|
+
if (!config) {
|
|
1943
|
+
throw new Error(`Unknown power unit: ${variant}`);
|
|
1944
|
+
}
|
|
1945
|
+
return baseValue / (config.factor ?? 1);
|
|
1946
|
+
}
|
|
1947
|
+
function convertPower(from, to, value) {
|
|
1948
|
+
if (from === to) {
|
|
1949
|
+
return value;
|
|
1950
|
+
}
|
|
1951
|
+
const baseValue = toBasePower(from, value);
|
|
1952
|
+
return fromBasePower(to, baseValue);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// src/roles/power/cast.ts
|
|
1956
|
+
function castPower(input, targetVariant = POWER_BASE) {
|
|
1957
|
+
if (typeof input === "number") {
|
|
1958
|
+
return Number.isFinite(input) ? input : null;
|
|
1959
|
+
}
|
|
1960
|
+
if (typeof input === "string") {
|
|
1961
|
+
const parsed = parsePowerString(input);
|
|
1962
|
+
if (parsed === null) {
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
if (parsed.unit && parsed.unit !== targetVariant) {
|
|
1966
|
+
return convertPower(parsed.unit, targetVariant, parsed.value);
|
|
1967
|
+
}
|
|
1968
|
+
return parsed.value;
|
|
1969
|
+
}
|
|
1970
|
+
return null;
|
|
1971
|
+
}
|
|
1972
|
+
function tryCastPower(input, targetVariant = POWER_BASE) {
|
|
1973
|
+
const result = castPower(input, targetVariant);
|
|
1974
|
+
if (result === null) {
|
|
1975
|
+
return { ok: false, error: `Cannot cast "${String(input)}" to power` };
|
|
1976
|
+
}
|
|
1977
|
+
return { ok: true, value: result };
|
|
1978
|
+
}
|
|
1979
|
+
function parsePowerString(input) {
|
|
1980
|
+
const trimmed = input.trim();
|
|
1981
|
+
if (!trimmed) {
|
|
1982
|
+
return null;
|
|
1983
|
+
}
|
|
1984
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
1985
|
+
if (!match) {
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
const numStr = match[1].replace(/\s/g, "").replace(",", ".");
|
|
1989
|
+
const value = parseFloat(numStr);
|
|
1990
|
+
if (isNaN(value) || !Number.isFinite(value)) {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
const unitStr = match[2]?.trim().toLowerCase() || "";
|
|
1994
|
+
let unit = null;
|
|
1995
|
+
if (unitStr) {
|
|
1996
|
+
if (unitStr in POWER_ALIASES) {
|
|
1997
|
+
unit = POWER_ALIASES[unitStr];
|
|
1998
|
+
} else if (unitStr in POWER_UNITS) {
|
|
1999
|
+
unit = unitStr;
|
|
2000
|
+
} else {
|
|
2001
|
+
for (const [u, config] of Object.entries(POWER_UNITS)) {
|
|
2002
|
+
if (config.symbol.toLowerCase() === unitStr) {
|
|
2003
|
+
unit = u;
|
|
2004
|
+
break;
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return { value, unit };
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// src/roles/pressure/constants.ts
|
|
2013
|
+
var PRESSURE_UNITS = {
|
|
2014
|
+
// SI Units (todos exatos)
|
|
2015
|
+
megapascal: {
|
|
2016
|
+
factor: 1e6,
|
|
2017
|
+
symbol: "MPa",
|
|
2018
|
+
singular: "megapascal",
|
|
2019
|
+
plural: "megapascals"
|
|
2020
|
+
},
|
|
2021
|
+
kilopascal: {
|
|
2022
|
+
factor: 1e3,
|
|
2023
|
+
symbol: "kPa",
|
|
2024
|
+
singular: "kilopascal",
|
|
2025
|
+
plural: "kilopascals"
|
|
2026
|
+
},
|
|
2027
|
+
hectopascal: {
|
|
2028
|
+
factor: 100,
|
|
2029
|
+
symbol: "hPa",
|
|
2030
|
+
singular: "hectopascal",
|
|
2031
|
+
plural: "hectopascals"
|
|
2032
|
+
},
|
|
2033
|
+
pascal: {
|
|
2034
|
+
factor: 1,
|
|
2035
|
+
symbol: "Pa",
|
|
2036
|
+
singular: "pascal",
|
|
2037
|
+
plural: "pascals"
|
|
2038
|
+
},
|
|
2039
|
+
// Bar (exatos por definição)
|
|
2040
|
+
bar: {
|
|
2041
|
+
factor: 1e5,
|
|
2042
|
+
symbol: "bar",
|
|
2043
|
+
singular: "bar",
|
|
2044
|
+
plural: "bar"
|
|
2045
|
+
},
|
|
2046
|
+
millibar: {
|
|
2047
|
+
factor: 100,
|
|
2048
|
+
// = 1 hPa
|
|
2049
|
+
symbol: "mbar",
|
|
2050
|
+
singular: "millibar",
|
|
2051
|
+
plural: "millibar"
|
|
2052
|
+
},
|
|
2053
|
+
// Atmosphere (exato, CGPM 1954)
|
|
2054
|
+
atmosphere: {
|
|
2055
|
+
factor: 101325,
|
|
2056
|
+
symbol: "atm",
|
|
2057
|
+
singular: "atmosphere",
|
|
2058
|
+
plural: "atmospheres"
|
|
2059
|
+
},
|
|
2060
|
+
// Mercury column
|
|
2061
|
+
torr: {
|
|
2062
|
+
factor: 101325 / 760,
|
|
2063
|
+
// ≈ 133.322368421
|
|
2064
|
+
symbol: "Torr",
|
|
2065
|
+
singular: "torr",
|
|
2066
|
+
plural: "torr"
|
|
2067
|
+
},
|
|
2068
|
+
mmhg: {
|
|
2069
|
+
factor: 133.322387415,
|
|
2070
|
+
// Convenção NIST
|
|
2071
|
+
symbol: "mmHg",
|
|
2072
|
+
singular: "millimeter of mercury",
|
|
2073
|
+
plural: "millimeters of mercury"
|
|
2074
|
+
},
|
|
2075
|
+
inhg: {
|
|
2076
|
+
factor: 3386.389,
|
|
2077
|
+
// Polegadas de mercúrio
|
|
2078
|
+
symbol: "inHg",
|
|
2079
|
+
singular: "inch of mercury",
|
|
2080
|
+
plural: "inches of mercury"
|
|
2081
|
+
},
|
|
2082
|
+
// Imperial (derivados de lb/in²)
|
|
2083
|
+
psi: {
|
|
2084
|
+
factor: 6894.757293168,
|
|
2085
|
+
// 1 lbf/in²
|
|
2086
|
+
symbol: "psi",
|
|
2087
|
+
singular: "pound per square inch",
|
|
2088
|
+
plural: "pounds per square inch"
|
|
2089
|
+
},
|
|
2090
|
+
ksi: {
|
|
2091
|
+
factor: 6894757293168e-6,
|
|
2092
|
+
// 1000 psi
|
|
2093
|
+
symbol: "ksi",
|
|
2094
|
+
singular: "kilopound per square inch",
|
|
2095
|
+
plural: "kilopounds per square inch"
|
|
2096
|
+
},
|
|
2097
|
+
// Water column
|
|
2098
|
+
cmh2o: {
|
|
2099
|
+
factor: 98.0665,
|
|
2100
|
+
// cm de água a 4°C
|
|
2101
|
+
symbol: "cmH\u2082O",
|
|
2102
|
+
singular: "centimeter of water",
|
|
2103
|
+
plural: "centimeters of water"
|
|
2104
|
+
},
|
|
2105
|
+
inh2o: {
|
|
2106
|
+
factor: 249.08891,
|
|
2107
|
+
// polegadas de água
|
|
2108
|
+
symbol: "inH\u2082O",
|
|
2109
|
+
singular: "inch of water",
|
|
2110
|
+
plural: "inches of water"
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
var PRESSURE_ALIASES = {
|
|
2114
|
+
// megapascal
|
|
2115
|
+
mpa: "megapascal",
|
|
2116
|
+
// kilopascal
|
|
2117
|
+
kpa: "kilopascal",
|
|
2118
|
+
// hectopascal
|
|
2119
|
+
hpa: "hectopascal",
|
|
2120
|
+
// pascal
|
|
2121
|
+
pa: "pascal",
|
|
2122
|
+
// bar
|
|
2123
|
+
bar: "bar",
|
|
2124
|
+
// millibar
|
|
2125
|
+
mbar: "millibar",
|
|
2126
|
+
mb: "millibar",
|
|
2127
|
+
// atmosphere
|
|
2128
|
+
atm: "atmosphere",
|
|
2129
|
+
// torr
|
|
2130
|
+
torr: "torr",
|
|
2131
|
+
// mmhg
|
|
2132
|
+
mmhg: "mmhg",
|
|
2133
|
+
// inhg
|
|
2134
|
+
inhg: "inhg",
|
|
2135
|
+
// psi
|
|
2136
|
+
psi: "psi",
|
|
2137
|
+
// ksi
|
|
2138
|
+
ksi: "ksi",
|
|
2139
|
+
// cmh2o
|
|
2140
|
+
cmh2o: "cmh2o",
|
|
2141
|
+
// inh2o
|
|
2142
|
+
inh2o: "inh2o"
|
|
2143
|
+
};
|
|
2144
|
+
Object.fromEntries(
|
|
2145
|
+
Object.entries(PRESSURE_UNITS).map(([unit, config]) => [
|
|
2146
|
+
unit,
|
|
2147
|
+
config.factor ?? 1
|
|
2148
|
+
])
|
|
2149
|
+
);
|
|
2150
|
+
|
|
2151
|
+
// src/roles/pressure/convert.ts
|
|
2152
|
+
var PRESSURE_BASE = "pascal";
|
|
2153
|
+
function toBasePressure(variant, value) {
|
|
2154
|
+
const config = PRESSURE_UNITS[variant];
|
|
2155
|
+
if (!config) {
|
|
2156
|
+
throw new Error(`Unknown pressure unit: ${variant}`);
|
|
2157
|
+
}
|
|
2158
|
+
return value * (config.factor ?? 1);
|
|
2159
|
+
}
|
|
2160
|
+
function fromBasePressure(variant, baseValue) {
|
|
2161
|
+
const config = PRESSURE_UNITS[variant];
|
|
2162
|
+
if (!config) {
|
|
2163
|
+
throw new Error(`Unknown pressure unit: ${variant}`);
|
|
2164
|
+
}
|
|
2165
|
+
return baseValue / (config.factor ?? 1);
|
|
2166
|
+
}
|
|
2167
|
+
function convertPressure(from, to, value) {
|
|
2168
|
+
if (from === to) {
|
|
2169
|
+
return value;
|
|
2170
|
+
}
|
|
2171
|
+
const baseValue = toBasePressure(from, value);
|
|
2172
|
+
return fromBasePressure(to, baseValue);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// src/roles/pressure/cast.ts
|
|
2176
|
+
function castPressure(input, targetVariant = PRESSURE_BASE) {
|
|
2177
|
+
if (typeof input === "number") {
|
|
2178
|
+
return Number.isFinite(input) ? input : null;
|
|
2179
|
+
}
|
|
2180
|
+
if (typeof input === "string") {
|
|
2181
|
+
const parsed = parsePressureString(input);
|
|
2182
|
+
if (parsed === null) {
|
|
2183
|
+
return null;
|
|
2184
|
+
}
|
|
2185
|
+
if (parsed.unit && parsed.unit !== targetVariant) {
|
|
2186
|
+
return convertPressure(parsed.unit, targetVariant, parsed.value);
|
|
2187
|
+
}
|
|
2188
|
+
return parsed.value;
|
|
2189
|
+
}
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
function tryCastPressure(input, targetVariant = PRESSURE_BASE) {
|
|
2193
|
+
const result = castPressure(input, targetVariant);
|
|
2194
|
+
if (result === null) {
|
|
2195
|
+
return { ok: false, error: `Cannot cast "${String(input)}" to pressure` };
|
|
2196
|
+
}
|
|
2197
|
+
return { ok: true, value: result };
|
|
2198
|
+
}
|
|
2199
|
+
function parsePressureString(input) {
|
|
2200
|
+
const trimmed = input.trim();
|
|
2201
|
+
if (!trimmed) {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
2205
|
+
if (!match) {
|
|
2206
|
+
return null;
|
|
2207
|
+
}
|
|
2208
|
+
const numStr = match[1].replace(/\s/g, "").replace(",", ".");
|
|
2209
|
+
const value = parseFloat(numStr);
|
|
2210
|
+
if (isNaN(value) || !Number.isFinite(value)) {
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2213
|
+
const unitStr = match[2]?.trim().toLowerCase() || "";
|
|
2214
|
+
let unit = null;
|
|
2215
|
+
if (unitStr) {
|
|
2216
|
+
if (unitStr in PRESSURE_ALIASES) {
|
|
2217
|
+
unit = PRESSURE_ALIASES[unitStr];
|
|
2218
|
+
} else if (unitStr in PRESSURE_UNITS) {
|
|
2219
|
+
unit = unitStr;
|
|
2220
|
+
} else {
|
|
2221
|
+
for (const [u, config] of Object.entries(PRESSURE_UNITS)) {
|
|
2222
|
+
if (config.symbol.toLowerCase() === unitStr) {
|
|
2223
|
+
unit = u;
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
return { value, unit };
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
// src/roles/frequency/constants.ts
|
|
2233
|
+
var FREQUENCY_UNITS = {
|
|
2234
|
+
// SI Units (todos exatos)
|
|
2235
|
+
terahertz: {
|
|
2236
|
+
factor: 1e12,
|
|
2237
|
+
symbol: "THz",
|
|
2238
|
+
singular: "terahertz",
|
|
2239
|
+
plural: "terahertz"
|
|
2240
|
+
},
|
|
2241
|
+
gigahertz: {
|
|
2242
|
+
factor: 1e9,
|
|
2243
|
+
symbol: "GHz",
|
|
2244
|
+
singular: "gigahertz",
|
|
2245
|
+
plural: "gigahertz"
|
|
2246
|
+
},
|
|
2247
|
+
megahertz: {
|
|
2248
|
+
factor: 1e6,
|
|
2249
|
+
symbol: "MHz",
|
|
2250
|
+
singular: "megahertz",
|
|
2251
|
+
plural: "megahertz"
|
|
2252
|
+
},
|
|
2253
|
+
kilohertz: {
|
|
2254
|
+
factor: 1e3,
|
|
2255
|
+
symbol: "kHz",
|
|
2256
|
+
singular: "kilohertz",
|
|
2257
|
+
plural: "kilohertz"
|
|
2258
|
+
},
|
|
2259
|
+
hertz: {
|
|
2260
|
+
factor: 1,
|
|
2261
|
+
symbol: "Hz",
|
|
2262
|
+
singular: "hertz",
|
|
2263
|
+
plural: "hertz"
|
|
2264
|
+
},
|
|
2265
|
+
millihertz: {
|
|
2266
|
+
factor: 1e-3,
|
|
2267
|
+
symbol: "mHz",
|
|
2268
|
+
singular: "millihertz",
|
|
2269
|
+
plural: "millihertz"
|
|
2270
|
+
},
|
|
2271
|
+
microhertz: {
|
|
2272
|
+
factor: 1e-6,
|
|
2273
|
+
symbol: "\u03BCHz",
|
|
2274
|
+
singular: "microhertz",
|
|
2275
|
+
plural: "microhertz"
|
|
2276
|
+
},
|
|
2277
|
+
// Other units
|
|
2278
|
+
rpm: {
|
|
2279
|
+
factor: 1 / 60,
|
|
2280
|
+
// 1 RPM = 1/60 Hz
|
|
2281
|
+
symbol: "rpm",
|
|
2282
|
+
singular: "revolution per minute",
|
|
2283
|
+
plural: "revolutions per minute"
|
|
2284
|
+
},
|
|
2285
|
+
bpm: {
|
|
2286
|
+
factor: 1 / 60,
|
|
2287
|
+
// 1 BPM = 1/60 Hz
|
|
2288
|
+
symbol: "bpm",
|
|
2289
|
+
singular: "beat per minute",
|
|
2290
|
+
plural: "beats per minute"
|
|
2291
|
+
},
|
|
2292
|
+
radians_per_second: {
|
|
2293
|
+
factor: 1 / (2 * Math.PI),
|
|
2294
|
+
// 1 rad/s = 1/(2π) Hz ≈ 0.159154943
|
|
2295
|
+
symbol: "rad/s",
|
|
2296
|
+
singular: "radian per second",
|
|
2297
|
+
plural: "radians per second"
|
|
2298
|
+
},
|
|
2299
|
+
cycles_per_minute: {
|
|
2300
|
+
factor: 1 / 60,
|
|
2301
|
+
// same as RPM
|
|
2302
|
+
symbol: "cpm",
|
|
2303
|
+
singular: "cycle per minute",
|
|
2304
|
+
plural: "cycles per minute"
|
|
2305
|
+
}
|
|
2306
|
+
};
|
|
2307
|
+
var FREQUENCY_ALIASES = {
|
|
2308
|
+
// terahertz
|
|
2309
|
+
thz: "terahertz",
|
|
2310
|
+
// gigahertz
|
|
2311
|
+
ghz: "gigahertz",
|
|
2312
|
+
// megahertz
|
|
2313
|
+
mhz: "megahertz",
|
|
2314
|
+
// kilohertz
|
|
2315
|
+
khz: "kilohertz",
|
|
2316
|
+
// hertz
|
|
2317
|
+
hz: "hertz",
|
|
2318
|
+
cps: "hertz",
|
|
2319
|
+
// microhertz
|
|
2320
|
+
"\u03BChz": "microhertz",
|
|
2321
|
+
uhz: "microhertz",
|
|
2322
|
+
// rpm
|
|
2323
|
+
rpm: "rpm",
|
|
2324
|
+
// bpm
|
|
2325
|
+
bpm: "bpm",
|
|
2326
|
+
// radians_per_second
|
|
2327
|
+
"rad/s": "radians_per_second",
|
|
2328
|
+
// cycles_per_minute
|
|
2329
|
+
cpm: "cycles_per_minute"
|
|
2330
|
+
};
|
|
2331
|
+
Object.fromEntries(
|
|
2332
|
+
Object.entries(FREQUENCY_UNITS).map(([unit, config]) => [
|
|
2333
|
+
unit,
|
|
2334
|
+
config.factor ?? 1
|
|
2335
|
+
])
|
|
2336
|
+
);
|
|
2337
|
+
|
|
2338
|
+
// src/roles/frequency/convert.ts
|
|
2339
|
+
var FREQUENCY_BASE = "hertz";
|
|
2340
|
+
function toBaseFrequency(variant, value) {
|
|
2341
|
+
const config = FREQUENCY_UNITS[variant];
|
|
2342
|
+
if (!config) {
|
|
2343
|
+
throw new Error(`Unknown frequency unit: ${variant}`);
|
|
2344
|
+
}
|
|
2345
|
+
return value * (config.factor ?? 1);
|
|
2346
|
+
}
|
|
2347
|
+
function fromBaseFrequency(variant, baseValue) {
|
|
2348
|
+
const config = FREQUENCY_UNITS[variant];
|
|
2349
|
+
if (!config) {
|
|
2350
|
+
throw new Error(`Unknown frequency unit: ${variant}`);
|
|
2351
|
+
}
|
|
2352
|
+
return baseValue / (config.factor ?? 1);
|
|
2353
|
+
}
|
|
2354
|
+
function convertFrequency(from, to, value) {
|
|
2355
|
+
if (from === to) {
|
|
2356
|
+
return value;
|
|
2357
|
+
}
|
|
2358
|
+
const baseValue = toBaseFrequency(from, value);
|
|
2359
|
+
return fromBaseFrequency(to, baseValue);
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
// src/roles/frequency/cast.ts
|
|
2363
|
+
function castFrequency(input, targetVariant = FREQUENCY_BASE) {
|
|
2364
|
+
if (typeof input === "number") {
|
|
2365
|
+
return Number.isFinite(input) ? input : null;
|
|
2366
|
+
}
|
|
2367
|
+
if (typeof input === "string") {
|
|
2368
|
+
const parsed = parseFrequencyString(input);
|
|
2369
|
+
if (parsed === null) {
|
|
2370
|
+
return null;
|
|
2371
|
+
}
|
|
2372
|
+
if (parsed.unit && parsed.unit !== targetVariant) {
|
|
2373
|
+
return convertFrequency(parsed.unit, targetVariant, parsed.value);
|
|
2374
|
+
}
|
|
2375
|
+
return parsed.value;
|
|
2376
|
+
}
|
|
2377
|
+
return null;
|
|
2378
|
+
}
|
|
2379
|
+
function tryCastFrequency(input, targetVariant = FREQUENCY_BASE) {
|
|
2380
|
+
const result = castFrequency(input, targetVariant);
|
|
2381
|
+
if (result === null) {
|
|
2382
|
+
return { ok: false, error: `Cannot cast "${String(input)}" to frequency` };
|
|
2383
|
+
}
|
|
2384
|
+
return { ok: true, value: result };
|
|
2385
|
+
}
|
|
2386
|
+
function parseFrequencyString(input) {
|
|
2387
|
+
const trimmed = input.trim();
|
|
2388
|
+
if (!trimmed) {
|
|
2389
|
+
return null;
|
|
2390
|
+
}
|
|
2391
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
2392
|
+
if (!match) {
|
|
2393
|
+
return null;
|
|
2394
|
+
}
|
|
2395
|
+
const numStr = match[1].replace(/\s/g, "").replace(",", ".");
|
|
2396
|
+
const value = parseFloat(numStr);
|
|
2397
|
+
if (isNaN(value) || !Number.isFinite(value)) {
|
|
2398
|
+
return null;
|
|
2399
|
+
}
|
|
2400
|
+
const unitStr = match[2]?.trim().toLowerCase() || "";
|
|
2401
|
+
let unit = null;
|
|
2402
|
+
if (unitStr) {
|
|
2403
|
+
if (unitStr in FREQUENCY_ALIASES) {
|
|
2404
|
+
unit = FREQUENCY_ALIASES[unitStr];
|
|
2405
|
+
} else if (unitStr in FREQUENCY_UNITS) {
|
|
2406
|
+
unit = unitStr;
|
|
2407
|
+
} else {
|
|
2408
|
+
for (const [u, config] of Object.entries(FREQUENCY_UNITS)) {
|
|
2409
|
+
if (config.symbol.toLowerCase() === unitStr) {
|
|
2410
|
+
unit = u;
|
|
2411
|
+
break;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
return { value, unit };
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
// src/roles/angle/constants.ts
|
|
2420
|
+
var DEGREES_PER_RADIAN = 180 / Math.PI;
|
|
2421
|
+
var DEGREES_PER_MILLIRADIAN = 180 / (1e3 * Math.PI);
|
|
2422
|
+
var ANGLE_UNITS = {
|
|
2423
|
+
// Volta completa
|
|
2424
|
+
turn: {
|
|
2425
|
+
factor: 360,
|
|
2426
|
+
symbol: "tr",
|
|
2427
|
+
singular: "turn",
|
|
2428
|
+
plural: "turns"
|
|
2429
|
+
},
|
|
2430
|
+
// Base
|
|
2431
|
+
degree: {
|
|
2432
|
+
factor: 1,
|
|
2433
|
+
symbol: "\xB0",
|
|
2434
|
+
singular: "degree",
|
|
2435
|
+
plural: "degrees",
|
|
2436
|
+
noSpace: true
|
|
2437
|
+
// 45° não 45 °
|
|
2438
|
+
},
|
|
2439
|
+
// Subdivisões do grau
|
|
2440
|
+
arcminute: {
|
|
2441
|
+
factor: 1 / 60,
|
|
2442
|
+
// 0.016666...
|
|
2443
|
+
symbol: "\u2032",
|
|
2444
|
+
singular: "arcminute",
|
|
2445
|
+
plural: "arcminutes",
|
|
2446
|
+
noSpace: true
|
|
2447
|
+
// 30′ não 30 ′
|
|
2448
|
+
},
|
|
2449
|
+
arcsecond: {
|
|
2450
|
+
factor: 1 / 3600,
|
|
2451
|
+
// 0.000277...
|
|
2452
|
+
symbol: "\u2033",
|
|
2453
|
+
singular: "arcsecond",
|
|
2454
|
+
plural: "arcseconds",
|
|
2455
|
+
noSpace: true
|
|
2456
|
+
// 45″ não 45 ″
|
|
2457
|
+
},
|
|
2458
|
+
milliarcsecond: {
|
|
2459
|
+
factor: 1 / 36e5,
|
|
2460
|
+
// 2.777...e-7
|
|
2461
|
+
symbol: "mas",
|
|
2462
|
+
singular: "milliarcsecond",
|
|
2463
|
+
plural: "milliarcseconds"
|
|
2464
|
+
},
|
|
2465
|
+
// Radianos (unidade SI)
|
|
2466
|
+
radian: {
|
|
2467
|
+
factor: DEGREES_PER_RADIAN,
|
|
2468
|
+
// 180/π ≈ 57.2958
|
|
2469
|
+
symbol: "rad",
|
|
2470
|
+
singular: "radian",
|
|
2471
|
+
plural: "radians"
|
|
2472
|
+
},
|
|
2473
|
+
milliradian: {
|
|
2474
|
+
factor: DEGREES_PER_MILLIRADIAN,
|
|
2475
|
+
// 180/(1000π) ≈ 0.0573
|
|
2476
|
+
symbol: "mrad",
|
|
2477
|
+
singular: "milliradian",
|
|
2478
|
+
plural: "milliradians"
|
|
2479
|
+
},
|
|
2480
|
+
// Gradiano (gon)
|
|
2481
|
+
gradian: {
|
|
2482
|
+
factor: 0.9,
|
|
2483
|
+
// 360/400
|
|
2484
|
+
symbol: "gon",
|
|
2485
|
+
singular: "gradian",
|
|
2486
|
+
plural: "gradians"
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
var ANGLE_ALIASES = {
|
|
2490
|
+
// turn
|
|
2491
|
+
tr: "turn",
|
|
2492
|
+
rev: "turn",
|
|
2493
|
+
// degree
|
|
2494
|
+
"\xB0": "degree",
|
|
2495
|
+
deg: "degree",
|
|
2496
|
+
// arcminute
|
|
2497
|
+
"\u2032": "arcminute",
|
|
2498
|
+
"'": "arcminute",
|
|
2499
|
+
arcmin: "arcminute",
|
|
2500
|
+
// arcsecond
|
|
2501
|
+
"\u2033": "arcsecond",
|
|
2502
|
+
'"': "arcsecond",
|
|
2503
|
+
arcsec: "arcsecond",
|
|
2504
|
+
// milliarcsecond
|
|
2505
|
+
mas: "milliarcsecond",
|
|
2506
|
+
// radian
|
|
2507
|
+
rad: "radian",
|
|
2508
|
+
// milliradian
|
|
2509
|
+
mrad: "milliradian",
|
|
2510
|
+
// gradian
|
|
2511
|
+
gon: "gradian",
|
|
2512
|
+
grad: "gradian"
|
|
2513
|
+
};
|
|
2514
|
+
Object.fromEntries(
|
|
2515
|
+
Object.entries(ANGLE_UNITS).map(([unit, config]) => [
|
|
2516
|
+
unit,
|
|
2517
|
+
config.factor ?? 1
|
|
2518
|
+
])
|
|
2519
|
+
);
|
|
2520
|
+
|
|
2521
|
+
// src/roles/angle/convert.ts
|
|
2522
|
+
var ANGLE_BASE = "degree";
|
|
2523
|
+
function toBaseAngle(variant, value) {
|
|
2524
|
+
const config = ANGLE_UNITS[variant];
|
|
2525
|
+
if (!config) {
|
|
2526
|
+
throw new Error(`Unknown angle unit: ${variant}`);
|
|
2527
|
+
}
|
|
2528
|
+
return value * (config.factor ?? 1);
|
|
2529
|
+
}
|
|
2530
|
+
function fromBaseAngle(variant, baseValue) {
|
|
2531
|
+
const config = ANGLE_UNITS[variant];
|
|
2532
|
+
if (!config) {
|
|
2533
|
+
throw new Error(`Unknown angle unit: ${variant}`);
|
|
2534
|
+
}
|
|
2535
|
+
return baseValue / (config.factor ?? 1);
|
|
2536
|
+
}
|
|
2537
|
+
function convertAngle(from, to, value) {
|
|
2538
|
+
if (from === to) {
|
|
2539
|
+
return value;
|
|
2540
|
+
}
|
|
2541
|
+
const baseValue = toBaseAngle(from, value);
|
|
2542
|
+
return fromBaseAngle(to, baseValue);
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
// src/roles/angle/cast.ts
|
|
2546
|
+
function castAngle(input, targetVariant = ANGLE_BASE) {
|
|
2547
|
+
if (typeof input === "number") {
|
|
2548
|
+
return Number.isFinite(input) ? input : null;
|
|
2549
|
+
}
|
|
2550
|
+
if (typeof input === "string") {
|
|
2551
|
+
const parsed = parseAngleString(input);
|
|
2552
|
+
if (parsed === null) {
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
if (parsed.unit && parsed.unit !== targetVariant) {
|
|
2556
|
+
return convertAngle(parsed.unit, targetVariant, parsed.value);
|
|
2557
|
+
}
|
|
2558
|
+
return parsed.value;
|
|
2559
|
+
}
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
function tryCastAngle(input, targetVariant = ANGLE_BASE) {
|
|
2563
|
+
const result = castAngle(input, targetVariant);
|
|
2564
|
+
if (result === null) {
|
|
2565
|
+
return { ok: false, error: `Cannot cast "${String(input)}" to angle` };
|
|
2566
|
+
}
|
|
2567
|
+
return { ok: true, value: result };
|
|
2568
|
+
}
|
|
2569
|
+
function parseAngleString(input) {
|
|
2570
|
+
const trimmed = input.trim();
|
|
2571
|
+
if (!trimmed) {
|
|
2572
|
+
return null;
|
|
2573
|
+
}
|
|
2574
|
+
const degreeMatch = trimmed.match(/^([+-]?[\d.,]+)°$/);
|
|
2575
|
+
if (degreeMatch) {
|
|
2576
|
+
const numStr2 = degreeMatch[1].replace(",", ".");
|
|
2577
|
+
const value2 = parseFloat(numStr2);
|
|
2578
|
+
if (isNaN(value2) || !Number.isFinite(value2)) {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
return { value: value2, unit: "degree" };
|
|
2582
|
+
}
|
|
2583
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
2584
|
+
if (!match) {
|
|
2585
|
+
return null;
|
|
2586
|
+
}
|
|
2587
|
+
const numStr = match[1].replace(/\s/g, "").replace(",", ".");
|
|
2588
|
+
const value = parseFloat(numStr);
|
|
2589
|
+
if (isNaN(value) || !Number.isFinite(value)) {
|
|
2590
|
+
return null;
|
|
2591
|
+
}
|
|
2592
|
+
const unitStr = match[2]?.trim().toLowerCase() || "";
|
|
2593
|
+
let unit = null;
|
|
2594
|
+
if (unitStr) {
|
|
2595
|
+
if (unitStr in ANGLE_ALIASES) {
|
|
2596
|
+
unit = ANGLE_ALIASES[unitStr];
|
|
2597
|
+
} else if (unitStr in ANGLE_UNITS) {
|
|
2598
|
+
unit = unitStr;
|
|
2599
|
+
} else {
|
|
2600
|
+
for (const [u, config] of Object.entries(ANGLE_UNITS)) {
|
|
2601
|
+
if (config.symbol.toLowerCase() === unitStr || config.symbol === unitStr) {
|
|
2602
|
+
unit = u;
|
|
2603
|
+
break;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return { value, unit };
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// src/roles/time/constants.ts
|
|
2612
|
+
var TIME_UNITS = {
|
|
2613
|
+
// SI prefixes (exatos)
|
|
2614
|
+
nanosecond: {
|
|
2615
|
+
factor: 1e-9,
|
|
2616
|
+
symbol: "ns",
|
|
2617
|
+
singular: "nanosecond",
|
|
2618
|
+
plural: "nanoseconds"
|
|
2619
|
+
},
|
|
2620
|
+
microsecond: {
|
|
2621
|
+
factor: 1e-6,
|
|
2622
|
+
symbol: "\u03BCs",
|
|
2623
|
+
singular: "microsecond",
|
|
2624
|
+
plural: "microseconds"
|
|
2625
|
+
},
|
|
2626
|
+
millisecond: {
|
|
2627
|
+
factor: 1e-3,
|
|
2628
|
+
symbol: "ms",
|
|
2629
|
+
singular: "millisecond",
|
|
2630
|
+
plural: "milliseconds"
|
|
2631
|
+
},
|
|
2632
|
+
second: {
|
|
2633
|
+
factor: 1,
|
|
2634
|
+
symbol: "s",
|
|
2635
|
+
singular: "second",
|
|
2636
|
+
plural: "seconds"
|
|
2637
|
+
},
|
|
2638
|
+
// Common (exatos)
|
|
2639
|
+
minute: {
|
|
2640
|
+
factor: 60,
|
|
2641
|
+
symbol: "min",
|
|
2642
|
+
singular: "minute",
|
|
2643
|
+
plural: "minutes"
|
|
2644
|
+
},
|
|
2645
|
+
hour: {
|
|
2646
|
+
factor: 3600,
|
|
2647
|
+
symbol: "h",
|
|
2648
|
+
singular: "hour",
|
|
2649
|
+
plural: "hours"
|
|
2650
|
+
},
|
|
2651
|
+
day: {
|
|
2652
|
+
factor: 86400,
|
|
2653
|
+
symbol: "d",
|
|
2654
|
+
singular: "day",
|
|
2655
|
+
plural: "days"
|
|
2656
|
+
},
|
|
2657
|
+
week: {
|
|
2658
|
+
factor: 604800,
|
|
2659
|
+
symbol: "wk",
|
|
2660
|
+
singular: "week",
|
|
2661
|
+
plural: "weeks"
|
|
2662
|
+
},
|
|
2663
|
+
// Calendar (aproximados - baseados no ano Gregoriano)
|
|
2664
|
+
month: {
|
|
2665
|
+
factor: 2629746,
|
|
2666
|
+
// 31556952 / 12
|
|
2667
|
+
symbol: "mo",
|
|
2668
|
+
singular: "month",
|
|
2669
|
+
plural: "months"
|
|
2670
|
+
},
|
|
2671
|
+
year: {
|
|
2672
|
+
factor: 31556952,
|
|
2673
|
+
// 365.2425 * 86400
|
|
2674
|
+
symbol: "yr",
|
|
2675
|
+
singular: "year",
|
|
2676
|
+
plural: "years"
|
|
2677
|
+
},
|
|
2678
|
+
decade: {
|
|
2679
|
+
factor: 315569520,
|
|
2680
|
+
// 10 * year
|
|
2681
|
+
symbol: "dec",
|
|
2682
|
+
singular: "decade",
|
|
2683
|
+
plural: "decades"
|
|
2684
|
+
},
|
|
2685
|
+
century: {
|
|
2686
|
+
factor: 3155695200,
|
|
2687
|
+
// 100 * year
|
|
2688
|
+
symbol: "c",
|
|
2689
|
+
singular: "century",
|
|
2690
|
+
plural: "centuries"
|
|
2691
|
+
},
|
|
2692
|
+
millennium: {
|
|
2693
|
+
factor: 31556952e3,
|
|
2694
|
+
// 1000 * year
|
|
2695
|
+
symbol: "ky",
|
|
2696
|
+
singular: "millennium",
|
|
2697
|
+
plural: "millennia"
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
var TIME_ALIASES = {
|
|
2701
|
+
// nanosecond
|
|
2702
|
+
ns: "nanosecond",
|
|
2703
|
+
// microsecond
|
|
2704
|
+
"\u03BCs": "microsecond",
|
|
2705
|
+
us: "microsecond",
|
|
2706
|
+
// millisecond
|
|
2707
|
+
ms: "millisecond",
|
|
2708
|
+
// second
|
|
2709
|
+
s: "second",
|
|
2710
|
+
sec: "second",
|
|
2711
|
+
// minute
|
|
2712
|
+
min: "minute",
|
|
2713
|
+
// hour
|
|
2714
|
+
h: "hour",
|
|
2715
|
+
hr: "hour",
|
|
2716
|
+
hrs: "hour",
|
|
2717
|
+
// day
|
|
2718
|
+
d: "day",
|
|
2719
|
+
// week
|
|
2720
|
+
wk: "week",
|
|
2721
|
+
// month
|
|
2722
|
+
mo: "month",
|
|
2723
|
+
// year
|
|
2724
|
+
yr: "year",
|
|
2725
|
+
y: "year",
|
|
2726
|
+
// decade
|
|
2727
|
+
dec: "decade",
|
|
2728
|
+
// century
|
|
2729
|
+
c: "century",
|
|
2730
|
+
// millennium
|
|
2731
|
+
ky: "millennium"
|
|
2732
|
+
};
|
|
2733
|
+
Object.fromEntries(
|
|
2734
|
+
Object.entries(TIME_UNITS).map(([unit, config]) => [
|
|
2735
|
+
unit,
|
|
2736
|
+
config.factor ?? 1
|
|
2737
|
+
])
|
|
2738
|
+
);
|
|
2739
|
+
|
|
2740
|
+
// src/roles/time/convert.ts
|
|
2741
|
+
var TIME_BASE = "second";
|
|
2742
|
+
function toBaseTime(variant, value) {
|
|
2743
|
+
const config = TIME_UNITS[variant];
|
|
2744
|
+
if (!config) {
|
|
2745
|
+
throw new Error(`Unknown time unit: ${variant}`);
|
|
2746
|
+
}
|
|
2747
|
+
return value * (config.factor ?? 1);
|
|
2748
|
+
}
|
|
2749
|
+
function fromBaseTime(variant, baseValue) {
|
|
2750
|
+
const config = TIME_UNITS[variant];
|
|
2751
|
+
if (!config) {
|
|
2752
|
+
throw new Error(`Unknown time unit: ${variant}`);
|
|
2753
|
+
}
|
|
2754
|
+
return baseValue / (config.factor ?? 1);
|
|
2755
|
+
}
|
|
2756
|
+
function convertTime(from, to, value) {
|
|
2757
|
+
if (from === to) {
|
|
2758
|
+
return value;
|
|
2759
|
+
}
|
|
2760
|
+
const baseValue = toBaseTime(from, value);
|
|
2761
|
+
return fromBaseTime(to, baseValue);
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// src/roles/time/cast.ts
|
|
2765
|
+
function castTime(input, targetVariant = TIME_BASE) {
|
|
2766
|
+
if (typeof input === "number") {
|
|
2767
|
+
return Number.isFinite(input) ? input : null;
|
|
2768
|
+
}
|
|
2769
|
+
if (typeof input === "string") {
|
|
2770
|
+
const parsed = parseTimeString(input);
|
|
2771
|
+
if (parsed === null) {
|
|
2772
|
+
return null;
|
|
2773
|
+
}
|
|
2774
|
+
if (parsed.unit && parsed.unit !== targetVariant) {
|
|
2775
|
+
return convertTime(parsed.unit, targetVariant, parsed.value);
|
|
2776
|
+
}
|
|
2777
|
+
return parsed.value;
|
|
2778
|
+
}
|
|
2779
|
+
return null;
|
|
2780
|
+
}
|
|
2781
|
+
function tryCastTime(input, targetVariant = TIME_BASE) {
|
|
2782
|
+
const result = castTime(input, targetVariant);
|
|
2783
|
+
if (result === null) {
|
|
2784
|
+
return { ok: false, error: `Cannot cast "${String(input)}" to time` };
|
|
2785
|
+
}
|
|
2786
|
+
return { ok: true, value: result };
|
|
2787
|
+
}
|
|
2788
|
+
function parseTimeString(input) {
|
|
2789
|
+
const trimmed = input.trim();
|
|
2790
|
+
if (!trimmed) {
|
|
2791
|
+
return null;
|
|
2792
|
+
}
|
|
2793
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
2794
|
+
if (!match) {
|
|
2795
|
+
return null;
|
|
2796
|
+
}
|
|
2797
|
+
const numStr = match[1].replace(/\s/g, "").replace(",", ".");
|
|
2798
|
+
const value = parseFloat(numStr);
|
|
2799
|
+
if (isNaN(value) || !Number.isFinite(value)) {
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
const unitStr = match[2]?.trim().toLowerCase() || "";
|
|
2803
|
+
let unit = null;
|
|
2804
|
+
if (unitStr) {
|
|
2805
|
+
if (unitStr in TIME_ALIASES) {
|
|
2806
|
+
unit = TIME_ALIASES[unitStr];
|
|
2807
|
+
} else if (unitStr in TIME_UNITS) {
|
|
2808
|
+
unit = unitStr;
|
|
2809
|
+
} else {
|
|
2810
|
+
for (const [u, config] of Object.entries(TIME_UNITS)) {
|
|
2811
|
+
if (config.symbol.toLowerCase() === unitStr) {
|
|
2812
|
+
unit = u;
|
|
2813
|
+
break;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
return { value, unit };
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// src/roles/digital/constants.ts
|
|
2822
|
+
var DIGITAL_UNITS = {
|
|
2823
|
+
// Fundamental
|
|
2824
|
+
bit: {
|
|
2825
|
+
factor: 0.125,
|
|
2826
|
+
// 1/8 byte
|
|
2827
|
+
symbol: "b",
|
|
2828
|
+
singular: "bit",
|
|
2829
|
+
plural: "bits"
|
|
2830
|
+
},
|
|
2831
|
+
byte: {
|
|
2832
|
+
factor: 1,
|
|
2833
|
+
symbol: "B",
|
|
2834
|
+
singular: "byte",
|
|
2835
|
+
plural: "bytes"
|
|
2836
|
+
},
|
|
2837
|
+
// IEC Binary (base 1024) - RAM, cache, file systems
|
|
2838
|
+
kibibyte: {
|
|
2839
|
+
factor: 1024,
|
|
2840
|
+
// 2^10
|
|
2841
|
+
symbol: "KiB",
|
|
2842
|
+
singular: "kibibyte",
|
|
2843
|
+
plural: "kibibytes"
|
|
2844
|
+
},
|
|
2845
|
+
mebibyte: {
|
|
2846
|
+
factor: 1048576,
|
|
2847
|
+
// 2^20
|
|
2848
|
+
symbol: "MiB",
|
|
2849
|
+
singular: "mebibyte",
|
|
2850
|
+
plural: "mebibytes"
|
|
2851
|
+
},
|
|
2852
|
+
gibibyte: {
|
|
2853
|
+
factor: 1073741824,
|
|
2854
|
+
// 2^30
|
|
2855
|
+
symbol: "GiB",
|
|
2856
|
+
singular: "gibibyte",
|
|
2857
|
+
plural: "gibibytes"
|
|
2858
|
+
},
|
|
2859
|
+
tebibyte: {
|
|
2860
|
+
factor: 1099511627776,
|
|
2861
|
+
// 2^40
|
|
2862
|
+
symbol: "TiB",
|
|
2863
|
+
singular: "tebibyte",
|
|
2864
|
+
plural: "tebibytes"
|
|
2865
|
+
},
|
|
2866
|
+
pebibyte: {
|
|
2867
|
+
factor: 1125899906842624,
|
|
2868
|
+
// 2^50
|
|
2869
|
+
symbol: "PiB",
|
|
2870
|
+
singular: "pebibyte",
|
|
2871
|
+
plural: "pebibytes"
|
|
2872
|
+
},
|
|
2873
|
+
exbibyte: {
|
|
2874
|
+
factor: 1152921504606847e3,
|
|
2875
|
+
// 2^60
|
|
2876
|
+
symbol: "EiB",
|
|
2877
|
+
singular: "exbibyte",
|
|
2878
|
+
plural: "exbibytes"
|
|
2879
|
+
},
|
|
2880
|
+
// SI Decimal (base 1000) - HD marketing, network speeds
|
|
2881
|
+
kilobyte: {
|
|
2882
|
+
factor: 1e3,
|
|
2883
|
+
// 10^3
|
|
2884
|
+
symbol: "kB",
|
|
2885
|
+
singular: "kilobyte",
|
|
2886
|
+
plural: "kilobytes"
|
|
2887
|
+
},
|
|
2888
|
+
megabyte: {
|
|
2889
|
+
factor: 1e6,
|
|
2890
|
+
// 10^6
|
|
2891
|
+
symbol: "MB",
|
|
2892
|
+
singular: "megabyte",
|
|
2893
|
+
plural: "megabytes"
|
|
2894
|
+
},
|
|
2895
|
+
gigabyte: {
|
|
2896
|
+
factor: 1e9,
|
|
2897
|
+
// 10^9
|
|
2898
|
+
symbol: "GB",
|
|
2899
|
+
singular: "gigabyte",
|
|
2900
|
+
plural: "gigabytes"
|
|
2901
|
+
},
|
|
2902
|
+
terabyte: {
|
|
2903
|
+
factor: 1e12,
|
|
2904
|
+
// 10^12
|
|
2905
|
+
symbol: "TB",
|
|
2906
|
+
singular: "terabyte",
|
|
2907
|
+
plural: "terabytes"
|
|
2908
|
+
},
|
|
2909
|
+
petabyte: {
|
|
2910
|
+
factor: 1e15,
|
|
2911
|
+
// 10^15
|
|
2912
|
+
symbol: "PB",
|
|
2913
|
+
singular: "petabyte",
|
|
2914
|
+
plural: "petabytes"
|
|
2915
|
+
},
|
|
2916
|
+
exabyte: {
|
|
2917
|
+
factor: 1e18,
|
|
2918
|
+
// 10^18
|
|
2919
|
+
symbol: "EB",
|
|
2920
|
+
singular: "exabyte",
|
|
2921
|
+
plural: "exabytes"
|
|
2922
|
+
}
|
|
2923
|
+
};
|
|
2924
|
+
var DIGITAL_ALIASES = {
|
|
2925
|
+
// bit
|
|
2926
|
+
b: "bit",
|
|
2927
|
+
// byte
|
|
2928
|
+
B: "byte",
|
|
2929
|
+
// kibibyte (IEC)
|
|
2930
|
+
KiB: "kibibyte",
|
|
2931
|
+
// mebibyte (IEC)
|
|
2932
|
+
MiB: "mebibyte",
|
|
2933
|
+
// gibibyte (IEC)
|
|
2934
|
+
GiB: "gibibyte",
|
|
2935
|
+
// tebibyte (IEC)
|
|
2936
|
+
TiB: "tebibyte",
|
|
2937
|
+
// pebibyte (IEC)
|
|
2938
|
+
PiB: "pebibyte",
|
|
2939
|
+
// exbibyte (IEC)
|
|
2940
|
+
EiB: "exbibyte",
|
|
2941
|
+
// kilobyte (SI)
|
|
2942
|
+
kB: "kilobyte",
|
|
2943
|
+
kb: "kilobyte",
|
|
2944
|
+
// megabyte (SI)
|
|
2945
|
+
MB: "megabyte",
|
|
2946
|
+
// gigabyte (SI)
|
|
2947
|
+
GB: "gigabyte",
|
|
2948
|
+
// terabyte (SI)
|
|
2949
|
+
TB: "terabyte",
|
|
2950
|
+
// petabyte (SI)
|
|
2951
|
+
PB: "petabyte",
|
|
2952
|
+
// exabyte (SI)
|
|
2953
|
+
EB: "exabyte"
|
|
2954
|
+
};
|
|
2955
|
+
Object.fromEntries(
|
|
2956
|
+
Object.entries(DIGITAL_UNITS).map(([unit, config]) => [
|
|
2957
|
+
unit,
|
|
2958
|
+
config.factor ?? 1
|
|
2959
|
+
])
|
|
2960
|
+
);
|
|
2961
|
+
|
|
2962
|
+
// src/roles/digital/convert.ts
|
|
2963
|
+
var DIGITAL_BASE = "byte";
|
|
2964
|
+
function toBaseDigital(variant, value) {
|
|
2965
|
+
const config = DIGITAL_UNITS[variant];
|
|
2966
|
+
if (!config) {
|
|
2967
|
+
throw new Error(`Unknown digital unit: ${variant}`);
|
|
2968
|
+
}
|
|
2969
|
+
return value * (config.factor ?? 1);
|
|
2970
|
+
}
|
|
2971
|
+
function fromBaseDigital(variant, baseValue) {
|
|
2972
|
+
const config = DIGITAL_UNITS[variant];
|
|
2973
|
+
if (!config) {
|
|
2974
|
+
throw new Error(`Unknown digital unit: ${variant}`);
|
|
2975
|
+
}
|
|
2976
|
+
return baseValue / (config.factor ?? 1);
|
|
2977
|
+
}
|
|
2978
|
+
function convertDigital(from, to, value) {
|
|
2979
|
+
if (from === to) {
|
|
2980
|
+
return value;
|
|
2981
|
+
}
|
|
2982
|
+
const baseValue = toBaseDigital(from, value);
|
|
2983
|
+
return fromBaseDigital(to, baseValue);
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
// src/roles/digital/cast.ts
|
|
2987
|
+
function castDigital(input, targetVariant = DIGITAL_BASE) {
|
|
2988
|
+
if (typeof input === "number") {
|
|
2989
|
+
return Number.isFinite(input) ? input : null;
|
|
2990
|
+
}
|
|
2991
|
+
if (typeof input === "string") {
|
|
2992
|
+
const parsed = parseDigitalString(input);
|
|
2993
|
+
if (parsed === null) {
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
if (parsed.unit && parsed.unit !== targetVariant) {
|
|
2997
|
+
return convertDigital(parsed.unit, targetVariant, parsed.value);
|
|
2998
|
+
}
|
|
2999
|
+
return parsed.value;
|
|
3000
|
+
}
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
function tryCastDigital(input, targetVariant = DIGITAL_BASE) {
|
|
3004
|
+
const result = castDigital(input, targetVariant);
|
|
3005
|
+
if (result === null) {
|
|
3006
|
+
return { ok: false, error: `Cannot cast "${String(input)}" to digital storage` };
|
|
3007
|
+
}
|
|
3008
|
+
return { ok: true, value: result };
|
|
3009
|
+
}
|
|
3010
|
+
function parseDigitalString(input) {
|
|
3011
|
+
const trimmed = input.trim();
|
|
3012
|
+
if (!trimmed) {
|
|
3013
|
+
return null;
|
|
3014
|
+
}
|
|
3015
|
+
const match = trimmed.match(/^([+-]?[\d.,\s]+)\s*(.*)$/);
|
|
3016
|
+
if (!match) {
|
|
3017
|
+
return null;
|
|
3018
|
+
}
|
|
3019
|
+
const numStr = match[1].replace(/\s/g, "").replace(",", ".");
|
|
3020
|
+
const value = parseFloat(numStr);
|
|
3021
|
+
if (isNaN(value) || !Number.isFinite(value)) {
|
|
3022
|
+
return null;
|
|
3023
|
+
}
|
|
3024
|
+
const unitStr = match[2]?.trim() || "";
|
|
3025
|
+
const lowerUnitStr = unitStr.toLowerCase();
|
|
3026
|
+
let unit = null;
|
|
3027
|
+
if (unitStr) {
|
|
3028
|
+
if (unitStr in DIGITAL_ALIASES) {
|
|
3029
|
+
unit = DIGITAL_ALIASES[unitStr];
|
|
3030
|
+
} else if (lowerUnitStr in DIGITAL_ALIASES) {
|
|
3031
|
+
unit = DIGITAL_ALIASES[lowerUnitStr];
|
|
3032
|
+
} else if (lowerUnitStr in DIGITAL_UNITS) {
|
|
3033
|
+
unit = lowerUnitStr;
|
|
3034
|
+
} else {
|
|
3035
|
+
for (const [u, config] of Object.entries(DIGITAL_UNITS)) {
|
|
3036
|
+
if (config.symbol === unitStr || config.symbol.toLowerCase() === lowerUnitStr) {
|
|
3037
|
+
unit = u;
|
|
3038
|
+
break;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
return { value, unit };
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// src/roles/color/types.ts
|
|
3047
|
+
var NAMED_COLORS = {
|
|
3048
|
+
// Basic colors
|
|
3049
|
+
black: { r: 0, g: 0, b: 0, a: 1 },
|
|
3050
|
+
white: { r: 255, g: 255, b: 255, a: 1 },
|
|
3051
|
+
red: { r: 255, g: 0, b: 0, a: 1 },
|
|
3052
|
+
green: { r: 0, g: 128, b: 0, a: 1 },
|
|
3053
|
+
blue: { r: 0, g: 0, b: 255, a: 1 },
|
|
3054
|
+
yellow: { r: 255, g: 255, b: 0, a: 1 },
|
|
3055
|
+
cyan: { r: 0, g: 255, b: 255, a: 1 },
|
|
3056
|
+
magenta: { r: 255, g: 0, b: 255, a: 1 },
|
|
3057
|
+
// Extended colors
|
|
3058
|
+
orange: { r: 255, g: 165, b: 0, a: 1 },
|
|
3059
|
+
purple: { r: 128, g: 0, b: 128, a: 1 },
|
|
3060
|
+
pink: { r: 255, g: 192, b: 203, a: 1 },
|
|
3061
|
+
brown: { r: 165, g: 42, b: 42, a: 1 },
|
|
3062
|
+
gray: { r: 128, g: 128, b: 128, a: 1 },
|
|
3063
|
+
grey: { r: 128, g: 128, b: 128, a: 1 },
|
|
3064
|
+
// Web colors
|
|
3065
|
+
lime: { r: 0, g: 255, b: 0, a: 1 },
|
|
3066
|
+
aqua: { r: 0, g: 255, b: 255, a: 1 },
|
|
3067
|
+
fuchsia: { r: 255, g: 0, b: 255, a: 1 },
|
|
3068
|
+
silver: { r: 192, g: 192, b: 192, a: 1 },
|
|
3069
|
+
maroon: { r: 128, g: 0, b: 0, a: 1 },
|
|
3070
|
+
olive: { r: 128, g: 128, b: 0, a: 1 },
|
|
3071
|
+
navy: { r: 0, g: 0, b: 128, a: 1 },
|
|
3072
|
+
teal: { r: 0, g: 128, b: 128, a: 1 },
|
|
3073
|
+
// Transparent
|
|
3074
|
+
transparent: { r: 0, g: 0, b: 0, a: 0 }
|
|
3075
|
+
};
|
|
3076
|
+
function clamp(value, min, max) {
|
|
3077
|
+
return Math.min(Math.max(value, min), max);
|
|
3078
|
+
}
|
|
3079
|
+
function normalizeRgbChannel(value) {
|
|
3080
|
+
return clamp(Math.round(value), 0, 255);
|
|
3081
|
+
}
|
|
3082
|
+
function normalizeAlpha(value) {
|
|
3083
|
+
if (value === void 0) return 1;
|
|
3084
|
+
return clamp(value, 0, 1);
|
|
3085
|
+
}
|
|
3086
|
+
function rgbToHsl(r, g, b) {
|
|
3087
|
+
r /= 255;
|
|
3088
|
+
g /= 255;
|
|
3089
|
+
b /= 255;
|
|
3090
|
+
const max = Math.max(r, g, b);
|
|
3091
|
+
const min = Math.min(r, g, b);
|
|
3092
|
+
const l = (max + min) / 2;
|
|
3093
|
+
if (max === min) {
|
|
3094
|
+
return { h: 0, s: 0, l: l * 100 };
|
|
3095
|
+
}
|
|
3096
|
+
const d = max - min;
|
|
3097
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
3098
|
+
let h;
|
|
3099
|
+
switch (max) {
|
|
3100
|
+
case r:
|
|
3101
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
3102
|
+
break;
|
|
3103
|
+
case g:
|
|
3104
|
+
h = ((b - r) / d + 2) / 6;
|
|
3105
|
+
break;
|
|
3106
|
+
default:
|
|
3107
|
+
h = ((r - g) / d + 4) / 6;
|
|
3108
|
+
break;
|
|
3109
|
+
}
|
|
3110
|
+
return {
|
|
3111
|
+
h: Math.round(h * 360),
|
|
3112
|
+
s: Math.round(s * 100),
|
|
3113
|
+
l: Math.round(l * 100)
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
function hslToRgb(h, s, l) {
|
|
3117
|
+
h /= 360;
|
|
3118
|
+
s /= 100;
|
|
3119
|
+
l /= 100;
|
|
3120
|
+
if (s === 0) {
|
|
3121
|
+
const gray = Math.round(l * 255);
|
|
3122
|
+
return { r: gray, g: gray, b: gray };
|
|
3123
|
+
}
|
|
3124
|
+
const hue2rgb = (p2, q2, t) => {
|
|
3125
|
+
if (t < 0) t += 1;
|
|
3126
|
+
if (t > 1) t -= 1;
|
|
3127
|
+
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
|
|
3128
|
+
if (t < 1 / 2) return q2;
|
|
3129
|
+
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
|
|
3130
|
+
return p2;
|
|
3131
|
+
};
|
|
3132
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
3133
|
+
const p = 2 * l - q;
|
|
3134
|
+
return {
|
|
3135
|
+
r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
|
|
3136
|
+
g: Math.round(hue2rgb(p, q, h) * 255),
|
|
3137
|
+
b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255)
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
// src/roles/color/convert.ts
|
|
3142
|
+
function hexToRgba(hex) {
|
|
3143
|
+
let h = hex.trim();
|
|
3144
|
+
if (h.startsWith("#")) h = h.slice(1);
|
|
3145
|
+
let r, g, b, a;
|
|
3146
|
+
if (h.length === 3) {
|
|
3147
|
+
r = parseInt(h[0] + h[0], 16);
|
|
3148
|
+
g = parseInt(h[1] + h[1], 16);
|
|
3149
|
+
b = parseInt(h[2] + h[2], 16);
|
|
3150
|
+
a = 1;
|
|
3151
|
+
} else if (h.length === 4) {
|
|
3152
|
+
r = parseInt(h[0] + h[0], 16);
|
|
3153
|
+
g = parseInt(h[1] + h[1], 16);
|
|
3154
|
+
b = parseInt(h[2] + h[2], 16);
|
|
3155
|
+
a = parseInt(h[3] + h[3], 16) / 255;
|
|
3156
|
+
} else if (h.length === 6) {
|
|
3157
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
3158
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
3159
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
3160
|
+
a = 1;
|
|
3161
|
+
} else if (h.length === 8) {
|
|
3162
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
3163
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
3164
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
3165
|
+
a = parseInt(h.slice(6, 8), 16) / 255;
|
|
3166
|
+
} else {
|
|
3167
|
+
throw new Error(`Invalid hex color: ${hex}`);
|
|
3168
|
+
}
|
|
3169
|
+
return { r, g, b, a };
|
|
3170
|
+
}
|
|
3171
|
+
function rgbObjectToRgba(rgb) {
|
|
3172
|
+
return {
|
|
3173
|
+
r: normalizeRgbChannel(rgb.r),
|
|
3174
|
+
g: normalizeRgbChannel(rgb.g),
|
|
3175
|
+
b: normalizeRgbChannel(rgb.b),
|
|
3176
|
+
a: normalizeAlpha(rgb.a)
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
function rgbStringToRgba(rgb) {
|
|
3180
|
+
const match = rgb.match(/rgba?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*(?:,\s*([\d.]+)\s*)?\)/i);
|
|
3181
|
+
if (!match) {
|
|
3182
|
+
throw new Error(`Invalid RGB string: ${rgb}`);
|
|
3183
|
+
}
|
|
3184
|
+
return {
|
|
3185
|
+
r: normalizeRgbChannel(parseFloat(match[1])),
|
|
3186
|
+
g: normalizeRgbChannel(parseFloat(match[2])),
|
|
3187
|
+
b: normalizeRgbChannel(parseFloat(match[3])),
|
|
3188
|
+
a: match[4] !== void 0 ? normalizeAlpha(parseFloat(match[4])) : 1
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
function hslObjectToRgba(hsl) {
|
|
3192
|
+
const { r, g, b } = hslToRgb(hsl.h, hsl.s, hsl.l);
|
|
3193
|
+
return {
|
|
3194
|
+
r,
|
|
3195
|
+
g,
|
|
3196
|
+
b,
|
|
3197
|
+
a: normalizeAlpha(hsl.a)
|
|
3198
|
+
};
|
|
3199
|
+
}
|
|
3200
|
+
function hslStringToRgba(hsl) {
|
|
3201
|
+
const match = hsl.match(/hsla?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)%?\s*,\s*([\d.]+)%?\s*(?:,\s*([\d.]+)\s*)?\)/i);
|
|
3202
|
+
if (!match) {
|
|
3203
|
+
throw new Error(`Invalid HSL string: ${hsl}`);
|
|
3204
|
+
}
|
|
3205
|
+
const h = parseFloat(match[1]);
|
|
3206
|
+
const s = parseFloat(match[2]);
|
|
3207
|
+
const l = parseFloat(match[3]);
|
|
3208
|
+
const a = match[4] !== void 0 ? parseFloat(match[4]) : 1;
|
|
3209
|
+
const { r, g, b } = hslToRgb(h, s, l);
|
|
3210
|
+
return { r, g, b, a: normalizeAlpha(a) };
|
|
3211
|
+
}
|
|
3212
|
+
function rgbaToHex(rgba, includeAlpha = false) {
|
|
3213
|
+
const r = rgba.r.toString(16).padStart(2, "0");
|
|
3214
|
+
const g = rgba.g.toString(16).padStart(2, "0");
|
|
3215
|
+
const b = rgba.b.toString(16).padStart(2, "0");
|
|
3216
|
+
if (includeAlpha || rgba.a < 1) {
|
|
3217
|
+
const a = Math.round(rgba.a * 255).toString(16).padStart(2, "0");
|
|
3218
|
+
return `#${r}${g}${b}${a}`;
|
|
3219
|
+
}
|
|
3220
|
+
return `#${r}${g}${b}`;
|
|
3221
|
+
}
|
|
3222
|
+
function rgbaToRgbObject(rgba) {
|
|
3223
|
+
if (rgba.a < 1) {
|
|
3224
|
+
return { r: rgba.r, g: rgba.g, b: rgba.b, a: rgba.a };
|
|
3225
|
+
}
|
|
3226
|
+
return { r: rgba.r, g: rgba.g, b: rgba.b };
|
|
3227
|
+
}
|
|
3228
|
+
function rgbaToRgbString(rgba) {
|
|
3229
|
+
if (rgba.a < 1) {
|
|
3230
|
+
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`;
|
|
3231
|
+
}
|
|
3232
|
+
return `rgb(${rgba.r}, ${rgba.g}, ${rgba.b})`;
|
|
3233
|
+
}
|
|
3234
|
+
function rgbaToHslObject(rgba) {
|
|
3235
|
+
const { h, s, l } = rgbToHsl(rgba.r, rgba.g, rgba.b);
|
|
3236
|
+
if (rgba.a < 1) {
|
|
3237
|
+
return { h, s, l, a: rgba.a };
|
|
3238
|
+
}
|
|
3239
|
+
return { h, s, l };
|
|
3240
|
+
}
|
|
3241
|
+
function rgbaToHslString(rgba) {
|
|
3242
|
+
const { h, s, l } = rgbToHsl(rgba.r, rgba.g, rgba.b);
|
|
3243
|
+
if (rgba.a < 1) {
|
|
3244
|
+
return `hsla(${h}, ${s}%, ${l}%, ${rgba.a})`;
|
|
3245
|
+
}
|
|
3246
|
+
return `hsl(${h}, ${s}%, ${l}%)`;
|
|
3247
|
+
}
|
|
3248
|
+
function fromBaseColor(variant, rgba) {
|
|
3249
|
+
switch (variant) {
|
|
3250
|
+
case "hex":
|
|
3251
|
+
return rgbaToHex(rgba);
|
|
3252
|
+
case "rgb_object":
|
|
3253
|
+
return rgbaToRgbObject(rgba);
|
|
3254
|
+
case "rgb_string":
|
|
3255
|
+
return rgbaToRgbString(rgba);
|
|
3256
|
+
case "hsl_object":
|
|
3257
|
+
return rgbaToHslObject(rgba);
|
|
3258
|
+
case "hsl_string":
|
|
3259
|
+
return rgbaToHslString(rgba);
|
|
3260
|
+
default:
|
|
3261
|
+
throw new Error(`Unknown color variant: ${variant}`);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// src/roles/color/cast.ts
|
|
3266
|
+
function detectObjectFormat(obj) {
|
|
3267
|
+
if (typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
3268
|
+
return "rgb_object";
|
|
3269
|
+
}
|
|
3270
|
+
if (typeof obj.h === "number" && typeof obj.s === "number" && typeof obj.l === "number") {
|
|
3271
|
+
return "hsl_object";
|
|
3272
|
+
}
|
|
3273
|
+
return null;
|
|
3274
|
+
}
|
|
3275
|
+
function parseToRgba(input) {
|
|
3276
|
+
if (typeof input === "object" && input !== null && "r" in input && "g" in input && "b" in input && "a" in input) {
|
|
3277
|
+
const rgba = input;
|
|
3278
|
+
if (typeof rgba.r === "number" && typeof rgba.g === "number" && typeof rgba.b === "number" && typeof rgba.a === "number") {
|
|
3279
|
+
return rgba;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
if (typeof input === "string") {
|
|
3283
|
+
const trimmed = input.trim();
|
|
3284
|
+
const namedColor = NAMED_COLORS[trimmed.toLowerCase()];
|
|
3285
|
+
if (namedColor) {
|
|
3286
|
+
return namedColor;
|
|
3287
|
+
}
|
|
3288
|
+
if (/^#?[0-9a-f]{3,8}$/i.test(trimmed)) {
|
|
3289
|
+
try {
|
|
3290
|
+
return hexToRgba(trimmed);
|
|
3291
|
+
} catch {
|
|
3292
|
+
return null;
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
if (/^rgba?\s*\(/i.test(trimmed)) {
|
|
3296
|
+
try {
|
|
3297
|
+
return rgbStringToRgba(trimmed);
|
|
3298
|
+
} catch {
|
|
3299
|
+
return null;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
if (/^hsla?\s*\(/i.test(trimmed)) {
|
|
3303
|
+
try {
|
|
3304
|
+
return hslStringToRgba(trimmed);
|
|
3305
|
+
} catch {
|
|
3306
|
+
return null;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return null;
|
|
3310
|
+
}
|
|
3311
|
+
if (typeof input === "object" && input !== null) {
|
|
3312
|
+
const format = detectObjectFormat(input);
|
|
3313
|
+
if (format === "rgb_object") {
|
|
3314
|
+
try {
|
|
3315
|
+
return rgbObjectToRgba(input);
|
|
3316
|
+
} catch {
|
|
3317
|
+
return null;
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
if (format === "hsl_object") {
|
|
3321
|
+
try {
|
|
3322
|
+
return hslObjectToRgba(input);
|
|
3323
|
+
} catch {
|
|
3324
|
+
return null;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
return null;
|
|
3329
|
+
}
|
|
3330
|
+
function castColor(variant, input) {
|
|
3331
|
+
const rgba = parseToRgba(input);
|
|
3332
|
+
if (!rgba) {
|
|
3333
|
+
return null;
|
|
3334
|
+
}
|
|
3335
|
+
try {
|
|
3336
|
+
return fromBaseColor(variant, rgba);
|
|
3337
|
+
} catch {
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
function tryCastColor(variant, input) {
|
|
3342
|
+
const result = castColor(variant, input);
|
|
3343
|
+
if (result === null) {
|
|
3344
|
+
return {
|
|
3345
|
+
ok: false,
|
|
3346
|
+
error: `Cannot cast "${String(input)}" to color variant "${variant}"`
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
return { ok: true, value: result };
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
// src/roles/date/cast.ts
|
|
3353
|
+
function castIso(input) {
|
|
3354
|
+
if (typeof input === "string") {
|
|
3355
|
+
const trimmed = input.trim();
|
|
3356
|
+
const date = new Date(trimmed);
|
|
3357
|
+
if (!isNaN(date.getTime())) {
|
|
3358
|
+
return date.toISOString();
|
|
3359
|
+
}
|
|
3360
|
+
return null;
|
|
3361
|
+
}
|
|
3362
|
+
if (typeof input === "number") {
|
|
3363
|
+
if (!Number.isFinite(input)) return null;
|
|
3364
|
+
const date = new Date(input);
|
|
3365
|
+
if (!isNaN(date.getTime())) {
|
|
3366
|
+
return date.toISOString();
|
|
3367
|
+
}
|
|
3368
|
+
return null;
|
|
3369
|
+
}
|
|
3370
|
+
if (input instanceof Date) {
|
|
3371
|
+
if (isNaN(input.getTime())) return null;
|
|
3372
|
+
return input.toISOString();
|
|
3373
|
+
}
|
|
3374
|
+
return null;
|
|
3375
|
+
}
|
|
3376
|
+
function castTimestamp(input) {
|
|
3377
|
+
if (typeof input === "number") {
|
|
3378
|
+
if (!Number.isFinite(input)) return null;
|
|
3379
|
+
return input;
|
|
3380
|
+
}
|
|
3381
|
+
if (typeof input === "string") {
|
|
3382
|
+
const trimmed = input.trim();
|
|
3383
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
3384
|
+
const num = parseInt(trimmed, 10);
|
|
3385
|
+
if (Number.isFinite(num)) return num;
|
|
3386
|
+
}
|
|
3387
|
+
const date = new Date(trimmed);
|
|
3388
|
+
if (!isNaN(date.getTime())) {
|
|
3389
|
+
return date.getTime();
|
|
3390
|
+
}
|
|
3391
|
+
return null;
|
|
3392
|
+
}
|
|
3393
|
+
if (input instanceof Date) {
|
|
3394
|
+
const ts = input.getTime();
|
|
3395
|
+
return isNaN(ts) ? null : ts;
|
|
3396
|
+
}
|
|
3397
|
+
return null;
|
|
3398
|
+
}
|
|
3399
|
+
function castEpoch(input) {
|
|
3400
|
+
if (typeof input === "number") {
|
|
3401
|
+
if (!Number.isFinite(input)) return null;
|
|
3402
|
+
if (Math.abs(input) > 1e11) {
|
|
3403
|
+
return Math.floor(input / 1e3);
|
|
3404
|
+
}
|
|
3405
|
+
return Math.floor(input);
|
|
3406
|
+
}
|
|
3407
|
+
if (typeof input === "string") {
|
|
3408
|
+
const trimmed = input.trim();
|
|
3409
|
+
if (/^-?\d+$/.test(trimmed)) {
|
|
3410
|
+
const num = parseInt(trimmed, 10);
|
|
3411
|
+
if (Number.isFinite(num)) {
|
|
3412
|
+
if (Math.abs(num) > 1e11) {
|
|
3413
|
+
return Math.floor(num / 1e3);
|
|
3414
|
+
}
|
|
3415
|
+
return num;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
const date = new Date(trimmed);
|
|
3419
|
+
if (!isNaN(date.getTime())) {
|
|
3420
|
+
return Math.floor(date.getTime() / 1e3);
|
|
3421
|
+
}
|
|
3422
|
+
return null;
|
|
3423
|
+
}
|
|
3424
|
+
if (input instanceof Date) {
|
|
3425
|
+
const ts = input.getTime();
|
|
3426
|
+
return isNaN(ts) ? null : Math.floor(ts / 1e3);
|
|
3427
|
+
}
|
|
3428
|
+
return null;
|
|
3429
|
+
}
|
|
3430
|
+
function castDate(variant, input) {
|
|
3431
|
+
switch (variant) {
|
|
3432
|
+
case "iso":
|
|
3433
|
+
return castIso(input);
|
|
3434
|
+
case "timestamp":
|
|
3435
|
+
return castTimestamp(input);
|
|
3436
|
+
case "epoch":
|
|
3437
|
+
return castEpoch(input);
|
|
3438
|
+
default:
|
|
3439
|
+
return null;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function tryCastDate(variant, input) {
|
|
3443
|
+
const result = castDate(variant, input);
|
|
3444
|
+
if (result === null) {
|
|
3445
|
+
return {
|
|
3446
|
+
ok: false,
|
|
3447
|
+
error: `Cannot cast "${String(input)}" to date variant "${variant}"`
|
|
3448
|
+
};
|
|
3449
|
+
}
|
|
3450
|
+
return { ok: true, value: result };
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
// src/roles/currency/cast.ts
|
|
3454
|
+
function castCurrency(input) {
|
|
3455
|
+
if (typeof input === "number") {
|
|
3456
|
+
return Number.isFinite(input) ? input : null;
|
|
3457
|
+
}
|
|
3458
|
+
if (typeof input === "string") {
|
|
3459
|
+
return parseCurrencyString(input);
|
|
3460
|
+
}
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
function tryCastCurrency(input) {
|
|
3464
|
+
const result = castCurrency(input);
|
|
3465
|
+
if (result === null) {
|
|
3466
|
+
return {
|
|
3467
|
+
ok: false,
|
|
3468
|
+
error: `Cannot cast "${String(input)}" to currency value`
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
return { ok: true, value: result };
|
|
3472
|
+
}
|
|
3473
|
+
var CURRENCY_SYMBOL_REGEX = /^[\s]*([A-Z]{1,3}\$?|[R$€£¥₹₩₽₺CHF]+)[\s]*/i;
|
|
3474
|
+
var CURRENCY_SYMBOL_SUFFIX_REGEX = /[\s]*([€₽])[\s]*$/;
|
|
3475
|
+
function parseCurrencyString(input) {
|
|
3476
|
+
let str = input.trim();
|
|
3477
|
+
if (!str) {
|
|
3478
|
+
return null;
|
|
3479
|
+
}
|
|
3480
|
+
let isNegative = false;
|
|
3481
|
+
if (str.startsWith("-")) {
|
|
3482
|
+
isNegative = true;
|
|
3483
|
+
str = str.slice(1).trim();
|
|
3484
|
+
} else if (str.startsWith("(") && str.endsWith(")")) {
|
|
3485
|
+
isNegative = true;
|
|
3486
|
+
str = str.slice(1, -1).trim();
|
|
3487
|
+
}
|
|
3488
|
+
str = str.replace(CURRENCY_SYMBOL_REGEX, "");
|
|
3489
|
+
str = str.replace(CURRENCY_SYMBOL_SUFFIX_REGEX, "");
|
|
3490
|
+
str = str.trim();
|
|
3491
|
+
if (!str) {
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
const numValue = parseNumber8(str);
|
|
3495
|
+
if (numValue === null) {
|
|
3496
|
+
return null;
|
|
3497
|
+
}
|
|
3498
|
+
return isNegative ? -numValue : numValue;
|
|
3499
|
+
}
|
|
3500
|
+
function parseNumber8(numStr) {
|
|
3501
|
+
numStr = numStr.replace(/\s/g, "");
|
|
3502
|
+
if (!/^[+-]?[\d.,]+$/.test(numStr)) {
|
|
3503
|
+
return null;
|
|
3504
|
+
}
|
|
3505
|
+
const lastComma = numStr.lastIndexOf(",");
|
|
3506
|
+
const lastDot = numStr.lastIndexOf(".");
|
|
3507
|
+
const hasComma = lastComma !== -1;
|
|
3508
|
+
const hasDot = lastDot !== -1;
|
|
3509
|
+
if (hasComma && hasDot) {
|
|
3510
|
+
if (lastComma > lastDot) {
|
|
3511
|
+
numStr = numStr.replace(/\./g, "").replace(",", ".");
|
|
3512
|
+
} else {
|
|
3513
|
+
numStr = numStr.replace(/,/g, "");
|
|
3514
|
+
}
|
|
3515
|
+
} else if (hasComma && !hasDot) {
|
|
3516
|
+
const parts = numStr.split(",");
|
|
3517
|
+
const afterComma = parts.slice(1);
|
|
3518
|
+
if (afterComma.length > 1 || afterComma.length === 1 && afterComma[0].length === 3 && parts[0].length <= 3) {
|
|
3519
|
+
numStr = numStr.replace(/,/g, "");
|
|
3520
|
+
} else {
|
|
3521
|
+
numStr = numStr.replace(",", ".");
|
|
3522
|
+
}
|
|
3523
|
+
} else if (!hasComma && hasDot) {
|
|
3524
|
+
const parts = numStr.split(".");
|
|
3525
|
+
const afterDot = parts.slice(1);
|
|
3526
|
+
if (afterDot.length > 1) {
|
|
3527
|
+
numStr = numStr.replace(/\./g, "");
|
|
3528
|
+
} else if (afterDot.length === 1 && afterDot[0].length === 3) {
|
|
3529
|
+
numStr = numStr.replace(/\./g, "");
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
const value = parseFloat(numStr);
|
|
3533
|
+
return isNaN(value) ? null : value;
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
exports.castAngle = castAngle;
|
|
3537
|
+
exports.castArea = castArea;
|
|
3538
|
+
exports.castColor = castColor;
|
|
3539
|
+
exports.castCurrency = castCurrency;
|
|
3540
|
+
exports.castDate = castDate;
|
|
3541
|
+
exports.castDigital = castDigital;
|
|
3542
|
+
exports.castEnergy = castEnergy;
|
|
3543
|
+
exports.castFrequency = castFrequency;
|
|
3544
|
+
exports.castLength = castLength;
|
|
3545
|
+
exports.castMass = castMass;
|
|
3546
|
+
exports.castPower = castPower;
|
|
3547
|
+
exports.castPressure = castPressure;
|
|
3548
|
+
exports.castSpeed = castSpeed;
|
|
3549
|
+
exports.castTemperature = castTemperature;
|
|
3550
|
+
exports.castTime = castTime;
|
|
3551
|
+
exports.castVolume = castVolume;
|
|
3552
|
+
exports.tryCastAngle = tryCastAngle;
|
|
3553
|
+
exports.tryCastArea = tryCastArea;
|
|
3554
|
+
exports.tryCastColor = tryCastColor;
|
|
3555
|
+
exports.tryCastCurrency = tryCastCurrency;
|
|
3556
|
+
exports.tryCastDate = tryCastDate;
|
|
3557
|
+
exports.tryCastDigital = tryCastDigital;
|
|
3558
|
+
exports.tryCastEnergy = tryCastEnergy;
|
|
3559
|
+
exports.tryCastFrequency = tryCastFrequency;
|
|
3560
|
+
exports.tryCastLength = tryCastLength;
|
|
3561
|
+
exports.tryCastMass = tryCastMass;
|
|
3562
|
+
exports.tryCastPower = tryCastPower;
|
|
3563
|
+
exports.tryCastPressure = tryCastPressure;
|
|
3564
|
+
exports.tryCastSpeed = tryCastSpeed;
|
|
3565
|
+
exports.tryCastTemperature = tryCastTemperature;
|
|
3566
|
+
exports.tryCastTime = tryCastTime;
|
|
3567
|
+
exports.tryCastVolume = tryCastVolume;
|
|
3568
|
+
//# sourceMappingURL=cast.js.map
|
|
3569
|
+
//# sourceMappingURL=cast.js.map
|