You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
3.4 KiB
JavaScript
128 lines
3.4 KiB
JavaScript
"use strict";
|
|
|
|
/* TODO:
|
|
toDisplay
|
|
conversion between unit scales (eg. IEC -> metric bytes)
|
|
ensure NaN is handled correctly
|
|
Track the originally-constructed value internally, so that stacked conversions can be done losslessly?
|
|
Additionally perhaps an isExact method that returns whether the current representation was the original one?
|
|
*/
|
|
|
|
const util = require("util");
|
|
const chalk = require("chalk");
|
|
|
|
const { validateArguments } = require("@validatem/core");
|
|
const required = require("@validatem/required");
|
|
const arrayOf = require("@validatem/array-of");
|
|
const isString = require("@validatem/is-string");
|
|
const isNumber = require("@validatem/is-number");
|
|
const dynamic = require("@validatem/dynamic");
|
|
const anything = require("@validatem/anything");
|
|
const allowExtraProperties = require("@validatem/allow-extra-properties");
|
|
|
|
function capitalize(string) {
|
|
return string[0].toUpperCase() + string.slice(1);
|
|
}
|
|
|
|
module.exports = function makeUnits(_unitSpecs) {
|
|
let [ unitSpecs ] = validateArguments(arguments, {
|
|
unitSpecs: [
|
|
arrayOf([
|
|
{
|
|
unit: [ required, isString ],
|
|
toNext: [ isNumber ]
|
|
},
|
|
dynamic((_value, { arrayIndex, arrayLength }) => {
|
|
// FIXME: Actually test this
|
|
let isLast = (arrayIndex === arrayLength - 1);
|
|
|
|
if (isLast) {
|
|
return anything;
|
|
} else {
|
|
return allowExtraProperties({ toNext: [ required ] });
|
|
}
|
|
})
|
|
]),
|
|
]
|
|
});
|
|
|
|
let resultObject = {};
|
|
|
|
unitSpecs.forEach((spec, i) => {
|
|
let proto = {
|
|
[util.inspect.custom]: function (_depth, options) {
|
|
let inspectString = `<Unit> ${this.amount} ${this.unit}`;
|
|
|
|
if (options.colors === true) {
|
|
return chalk.cyan(inspectString);
|
|
} else {
|
|
return inspectString;
|
|
}
|
|
},
|
|
toString: function () {
|
|
return `${this.amount} ${this.unit}`;
|
|
},
|
|
toDisplay: function (decimals) {
|
|
let roundingFactor = Math.pow(10, decimals);
|
|
let unitMagnitude = i;
|
|
let amount = this.amount;
|
|
let unitsLeft = (i < unitSpecs.length - 1);
|
|
|
|
function createOfCurrentMagnitude() {
|
|
let roundedValue = Math.round(amount * roundingFactor) / roundingFactor;
|
|
|
|
return resultObject[unitSpecs[unitMagnitude].unit](roundedValue);
|
|
}
|
|
|
|
while (unitsLeft === true) {
|
|
let currentUnit = unitSpecs[unitMagnitude];
|
|
|
|
if (amount < currentUnit.toNext) {
|
|
return createOfCurrentMagnitude();
|
|
} else {
|
|
amount = amount / currentUnit.toNext;
|
|
unitMagnitude += 1;
|
|
|
|
unitsLeft = (unitMagnitude < unitSpecs.length - 1);
|
|
}
|
|
}
|
|
|
|
return createOfCurrentMagnitude();
|
|
}
|
|
};
|
|
|
|
unitSpecs.forEach((otherSpec, otherI) => {
|
|
let factor = 1;
|
|
|
|
if (otherI < i) {
|
|
/* Convert downwards, to smaller units (== larger numbers) */
|
|
unitSpecs.slice(otherI, i).reverse().forEach((specStep) => {
|
|
factor = factor * specStep.toNext;
|
|
});
|
|
} else if (otherI > i) {
|
|
/* Convert upwards, to larger units (== smaller numbers) */
|
|
unitSpecs.slice(i, otherI).forEach((specStep) => {
|
|
factor = factor / specStep.toNext;
|
|
});
|
|
}
|
|
|
|
proto[`to${capitalize(otherSpec.unit)}`] = function () {
|
|
return resultObject[otherSpec.unit](this.amount * factor);
|
|
};
|
|
});
|
|
|
|
resultObject[spec.unit] = function createUnit(value) {
|
|
if (typeof value !== "number") {
|
|
throw new Error("Value must be numeric");
|
|
} else {
|
|
return Object.assign(Object.create(proto), {
|
|
unit: spec.unit,
|
|
amount: value
|
|
});
|
|
}
|
|
};
|
|
});
|
|
|
|
return resultObject;
|
|
};
|