From dd63a81e3b879ba1c4d9c86b4c559fa3c0f1d0aa Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 24 Sep 2024 19:40:38 -0700 Subject: [PATCH] remove convert-units dependency --- bun.lockb | Bin 85425 -> 78209 bytes package.json | 2 +- src/utils/convert-si-unit-to-number.ts | 236 +++++++++++++++++------- tests/parse-and-convert-si-unit.test.ts | 141 ++++++++++++++ 4 files changed, 315 insertions(+), 64 deletions(-) create mode 100644 tests/parse-and-convert-si-unit.test.ts diff --git a/bun.lockb b/bun.lockb index a3cf66115dea2ab235b51a0c403574bbceb35650..46352ebe7c5b57a7a2ed24ffa538f16865d1c7f1 100755 GIT binary patch delta 13655 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 KgClKXnEYQQgxwfA0ooqcA8 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 HcFO+)>DMf~ diff --git a/package.json b/package.json index 31e162c..7f3a9e1 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,13 @@ "@anthropic-ai/sdk": "^0.27.3", "@biomejs/biome": "^1.9.2", "@types/convert-units": "^2.3.9", + "@types/node": "^22.7.0", "esbuild": "^0.20.2", "ts-expect": "^1.3.0", "tsup": "^8.3.0", "typescript": "^5.4.5" }, "dependencies": { - "convert-units": "^2.3.4", "nanoid": "^5.0.7", "zod": "^3.23.6" } diff --git a/src/utils/convert-si-unit-to-number.ts b/src/utils/convert-si-unit-to-number.ts index a7a1894..cb6c538 100644 --- a/src/utils/convert-si-unit-to-number.ts +++ b/src/utils/convert-si-unit-to-number.ts @@ -1,46 +1,162 @@ -import convertUnits from "convert-units" - -const si_prefix_multiplier = { - tera: 10e12, - T: 10e12, - giga: 10e9, - G: 10e9, - mega: 10e6, - M: 10e6, - kilo: 10e3, - k: 10e3, - deci: 10e-1, - d: 10e-1, - centi: 10e-2, - c: 10e-2, - milli: 10e-3, - m: 10e-3, - micro: 10e-6, - u: 10e-6, - µ: 10e-6, - nano: 10e-9, - n: 10e-9, - pico: 10e-12, - p: 10e-12, +type UnitInfo = { + baseUnit: BaseTscircuitUnit + conversionFactor: number } -const si_prefixes = Object.keys(si_prefix_multiplier) -const target_conversion = { - mass: "g", - length: "mm", - time: "ms", - volume: "ml", - angle: "deg", +const unitMappings: Record< + string, + { baseUnit: BaseTscircuitUnit; variants: Record } +> = { + Hz: { + baseUnit: "Hz", + variants: { + MHz: 1e6, + kHz: 1e3, + Hz: 1, + }, + }, + g: { + baseUnit: "g", + variants: { + kg: 1e3, + g: 1, + }, + }, + Ω: { + baseUnit: "Ω", + variants: { + mΩ: 1e-3, + Ω: 1, + kΩ: 1e3, + MΩ: 1e6, + GΩ: 1e9, + TΩ: 1e12, + }, + }, + V: { + baseUnit: "V", + variants: { + mV: 1e-3, + V: 1, + kV: 1e3, + MV: 1e6, + GV: 1e9, + TV: 1e12, + }, + }, + A: { + baseUnit: "A", + variants: { + µA: 1e-6, + mA: 1e-3, + ma: 1e-3, + A: 1, + kA: 1e3, + MA: 1e6, + }, + }, + F: { + baseUnit: "F", + variants: { + pF: 1e-12, + nF: 1e-9, + µF: 1e-6, + mF: 1e-3, + F: 1, + }, + }, + ml: { + baseUnit: "ml", + variants: { + ml: 1, + mL: 1, + l: 1e3, + L: 1e3, + }, + }, + deg: { + baseUnit: "deg", + variants: { + rad: 180 / Math.PI, + }, + }, + ms: { + baseUnit: "ms", + variants: { + s: 1000, + }, + }, + mm: { + baseUnit: "mm", + variants: { + nm: 1e-6, + µm: 1e-3, + um: 1e-3, + mm: 1, + cm: 10, + dm: 100, + m: 1000, + km: 1e6, + in: 25.4, + ft: 304.8, + IN: 25.4, + FT: 304.8, + yd: 914.4, + mi: 1.609344e6, + }, + }, } -function getSiPrefixMultiplierFromUnit(v: string): number { - for (const prefix of si_prefixes) { - if (v.startsWith(prefix)) { - return si_prefix_multiplier[prefix as keyof typeof si_prefix_multiplier] +function getBaseTscircuitUnit(unit: string): UnitInfo { + for (const [baseUnit, info] of Object.entries(unitMappings)) { + if (unit in info.variants) { + return { + baseUnit: info.baseUnit, + conversionFactor: info.variants[unit]!, + } } } - return 1 + return { + baseUnit: unit as BaseTscircuitUnit, + conversionFactor: 1, + } +} + +const si_prefix_multiplier = { + tera: 1e12, + T: 1e12, + giga: 1e9, + G: 1e9, + mega: 1e6, + M: 1e6, + kilo: 1e3, + k: 1e3, + deci: 1e-1, + d: 1e-1, + centi: 1e-2, + c: 1e-2, + milli: 1e-3, + m: 1e-3, + micro: 1e-6, + u: 1e-6, + µ: 1e-6, + nano: 1e-9, + n: 1e-9, + pico: 1e-12, + p: 1e-12, } +type BaseTscircuitUnit = + | "ms" + | "mm" + | "g" + | "deg" + | "Hz" + | "ml" + | "V" + | "A" + | "Ω" + | "F" + | "H" export const parseAndConvertSiUnit = < T extends @@ -51,18 +167,27 @@ export const parseAndConvertSiUnit = < >( v: T, ): { - unit: string | null + parsedUnit: string | null + unitOfValue: BaseTscircuitUnit | null value: T extends { x: string | number; y: string | number } ? null | { x: number; y: number } : null | number } => { - if (typeof v === "undefined") return { unit: null, value: null } + if (typeof v === "undefined") + return { parsedUnit: null, unitOfValue: null, value: null } if (typeof v === "string" && v.match(/^[\d\.]+$/)) - return { value: parseFloat(v) as any, unit: null } - if (typeof v === "number") return { value: v as any, unit: null } + return { + value: Number.parseFloat(v) as any, + parsedUnit: null, + unitOfValue: null, + } + if (typeof v === "number") + return { value: v as any, parsedUnit: null, unitOfValue: null } if (typeof v === "object" && "x" in v && "y" in v) { + const { parsedUnit, unitOfValue } = parseAndConvertSiUnit(v.x) return { - unit: parseAndConvertSiUnit(v.x).unit, + parsedUnit: parsedUnit, + unitOfValue: unitOfValue, value: { x: parseAndConvertSiUnit(v.x as any).value as number, y: parseAndConvertSiUnit(v.y as any).value as number, @@ -79,27 +204,12 @@ export const parseAndConvertSiUnit = < } const unit = unit_reversed.split("").reverse().join("") const value = v.slice(0, -unit.length) - let measure - try { - measure = convertUnits().describe(unit as any)?.measure - } catch (e) {} - if (measure) { - const target_unit = (target_conversion as any)[measure] - if (!target_unit) { - throw new Error( - `Could not determine target unit for measure: "${measure}"`, - ) - } - return { - unit, - value: convertUnits(parseFloat(value)) - .from(unit as any) - .to(target_unit) as any, - } - } else { - return { - unit, - value: (getSiPrefixMultiplierFromUnit(unit) * parseFloat(value)) as any, - } + + const { baseUnit, conversionFactor } = getBaseTscircuitUnit(unit) + + return { + parsedUnit: unit, + unitOfValue: baseUnit, + value: (conversionFactor * Number.parseFloat(value)) as any, } } diff --git a/tests/parse-and-convert-si-unit.test.ts b/tests/parse-and-convert-si-unit.test.ts new file mode 100644 index 0000000..0f9707e --- /dev/null +++ b/tests/parse-and-convert-si-unit.test.ts @@ -0,0 +1,141 @@ +import { expect, test } from "bun:test" +import { parseAndConvertSiUnit } from "../src/utils/convert-si-unit-to-number" + +test("parseAndConvertSiUnit", () => { + expect(parseAndConvertSiUnit(undefined)).toEqual({ + parsedUnit: null, + unitOfValue: null, + value: null, + }) + + // Test numeric string without unit + expect(parseAndConvertSiUnit("42")).toEqual({ + parsedUnit: null, + unitOfValue: null, + value: 42, + }) + + // Test number input + expect(parseAndConvertSiUnit(3.14)).toEqual({ + parsedUnit: null, + unitOfValue: null, + value: 3.14, + }) + + // Test object with x and y as numbers + expect(parseAndConvertSiUnit({ x: 10, y: 20 })).toEqual({ + parsedUnit: null, + unitOfValue: null, + value: { x: 10, y: 20 }, + }) + + // Test object with x and y as strings with units + expect(parseAndConvertSiUnit({ x: "10mm", y: "20mm" })).toEqual({ + parsedUnit: "mm", + unitOfValue: "mm", + value: { x: 10, y: 20 }, + }) + + // Test string with unit that has a target conversion (length) + expect(parseAndConvertSiUnit("5cm")).toEqual({ + parsedUnit: "cm", + unitOfValue: "mm", + value: 50, + }) + + // Test SI prefix units (length) + expect(parseAndConvertSiUnit("1km")).toEqual({ + parsedUnit: "km", + unitOfValue: "mm", + value: 1000000, + }) + + // Test mass conversion + expect(parseAndConvertSiUnit("2kg")).toEqual({ + parsedUnit: "kg", + unitOfValue: "g", + value: 2000, + }) + + // Test time conversion + expect(parseAndConvertSiUnit("1s")).toEqual({ + parsedUnit: "s", + unitOfValue: "ms", + value: 1000, + }) + + // Test angle conversion + expect(parseAndConvertSiUnit("1rad")).toEqual({ + parsedUnit: "rad", + unitOfValue: "deg", + value: 180 / Math.PI, + }) + + // Test volume conversion + expect(parseAndConvertSiUnit("1l")).toEqual({ + parsedUnit: "l", + unitOfValue: "ml", + value: 1000, + }) + + // Test empty string + expect(() => parseAndConvertSiUnit("")).toThrow( + 'Could not determine unit: ""', + ) + + // Test SI prefix unit without measure (frequency) + expect(parseAndConvertSiUnit("50MHz")).toEqual({ + parsedUnit: "MHz", + unitOfValue: "Hz", + value: 50000000, + }) + + // Test object with mixed types + expect(parseAndConvertSiUnit({ x: "10cm", y: 200 })).toEqual({ + parsedUnit: "cm", + unitOfValue: "mm", + value: { x: 100, y: 200 }, + }) + + // Test unit with decimal value + expect(parseAndConvertSiUnit("3.5kg")).toEqual({ + parsedUnit: "kg", + unitOfValue: "g", + value: 3500, + }) + + // Test unit with SI prefix and decimal value + expect(parseAndConvertSiUnit("2.5um")).toEqual({ + parsedUnit: "um", + unitOfValue: "mm", + value: 0.0025, + }) + + // Test zero value with unit + expect(parseAndConvertSiUnit("0kg")).toEqual({ + parsedUnit: "kg", + unitOfValue: "g", + value: 0, + }) + + // Test negative value with unit + expect(parseAndConvertSiUnit("-5m")).toEqual({ + parsedUnit: "m", + unitOfValue: "mm", + value: -5000, + }) + + // Test unit with exponent notation + expect(parseAndConvertSiUnit("1e3kg")).toEqual({ + parsedUnit: "kg", + unitOfValue: "g", + value: 1000000, + }) + + // Test unit with whitespace + expect(parseAndConvertSiUnit(" 2 kg ")).toEqual({ + parsedUnit: "kg", + unitOfValue: "g", + value: 2000, + }) +})