Skip to content

Commit

Permalink
Cleans up the code a bit and adds a bunch of docs
Browse files Browse the repository at this point in the history
  • Loading branch information
humphreyja committed May 12, 2021
1 parent 6778791 commit f8c74d8
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 118 deletions.
32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,35 @@ const isCompatibleUnit = UnitsHelper.isCompatibleUnit('lbs', 'tons');
console.log(isCompatibleUnit); // true
```

Other useful functions...
More commonly, you will use the line item/product functions or to generate units for select boxes

```js
UnitsHelper.isLiquidUnit('floz') // true
UnitsHelper.convertToGallons(8, 'pints') // 1
UnitsHelper.convertToUnit(8, 'pints', 'gallons') // 1
UnitsHelper.convertToUnit(8, 'lbs', 'gallons') // throw ConversionError
UnitsHelper.perAcreCost(product, item, acres) // 3.50
UnitsHelper.listAvailableUnits(product) // ['gallons', 'floz', 'milliliters', ...]
```

All of this is build off of the `Units` object and a set of definitions declared in this repo.
You can use this object to handle any conversions or any other interaction with those units.

```js
import { Units } from '@harvest-profit/units';

const amount = new Units(1, 'gallon');
amount.to('pints').toNumber() // 8 pints

amount.isCompatible('lbs') // false


// Can use any different name. If a name is missing, just add it to the aliases in the definition in a PR
const gal = new Units(1, 'gal');
const gallon = new Units(1, 'gallon');
const gallons = new Units(1, 'gallons');

Units.selectableUnits('liquid') // ['gallons', 'floz', 'milliliters', ...]

Units.isCompatible('g', 'lb') // true
Units.isCompatible(gallon, 'lb') // false
Units.isCompatible(gal, gallon) // true
```

## Development
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@harvest-profit/units",
"version": "1.4.0",
"version": "1.4.1",
"description": "Units helper for Harvest Profit javascript applications",
"main": "dist/index.js",
"repository": "https://github.com/HarvestProfit/harvest-profit-units",
Expand Down
90 changes: 18 additions & 72 deletions src/Units.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,5 @@
import liquidDefinitions from './definitions/liquid';
import solidDefinitions from './definitions/solid';
import seedDefinitions from './definitions/seed';
import yieldDefinitions from './definitions/yield';

const inflatedUnits = {};

class UnitRedefinitionError extends Error {
constructor(message) {
super(message);
this.name = 'UnitRedefinitionError';
}
}

class UndefinedUnitError extends Error {
constructor(message) {
super(message);
this.name = 'UndefinedUnitError';
}
}

class UnitCompatibilityError extends Error {
constructor(message) {
super(message);
this.name = 'UnitCompatibilityError';
}
}

function addUnitDefinitionWithoutError(group, name, value, primaryName, fullName) {
if (!inflatedUnits[name]) {
inflatedUnits[name] = {
group,
value,
primaryName,
fullName
};
}


}
function addUnitDefinition(group, name, value, primaryName, fullName) {
if (inflatedUnits[name]) {
throw new UnitRedefinitionError(`${name} is already a defined unit. Do not redefine units`);
}

addUnitDefinitionWithoutError(group, name, value, primaryName, fullName);
}

function inflateUnits(compatibilityGroup, definitions) {
Object.keys(definitions).forEach((unitName) => {
const definition = definitions[unitName];
addUnitDefinition(compatibilityGroup, unitName, definition.value, unitName, definition.name);
addUnitDefinitionWithoutError(compatibilityGroup, definition.name, definition.value, unitName, definition.name);
const plural = definition.name + (definition.name[definition.name.length - 1] === 's' ? 'es' : 's');
addUnitDefinitionWithoutError(compatibilityGroup, plural, definition.value, unitName, definition.name);


(definition.aliases || []).forEach((aliasName) => {
addUnitDefinition(compatibilityGroup, aliasName, definition.value, unitName, definition.name);
const plural = aliasName + (aliasName[aliasName.length - 1] === 's' ? 'es' : 's');
addUnitDefinitionWithoutError(compatibilityGroup, plural, definition.value, unitName, definition.name);
});
});
}

inflateUnits('liquid', liquidDefinitions);
inflateUnits('weight', solidDefinitions);
inflateUnits('seed', seedDefinitions);
inflateUnits('yield', yieldDefinitions);

import { inflatedUnits, selectableUnitsByGroup } from './definitions';
import { UndefinedUnitError, UnitCompatibilityError } from './errors';

function retrieveUnit(unit) {
if (typeof unit === 'string') return unit;
Expand All @@ -84,6 +16,8 @@ function checkUnitValidity(unitArgument) {
}


// This class is used to operate with units. Providing invalid units to its functions will result in
// it throwing a UndefinedUnitError
class Units {
constructor(value, unit) {
checkUnitValidity(unit);
Expand All @@ -95,21 +29,31 @@ class Units {
}
}

// Given a compatibility group name, it will return the common units in that group.
// group names include: weight, liquid, seed, and yield
static selectableUnits(group) {
return selectableUnitsByGroup[group];
}

// Checks if 2 units are compatible. Units may be a Units object.
static isCompatible(unit1, unit2) {
checkUnitValidity(unit1);
checkUnitValidity(unit2);

return inflatedUnits[retrieveUnit(unit1)].group === inflatedUnits[retrieveUnit(unit2)].group;
}

equalBase = (unit) => this.isCompatible(unit);
// Checks if a unit is compatible with the object's unit.
isCompatible(unit) {
checkUnitValidity(unit);

return inflatedUnits[this.unit].group === inflatedUnits[retrieveUnit(unit)].group;
}

to(unitArgument, conversionObject = null) {
// Converts the object's value into the provided unit. The provided unit may be a Units object.
// This will throw a UnitCompatibilityError when a unit is not compatible.
// returns a Units object (so you can chain)
to(unitArgument) {
const unit = retrieveUnit(unitArgument)
if (!this.isCompatible(unit)) {
throw new UnitCompatibilityError(`${unit} is not compatible with ${this.unit}`);
Expand All @@ -120,10 +64,12 @@ class Units {
return new Units(convertedValue, unit);
}

// Returns the numeric value of the unit.
toNumber() {
return this.value;
}

// Prints out the unit as "value unit"
toString() {
return `${this.value} ${this.unit}`
}
Expand Down
47 changes: 8 additions & 39 deletions src/UnitsHelper.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,12 @@
import Units from './Units';

export const availableBushelUnits = [
'bushels',
];

export const availableSeedUnits = [
'seeds',
'bags',
'units - 130k',
'units - 140k',
];

export const availableSolidUnits = [
'lbs',
'oz',
'tons',
'grams',
'kilograms',
'metric tons',
];

export const availableLiquidUnits = [
'gallons',
'floz',
'liters',
'milliliters',
'pints',
'quarts',
];

export class ConversionError extends Error {
constructor(message) {
super(message);
this.name = 'ConversionError';
}
}
export const availableBushelUnits = Units.selectableUnits('yield');

export const availableSeedUnits = Units.selectableUnits('seed');

export const availableSolidUnits = Units.selectableUnits('weight');

export const availableLiquidUnits = Units.selectableUnits('liquid');


export default class UnitsHelper {
Expand Down Expand Up @@ -123,10 +95,7 @@ export default class UnitsHelper {
static convertToUnit(amount, fromUnit, toUnit) {
const parsedFromUnit = this.parseUnit(fromUnit);
const parsedToUnit = this.parseUnit(toUnit);
if (UnitsHelper.isCompatibleUnit(parsedFromUnit, parsedToUnit)) {
return new Units(amount, parsedFromUnit).to(parsedToUnit).toNumber();
}
throw new ConversionError(`Cannot convert ${parsedFromUnit} to ${parsedToUnit}`);
return new Units(amount, parsedFromUnit).to(parsedToUnit).toNumber();
}

static convertToGallons(amount, unit) {
Expand Down
58 changes: 58 additions & 0 deletions src/definitions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import liquidDefinitions from './liquid';
import solidDefinitions from './solid';
import seedDefinitions from './seed';
import yieldDefinitions from './yield';
import { UnitRedefinitionError, UndefinedUnitError } from '../errors';

export const inflatedUnits = {};
export const selectableUnitsByGroup = {};

function addUnitDefinitionWithoutError(group, name, value, primaryName, fullName) {
if (!inflatedUnits[name]) {
inflatedUnits[name] = {
group,
value,
primaryName,
fullName
};
}
}

function addUnitDefinition(group, name, value, primaryName, fullName) {
if (inflatedUnits[name]) {
throw new UnitRedefinitionError(`${name} is already a defined unit. Do not redefine units`);
}

addUnitDefinitionWithoutError(group, name, value, primaryName, fullName);
}

// Used to expand the definitions provided into a more robust object with multiple lookup keys.
// allows looking up units by their short name, plural name, or any alias.
// Additionally, it builds a list of common selectable units.
function inflateUnits(compatibilityGroup, definitions) {
Object.keys(definitions).forEach((unitName) => {
const definition = definitions[unitName];
addUnitDefinition(compatibilityGroup, unitName, definition.value, unitName, definition.name);
addUnitDefinitionWithoutError(compatibilityGroup, definition.name, definition.value, unitName, definition.name);
const plural = definition.name + (definition.name[definition.name.length - 1] === 's' ? 'es' : 's');
addUnitDefinitionWithoutError(compatibilityGroup, plural, definition.value, unitName, definition.name);


(definition.aliases || []).forEach((aliasName) => {
addUnitDefinition(compatibilityGroup, aliasName, definition.value, unitName, definition.name);
const plural = aliasName + (aliasName[aliasName.length - 1] === 's' ? 'es' : 's');
addUnitDefinitionWithoutError(compatibilityGroup, plural, definition.value, unitName, definition.name);
});

if (definition.selectableAs) {
selectableUnitsByGroup[compatibilityGroup] = selectableUnitsByGroup[compatibilityGroup] || [];
selectableUnitsByGroup[compatibilityGroup].push(definition.selectableAs);
}
});
}


inflateUnits('liquid', liquidDefinitions);
inflateUnits('weight', solidDefinitions);
inflateUnits('seed', seedDefinitions);
inflateUnits('yield', yieldDefinitions);
14 changes: 14 additions & 0 deletions src/definitions/liquid.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
/*
key = common name/short name
name = full name of unit. A plural version of this is also added
aliases = other names for unit (tonnes and metric tons for example). Plural versions of these are also added
value = conversion number. If liters have a value of 1, then milliliters have a value of 0.001 (there is 0.001 liters in 1 milliliter)
selectableAs = the name that shows up in the list of units that we want to be able to select from. Not every unit should go on here, just the common ones
*/

export default {
l: {
name: 'liter',
aliases: ['litre'],
value: 1,
selectableAs: 'liters',
},
ml: {
name: 'milliliter',
value: 0.001,
selectableAs: 'milliliters',
},
pt: {
name: 'pint',
value: 0.473176473,
selectableAs: 'pints',
},
qt: {
name: 'quart',
value: 0.946352946,
selectableAs: 'quarts',
},
gal: {
name: 'gallon',
value: 3.785411784,
selectableAs: 'gallons',
},
floz: {
name: 'fluid ounce',
aliases: ['fl oz'],
value: 0.02957353,
selectableAs: 'floz',
}
}
12 changes: 12 additions & 0 deletions src/definitions/seed.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
/*
key = common name/short name
name = full name of unit. A plural version of this is also added
aliases = other names for unit (tonnes and metric tons for example). Plural versions of these are also added
value = conversion number. If liters have a value of 1, then milliliters have a value of 0.001 (there is 0.001 liters in 1 milliliter)
selectableAs = the name that shows up in the list of units that we want to be able to select from. Not every unit should go on here, just the common ones
*/

export default {
seed: {
name: 'seed',
value: 1,
selectableAs: 'seeds'
},
bag: {
name: 'bag',
value: 80000,
selectableAs: 'bags'
},
'units - 130k': {
name: 'units - 130k',
aliases: ['units130k'],
value: 130000,
selectableAs: 'units - 130k'
},
'units - 140k': {
name: 'units - 140k',
aliases: ['units140k'],
value: 140000,
selectableAs: 'units - 140k',
},
}
Loading

0 comments on commit f8c74d8

Please sign in to comment.