diff --git a/.sealog/changes/2-0-0.edn b/.sealog/changes/2-0-0.edn new file mode 100644 index 0000000..e3b6039 --- /dev/null +++ b/.sealog/changes/2-0-0.edn @@ -0,0 +1,22 @@ +{:version {:major 2 + :minor 0 + :patch 0} + :version-type :semver3 + :changes {:added ["`brewtility.units.color` for converting between SRM, EBC, and Lovibond." + "`brewtility.units.pressure` for converting between PSI, Bar, etc." + "`brewtility.units.specific-gravity` for converting Specific Gravity." + "`brewtility.units.temperature` for converting between Celsius, Fahrenheit, and Kelvin." + "`brewtility.units.time` for converting between seconds, minutes, hours, etc." + "`brewtility.units.volume` for converting between gallons, liters, etc." + "`brewtility.units.weight` for converting between pounds, kilograms, etc." + "`brewtility.units.options` to introduce the symbolic keyword pattern" + "Display functions for all unit types."] + :changed ["Internal functions that convert between unit systems now use `brewtility.units`"] + :deprecated [] + :removed ["`brewility.color` has been removed in favor of `brewtility.units.color`" + "`brewtility.units/convert-weight` has been removed in favor of `brewtility.units/convert` and `brewtility.units.weight/convert`" + "`brewtility.units/convert-volume` has been removed in favor of `brewtility.units/convert` and `brewtility.units.volume/convert`" + "`brewtility.units/convert-temperature` has been removed in favor of `brewtility.units/convert` and `brewtility.units.temperature/convert`"] + :fixed [] + :security []} + :timestamp "2023-06-11T18:47:32.340619900Z"} diff --git a/CHANGELOG.md b/CHANGELOG.md index 53cb6f5..4f1e9b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Table of Contents +* [2.0.0 - 2023-06-11](#200---2023-06-11) * [1.5.0 - 2023-02-13](#150---2023-02-13) * [1.4.0 - 2023-02-12](#140---2023-02-12) * [1.3.0 - 2023-02-12](#130---2023-02-12) @@ -13,6 +14,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * [1.1.0 - 2020-08-15](#110---2020-08-15) * [1.0.0 - 2020-07-19](#100---2020-07-19) +## 2.0.0 - 2023-06-11 + +* Added + * `brewtility.units.color` for converting between SRM, EBC, and Lovibond. + * `brewtility.units.pressure` for converting between PSI, Bar, etc. + * `brewtility.units.specific-gravity` for converting Specific Gravity. + * `brewtility.units.temperature` for converting between Celsius, Fahrenheit, and Kelvin. + * `brewtility.units.time` for converting between seconds, minutes, hours, etc. + * `brewtility.units.volume` for converting between gallons, liters, etc. + * `brewtility.units.weight` for converting between pounds, kilograms, etc. + * `brewtility.units.options` to introduce the symbolic keyword pattern + * Display functions for all unit types. +* Changed + * Internal functions that convert between unit systems now use `brewtility.units` +* Removed + * `brewility.color` has been removed in favor of `brewtility.units.color` + * `brewtility.units/convert-weight` has been removed in favor of `brewtility.units/convert` and `brewtility.units.weight/convert` + * `brewtility.units/convert-volume` has been removed in favor of `brewtility.units/convert` and `brewtility.units.volume/convert` + * `brewtility.units/convert-temperature` has been removed in favor of `brewtility.units/convert` and `brewtility.units.temperature/convert` + ## 1.5.0 - 2023-02-13 * Added diff --git a/README.md b/README.md index 4d59195..d93f3ba 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,18 @@ Below are examples of provided functionality for each namespace. The library does provide access to other functions, but those primarily exist in support of those outlined here. - [Calculations](doc/api/calculations.md) -- [Color](doc/api/color.md) - [Precision](doc/api/precision.md) - [Predicates](doc/api/predicates.md) - [Units of Measure](doc/api/units.md) - [Wrapping](doc/api/wrapping.md) +## Common Patterns + +Brewtility follows several conventions and design patterns that result in artifacts available to library consumers. +These are not strictly required for use; however, they provide additional documentation and ease-of-use to those who adopt them. + +- [Symbolic Keywords](doc/patterns/symbolic_keywords.md) + ## Testing [doo](https://github.com/bensu/doo), a Leiningen plugin used to run ClojureScript tests in many JS environments, is already in `project.clj`. diff --git a/doc/api/color.md b/doc/api/color.md deleted file mode 100644 index 00fa132..0000000 --- a/doc/api/color.md +++ /dev/null @@ -1,45 +0,0 @@ -# Color - -The color of a beer is derived from the color of the fermentables used in the mash. -Brewtility currently supports conversions between three color systems: - -* [Standard Reference Method (SRM)](https://en.wikipedia.org/wiki/Standard_Reference_Method) -* [European Brewery Convention (EBC)](https://en.wikipedia.org/wiki/European_Brewery_Convention) -* [Lovibond](https://en.wikipedia.org/wiki/Beer_measurement#Colour) - -Additionally, brewtility maintains functionality to render these color systems as RGBa color codes. - -## Color Unit Conversion - -Given a color measurement in one of the three most common systems, convert between each system of measure. - -```clj -(:require [brewtility.color :refer :all]) - -(lovibond->srm 7.94) -;; => 10.0 - -(srm->ebc 23.1) -;; => 45.51 - -(ebc->lovibond 60.38) -;; => 23.2 -``` - -## Display Color Lookup - -Given a color measurement in one of the three most common systems, look up a corresponding RGBa color. -These functions are opinionated, and support and normalize down to the most common SRM scale: 1-40. - -```clj -(:require [brewtility.color :refer :all]) - -(lovibond->rgba 0.56) -;; => "rgba(255,230,153,1)" - -(srm->rgba 6.2) -;; => "rgba(248,166,0,1)" - -(ebc->rgba 25.61) -;; => "rgba(203,98,0,1)" -``` diff --git a/doc/api/units.md b/doc/api/units.md index b417c2f..a6a3e05 100644 --- a/doc/api/units.md +++ b/doc/api/units.md @@ -1,94 +1,169 @@ # Units of Measure -Beer has become an international interest/ +Beer has become an international interest. Additionally, brewing spans across the commercial and home environments. As a result, the systems of measure and scales of measure can very greatly between brewers. The BeerXML spec clearly delineates what units of measure are to be used to storing and transmitting data; however, they also allow users to pass `display` formats for values. These are used to translate uniform, machine-friendly systems into more appropriate systems and scales based on geographic region and user case. -## Systems of Measure +The `brewtility.units` namespace contains functions to convert between different systems and units of measure across several different types of measurement. +Additionally, this namespace may be used to render common display formats. -Brewtility supports four systems of measure: +The names of these systems and units are frequently used in code, and shorthand symbolic references to these names may be found in `brewtility.units.options` -- [The British Imperial System](https://en.wikipedia.org/wiki/Imperial_units) -- [The Metric System](https://en.wikipedia.org/wiki/Metric_system) -- [The International System](https://en.wikipedia.org/wiki/International_System_of_Units) -- [The US Customary Scale](https://en.wikipedia.org/wiki/United_States_customary_units) +## Basic Use -## Volume Conversion - -Given a `volume` in `source-measurement`, convert it to the `target-measurement`. -Supported measurements are: - -- Teaspoon -- Tablespoon -- Imperial Fluid Ounce -- American Fluid Ounce -- Cup -- Imperial Pint -- American Pint -- Imperial Quart -- American Quart -- Imperial Gallon -- American Gallon -- Litre (Or Liter, depending on regional convention) -- Millilitre (Or Milliliter, depending on regional convention) +The majority of the functionality belongs to the functions `convert` and `display`. +This provides a consistent interface to every system of measure, unit type, and more. ```clj -(:require [brewtility.units :refer :all]) +(:require [brewtility.units :as units] + [brewtility.units.options :as options] + [brewtility.units.volume :as volume]) -(convert-volume 1.0 :liter :litre) +;; You can use the keys in `brewtility.units.options` as arguments. +;; This guarantees you don't have typos in code, and can link to helpful documentation +(units/convert units/volume 1.0 options/liter options/litre) ;; => 1.0 -(convert-volume 20 :teaspoon :liter) +;; Or, if you prefer, bare keywords are accepted too +(units/convert units/volume 20 :teaspoon :liter) ;; => 0.099 -(convert-volume 9.99209 :imperial-pint :american-pint) +;; If you only plan on dealing with volumes, +;; then you can import the `brewtility.units.volume` namespace +(volume/convert 9.99209 :imperial-pint :american-pint) ;; => 12.0 + +;; You can also render display values +(units/display :volume 1.5 :liter) +;; => \"1.5 l\" + +;; The keys in `brewtility.units.options` are also acceptable +(units/display options/volume 1.5 options/liter) +;; => \"1.5 l\" + +;; You may supply additional options, such as the default precision for rounding +;; and the type of suffix to use +(units/display options/volume 1.45 options/liter {options/precision 1}) +;; => \"1.5 l\" + +(units/display options/volume 1.45 options/liter {options/suffix options/full}) +;; => \"1.45 liter\" + +;; Like `convert`, you can also call the measurement-type's functionality directly +(volume/display 1.45 options/liter {options/precision 1}) +;; => \"1.5 l\" + +;; And, of course, the symbolic keywords are ultimately plain keywords. +(volume/display 1.45 :liter {:suffix :full :precision 1}) +;; => \"1.5 liter\" ``` -## Weight Conversion +## Supported Systems -Given a `weight` in `source-measurement`, convert it to the `target-measurement`. -Supported measurements are: +Brewtility supports four systems of measure: -- Ounce -- Pound -- Milligram -- Gram -- Kilogram +- [The British Imperial System](https://en.wikipedia.org/wiki/Imperial_units) +- [The Metric System](https://en.wikipedia.org/wiki/Metric_system) +- [The International System](https://en.wikipedia.org/wiki/International_System_of_Units) +- [The US Customary Scale](https://en.wikipedia.org/wiki/United_States_customary_units) -```clj -(:require [brewtility.units :refer :all]) +These are the most commonly seen systems in brewing. +There are measurement functions for the most common types of measurements within these systems: -(convert-weight 1.0 :kilogram :kilogram) -;; => 1.0 +- [Color](##color) +- [Pressure](##pressure) +- [Specific Gravity](##specific-gravity) +- [Temperature](##temperature) +- [Time](##time) +- [Volume](##volume) +- [Weight](##weight) -(convert-weight 15.0 :pound :ounce) -;; => 240.0 +### Color -(convert-weight 1205.5 :milligram :pound) -;; => 0.003 -``` +Currently, brewtility supports the following types of color: -## Temperature Conversion +- [SRM](https://en.wikipedia.org/wiki/Standard_Reference_Method) +- [EBC](https://en.wikipedia.org/wiki/European_Brewery_Convention) +- [Lovibond](https://en.wikipedia.org/wiki/Beer_measurement#Colour) +- [RGBa](https://en.wikipedia.org/wiki/RGBA_color_model) -Given a `temperature` in `source-measurement`, convert it to the `target-measurement`. -Supported measurements are: +The `RGBa` system is special, as it can only be used as an argument for the result of a unit conversion. +Unfortunately, there is not a great deterministic way to cast the values back to the other systems. +brewtility will thrown an exception in this case and explain the problem. -- Celsius (Or Centigrade, depending on regional convention. Or C, for convenience) -- Kelvin (Or K, for convenience) -- Fahrenheit (Or F, for convenience) +### Pressure -```clj -(:require [brewtility.units :refer :all]) +Currently, brewtility supports the following types of pressure: -(convert-temperature 32.0 :fahrenheit :fahrenheit) -;; => 32.0 +- [pascal](https://en.wikipedia.org/wiki/Pascal_(unit)#Multiples_and_submultiples) +- [kilopascal](https://en.wikipedia.org/wiki/Pascal_(unit)#Multiples_and_submultiples) +- [bar](https://en.wikipedia.org/wiki/Bar_(unit)) +- [atmosphere](https://en.wikipedia.org/wiki/Atmosphere_(unit)) +- [torr](https://en.wikipedia.org/wiki/Torr) +- [psi]( -(convert-temperature 0.0 :c :f) -;; => 32.0 +### Specific Gravity -(convert-temperature 2016.3 :kelvin :c) -;; => 1743.15 -``` +Currently, brewtility supports the following types of specific gravity: + +- [specific-gravity](https://en.wikipedia.org/wiki/Specific_gravity) + +While there is currently only one system, the same namespace and functionality exists as the other measurement types. +This allows for progressive evolution, and provides a consistent interface to every measurement type encoded in the BeerXML specification. + +### Temperature + +Currently, brewtility supports the following types of temperature measurements: + +- [celsius](https://en.wikipedia.org/wiki/Celsius) +- [fahrenheit](https://en.wikipedia.org/wiki/Fahrenheit) +- [kelvin](https://en.wikipedia.org/wiki/Kelvin_(unit)) + +Given the prevalence of shorthand names in temperature measurements, brewtility also accepts `c`, `k`, and `f`. + +### Time + +Currently, brewtility supports the following types of time measurements: + +- [microsecond](https://en.wikipedia.org/wiki/Microsecond) +- [nanosecond](https://en.wikipedia.org/wiki/Nanosecond) +- [millisecond](https://en.wikipedia.org/wiki/Millisecond) +- [second](https://en.wikipedia.org/wiki/Second) +- [minute](https://en.wikipedia.org/wiki/Minute) +- [hour](https://en.wikipedia.org/wiki/Hour) +- [day](https://en.wikipedia.org/wiki/Day) +- [week](https://en.wikipedia.org/wiki/Week) + +### Volume + +Currently, brewtility supports the following types of volume: + +- [american-fluid-ounce](https://en.wikipedia.org/wiki/Fluid_ounce) +- [american-gallon](https://en.wikipedia.org/wiki/Gallon) +- [american-pint](https://en.wikipedia.org/wiki/Pint) +- [american-quart](https://en.wikipedia.org/wiki/Quart) +- [cup](https://en.wikipedia.org/wiki/Cup_(unit)) +- [imperial-fluid-ounce](https://en.wikipedia.org/wiki/Fluid_ounce) +- [imperial-gallon](https://en.wikipedia.org/wiki/Gallon) +- [imperial-pint](https://en.wikipedia.org/wiki/Pint) +- [imperial-quart](https://en.wikipedia.org/wiki/Quart) +- [liter](https://en.wikipedia.org/wiki/Litre) +- [litre](https://en.wikipedia.org/wiki/Litre) +- [milliliter](https://en.wikipedia.org/wiki/Millilitre) +- [millilitre](https://en.wikipedia.org/wiki/Millilitre) +- [tablespoon](https://en.wikipedia.org/wiki/Tablespoon) +- [teaspoon](https://en.wikipedia.org/wiki/Teaspoon)) + +Given the prevalence of the French spellings in English recipes, both `:litre` and `:liter` can be passed as options. + +### Weight + +Currently, brewtility supports the following types of weight: + +- [gram](https://en.wikipedia.org/wiki/Gram) +- [milligram](https://en.wikipedia.org/wiki/Milligram) +- [kilogram](https://en.wikipedia.org/wiki/Kilogram) +- [ounce](https://en.wikipedia.org/wiki/Ounce) +- [pound](https://en.wikipedia.org/wiki/Pound_(mass)) diff --git a/doc/cljdoc.edn b/doc/cljdoc.edn index 251a383..f363c4b 100644 --- a/doc/cljdoc.edn +++ b/doc/cljdoc.edn @@ -2,11 +2,14 @@ ["Changelog" {:file "CHANGELOG.md"}] ["Community" {} ["Contributing" {:file "CONTRIBUTING.md"}] - ["Code of Conduct" {:file "CODE_OF_CONDUCT.md"}]] + ["Code of Conduct" {:file "CODE_OF_CONDUCT.md"}] + ["Security Policy" {:file "SECURITY.md"}]] ["Functionality" {} ["Calculations" {:file "doc/api/calculations.md"}] ["Color" {:file "doc/api/color.md"}] ["Precision" {:file "doc/api/precision.md"}] ["Predicates" {:file "doc/api/predicates.md"}] ["Units of Measure" {:file "doc/api/units.md"}] - ["Wrapping" {:file "doc/api/wrapping.md"}]]]} + ["Wrapping" {:file "doc/api/wrapping.md"}]] + ["Adopted Patterns" {} + ["Symbolic Keywords" {:file "doc/patterns/symbolic_keywords.md"}]]]} diff --git a/doc/patterns/symbolic_keywords.md b/doc/patterns/symbolic_keywords.md new file mode 100644 index 0000000..cdda860 --- /dev/null +++ b/doc/patterns/symbolic_keywords.md @@ -0,0 +1,51 @@ +# Symbolic Keywords + +In many clojure libraries, the behavior of complex functions may be controlled by a map. +For example: + +```clojure +(:require [brewtility.units :as units]) +(units/display :volume 1.5 :liter) ;; => "1.5 l" +(units/display :volume 1.5 :liter {:suffix :full}) ;; => "1.5 liter" +``` + +This allows us to easily extend the definition of a single function to fulfill multiple complex needs; however, option maps come with considerable drawbacks. +When a map is keyed with keywords, it is easy to introduce subtle, hard-to-detect errors. +Since most of these functions select default values for keys not present, typos can lead to meaningful differences in behavior. +For example: + +```clojure +(:require [brewtility.units :as units]) +(units/display :volume 1.5 :liter) ;; => "1.5 l" + +;; Not the missing "f" in the key :sufix +(units/display :volume 1.5 :liter {:sufix :full}) ;; => "1.5 l" +``` + +Because keywords aren't picked up by the auto-complete features of most editors, they're vulnerable to all classes of transpositional errors. +Further, the options themselves are unable to carry metadata and documentation with them, making them little more than magic values. + +For this reason, it is helpful for libraries to include symbols which point to the "magic" keywords used in option maps. +This has several benefits: + +- Misspelled or incorrect option keys are compile-time errors, instead of runtime bugs +- Symbols can carry metadata like documentation, deprecation notices, and point to alternative options +- Context-aware editors can auto-complete options + +Here's can example: + +```clojure +(:require [brewtility.units :as units] + [brewtility.units.options :as options]) + +(units/display options/volume 1.5 options/liter) ;; => "1.5 l" +(units/display options/volume 1.5 options/liter {options/suffix options/full}) ;; => "1.5 liter" +``` + +Most option maps in `brewtility` support symbolic keywords. +These keywords are available in the namespaces ending in `.options`. +These files live as close to the code relying on them as possible; +for example, the options for all unit conversion operations (e.g. `brewtility.units`, `brewtility.units.time`, `brewtility.units.color`, etc.) are available in `brewtility.units.options`. + +Further, the option keywords are preferred for library development. +A single source of truth for the name of a common option, such as `precision`, limits the possibility of incorrectly retrieving the values used by that option. diff --git a/package-lock.json b/package-lock.json index e9cf449..4926585 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "brewtility", - "version": "1.5.0", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "brewtility", - "version": "1.5.0", + "version": "2.0.0", "license": "MIT", "devDependencies": { "karma": "^6.3.16", diff --git a/package.json b/package.json index f90ea26..322b22c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "brewtility", - "version": "1.5.0", + "version": "2.0.0", "description": "Utility functions for all of your brewing needs.", "main": "index.js", "directories": { diff --git a/pom.xml b/pom.xml index 8176bf0..f9713fb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.wallbrew brewtility jar - 1.5.0 + 2.0.0 brewtility Utility functions for all of your brewing needs. https://github.com/Wall-Brew-Co/brewtility @@ -20,7 +20,7 @@ https://github.com/Wall-Brew-Co/brewtility scm:git:git://github.com/Wall-Brew-Co/brewtility.git scm:git:ssh://git@github.com/Wall-Brew-Co/brewtility.git - 47bfea691592555cd7be8f910c170d69591be94d + 1e9b875c50a61b04b3711da1cee211f836ab8662 src diff --git a/project.clj b/project.clj index 2588951..165d112 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject com.wallbrew/brewtility "1.5.0" +(defproject com.wallbrew/brewtility "2.0.0" :description "Utility functions for all of your brewing needs." :url "https://github.com/Wall-Brew-Co/brewtility" :license {:name "MIT" diff --git a/src/brewtility/calculations.cljc b/src/brewtility/calculations.cljc index 894841f..14e1259 100644 --- a/src/brewtility/calculations.cljc +++ b/src/brewtility/calculations.cljc @@ -2,9 +2,12 @@ "Namespace for handling recipe calculations. This namespace assumes ingredients that conform to the common-beer-format." {:added "1.0"} - (:require [brewtility.color :as color] - [brewtility.predicates.fermentables :as fermentables] - [brewtility.units :as units])) + (:require [brewtility.predicates.fermentables :as fermentables] + [brewtility.units.color :as color] + [brewtility.units.options :as options] + [brewtility.units.time :as time] + [brewtility.units.volume :as volume] + [brewtility.units.weight :as weight])) (defn normalize-fermentable @@ -12,10 +15,10 @@ {:added "1.0"} [fermentable] (let [is-not-grain? (not (fermentables/grain? fermentable)) - kg->lbs (fn [w] (units/convert-weight w :kilogram :pound))] ; MCU is calculated against pounds + kg->lbs (fn [w] (weight/convert w options/kilogram options/pound))] ; MCU is calculated against pounds (cond-> fermentable true (update :amount kg->lbs) - is-not-grain? (update :color color/srm->lovibond)))) ; Grain color is in Lovibond, all other fermentables use SRM + is-not-grain? (update :color #(color/convert % options/srm options/lovibond))))) ; Grain color is in Lovibond, all other fermentables use SRM (defn calculate-malt-color-units "Given a collection of `common-beer-format` conforming `fermentables`, and a conformed `batch-size` in liters, return the overall Malt Color Units for a recipe." @@ -25,7 +28,7 @@ (let [normalized-fermentables (map normalize-fermentable fermentables) reducing-fn (fn [acc v] (+ acc (* (:amount v) (:color v)))) color (reduce reducing-fn 0 normalized-fermentables) - imperial-volume (units/convert-volume batch-size :litre :american-gallon)] + imperial-volume (volume/convert batch-size options/litre options/american-gallon)] (/ color imperial-volume))) @@ -48,7 +51,7 @@ [fermentables batch-size] (-> fermentables (calculate-srm-color batch-size) - color/srm->ebc)) + (color/convert options/srm options/ebc))) (defn calculate-lovibond-color @@ -61,7 +64,7 @@ [fermentables batch-size] (-> fermentables (calculate-srm-color batch-size) - color/srm->lovibond)) + (color/convert options/srm options/lovibond))) (defn calculate-rgba-color @@ -74,7 +77,7 @@ [fermentables batch-size] (-> fermentables (calculate-srm-color batch-size) - color/srm->rgba)) + (color/convert options/srm options/rgba))) (defn potential-gravity->gravity-points @@ -82,7 +85,7 @@ {:added "1.0" :see-also ["gravity-points->potential-gravity"]} [potential-gravity weight] - (let [weight-in-pounds (units/convert-weight weight :kilogram :pound)] + (let [weight-in-pounds (weight/convert weight options/kilogram options/pound)] (-> potential-gravity (* 1000) (- 1000) @@ -94,7 +97,7 @@ {:added "1.0" :see-also ["potential-gravity->gravity-points"]} [gravity-points volume] - (let [volume-in-gallons (units/convert-volume volume :litre :american-gallon)] + (let [volume-in-gallons (volume/convert volume options/litre options/american-gallon)] (-> gravity-points (/ volume-in-gallons) (+ 1000) @@ -113,14 +116,14 @@ (gravity-points->potential-gravity total-gravity-points batch-size))) -(def ^:const gravity->abv-multiplier +(def gravity->abv-multiplier "The multiplier used to convert gravity to ABV. This is a constant, and is not configurable." 0.00135) -(def ^:const default-attenuation +(def default-attenuation "A default attenuation for yeast if none is provided. This represents a common level home brewers should expect from their yeast." 0.75) @@ -176,7 +179,7 @@ "Calculate the maximum amount of alpha acid released by `weight` ounce of a hop at `percent` alpha acid." {:added "1.0"} [weight alpha] - (let [weight-in-ounces (units/convert-weight weight :kilogram :ounce) + (let [weight-in-ounces (weight/convert weight options/kilogram options/ounce) aau-normalization-factor 100] (* aau-normalization-factor weight-in-ounces alpha))) @@ -187,7 +190,7 @@ [hop batch-size potential-gravity] (let [utilization (calculate-hop-utilization potential-gravity (:time hop)) alpha-acid-units (calculate-alpha-acid-units (:amount hop) (:alpha hop)) - imperial-volume (units/convert-volume batch-size :litre :american-gallon) + imperial-volume (volume/convert batch-size options/litre options/american-gallon) conversion-factor 74.89] (/ (* alpha-acid-units utilization conversion-factor) imperial-volume))) @@ -207,7 +210,7 @@ [{:keys [batch-size top-up-water trub-chiller-loss boil-time evap-rate]}] (if (every? number? [batch-size top-up-water trub-chiller-loss boil-time evap-rate]) (let [starting-water (- batch-size top-up-water trub-chiller-loss) - boil-time-in-hours (/ boil-time 60.0) + boil-time-in-hours (time/convert boil-time options/minute options/hour) evaporation-decimal (/ evap-rate 100.0) evaporated-water (+ 1 (* boil-time-in-hours evaporation-decimal))] (* starting-water evaporated-water)) diff --git a/src/brewtility/color.cljc b/src/brewtility/color.cljc deleted file mode 100644 index 5fbfe95..0000000 --- a/src/brewtility/color.cljc +++ /dev/null @@ -1,143 +0,0 @@ -(ns brewtility.color - "Namespace for calculating beer colors") - - -;; -;; SRM Beer Colors (https://en.wikipedia.org/wiki/Standard_Reference_Method) -;; -(def ^:const srm-1 "rgba(255,230,153,1)") -(def ^:const srm-2 "rgba(255,216,120,1)") -(def ^:const srm-3 "rgba(255,202,90,1)") -(def ^:const srm-4 "rgba(255,191,66,1)") -(def ^:const srm-5 "rgba(251,177,35,1)") -(def ^:const srm-6 "rgba(248,166,0,1)") -(def ^:const srm-7 "rgba(243,156,0,1)") -(def ^:const srm-8 "rgba(234,143,0,1)") -(def ^:const srm-9 "rgba(229,133,0,1)") -(def ^:const srm-10 "rgba(222,124,0,1)") -(def ^:const srm-11 "rgba(215,114,0,1)") -(def ^:const srm-12 "rgba(207,105,0,1)") -(def ^:const srm-13 "rgba(203,98,0,1)") -(def ^:const srm-14 "rgba(195,89,0,1)") -(def ^:const srm-15 "rgba(187,81,0,1)") -(def ^:const srm-16 "rgba(181,76,0,1)") -(def ^:const srm-17 "rgba(176,69,0,1)") -(def ^:const srm-18 "rgba(166,62,0,1)") -(def ^:const srm-19 "rgba(161,55,0,1)") -(def ^:const srm-20 "rgba(155,50,0,1)") -(def ^:const srm-21 "rgba(149,45,0,1)") -(def ^:const srm-22 "rgba(142,41,0,1)") -(def ^:const srm-23 "rgba(136,35,0,1)") -(def ^:const srm-24 "rgba(130,30,0,1)") -(def ^:const srm-25 "rgba(123,26,0,1)") -(def ^:const srm-26 "rgba(119,25,0,1)") -(def ^:const srm-27 "rgba(112,20,0,1)") -(def ^:const srm-28 "rgba(106,14,0,1)") -(def ^:const srm-29 "rgba(102,13,0,1)") -(def ^:const srm-30 "rgba(94,11,0,1)") -(def ^:const srm-31 "rgba(90,10,2,1)") -(def ^:const srm-32 "rgba(96,9,3,1)") -(def ^:const srm-33 "rgba(82,9,7,1)") -(def ^:const srm-34 "rgba(76,5,5,1)") -(def ^:const srm-35 "rgba(71,6,6,1)") -(def ^:const srm-36 "rgba(68,6,7,1)") -(def ^:const srm-37 "rgba(63,7,8,1)") -(def ^:const srm-38 "rgba(59,6,7,1)") -(def ^:const srm-39 "rgba(58,7,11,1)") -(def ^:const srm-40 "rgba(3,4,3,1)") - - -(def srm-color-map - {1 srm-1 - 2 srm-2 - 3 srm-3 - 4 srm-4 - 5 srm-5 - 6 srm-6 - 7 srm-7 - 8 srm-8 - 9 srm-9 - 10 srm-10 - 11 srm-11 - 12 srm-12 - 13 srm-13 - 14 srm-14 - 15 srm-15 - 16 srm-16 - 17 srm-17 - 18 srm-18 - 19 srm-19 - 20 srm-20 - 21 srm-21 - 22 srm-22 - 23 srm-23 - 24 srm-24 - 25 srm-25 - 26 srm-26 - 27 srm-27 - 28 srm-28 - 29 srm-29 - 30 srm-30 - 31 srm-31 - 32 srm-32 - 33 srm-33 - 34 srm-34 - 35 srm-35 - 36 srm-36 - 37 srm-37 - 38 srm-38 - 39 srm-39 - 40 srm-40}) - - -(defn lovibond->srm - "Convert the color described in degrees `lovibond` to the equivalent SRM color" - [lovibond] - (- (* lovibond 1.3546) 0.76)) - - -(defn srm->ebc - "Convert the color described by the `srm` to the equivalent EBC color" - [srm] - (* srm 1.97)) - - -(defn ebc->srm - "Convert the color described by the `ebc` to the equivalent SRM color" - [ebc] - (* ebc 0.508)) - - -(defn srm->lovibond - "Convert the color described in 'srm` to the equivalent degrees Lovibond" - [srm] - (/ (+ srm 0.76) 1.3546)) - - -(def lovibond->ebc - "Convert the color described in degrees 'lovibond` to the equivalent EBC color" - (comp srm->ebc lovibond->srm)) - - -(def ebc->lovibond - "Convert the color described in 'ebc` to the equivalent degrees Lovibond" - (comp srm->lovibond ebc->srm)) - - -(defn srm->rgba - "Given `srm-number`, return the closest, bounded applicable RGBA color string. - OPINIONATED: The provided `srm-number` will be bound to the common range from 1 to 40 - Decimal-like values are trimmed, not rounded." - [srm-number] - (let [srm-color (min (max (int srm-number) 1) 40)] - (get srm-color-map srm-color))) - - -(def lovibond->rgba - "Given `lovibond-number`, return the closest, bounded applicable RGBA color string." - (comp srm->rgba lovibond->srm)) - - -(def ebc->rgba - "Given `ebc-number`, return the closest, bounded applicable RGBA color string." - (comp srm->rgba ebc->srm)) diff --git a/src/brewtility/precision.cljc b/src/brewtility/precision.cljc index cc4a1af..703a48f 100644 --- a/src/brewtility/precision.cljc +++ b/src/brewtility/precision.cljc @@ -1,10 +1,12 @@ (ns brewtility.precision - "Namespace for handling numeric precision" + "Namespace for handling numeric precision, rounding, and approximation." + {:added "1.0"} #?(:clj (:import [java.math RoundingMode]))) (defn approximates? - "Determine if `n2` approximates `n1` within `variance` percent" + "Determine if `n2` approximates `n1` within `variance` percent." + {:added "1.0"} [n1 n2 variance] (let [upper-bound (* n1 (+ 1.0 variance)) lower-bound (* n1 (- 1.0 variance))] @@ -12,7 +14,8 @@ (defn ->precision - "Given a decimal `x` and the number of decimal places, returns that number rounded to `num-decimals` precision" + "Given a decimal `x` and the number of decimal places, returns that number rounded to `num-decimals` precision." + {:added "1.0"} [^double x ^long num-decimals] (double #?(:clj (.setScale (bigdec x) num-decimals RoundingMode/HALF_UP) @@ -23,17 +26,23 @@ (defn ->1dp "Given a decimal `x`, returns that number rounded to one decimal place." + {:added "1.0" + :see-also ["->precision" "->2dp" "->3dp"]} [^double x] (->precision x 1)) (defn ->2dp "Given a decimal `x`, returns that number rounded to two decimal places." + {:added "1.0" + :see-also ["->precision" "->1dp" "->3dp"]} [^double x] (->precision x 2)) (defn ->3dp "Given a decimal `x`, returns that number rounded to three decimal places." + {:added "1.0" + :see-also ["->precision" "->1dp" "->2dp"]} [^double x] (->precision x 3)) diff --git a/src/brewtility/units.cljc b/src/brewtility/units.cljc index 832e790..76b1735 100644 --- a/src/brewtility/units.cljc +++ b/src/brewtility/units.cljc @@ -1,107 +1,103 @@ (ns brewtility.units - "Namespace for converting between different units of measure") - - -(def ^:const volume-measurement->litre - {:teaspoon 0.00492892 - :tablespoon 0.0147868 - :imperial-fluid-ounce 0.0284131 - :american-fluid-ounce 0.0295735 - :cup 0.236588 - :imperial-pint 0.568261 - :american-pint 0.473176 - :imperial-quart 1.13652 - :american-quart 0.946353 - :imperial-gallon 4.54609 - :american-gallon 3.78541 - :litre 1.0 - :liter 1.0 ; To support regional spelling - :millilitre 0.001 - :milliliter 0.001}) ; To support regional spelling - -(defn liter->volume-measurement - [measurement-name] - (/ 1.0 (get volume-measurement->litre measurement-name))) - - -(defn convert-volume - "Given a `volume` in `source-measurement`, convert it to the `target-measurement`." - [volume source-measurement target-measurement] - (if (= source-measurement target-measurement) - volume - (let [source->litre-multiplier (volume-measurement->litre source-measurement) - litre->target-multiplier (liter->volume-measurement target-measurement)] - (* volume source->litre-multiplier litre->target-multiplier)))) - - -(def ^:const weight-measurement->kilogram - {:ounce 0.02834952 - :pound 0.45359237 - :milligram 0.000001 - :gram 0.001 - :kilogram 1.0}) - - -(defn kilogram->weight-measurement - [measurement-name] - (/ 1.0 (get weight-measurement->kilogram measurement-name))) - - -(defn convert-weight - "Given a `weight` in `source-measurement`, convert it to the `target-measurement`." - [weight source-measurement target-measurement] - (if (= source-measurement target-measurement) - weight - (let [source->kilogram-multiplier (weight-measurement->kilogram source-measurement) - kilogram->target-multiplier (kilogram->weight-measurement target-measurement)] - (* weight source->kilogram-multiplier kilogram->target-multiplier)))) - - -(defn celsius->fahrenheit - [temp] - (+ 32 (* 1.8 temp))) - - -(defn celsius->kelvin - [temp] - (+ temp 273.15)) - - -(defn fahrenheit->celsius - [temp] - (/ (* (- temp 32) 5) 9.0)) - - -(defn kelvin->celsius - [temp] - (- temp 273.15)) - - -(def ^:const temperature-measurement->celsius - {:celsius identity - :c identity - :centigrade identity - :fahrenheit fahrenheit->celsius - :f fahrenheit->celsius - :kelvin kelvin->celsius - :k kelvin->celsius}) - - -(def ^:const celsius->temperature-measurement - {:celsius identity - :c identity - :centigrade identity - :fahrenheit celsius->fahrenheit - :f celsius->fahrenheit - :kelvin celsius->kelvin - :k celsius->kelvin}) - - -(defn convert-temperature - "Given a `temp` in `source-measurement`, convert it to the `target-measurement`." - [temp source-measurement target-measurement] - (if (= source-measurement target-measurement) - temp - (let [source->celsius-fn (temperature-measurement->celsius source-measurement) - celsius->target-fn (celsius->temperature-measurement target-measurement)] - (-> temp source->celsius-fn celsius->target-fn)))) + "Namespace for converting between different units of measure. + + Currently, brewtility supports the following types of measurements: + + - [Color](https://en.wikipedia.org/wiki/Beer_measurement#Colour) + - [Pressure](https://en.wikipedia.org/wiki/Pressure) + - [Specific Gravity](https://en.wikipedia.org/wiki/Relative_density) + - [Temperature](https://en.wikipedia.org/wiki/Temperature) + - [Time](https://en.wikipedia.org/wiki/Time) + - [Volume](https://en.wikipedia.org/wiki/Volume) + - [Weight](https://en.wikipedia.org/wiki/Mass) + + These types of measurement cover four systems of measure: + + - [The British Imperial System](https://en.wikipedia.org/wiki/Imperial_units) + - [The Metric System](https://en.wikipedia.org/wiki/Metric_system) + - [The International System](https://en.wikipedia.org/wiki/International_System_of_Units) + - [The US Customary Scale](https://en.wikipedia.org/wiki/United_States_customary_units)" + {:added "1.0" + :changed "2.0"} + (:require [brewtility.units.color :as color] + [brewtility.units.options :as options] + [brewtility.units.pressure :as pressure] + [brewtility.units.specific-gravity :as specific-gravity] + [brewtility.units.temperature :as temperature] + [brewtility.units.time :as time] + [brewtility.units.volume :as volume] + [brewtility.units.weight :as weight])) + + +(defn convert + "Given a `measurement` in `source-units`, convert it to the `target-units` in a given `measurement-type`. + + For example: + + ```clj + (convert :weight 1.5 :kg :lb) ;; => 3.31 + ``` + + Supported values for `source-units` and `target-units` are enumerated in each unit system namespace. + This function will throw an exception if unsupported unit values are passed." + {:added "2.0" + :see-also ["brewtility.units.color/convert" + "brewtility.units.pressure/convert" + "brewtility.units.specific-gravity/convert" + "brewtility.units.temperature/convert" + "brewtility.units.time/convert" + "brewtility.units.volume/convert" + "brewtility.units.weight/convert"]} + [measurement-type measurement source-units target-units] + (case measurement-type + :color (color/convert measurement source-units target-units) + :pressure (pressure/convert measurement source-units target-units) + :specific-gravity (specific-gravity/convert measurement source-units target-units) + :temperature (temperature/convert measurement source-units target-units) + :time (time/convert measurement source-units target-units) + :volume (volume/convert measurement source-units target-units) + :weight (weight/convert measurement source-units target-units) + (throw (ex-info "Unsupported unit system" + {:measurement-type measurement-type + :allowed-values options/measurement-types + :measurement measurement})))) + + +(defn display + "A function to render a human-readable `measurement` in `source-units` for a `measurement-type`. + + For example, + ```clj + (display :weight 1.5 :pound) ;; => \"1.5 lb\" + ``` + This function accepts an option map as an optional fourth argument. The following options are available: + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0" + :see-also ["brewtility.units.color/display" + "brewtility.units.pressure/display" + "brewtility.units.specific-gravity/display" + "brewtility.units.temperature/display" + "brewtility.units.time/display" + "brewtility.units.volume/display" + "brewtility.units.weight/display"]} + ([measurement-type measurement source-units] + (display measurement-type measurement source-units {})) + ([measurement-type measurement source-units opts] + + (let [options (merge {options/precision options/default-precision + options/suffix options/short} opts)] + (case measurement-type + :color (color/display measurement source-units options) + :pressure (pressure/display measurement source-units options) + :specific-gravity (specific-gravity/display measurement source-units options) + :temperature (temperature/display measurement source-units options) + :time (time/display measurement source-units options) + :volume (volume/display measurement source-units options) + :weight (weight/display measurement source-units options) + (throw (ex-info "Unsupported unit system" + {:measurement-type measurement-type + :allowed-values options/measurement-types + :measurement measurement})))))) diff --git a/src/brewtility/units/color.cljc b/src/brewtility/units/color.cljc new file mode 100644 index 0000000..0c50262 --- /dev/null +++ b/src/brewtility/units/color.cljc @@ -0,0 +1,402 @@ +(ns brewtility.units.color + "A namespace for converting between different units of color. + + In the BeerXML spec, color is measured in different units in different contexts. + This namespace provides a way to convert between SRM and other units of color. + + Currently, brewtility supports the following types of color: + - [SRM](https://en.wikipedia.org/wiki/Standard_Reference_Method) + - [EBC](https://en.wikipedia.org/wiki/European_Brewery_Convention) + - [Lovibond](https://en.wikipedia.org/wiki/Beer_measurement#Colour) + - [RGBa](https://en.wikipedia.org/wiki/RGBA_color_model)" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options])) + + +(def ^:const measurements + "The color systems available across brewtility." + #{options/srm + options/ebc + options/lovibond + options/rgba}) + + +(def ^:const measurements->display-name + "A map from color system names to their full and short unit names" + {options/srm {options/full "standard reference method" + options/short "srm"} + options/ebc {options/full "ebc" + options/short "ebc"} + options/lovibond {options/full "degrees lovibond" + options/short "°L"} + options/rgba {options/full "" + options/short ""}}) + + +;; +;; SRM Beer Colors (https://en.wikipedia.org/wiki/Standard_Reference_Method) +;; +(def srm-1 + "An SRM of 1 mapped to an RGBa color code." + + "rgba(255,230,153,1)") + + +(def srm-2 + "An SRM of 2 mapped to an RGBa color code." + + "rgba(255,216,120,1)") + + +(def srm-3 + "An SRM of 3 mapped to an RGBa color code." + + "rgba(255,202,90,1)") + + +(def srm-4 + "An SRM of 4 mapped to an RGBa color code." + "rgba(255,191,66,1)") + + +(def srm-5 + "An SRM of 5 mapped to an RGBa color code." + "rgba(251,177,35,1)") + + +(def srm-6 + "An SRM of 6 mapped to an RGBa color code." + "rgba(248,166,0,1)") + + +(def srm-7 + "An SRM of 7 mapped to an RGBa color code." + "rgba(243,156,0,1)") + + +(def srm-8 + "An SRM of 8 mapped to an RGBa color code." + "rgba(234,143,0,1)") + + +(def srm-9 + "An SRM of 9 mapped to an RGBa color code." + "rgba(229,133,0,1)") + + +(def srm-10 + "An SRM of 10 mapped to an RGBa color code." + "rgba(222,124,0,1)") + + +(def srm-11 + "An SRM of 11 mapped to an RGBa color code." + "rgba(215,114,0,1)") + + +(def srm-12 + "An SRM of 12 mapped to an RGBa color code." + "rgba(207,105,0,1)") + + +(def srm-13 + "An SRM of 13 mapped to an RGBa color code." + "rgba(203,98,0,1)") + + +(def srm-14 + "An SRM of 14 mapped to an RGBa color code." + "rgba(195,89,0,1)") + + +(def srm-15 + "An SRM of 15 mapped to an RGBa color code." + "rgba(187,81,0,1)") + + +(def srm-16 + "An SRM of 16 mapped to an RGBa color code." + "rgba(181,76,0,1)") + + +(def srm-17 + "An SRM of 17 mapped to an RGBa color code." + "rgba(176,69,0,1)") + + +(def srm-18 + "An SRM of 18 mapped to an RGBa color code." + "rgba(166,62,0,1)") + + +(def srm-19 + "An SRM of 19 mapped to an RGBa color code." + "rgba(161,55,0,1)") + + +(def srm-20 + "An SRM of 20 mapped to an RGBa color code." + "rgba(155,50,0,1)") + + +(def srm-21 + "An SRM of 21 mapped to an RGBa color code." + "rgba(149,45,0,1)") + + +(def srm-22 + "An SRM of 22 mapped to an RGBa color code." + "rgba(142,41,0,1)") + + +(def srm-23 + "An SRM of 23 mapped to an RGBa color code." + "rgba(136,35,0,1)") + + +(def srm-24 + "An SRM of 24 mapped to an RGBa color code." + "rgba(130,30,0,1)") + + +(def srm-25 + "An SRM of 25 mapped to an RGBa color code." + "rgba(123,26,0,1)") + + +(def srm-26 + "An SRM of 26 mapped to an RGBa color code." + "rgba(119,25,0,1)") + + +(def srm-27 + "An SRM of 27 mapped to an RGBa color code." + "rgba(112,20,0,1)") + + +(def srm-28 + "An SRM of 28 mapped to an RGBa color code." + "rgba(106,14,0,1)") + + +(def srm-29 + "An SRM of 29 mapped to an RGBa color code." + "rgba(102,13,0,1)") + + +(def srm-30 + "An SRM of 30 mapped to an RGBa color code." + "rgba(94,11,0,1)") + + +(def srm-31 + "An SRM of 31 mapped to an RGBa color code." + "rgba(90,10,2,1)") + + +(def srm-32 + "An SRM of 32 mapped to an RGBa color code." + "rgba(96,9,3,1)") + + +(def srm-33 + "An SRM of 33 mapped to an RGBa color code." + "rgba(82,9,7,1)") + + +(def srm-34 + "An SRM of 34 mapped to an RGBa color code." + "rgba(76,5,5,1)") + + +(def srm-35 + "An SRM of 35 mapped to an RGBa color code." + "rgba(71,6,6,1)") + + +(def srm-36 + "An SRM of 36 mapped to an RGBa color code." + "rgba(68,6,7,1)") + + +(def srm-37 + "An SRM of 37 mapped to an RGBa color code." + "rgba(63,7,8,1)") + + +(def srm-38 + "An SRM of 38 mapped to an RGBa color code." + "rgba(59,6,7,1)") + + +(def srm-39 + "An SRM of 39 mapped to an RGBa color code." + "rgba(58,7,11,1)") + + +(def srm-40 + "An SRM of 40 mapped to an RGBa color code." + "rgba(3,4,3,1)") + + +(def srm-color-map + "A map of integer values to their closest SRM value as an RGBa color code." + {1 srm-1 + 2 srm-2 + 3 srm-3 + 4 srm-4 + 5 srm-5 + 6 srm-6 + 7 srm-7 + 8 srm-8 + 9 srm-9 + 10 srm-10 + 11 srm-11 + 12 srm-12 + 13 srm-13 + 14 srm-14 + 15 srm-15 + 16 srm-16 + 17 srm-17 + 18 srm-18 + 19 srm-19 + 20 srm-20 + 21 srm-21 + 22 srm-22 + 23 srm-23 + 24 srm-24 + 25 srm-25 + 26 srm-26 + 27 srm-27 + 28 srm-28 + 29 srm-29 + 30 srm-30 + 31 srm-31 + 32 srm-32 + 33 srm-33 + 34 srm-34 + 35 srm-35 + 36 srm-36 + 37 srm-37 + 38 srm-38 + 39 srm-39 + 40 srm-40}) + + +(defn- lovibond->srm + "Convert the color described in degrees `lovibond` to the equivalent SRM color." + {:added "2.0"} + [degrees-lovibond] + (- (* degrees-lovibond 1.3546) 0.76)) + + +(defn- srm->ebc + "Convert the color described by the `srm` to the equivalent EBC color." + {:added "2.0"} + [srm-value] + (* srm-value 1.97)) + + +(defn- ebc->srm + "Convert the color described by the `ebc` to the equivalent SRM color." + {:added "2.0"} + [ebc-value] + (* ebc-value 0.508)) + + +(defn- srm->lovibond + "Convert the color described in 'srm` to the equivalent degrees Lovibond." + {:added "2.0"} + [srm-value] + (/ (+ srm-value 0.76) 1.3546)) + + +(defn- srm->rgba + "Given `srm-number`, return the closest, bounded applicable RGBA color string. + OPINIONATED: The provided `srm-number` will be bound to the common range from 1 to 40. + Decimal-like values are trimmed, not rounded." + {:added "2.0"} + [srm-number] + (let [srm-color (min (max (int srm-number) 1) 40)] + (get srm-color-map srm-color))) + + +(def measurement->srm + "A map from color systems to the implementation function that converts to SRM. + + Note: RGBa is not included because it is not a color system, but a color representation." + {options/ebc ebc->srm + options/lovibond lovibond->srm + options/srm identity}) + + +(def srm->measurement + "A map from color systems to the implementation function that converts from SRM" + {options/ebc srm->ebc + options/lovibond srm->lovibond + options/srm identity + options/rgba srm->rgba}) + + +(defn convert + "Given a `color-val` in `source-system`, convert it to the `target-system`. + + Supported values for `source-system` and `target-system` are enumerated in [[color-systems]]. + This function will throw an exception if unsupported measurement values are passed." + {:added "2.0"} + [color-val source-system target-system] + (if (and (contains? measurement->srm source-system) + (contains? measurements target-system) + (number? color-val)) + (if (= source-system target-system) + color-val + (let [source->srm-fn (measurement->srm source-system) + srm->target-fn (srm->measurement target-system)] + (-> color-val + source->srm-fn + srm->target-fn))) + (throw (ex-info "Unsupported color conversion units" + {:source-system source-system + :target-system target-system + :allowed-values (-> measurement->srm keys set) + :color color-val})))) + + +(defn display + "A function to render a human-readable `color` in `source-units`. + + For example, + ```clj + (display 1.5 :lovibond) ;; => \"1.5 °L\" + ``` + + This function accepts an option map as an optional third argument. The following options are available: + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"°L\"` for `\"Degrees Lovibond\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([color source-units] + (display color source-units {})) + ([color source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (= source-units :rgba) + color + (if (and (contains? measurements source-units) + (number? color) + (integer? precision) + (contains? options/supported-suffixes suffix)) + + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> color + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported color display options" + {:source-units source-units + :allowed-values measurements + :color color + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes})))))) diff --git a/src/brewtility/units/options.cljc b/src/brewtility/units/options.cljc new file mode 100644 index 0000000..b1f2676 --- /dev/null +++ b/src/brewtility/units/options.cljc @@ -0,0 +1,561 @@ +(ns brewtility.units.options + "A namespace of static symbolic keywords and values used in the option maps throughout brewtility. + + Implemented as part of the Symbolic Keyword pattern." + {:added "2.0"} + (:refer-clojure :exclude [second short time])) + + +;; Defaults + +(def default-precision + "The default precision to use in `->displayable` functions" + 3) + + +;; Systems of Measure + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} + + +(def system-of-measure + "The system of measure used in the recipe or for a given unit. + + Commonly used with `brewtility.units` and in argument/option maps. + Brewility supports the following systems of measure: + - [British imperial](https://en.wikipedia.org/wiki/Imperial_units) + - [Metric system](https://en.wikipedia.org/wiki/Metric_system) + - [United States Customary Units](https://en.wikipedia.org/wiki/United_States_customary_units) + - [International System of Units](https://en.wikipedia.org/wiki/International_System_of_Units)" + :system-of-measure) + + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} + + +(def imperial + "The [British imperial](https://en.wikipedia.org/wiki/Imperial_units) system of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :imperial) + + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} + + +(def metric + "The [metric system](https://en.wikipedia.org/wiki/Metric_system) of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :metric) + + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} + + +(def us-customary + "The [United States Customary Units](https://en.wikipedia.org/wiki/United_States_customary_units) system of measure. + + Commonly used with `brewtility.units` and in argument/option maps.." + :us) + + +#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} + + +(def international-system + "The [International System of Units](https://en.wikipedia.org/wiki/International_System_of_Units) system of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :si) + + +;; Enricher Setting Keys + + +(def precision + "The number of decimal places to which a value is rounded. + + Commonly used with `brewtility.units` and in argument/option maps." + :precision) + + +(def suffix + "The type of suffix to add on to displayed units of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Brewility supports the following suffix types: + - `:short` - A short suffix. (e.g. \"tsp\" instead of \"teaspoon\") + - `:full` - The full name as a suffix (e.g. \"teaspoon\")" + :suffix) + + +(def short + "A short suffix. + (e.g. \"tsp\" instead of \"teaspoon\") + + Commonly used with `brewtility.units` and in argument/option maps." + :short) + + +(def full + "The full name as a suffix. + (e.g. \"teaspoon\") + + Commonly used with `brewtility.units` and in argument/option maps." + :full) + + +(def supported-suffixes + "A set of supported suffix types." + #{short full}) + + +(def color + "The color systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.color` and in argument/option maps. + Brewility supports the following color systems: + - [Standard Reference Method](https://en.wikipedia.org/wiki/Standard_Reference_Method) + - [European Brewery Convention](https://en.wikipedia.org/wiki/European_Brewery_Convention) + - [Lovibond](https://en.wikipedia.org/wiki/Beer_measurement#Colour) + - [RGBA](https://en.wikipedia.org/wiki/RGBA_color_space)" + :color) + + +(def pressure + "The pressure systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.pressure` and in argument/option maps. + Currently, brewtility supports the following types of pressure: + - [pascal](https://en.wikipedia.org/wiki/Pascal_(unit)#Multiples_and_submultiples) + - [kilopascal](https://en.wikipedia.org/wiki/Pascal_(unit)#Multiples_and_submultiples) + - [bar](https://en.wikipedia.org/wiki/Bar_(unit)) + - [atmosphere](https://en.wikipedia.org/wiki/Atmosphere_(unit)) + - [torr](https://en.wikipedia.org/wiki/Torr) + - [psi](https://en.wikipedia.org/wiki/Pound-force_per_square_inch" + :pressure) + + +(def specific-gravity + "The specific gravity systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.specific-gravity` and in argument/option maps. + Currently, brewtility supports the following types of specific gravity: + Currently, brewtility supports the following types of specific gravity: + - [specific-gravity](https://en.wikipedia.org/wiki/Specific_gravity)" + :specific-gravity) + + +(def temperature + "The temperature systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.temperature` and in argument/option maps. + Currently, brewtility supports the following types of temperature: + - [celsius](https://en.wikipedia.org/wiki/Celsius) + - [fahrenheit](https://en.wikipedia.org/wiki/Fahrenheit) + - [kelvin](https://en.wikipedia.org/wiki/Kelvin)" + :temperature) + + +(def time + "The time systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.time` and in argument/option maps. + Currently, brewtility supports the following types of time measurements: + - [microsecond](https://en.wikipedia.org/wiki/Microsecond) + - [nanosecond](https://en.wikipedia.org/wiki/Nanosecond) + - [millisecond](https://en.wikipedia.org/wiki/Millisecond) + - [second](https://en.wikipedia.org/wiki/Second) + - [minute](https://en.wikipedia.org/wiki/Minute) + - [hour](https://en.wikipedia.org/wiki/Hour) + - [day](https://en.wikipedia.org/wiki/Day) + - [week](https://en.wikipedia.org/wiki/Week)" + :time) + + +(def volume + "The volume systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.volume` and in argument/option maps. + Currently, brewtility supports the following types of volume: + - [american-fluid-ounce](https://en.wikipedia.org/wiki/Fluid_ounce) + - [american-gallon](https://en.wikipedia.org/wiki/Gallon) + - [american-pint](https://en.wikipedia.org/wiki/Pint) + - [american-quart](https://en.wikipedia.org/wiki/Quart) + - [cup](https://en.wikipedia.org/wiki/Cup_(unit)) + - [imperial-fluid-ounce](https://en.wikipedia.org/wiki/Fluid_ounce) + - [imperial-gallon](https://en.wikipedia.org/wiki/Gallon) + - [imperial-pint](https://en.wikipedia.org/wiki/Pint) + - [imperial-quart](https://en.wikipedia.org/wiki/Quart) + - [liter](https://en.wikipedia.org/wiki/Litre) + - [litre](https://en.wikipedia.org/wiki/Litre) + - [milliliter](https://en.wikipedia.org/wiki/Millilitre) + - [millilitre](https://en.wikipedia.org/wiki/Millilitre) + - [tablespoon](https://en.wikipedia.org/wiki/Tablespoon) + - [teaspoon](https://en.wikipedia.org/wiki/Teaspoon))" + :volume) + + +(def weight + "The weight systems used in the recipe or for a given unit. + + Commonly used with `brewtility.units.weight` and in argument/option maps. + Currently, brewtility supports the following types of weight: + - [gram](https://en.wikipedia.org/wiki/Gram) + - [milligram](https://en.wikipedia.org/wiki/Milligram) + - [kilogram](https://en.wikipedia.org/wiki/Kilogram) + - [ounce](https://en.wikipedia.org/wiki/Ounce) + - [pound](https://en.wikipedia.org/wiki/Pound_(mass))" + :weight) + + +(def measurement-types + "The measurement types available across brewtility." + #{color + pressure + specific-gravity + temperature + time + volume + weight}) + + +;; Color Systems + +(def srm + "The [Standard Reference Method](https://en.wikipedia.org/wiki/Standard_Reference_Method) color system. + + Commonly used with `brewtility.units.color` and in argument/option maps." + :srm) + + +(def ebc + "The [European Brewery Convention](https://en.wikipedia.org/wiki/European_Brewery_Convention) color system. + + Commonly used with `brewtility.units.color` and in argument/option maps." + :ebc) + + +(def lovibond + "The [Lovibond](https://en.wikipedia.org/wiki/Beer_measurement#Colour) color system. + + Commonly used with `brewtility.units.color` and in argument/option maps." + :lovibond) + + +(def rgba + "The [RGBA](https://en.wikipedia.org/wiki/RGBA_color_space) color system. + + Commonly used with `brewtility.units.color` and in argument/option maps." + :rgba) + + +;; Volume Units + +(def american-fluid-ounce + "The [American fluid ounce](https://en.wikipedia.org/wiki/Fluid_ounce#American_fluid_ounce) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :american-fluid-ounce) + + +(def american-gallon + "The [American gallon](https://en.wikipedia.org/wiki/Gallon#Us_liquid_gallon) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :american-gallon) + + +(def american-pint + "The [American pint](https://en.wikipedia.org/wiki/Pint#US_liquid_pint) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :american-pint) + + +(def american-quart + "The [American quart](https://en.wikipedia.org/wiki/Quart#US_liquid_quart) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :american-quart) + + +(def cup + "The [cup](https://en.wikipedia.org/wiki/Cup_(unit)) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :cup) + + +(def imperial-fluid-ounce + "The [Imperial fluid ounce](https://en.wikipedia.org/wiki/Fluid_ounce#Imperial_fluid_ounce) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :imperial-fluid-ounce) + + +(def imperial-gallon + "The [Imperial gallon](https://en.wikipedia.org/wiki/Gallon#Imperial_gallon) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :imperial-gallon) + + +(def imperial-pint + "The [Imperial pint](https://en.wikipedia.org/wiki/Pint#Imperial_pint) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :imperial-pint) + + +(def imperial-quart + "The [Imperial quart](https://en.wikipedia.org/wiki/Quart#Imperial_quart) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :imperial-quart) + + +(def liter + "The [liter](https://en.wikipedia.org/wiki/Liter) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: Both `litre` and `liter` are valid keys throughout brewtility to support regional spelling." + :liter) + + +(def litre + "The [liter](https://en.wikipedia.org/wiki/Litre) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: Both `litre` and `liter` are valid keys throughout brewtility to support regional spelling." + :litre) + + +(def milliliter + "The [milliliter](https://en.wikipedia.org/wiki/Milliliter) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: Both `millilitre` and `milliliter` are valid keys throughout brewtility to support regional spelling." + :milliliter) + + +(def millilitre + "The [milliliter](https://en.wikipedia.org/wiki/Millilitre) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: Both `millilitre` and `milliliter` are valid keys throughout brewtility to support regional spelling." + :millilitre) + + +(def teaspoon + "The [teaspoon](https://en.wikipedia.org/wiki/Teaspoon) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :teaspoon) + + +(def tablespoon + "The [tablespoon](https://en.wikipedia.org/wiki/Tablespoon) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :tablespoon) + + +;; Weight/Mass Units + +(def gram + "The [gram](https://en.wikipedia.org/wiki/Gram) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :gram) + + +(def kilogram + "The [kilogram](https://en.wikipedia.org/wiki/Kilogram) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :kilogram) + + +(def milligram + "The [milligram](https://en.wikipedia.org/wiki/Milligram) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :milligram) + + +(def ounce + "The [ounce](https://en.wikipedia.org/wiki/Ounce#International_avoirdupois_ounce) unit of measure; specifically, the International avoirdupois ounce. + + Commonly used with `brewtility.units` and in argument/option maps." + :ounce) + + +(def pound + "The [pound](https://en.wikipedia.org/wiki/Pound_(mass)) unit of measure + + Commonly used with `brewtility.units` and in argument/option maps." + :pound) + + +;; Temperature Units + +(def celsius + "The [Celsius](https://en.wikipedia.org/wiki/Celsius) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `celsius`, `centigrade`, and `c` are all valid keys throughout brewtility to support multiple conventions." + :celsius) + + +(def c + "The [Celsius](https://en.wikipedia.org/wiki/Celsius) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `celsius`, `centigrade`, and `c` are all valid keys throughout brewtility to support multiple conventions." + :c) + + +(def centigrade + "The [Celsius](https://en.wikipedia.org/wiki/Celsius) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `celsius`, `centigrade`, and `c` are all valid keys throughout brewtility to support multiple conventions." + :centigrade) + + +(def fahrenheit + "The [Fahrenheit](https://en.wikipedia.org/wiki/Fahrenheit) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `fahrenheit` and `f` are all valid keys throughout brewtility to support multiple conventions." + :fahrenheit) + + +(def f + "The [Fahrenheit](https://en.wikipedia.org/wiki/Fahrenheit) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `fahrenheit` and `f` are all valid keys throughout brewtility to support multiple conventions." + :f) + + +(def kelvin + "The [Kelvin](https://en.wikipedia.org/wiki/Kelvin) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `kelvin` and `k` are all valid keys throughout brewtility to support multiple conventions." + :kelvin) + + +(def k + "The [Kelvin](https://en.wikipedia.org/wiki/Kelvin) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps. + Note: `kelvin` and `k` are all valid keys throughout brewtility to support multiple conventions." + :k) + + +;; Time Units + +(def day + "The [day](https://en.wikipedia.org/wiki/Day) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :day) + + +(def hour + "The [hour](https://en.wikipedia.org/wiki/Hour) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :hour) + + +(def microsecond + "The [microsecond](https://en.wikipedia.org/wiki/Microsecond) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :microsecond) + + +(def millisecond + "The [millisecond](https://en.wikipedia.org/wiki/Millisecond) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :millisecond) + + +(def minute + "The [minute](https://en.wikipedia.org/wiki/Minute) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :minute) + + +(def nanosecond + "The [nanosecond](https://en.wikipedia.org/wiki/Nanosecond) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :nanosecond) + + +(def second + "The [second](https://en.wikipedia.org/wiki/Second) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :second) + + +(def week + "The [week](https://en.wikipedia.org/wiki/Week) unit of measure. + + Commonly used with `brewtility.units` and in argument/option maps." + :week) + + +;; Pressure Units + +(def pascal + "The [Pascal](https://en.wikipedia.org/wiki/Pascal_(unit)) unit of measure. + + Commonly used with `brewtility.units.pressure` and in argument maps." + :pascal) + + +(def kilopascal + "The [Kilopascal](https://en.wikipedia.org/wiki/Kilopascal) unit of measure. + + Commonly used with `brewtility.units.pressure` and in argument maps." + :kilopascal) + + +(def bar + "The [Bar](https://en.wikipedia.org/wiki/Bar_(unit)) unit of measure. + + Commonly used with `brewtility.units.pressure` and in argument maps." + :bar) + + +(def atmosphere + "The [Atmosphere](https://en.wikipedia.org/wiki/Atmosphere_(unit)) unit of measure. + Specifically, this is a [standard atmosphere](https://en.wikipedia.org/wiki/Standard_atmosphere). + + Commonly used with `brewtility.units.pressure` and in argument maps." + :atmosphere) + + +(def torr + "The [Torr](https://en.wikipedia.org/wiki/Torr_(unit)) unit of measure. + + Commonly used with `brewtility.units.pressure` and in argument maps." + :torr) + + +(def psi + "The [Pounds per square inch](https://en.wikipedia.org/wiki/Pound-force_per_square_inch) unit of measure. + + Commonly used with `brewtility.units.pressure` and in argument maps." + :psi) + diff --git a/src/brewtility/units/pressure.cljc b/src/brewtility/units/pressure.cljc new file mode 100644 index 0000000..26efaba --- /dev/null +++ b/src/brewtility/units/pressure.cljc @@ -0,0 +1,197 @@ +(ns brewtility.units.pressure + "A namespace for converting between different units of pressure. + + In the BeerXML spec, pressure is measured in kilopascals (kPa). + This namespace provides a way to convert between kilopascals and other units. + + Currently, brewtility supports the following types of pressure: + - [pascal](https://en.wikipedia.org/wiki/Pascal_(unit)#Multiples_and_submultiples) + - [kilopascal](https://en.wikipedia.org/wiki/Pascal_(unit)#Multiples_and_submultiples) + - [bar](https://en.wikipedia.org/wiki/Bar_(unit)) + - [atmosphere](https://en.wikipedia.org/wiki/Atmosphere_(unit)) + - [torr](https://en.wikipedia.org/wiki/Torr) + - [psi](https://en.wikipedia.org/wiki/Pound-force_per_square_inch" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options])) + + +(def ^:const measurements + "The pressure measurements supported by brewtility." + #{options/pascal + options/kilopascal + options/bar + options/atmosphere + options/torr + options/psi}) + + +(def ^:const measurements->display-name + "A map of pressure measurements to their display names." + {options/pascal {options/full "pascal" + options/short "pa"} + options/kilopascal {options/full "kilopascals" + options/short "kpa"} + options/bar {options/full "bar" + options/short "bar"} + options/atmosphere {options/full "atmosphere" + options/short "atm"} + options/torr {options/full "torr" + options/short "torr"} + options/psi {options/full "pounds per square inch" + options/short "psi"}}) + + +(defn- pascal->kilopascal + "An implementation to convert `pressure` from pascal to kilopascal." + {:no-doc true + :added "2.0"} + [pressure] + (/ pressure 1000.0)) + + +(defn- bar->kilopascal + "An implementation to convert `pressure` from bar to kilopascal." + {:no-doc true + :added "2.0"} + [pressure] + (* pressure 100.0)) + + +(defn- atmosphere->kilopascal + "An implementation to convert `pressure` from atmosphere to kilopascal." + {:no-doc true + :added "2.0"} + [pressure] + (* pressure 101.325)) + + +(defn- torr->kilopascal + "An implementation to convert `pressure` from torr to kilopascal." + {:no-doc true + :added "2.0"} + [pressure] + (* pressure 0.133322)) + + +(defn- psi->kilopascal + "An implementation to convert `pressure` from psi to kilopascal." + {:no-doc true + :added "2.0"} + [pressure] + (* pressure 6.894757)) + + +(defn- kilopascal->pascal + "An implementation to convert `pressure` from kilopascal to pascal." + {:no-doc true + :added "2.0"} + [pressure] + (* pressure 1000.0)) + + +(defn- kilopascal->bar + "An implementation to convert `pressure` from kilopascal to bar." + {:no-doc true + :added "2.0"} + [pressure] + (/ pressure 100.0)) + + +(defn- kilopascal->atmosphere + "An implementation to convert `pressure` from kilopascal to atmosphere." + {:no-doc true + :added "2.0"} + [pressure] + (/ pressure 101.325)) + + +(defn- kilopascal->torr + "An implementation to convert `pressure` from kilopascal to torr." + {:no-doc true + :added "2.0"} + [pressure] + (/ pressure 0.133322)) + + +(defn- kilopascal->psi + "An implementation to convert `pressure` from kilopascal to psi." + {:no-doc true + :added "2.0"} + [pressure] + (/ pressure 6.894757)) + + +(def ^:const measurement->kilopascal + "A map of pressure measurements to functions that convert to kilopascals." + {options/pascal pascal->kilopascal + options/kilopascal identity + options/bar bar->kilopascal + options/atmosphere atmosphere->kilopascal + options/torr torr->kilopascal + options/psi psi->kilopascal}) + + +(def ^:const kilopascal->measurement + "A map of pressure measurements to functions that convert from kilopascals." + {options/pascal kilopascal->pascal + options/kilopascal identity + options/bar kilopascal->bar + options/atmosphere kilopascal->atmosphere + options/torr kilopascal->torr + options/psi kilopascal->psi}) + + +(defn convert + "Given a `pressure` in `source-measurement`, convert it to the `target-measurement`. + Supported values for `source-measurement` and `target-measurement` are enumerated in [[pressure-measurements]]. + This function will throw an exception if unsupported measurement values are passed." + {:changed "2.0"} + [pressure source-measurement target-measurement] + (if (and (contains? measurements source-measurement) + (contains? measurements target-measurement) + (number? pressure)) + (if (= source-measurement target-measurement) + pressure + (let [source->kilopascal-fn (measurement->kilopascal source-measurement) + kilopascal->target-fn (kilopascal->measurement target-measurement)] + (-> pressure source->kilopascal-fn kilopascal->target-fn))) + (throw (ex-info "Unsupported pressure conversion units" + {:source-measurement source-measurement + :target-measurement target-measurement + :allowed-values measurements + :pressure pressure})))) + + +(defn display + "A function to render a human-readable `pressure` in `source-units`. + For example, + ```clj + (display 1.5 :pascal) ;; => \"1.5 pa\" + ``` + This function accepts an option map as an optional third argument. The following options are available: + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([pressure source-units] + (display pressure source-units {})) + ([pressure source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (and (contains? measurements source-units) + (number? pressure) + (integer? precision) + (contains? options/supported-suffixes suffix)) + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> pressure + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported pressure display options" + {:source-units source-units + :allowed-values measurements + :pressure pressure + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes}))))) diff --git a/src/brewtility/units/specific_gravity.cljc b/src/brewtility/units/specific_gravity.cljc new file mode 100644 index 0000000..808c4af --- /dev/null +++ b/src/brewtility/units/specific_gravity.cljc @@ -0,0 +1,89 @@ +(ns brewtility.units.specific-gravity + "A namespace for converting between different units of specific gravity. + + In the BeerXML spec, specific gravity is measured in relative to the weight of the same size sample of water. + This namespace converts between that ratio and other units. + + Currently, brewtility supports the following types of specific gravity: + - [specific-gravity](https://en.wikipedia.org/wiki/Specific_gravity)" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options])) + + +(def ^:const measurements + "The specific gravity systems available across brewtility." + #{options/specific-gravity}) + + +(def ^:const measurements->display-name + "A map from specific gravity system names to their full and short unit names." + {options/specific-gravity {options/full "specific gravity" + options/short "sg"}}) + + +(def ^:const measurement->specific-gravity + "A map from specific gravity system names to the conversion function to specific gravity." + {options/specific-gravity identity}) + + +(def ^:const specific-gravity->measurement + "A map from specific gravity system names to the conversion function from specific gravity." + {options/specific-gravity identity}) + + +(defn convert + "Given a `specific-gravity` in `source-measurement`, convert it to the `target-measurement`. + Supported values for `source-measurement` and `target-measurement` are enumerated in [[specific-gravity-measurements]]. + This function will throw an exception if unsupported measurement values are passed." + {:changed "2.0"} + [gravity source-measurement target-measurement] + (if (and (contains? measurements source-measurement) + (contains? measurements target-measurement) + (number? gravity)) + (if (= source-measurement target-measurement) + gravity + (let [source->specific-gravity-fn (measurement->specific-gravity source-measurement) + specific-gravity->target-fn (specific-gravity->measurement target-measurement)] + (-> gravity source->specific-gravity-fn specific-gravity->target-fn))) + (throw (ex-info "Unsupported specific-gravity conversion units" + {:source-measurement source-measurement + :target-measurement target-measurement + :allowed-values measurements + :specific-gravity gravity})))) + + +(defn display + "A function to render a human-readable `specific-gravity` in `source-units`. + + For example, + ```clj + (display 1.5 :specific-gravity) ;; => \"1.5 sg\" + ``` + + This function accepts an option map as an optional third argument. The following options are available: + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([gravity source-units] + (display gravity source-units {})) + ([gravity source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (and (contains? measurements source-units) + (number? gravity) + (integer? precision) + (contains? options/supported-suffixes suffix)) + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> gravity + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported specific-gravity display options" + {:source-units source-units + :allowed-values measurements + :specific-gravity gravity + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes}))))) diff --git a/src/brewtility/units/temperature.cljc b/src/brewtility/units/temperature.cljc new file mode 100644 index 0000000..b7d3a9a --- /dev/null +++ b/src/brewtility/units/temperature.cljc @@ -0,0 +1,206 @@ +(ns brewtility.units.temperature + "A namespace for converting between different units of temperature + + In the BeerXML spec, temperature is measured in degrees Celsius. + This namespace converts between that and other units. + + Currently, brewtility supports the following types of temperature measurements: + - [celsius](https://en.wikipedia.org/wiki/Celsius) + - [fahrenheit](https://en.wikipedia.org/wiki/Fahrenheit) + - [kelvin](https://en.wikipedia.org/wiki/Kelvin_(unit))" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options])) + + +(def ^:const measurements + "The temperature measurements supported by brewtility." + #{options/c + options/celsius + options/centigrade + options/f + options/fahrenheit + options/k + options/kelvin}) + + +(def ^:const measurements->display-name + "The temperature measurements supported by brewtility." + {options/c {options/full "celsius" + options/short "c"} + options/celsius {options/full "celsius" + options/short "c"} + options/centigrade {options/full "centigrade" + options/short "mg"} + options/f {options/full "fahrenheit" + options/short "f"} + options/fahrenheit {options/full "fahrenheit" + options/short "f"} + options/k {options/full "kelvin" + options/short "k"} + options/kelvin {options/full "kelvin" + options/short "k"}}) + + +(defn- celsius->fahrenheit + "An implementation function to convert `temp` from celsius to fahrenheit" + {:no-doc true + :added "2.0"} + [temp] + (+ 32 (* 1.8 temp))) + + +(defn- celsius->kelvin + "An implementation function to convert `temp` from celsius to kelvin" + {:no-doc true + :added "2.0"} + [temp] + (+ temp 273.15)) + + +(defn- fahrenheit->celsius + "An implementation function to convert `temp` from fahrenheit to celsius" + {:no-doc true + :added "2.0"} + [temp] + (/ (* (- temp 32) 5) 9.0)) + + +(defn- kelvin->celsius + "An implementation function to convert `temp` from kelvin to celsius" + {:no-doc true + :added "2.0"} + [temp] + (- temp 273.15)) + + +(def ^:const measurement->celsius + "A map from measurement names to the implementation function which converts them to degrees celsius" + {options/c identity + options/celsius identity + options/centigrade identity + options/f fahrenheit->celsius + options/fahrenheit fahrenheit->celsius + options/k kelvin->celsius + options/kelvin kelvin->celsius}) + + +(def ^:const celsius->measurement + "A map from measurement names to the implementation function which converts them from degrees celsius" + {options/celsius identity + options/c identity + options/centigrade identity + options/fahrenheit celsius->fahrenheit + options/f celsius->fahrenheit + options/kelvin celsius->kelvin + options/k celsius->kelvin}) + + +(defn- is-celsius? + "A function to determine if a temperature measurement is in celsius." + {:no-doc true + :added "2.0"} + [measurement] + (boolean (or (= measurement options/c) + (= measurement options/celsius)))) + + +(defn- is-fahrenheit? + "A function to determine if a temperature measurement is in fahrenheit." + {:no-doc true + :added "2.0"} + [measurement] + (boolean (or (= measurement options/f) + (= measurement options/fahrenheit)))) + + +(defn- is-kelvin? + "A function to determine if a temperature measurement is in kelvin." + {:no-doc true + :added "2.0"} + [measurement] + (boolean (or (= measurement options/k) + (= measurement options/kelvin)))) + + +(defn- is-centigrade? + "A function to determine if a temperature measurement is in centigrade." + {:no-doc true + :added "2.0"} + [measurement] + (= measurement options/centigrade)) + + +(defn- same-measurement? + "A function to determine if two temperature measurements systems are the same." + {:no-doc true + :added "2.0"} + [measurement1 measurement2] + (boolean (or (and (is-celsius? measurement1) + (is-celsius? measurement2)) + (and (is-fahrenheit? measurement1) + (is-fahrenheit? measurement2)) + (and (is-kelvin? measurement1) + (is-kelvin? measurement2)) + (and (is-centigrade? measurement1) + (is-centigrade? measurement2))))) + + +(defn convert + "Given a `temperature` in `source-measurement`, convert it to the `target-measurement`. + Supported values for `source-measurement` and `target-measurement` are enumerated in [[temperature-measurements]]. + + This function will throw an exception if unsupported measurement values are passed." + {:changed "2.0"} + [temperature source-measurement target-measurement] + (if (and (contains? measurements source-measurement) + (contains? measurements target-measurement) + (number? temperature)) + (if (same-measurement? source-measurement target-measurement) + temperature + (let [source->celsius-fn (measurement->celsius source-measurement) + celsius->target-fn (celsius->measurement target-measurement)] + (-> temperature source->celsius-fn celsius->target-fn))) + (throw (ex-info "Unsupported temperature conversion units" + {:source-measurement source-measurement + :target-measurement target-measurement + :allowed-values measurements + :temperature temperature})))) + + +(defn display + "A function to render a human-readable `temperature` in `source-units`. + + For example, + + ```clj + (display 1.5 :celsius) ;; => \"1.5 c\" + ``` + + This function accepts an option map as an optional third argument. The following options are available: + + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([temperature source-units] + (display temperature source-units {})) + ([temperature source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (and (contains? measurements source-units) + (number? temperature) + (integer? precision) + (contains? options/supported-suffixes suffix)) + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> temperature + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported temperature display options" + {:source-units source-units + :allowed-values measurements + :temperature temperature + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes}))))) diff --git a/src/brewtility/units/time.cljc b/src/brewtility/units/time.cljc new file mode 100644 index 0000000..3b4bcee --- /dev/null +++ b/src/brewtility/units/time.cljc @@ -0,0 +1,131 @@ +(ns brewtility.units.time + "A namespace for converting between different units of time. + + In the BeerXML spec, temperature is measured in minutes. + This namespace converts between that and other units. + + Currently, brewtility supports the following types of time measurements: + - [microsecond](https://en.wikipedia.org/wiki/Microsecond) + - [nanosecond](https://en.wikipedia.org/wiki/Nanosecond) + - [millisecond](https://en.wikipedia.org/wiki/Millisecond) + - [second](https://en.wikipedia.org/wiki/Second) + - [minute](https://en.wikipedia.org/wiki/Minute) + - [hour](https://en.wikipedia.org/wiki/Hour) + - [day](https://en.wikipedia.org/wiki/Day) + - [week](https://en.wikipedia.org/wiki/Week)" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options]) + (:refer-clojure :exclude [time])) + + +(def ^:const measurements + "The time measurements available across brewtility" + #{options/day + options/hour + options/microsecond + options/millisecond + options/minute + options/nanosecond + options/second + options/week}) + + +(def ^:const measurement->minute + "A map from measurement names to doubles representing their fractional value to one minute" + {options/day 1440.0 + options/hour 60.0 + options/microsecond (/ 1.0 60000000) + options/millisecond (/ 1.0 60000) + options/minute 1.0 + options/nanosecond (/ 1.0 60000000000) + options/second (/ 1.0 60) + options/week 10080.0}) + + +(def ^:const measurements->display-name + "A map from measurement names to their full name and abbreviation" + {options/day {options/full "days" + options/short "d"} + options/hour {options/full "hour" + options/short "hr"} + options/microsecond {options/full "microsecond" + options/short "µs"} + options/millisecond {options/full "millisecond" + options/short "ms"} + options/minute {options/full "minute" + options/short "m"} + options/nanosecond {options/full "nanosecond" + options/short "ns"} + options/second {options/full "second" + options/short "s"} + options/week {options/full "week" + options/short "w"}}) + + +(defn- minute->measurement + "An implementation function to convert minutes to another unit of time" + {:no-doc true + :added "2.0"} + [measurement-name] + (/ 1.0 (get measurement->minute measurement-name))) + + +(defn convert + "Given a `time-val` in `source-measurement`, convert it to the `target-measurement`. + Supported values for `source-measurement` and `target-measurement` are enumerated in [[time-measurements]]. + + This function will throw an exception if unsupported measurement values are passed." + {:added "2.0"} + [time-val source-measurement target-measurement] + (if (and (contains? measurements source-measurement) + (contains? measurements target-measurement) + (number? time-val)) + (if (= source-measurement target-measurement) + time-val + (let [source->litre-multiplier (measurement->minute source-measurement) + litre->target-multiplier (minute->measurement target-measurement)] + (* time-val source->litre-multiplier litre->target-multiplier))) + (throw (ex-info "Unsupported time conversion units" + {:source-measurement source-measurement + :target-measurement target-measurement + :allowed-values measurements + :time time-val})))) + + +(defn display + "A function to render a human-readable `time` in `source-units`. + + For example, + + ```clj + (display 1.5 :second) ;; => \"1.5 s\" + ``` + + This function accepts an option map as an optional third argument. The following options are available: + + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([time source-units] + (display time source-units {})) + ([time source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (and (contains? measurements source-units) + (number? time) + (integer? precision) + (contains? options/supported-suffixes suffix)) + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> time + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported time display units" + {:source-units source-units + :allowed-values measurements + :time time + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes}))))) diff --git a/src/brewtility/units/volume.cljc b/src/brewtility/units/volume.cljc new file mode 100644 index 0000000..0c009a6 --- /dev/null +++ b/src/brewtility/units/volume.cljc @@ -0,0 +1,195 @@ +(ns brewtility.units.volume + "A namespace for converting between different units of volume. + + In the BeerXML spec, volume is measured in liters. + This namespace provides a way to convert between liters and other units. + + Currently, brewtility supports the following types of volume: + - [american-fluid-ounce](https://en.wikipedia.org/wiki/Fluid_ounce) + - [american-gallon](https://en.wikipedia.org/wiki/Gallon) + - [american-pint](https://en.wikipedia.org/wiki/Pint) + - [american-quart](https://en.wikipedia.org/wiki/Quart) + - [cup](https://en.wikipedia.org/wiki/Cup_(unit)) + - [imperial-fluid-ounce](https://en.wikipedia.org/wiki/Fluid_ounce) + - [imperial-gallon](https://en.wikipedia.org/wiki/Gallon) + - [imperial-pint](https://en.wikipedia.org/wiki/Pint) + - [imperial-quart](https://en.wikipedia.org/wiki/Quart) + - [liter](https://en.wikipedia.org/wiki/Litre) + - [litre](https://en.wikipedia.org/wiki/Litre) + - [milliliter](https://en.wikipedia.org/wiki/Millilitre) + - [millilitre](https://en.wikipedia.org/wiki/Millilitre) + - [tablespoon](https://en.wikipedia.org/wiki/Tablespoon) + - [teaspoon](https://en.wikipedia.org/wiki/Teaspoon))" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options])) + + +(def ^:const measurements + "The volume measurements available across brewtility" + #{options/american-gallon + options/american-pint + options/american-quart + options/cup + options/imperial-gallon + options/imperial-pint + options/imperial-quart + options/liter + options/litre + options/milliliter + options/millilitre + options/tablespoon + options/teaspoon + options/american-fluid-ounce + options/imperial-fluid-ounce}) + + +(def ^:const measurement->litre + "A map from measurement names to doubles representing their fractional value to one liter" + {options/american-fluid-ounce 0.0295735 + options/american-gallon 3.78541 + options/american-pint 0.473176 + options/american-quart 0.946353 + options/cup 0.236588 + options/imperial-fluid-ounce 0.0284131 + options/imperial-gallon 4.54609 + options/imperial-pint 0.568261 + options/imperial-quart 1.13652 + options/liter 1.0 + options/litre 1.0 + options/milliliter 0.001 + options/millilitre 0.001 + options/tablespoon 0.0147868 + options/teaspoon 0.00492892}) + + +(def ^:const measurements->display-name + "A map from measurement names to their full name and abbreviation" + {options/american-fluid-ounce {options/full "fluid ounce" + options/short "fl oz"} + options/american-gallon {options/full "US gallon" + options/short "gal"} + options/american-pint {options/full "american pint" + options/short "pt"} + options/american-quart {options/full "american quart" + options/short "qt"} + options/cup {options/full "cup" + options/short "c"} + options/imperial-fluid-ounce {options/full "imperial fluid ounce" + options/short "imp fl oz"} + options/imperial-gallon {options/full "imperial gallon" + options/short "gal"} + options/imperial-pint {options/full "imperial pint" + options/short "pt"} + options/imperial-quart {options/full "imperial quart" + options/short "qt"} + options/liter {options/full "liter" + options/short "l"} + options/litre {options/full "litre" + options/short "l"} + options/milliliter {options/full "milliliter" + options/short "ml"} + options/millilitre {options/full "millilitre" + options/short "ml"} + options/tablespoon {options/full "tablespoon" + options/short "tbsp"} + options/teaspoon {options/full "teaspoon" + options/short "tsp"}}) + + +(defn- liters? + "An implementation function to determine if a measurement is in liters." + {:no-doc true + :added "2.0"} + [measurement-name] + (boolean (or (= measurement-name options/liter) + (= measurement-name options/litre)))) + + +(defn- milliliters? + "An implementation function to determine if a measurement is in milliliters." + {:no-doc true + :added "2.0"} + [measurement-name] + (boolean (or (= measurement-name options/milliliter) + (= measurement-name options/millilitre)))) + + +(defn- same-measurement? + "An implementation function to determine if two measurement systems are the same." + {:no-doc true + :added "2.0"} + [measurement-name1 measurement-name2] + (boolean (or (= measurement-name1 measurement-name2) + (and (liters? measurement-name1) + (liters? measurement-name2)) + (and (milliliters? measurement-name1) + (milliliters? measurement-name2))))) + + +(defn- liter->measurement + "An implementation function to convert liters to another unit of volume" + {:no-doc true + :added "2.0"} + [measurement-name] + (/ 1.0 (get measurement->litre measurement-name))) + + +(defn convert + "Given a `volume` in `source-measurement`, convert it to the `target-measurement`. + Supported values for `source-measurement` and `target-measurement` are enumerated in [[measurements]]. + + This function will throw an exception if unsupported measurement values are passed." + {:changed "2.0"} + [volume source-measurement target-measurement] + (if (and (contains? measurements source-measurement) + (contains? measurements target-measurement) + (number? volume)) + (if (same-measurement? source-measurement target-measurement) + volume + (let [source->litre-multiplier (measurement->litre source-measurement) + litre->target-multiplier (liter->measurement target-measurement)] + (* volume source->litre-multiplier litre->target-multiplier))) + (throw (ex-info "Unsupported volume conversion units" + {:source-measurement source-measurement + :target-measurement target-measurement + :allowed-values measurements + :volume volume})))) + + +(defn display + "A function to render a human-readable `volume` in `source-units`. + + For example, + + ```clj + (display 1.5 :litre) ;; => \"1.5 l\" + ``` + + This function accepts an option map as an optional third argument. The following options are available: + + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([volume source-units] + (display volume source-units {})) + ([volume source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (and (contains? measurements source-units) + (number? volume) + (integer? precision) + (contains? options/supported-suffixes suffix)) + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> volume + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported volume display units" + {:source-units source-units + :allowed-values measurements + :volume volume + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes}))))) diff --git a/src/brewtility/units/weight.cljc b/src/brewtility/units/weight.cljc new file mode 100644 index 0000000..eb09fe8 --- /dev/null +++ b/src/brewtility/units/weight.cljc @@ -0,0 +1,115 @@ +(ns brewtility.units.weight + "A namespace for converting between different units of weight. + + By the BeerXML spec, weight is measured in kilograms. + This namespace provides a way to convert between kilograms and other units of weight. + + Currently, brewtility supports the following types of weight: + - [gram](https://en.wikipedia.org/wiki/Gram) + - [milligram](https://en.wikipedia.org/wiki/Milligram) + - [kilogram](https://en.wikipedia.org/wiki/Kilogram) + - [ounce](https://en.wikipedia.org/wiki/Ounce) + - [pound](https://en.wikipedia.org/wiki/Pound_(mass))" + {:added "2.0"} + (:require [brewtility.precision :as precision] + [brewtility.units.options :as options])) + + +(def ^:const measurements + "The weight measurements available across brewtility" + #{options/gram + options/kilogram + options/milligram + options/ounce + options/pound}) + + +(def ^:const measurement->kilogram + "A map from measurement names to doubles representing their fractional value to one kilogram" + {options/gram 0.001 + options/kilogram 1.0 + options/milligram 0.000001 + options/ounce 0.02834952 + options/pound 0.45359237}) + + +(def ^:const measurements->display-name + "A map from measurement names to their full name and abbreviation" + {options/gram {options/full "gram" + options/short "g"} + options/kilogram {options/full "kilogram" + options/short "kg"} + options/milligram {options/full "milligram" + options/short "mg"} + options/ounce {options/full "ounce" + options/short "oz"} + options/pound {options/full "pound" + options/short "lb"}}) + + +(defn- kilogram->measurement + "An implementation function to convert kilograms to another unit of weight." + {:no-doc true + :added "2.0"} + [measurement-name] + (/ 1.0 (get measurement->kilogram measurement-name))) + + +(defn convert + "Given a `weight` in `source-measurement`, convert it to the `target-measurement`. + Supported values for `source-measurement` and `target-measurement` are enumerated in [[weight-measurements]]. + + This function will throw an exception if unsupported measurement values are passed." + {:added "2.0"} + [weight source-measurement target-measurement] + (if (and (contains? measurements source-measurement) + (contains? measurements target-measurement) + (number? weight)) + (if (= source-measurement target-measurement) + weight + (let [source->kilogram-multiplier (measurement->kilogram source-measurement) + kilogram->target-multiplier (kilogram->measurement target-measurement)] + (* weight source->kilogram-multiplier kilogram->target-multiplier))) + (throw (ex-info "Unsupported weight conversion units" + {:source-measurement source-measurement + :target-measurement target-measurement + :allowed-values measurements + :weight weight})))) + + +(defn display + "A function to render a human-readable `weight` in `source-units`. + + For example, + + ```clj + (display 1.5 :pound) ;; => \"1.5 lb\" + ``` + + This function accepts an option map as an optional third argument. The following options are available: + + - `precision`: The number of decimal places to round to. Defaults to 3. + - `suffix`: The suffix type to append to the displayable fields. Defaults to `:short`. Acceptable values are: + - `:short`: A customary abbreviation for the selected unit. For example, `\"gal\"` for `\" US gallons\"`. + - `:full`: The full name of the selected unit. For example, `\"teaspoon\"` for `\"teaspoon\"`." + {:added "2.0"} + ([weight source-units] + (display weight source-units {})) + ([weight source-units {:keys [precision suffix] + :or {precision options/default-precision + suffix options/short}}] + (if (and (contains? measurements source-units) + (number? weight) + (integer? precision) + (contains? options/supported-suffixes suffix)) + (let [display-name (get-in measurements->display-name [source-units suffix])] + (-> weight + (precision/->precision precision) + (str " " display-name))) + (throw (ex-info "Unsupported weight display options" + {:source-units source-units + :allowed-values measurements + :weight weight + :precision precision + :suffix suffix + :allowed-suffixes options/supported-suffixes}))))) diff --git a/test/brewtility/calculations_test.cljc b/test/brewtility/calculations_test.cljc index 94fb4c9..7b3a7a6 100644 --- a/test/brewtility/calculations_test.cljc +++ b/test/brewtility/calculations_test.cljc @@ -1,9 +1,9 @@ (ns brewtility.calculations-test - (:require #? (:clj [clojure.test :refer [deftest is testing]]) - #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) [brewtility.calculations :as sut] - [brewtility.color :as color] [brewtility.precision :as bp] + [brewtility.units.color :as color] [common-beer-data.fermentables.adjuncts :as adjuncts] [common-beer-data.fermentables.grains :as grains] [common-beer-data.hops.both :as hops])) diff --git a/test/brewtility/color_test.cljc b/test/brewtility/color_test.cljc deleted file mode 100644 index c4f405b..0000000 --- a/test/brewtility/color_test.cljc +++ /dev/null @@ -1,42 +0,0 @@ -(ns brewtility.color-test - (:require #? (:clj [clojure.test :refer [deftest is testing]]) - #? (:cljs [cljs.test :refer-macros [deftest is testing]]) - [brewtility.color :as sut] - [brewtility.precision :as bp])) - - -(deftest srm->rgba-test - (testing "SRM color lookup behaves as expected" - (is (= sut/srm-13 (sut/srm->rgba 13))) - (is (= sut/srm-1 (sut/srm->rgba 0))) - (is (= sut/srm-1 (sut/srm->rgba -1))) - (is (= sut/srm-6 (sut/srm->rgba 6.2))) - (is (= sut/srm-6 (sut/srm->rgba 6.8))) - (is (= sut/srm-40 (sut/srm->rgba 41))))) - - -(deftest lovibond->rgba-test - (testing "lovibond -> SRM -> rgba lookup behaves as expected" - (is (= sut/srm-13 (sut/lovibond->rgba 10.16))) - (is (= sut/srm-1 (sut/lovibond->rgba 0.56))) - (is (= sut/srm-1 (sut/lovibond->rgba -0.18))) - (is (= sut/srm-6 (sut/lovibond->rgba 5.14))) - (is (= sut/srm-6 (sut/lovibond->rgba 5.58))) - (is (= sut/srm-40 (sut/lovibond->rgba 30.83))))) - - -(deftest ebc->rgba-test - (testing "EBC -> SRM -> rgba lookup behaves as expected" - (is (= sut/srm-13 (sut/ebc->rgba 25.61))) - (is (= sut/srm-1 (sut/ebc->rgba 0))) - (is (= sut/srm-1 (sut/ebc->rgba -1.97))) - (is (= sut/srm-6 (sut/ebc->rgba 12.21))) - (is (= sut/srm-6 (sut/ebc->rgba 13.40))) - (is (= sut/srm-40 (sut/ebc->rgba 80.77))))) - - -(deftest conversion-test - (testing "Ensure various color unit conversions behave as expected" - (is (= 10.0 (bp/->2dp (sut/lovibond->srm 7.94)) (bp/->2dp (sut/ebc->srm 19.69)))) - (is (= 23.2 (bp/->2dp (sut/srm->lovibond 30.66)) (bp/->2dp (sut/ebc->lovibond 60.38)))) - (is (= -45.53 (bp/->2dp (sut/srm->ebc -23.11)) (bp/->2dp (sut/lovibond->ebc -16.5)))))) diff --git a/test/brewtility/data/equipment.cljc b/test/brewtility/data/equipment.cljc index e9941ef..76b7864 100644 --- a/test/brewtility/data/equipment.cljc +++ b/test/brewtility/data/equipment.cljc @@ -8,7 +8,7 @@ #? (:cljs [cljs.test :refer-macros [deftest is testing]]))) -(def ^:const sample-equipment +(def sample-equipment "A hard-coded sample equipment for static unit tests" {:batch-size 19.9 :boil-size 26.2 @@ -28,7 +28,7 @@ :version 1}) -(def ^:const sample-equipment-wrapper +(def sample-equipment-wrapper "A hard-coded sample equipment-wrapper for static unit tests" {:equipment sample-equipment}) diff --git a/test/brewtility/data/fermentables.cljc b/test/brewtility/data/fermentables.cljc index ca23319..544030e 100644 --- a/test/brewtility/data/fermentables.cljc +++ b/test/brewtility/data/fermentables.cljc @@ -8,7 +8,7 @@ #? (:cljs [cljs.test :refer-macros [deftest is testing]]))) -(def ^:const sample-fermentable +(def sample-fermentable "A hard-coded sample fermentable for static unit tests" {:amount 0.45 :coarse-fine-diff 1.5 @@ -26,17 +26,17 @@ :yield 78.0}) -(def ^:const sample-fermentable-wrapper +(def sample-fermentable-wrapper "A hard-coded sample fermentable-wrapper for static unit tests" {:fermentable sample-fermentable}) -(def ^:const sample-fermentables +(def sample-fermentables "A hard-coded sample fermentables for static unit tests" [sample-fermentable-wrapper]) -(def ^:const sample-fermentables-wrapper +(def sample-fermentables-wrapper "A hard-coded sample fermentables-wrapper for static unit tests" {:fermentables sample-fermentables}) diff --git a/test/brewtility/data/hops.cljc b/test/brewtility/data/hops.cljc index e7b781e..0ed0a19 100644 --- a/test/brewtility/data/hops.cljc +++ b/test/brewtility/data/hops.cljc @@ -41,7 +41,7 @@ (is (spoon.spec/test-valid? ::hops.format/form (random-hop-form))))) -(def ^:const sample-hop +(def sample-hop "A hard-coded sample hop for static unit tests" {:alpha 5.0 :amount 0.0638 @@ -52,17 +52,17 @@ :version 1}) -(def ^:const sample-hop-wrapper +(def sample-hop-wrapper "A hard-coded sample hop-wrapper for static unit tests" {:hop sample-hop}) -(def ^:const sample-hops +(def sample-hops "A hard-coded sample hops for static unit tests" [sample-hop-wrapper]) -(def ^:const sample-hops-wrapper +(def sample-hops-wrapper "A hard-coded sample hops-wrapper for static unit tests" {:hops sample-hops}) diff --git a/test/brewtility/data/mash.cljc b/test/brewtility/data/mash.cljc index 8a688a7..99f3234 100644 --- a/test/brewtility/data/mash.cljc +++ b/test/brewtility/data/mash.cljc @@ -8,7 +8,7 @@ #? (:cljs [cljs.test :refer-macros [deftest is testing]]))) -(def ^:const sample-mash-step +(def sample-mash-step "A hard-coded sample mash step for static unit tests" {:infuse-amount 10.0 :name "Conversion Step, 68C" @@ -18,17 +18,17 @@ :version 1}) -(def ^:const sample-mash-step-wrapper +(def sample-mash-step-wrapper "A hard-coded sample mash-step-wrapper for static unit tests" {:mash-step sample-mash-step}) -(def ^:const sample-mash-steps +(def sample-mash-steps "A hard-coded sample mash-steps for static unit tests" [sample-mash-step-wrapper]) -(def ^:const sample-mash +(def sample-mash "A hard-coded sample mash for static unit tests" {:name "Single Step Infusion, 68 C" :version 1 @@ -36,7 +36,7 @@ :mash-steps sample-mash-steps}) -(def ^:const sample-mash-wrapper +(def sample-mash-wrapper "A hard-coded sample mash-wrapper for static unit tests" {:mash sample-mash}) diff --git a/test/brewtility/data/miscs.cljc b/test/brewtility/data/miscs.cljc index d81c9a2..b40f46f 100644 --- a/test/brewtility/data/miscs.cljc +++ b/test/brewtility/data/miscs.cljc @@ -8,7 +8,7 @@ #? (:cljs [cljs.test :refer-macros [deftest is testing]]))) -(def ^:const sample-misc +(def sample-misc "A hard-coded sample misc for static unit tests" {:amount 0.010 :name "Irish Moss" @@ -19,17 +19,17 @@ :version 1}) -(def ^:const sample-misc-wrapper +(def sample-misc-wrapper "A hard-coded sample misc-wrapper for static unit tests" {:misc sample-misc}) -(def ^:const sample-miscs +(def sample-miscs "A hard-coded sample miscs for static unit tests" [sample-misc-wrapper]) -(def ^:const sample-miscs-wrapper +(def sample-miscs-wrapper "A hard-coded sample miscs-wrapper for static unit tests" {:miscs sample-miscs}) diff --git a/test/brewtility/data/recipes.cljc b/test/brewtility/data/recipes.cljc index 5fd8b06..d7f6fe5 100644 --- a/test/brewtility/data/recipes.cljc +++ b/test/brewtility/data/recipes.cljc @@ -33,7 +33,7 @@ (is (spoon.spec/test-valid? ::recipes.format/ibu-method (random-ibu-method))))) -(def ^:const sample-recipe +(def sample-recipe "A hard-coded sample recipe for static unit tests" (merge {:age 24.0 :age-temp 17.0 @@ -63,17 +63,17 @@ yeasts/sample-yeasts-wrapper)) -(def ^:const sample-recipe-wrapper +(def sample-recipe-wrapper "A hard-coded sample recipe-wrapper for static unit tests" {:recipe sample-recipe}) -(def ^:const sample-recipes +(def sample-recipes "A hard-coded sample recipes for static unit tests" [sample-recipe-wrapper]) -(def ^:const sample-recipes-wrapper +(def sample-recipes-wrapper "A hard-coded sample recipes-wrapper for static unit tests" {:recipes sample-recipes}) diff --git a/test/brewtility/data/styles.cljc b/test/brewtility/data/styles.cljc index debc1a4..41c744b 100644 --- a/test/brewtility/data/styles.cljc +++ b/test/brewtility/data/styles.cljc @@ -8,7 +8,7 @@ #? (:cljs [cljs.test :refer-macros [deftest is testing]]))) -(def ^:const sample-style +(def sample-style "A hard-coded sample style for static unit tests" {:abv-max 5.5 :abv-min 3.2 @@ -32,17 +32,17 @@ :version 1}) -(def ^:const sample-style-wrapper +(def sample-style-wrapper "A hard-coded sample style-wrapper for static unit tests" {:style sample-style}) -(def ^:const sample-styles +(def sample-styles "A hard-coded sample styles for static unit tests" [sample-style-wrapper]) -(def ^:const sample-styles-wrapper +(def sample-styles-wrapper "A hard-coded sample styles-wrapper for static unit tests" {:styles sample-styles}) diff --git a/test/brewtility/data/waters.cljc b/test/brewtility/data/waters.cljc index 16c8834..19b7144 100644 --- a/test/brewtility/data/waters.cljc +++ b/test/brewtility/data/waters.cljc @@ -21,7 +21,7 @@ (is (spoon.spec/test-valid? ::waters.format/ph (random-ph))))) -(def ^:const sample-water +(def sample-water "A hard-coded sample water for static unit tests" {:amount 20.0 :bicarbonate 300.0 @@ -36,17 +36,17 @@ :version 1}) -(def ^:const sample-water-wrapper +(def sample-water-wrapper "A hard-coded sample water-wrapper for static unit tests" {:water sample-water}) -(def ^:const sample-waters +(def sample-waters "A hard-coded sample waters for static unit tests" [sample-water-wrapper]) -(def ^:const sample-waters-wrapper +(def sample-waters-wrapper "A hard-coded sample waters-wrapper for static unit tests" {:waters sample-waters}) diff --git a/test/brewtility/data/yeasts.cljc b/test/brewtility/data/yeasts.cljc index 73fee94..1095176 100644 --- a/test/brewtility/data/yeasts.cljc +++ b/test/brewtility/data/yeasts.cljc @@ -25,7 +25,7 @@ (is (spoon.spec/test-valid? ::yeasts.format/flocculation (random-flocculation))))) -(def ^:const sample-yeast +(def sample-yeast "A hard-coded sample yeast for static unit tests" {:amount 0.250 :attenuation 73.0 @@ -42,17 +42,17 @@ :version 1}) -(def ^:const sample-yeast-wrapper +(def sample-yeast-wrapper "A hard-coded sample yeast-wrapper for static unit tests" {:yeast sample-yeast}) -(def ^:const sample-yeasts +(def sample-yeasts "A hard-coded sample yeasts for static unit tests" [sample-yeast-wrapper]) -(def ^:const sample-yeasts-wrapper +(def sample-yeasts-wrapper "A hard-coded sample yeasts-wrapper for static unit tests" {:yeasts sample-yeasts}) diff --git a/test/brewtility/precision_test.cljc b/test/brewtility/precision_test.cljc index d9010ff..e0146b3 100644 --- a/test/brewtility/precision_test.cljc +++ b/test/brewtility/precision_test.cljc @@ -1,6 +1,6 @@ (ns brewtility.precision-test - (:require #? (:clj [clojure.test :refer [deftest is testing]]) - #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) [brewtility.precision :as sut])) @@ -12,27 +12,32 @@ (deftest ->1dp-test - (testing "Ensure that rounding to 1 decimal point works" + (testing "Ensure that rounding to 1 decimal point works." (is (= 0.0 (sut/->1dp 0.0))) (is (= 0.0 (sut/->1dp 0.001))) (is (= 5.1 (sut/->1dp 5.05))) + (is (= 5.0 (sut/->1dp 5.04))) (is (= -12.3 (sut/->1dp -12.3))))) (deftest ->2dp-test - (testing "Ensure that rounding to 2 decimal point works" + (testing "Ensure that rounding to 2 decimal point works." (is (= 0.00 (sut/->2dp 0.0))) (is (= 0.00 (sut/->2dp 0.001))) (is (= 5.05 (sut/->2dp 5.0513))) + (is (= 5.06 (sut/->2dp 5.0553))) (is (= -12.30 (sut/->2dp -12.3))) (is (= 100.01 (sut/->2dp 100.005))))) (deftest ->3dp-test - (testing "Ensure that rounding to 3 decimal point works" + (testing "Ensure that rounding to 3 decimal point works." (is (= 0.000 (sut/->3dp 0.0))) (is (= 0.001 (sut/->3dp 0.001))) (is (= 5.050 (sut/->3dp 5.05))) + (is (= 5.055 (sut/->3dp 5.055))) + (is (= 5.055 (sut/->3dp 5.0554))) + (is (= 5.056 (sut/->3dp 5.0555))) (is (= -12.300 (sut/->3dp -12.3))) (is (= 100.005 (sut/->3dp 100.005))) (is (= 404.001 (sut/->3dp 404.0009654))))) diff --git a/test/brewtility/runner.cljs b/test/brewtility/runner.cljs index ce2e9f5..d4e6855 100644 --- a/test/brewtility/runner.cljs +++ b/test/brewtility/runner.cljs @@ -1,6 +1,9 @@ (ns brewtility.runner + "The ClojureScript test runner for brewtility. + + This namespace is responsible for running all of the ClojureScript tests. + To add new test namespaces, add them to the `:require` and `doo-tests` clauses below." (:require [brewtility.calculations-test] - [brewtility.color-test] [brewtility.data.equipment] [brewtility.data.fermentables] [brewtility.data.hops] @@ -22,12 +25,18 @@ [brewtility.predicates.waters-test] [brewtility.predicates.yeasts-test] [brewtility.units-test] + [brewtility.units.color-test] + [brewtility.units.pressure-test] + [brewtility.units.specific-gravity-test] + [brewtility.units.temperature-test] + [brewtility.units.time-test] + [brewtility.units.volume-test] + [brewtility.units.weight-test] [brewtility.wrapping-test] [doo.runner :refer-macros [doo-tests]])) (doo-tests 'brewtility.calculations-test - 'brewtility.color-test 'brewtility.data.equipment 'brewtility.data.fermentables 'brewtility.data.hops @@ -49,4 +58,11 @@ 'brewtility.predicates.waters-test 'brewtility.predicates.yeasts-test 'brewtility.units-test + 'brewtility.units.color-test + 'brewtility.units.pressure-test + 'brewtility.units.specific-gravity-test + 'brewtility.units.temperature-test + 'brewtility.units.time-test + 'brewtility.units.volume-test + 'brewtility.units.weight-test 'brewtility.wrapping-test) diff --git a/test/brewtility/units/color_test.cljc b/test/brewtility/units/color_test.cljc new file mode 100644 index 0000000..de39d4e --- /dev/null +++ b/test/brewtility/units/color_test.cljc @@ -0,0 +1,147 @@ +(ns brewtility.units.color-test + (:require [brewtility.precision :as precision] + [brewtility.units.color :as sut] + [brewtility.units.options :as options] + #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]))) + + +(deftest srm->rgba-test + (testing "SRM color lookup behaves as expected" + (is (= sut/srm-1 (sut/convert 1 options/srm options/rgba))) + (is (= sut/srm-1 (sut/convert 1 :srm :rgba))) + (is (= sut/srm-2 (sut/convert 2 options/srm options/rgba))) + (is (= sut/srm-3 (sut/convert 3 options/srm options/rgba))) + (is (= sut/srm-4 (sut/convert 4 options/srm options/rgba))) + (is (= sut/srm-5 (sut/convert 5 options/srm options/rgba))) + (is (= sut/srm-6 (sut/convert 6 options/srm options/rgba))) + (is (= sut/srm-7 (sut/convert 7 options/srm options/rgba))) + (is (= sut/srm-8 (sut/convert 8 options/srm options/rgba))) + (is (= sut/srm-9 (sut/convert 9 options/srm options/rgba))) + (is (= sut/srm-10 (sut/convert 10 options/srm options/rgba))) + (is (= sut/srm-11 (sut/convert 11 options/srm options/rgba))) + (is (= sut/srm-12 (sut/convert 12 options/srm options/rgba))) + (is (= sut/srm-13 (sut/convert 13 options/srm options/rgba))) + (is (= sut/srm-14 (sut/convert 14 options/srm options/rgba))) + (is (= sut/srm-15 (sut/convert 15 options/srm options/rgba))) + (is (= sut/srm-16 (sut/convert 16 options/srm options/rgba))) + (is (= sut/srm-17 (sut/convert 17 options/srm options/rgba))) + (is (= sut/srm-18 (sut/convert 18 options/srm options/rgba))) + (is (= sut/srm-19 (sut/convert 19 options/srm options/rgba))) + (is (= sut/srm-20 (sut/convert 20 options/srm options/rgba))) + (is (= sut/srm-21 (sut/convert 21 options/srm options/rgba))) + (is (= sut/srm-22 (sut/convert 22 options/srm options/rgba))) + (is (= sut/srm-23 (sut/convert 23 options/srm options/rgba))) + (is (= sut/srm-24 (sut/convert 24 options/srm options/rgba))) + (is (= sut/srm-25 (sut/convert 25 options/srm options/rgba))) + (is (= sut/srm-26 (sut/convert 26 options/srm options/rgba))) + (is (= sut/srm-27 (sut/convert 27 options/srm options/rgba))) + (is (= sut/srm-28 (sut/convert 28 options/srm options/rgba))) + (is (= sut/srm-29 (sut/convert 29 options/srm options/rgba))) + (is (= sut/srm-30 (sut/convert 30 options/srm options/rgba))) + (is (= sut/srm-31 (sut/convert 31 options/srm options/rgba))) + (is (= sut/srm-32 (sut/convert 32 options/srm options/rgba))) + (is (= sut/srm-33 (sut/convert 33 options/srm options/rgba))) + (is (= sut/srm-34 (sut/convert 34 options/srm options/rgba))) + (is (= sut/srm-35 (sut/convert 35 options/srm options/rgba))) + (is (= sut/srm-36 (sut/convert 36 options/srm options/rgba))) + (is (= sut/srm-37 (sut/convert 37 options/srm options/rgba))) + (is (= sut/srm-38 (sut/convert 38 options/srm options/rgba))) + (is (= sut/srm-39 (sut/convert 39 options/srm options/rgba))) + (is (= sut/srm-40 (sut/convert 40 options/srm options/rgba))) + (is (= sut/srm-1 (sut/convert 0 options/srm options/rgba))) + (is (= sut/srm-1 (sut/convert -1 options/srm options/rgba))) + (is (= sut/srm-6 (sut/convert 6.2 options/srm options/rgba))) + (is (= sut/srm-6 (sut/convert 6.8 options/srm options/rgba))) + (is (= sut/srm-40 (sut/convert 41 options/srm options/rgba))))) + + +(deftest lovibond->rgba-test + (testing "lovibond -> SRM -> rgba lookup behaves as expected" + (is (= sut/srm-13 (sut/convert 10.16 options/lovibond options/rgba))) + (is (= sut/srm-13 (sut/convert 10.16 :lovibond :rgba))) + (is (= sut/srm-1 (sut/convert 0.56 options/lovibond options/rgba))) + (is (= sut/srm-1 (sut/convert -0.18 options/lovibond options/rgba))) + (is (= sut/srm-6 (sut/convert 5.14 options/lovibond options/rgba))) + (is (= sut/srm-6 (sut/convert 5.58 options/lovibond options/rgba))) + (is (= sut/srm-40 (sut/convert 30.83 options/lovibond options/rgba))))) + + +(deftest ebc->rgba-test + (testing "EBC -> SRM -> rgba lookup behaves as expected" + (is (= sut/srm-13 (sut/convert 25.61 options/ebc options/rgba))) + (is (= sut/srm-13 (sut/convert 25.61 :ebc :rgba))) + (is (= sut/srm-1 (sut/convert 0 options/ebc options/rgba))) + (is (= sut/srm-1 (sut/convert -1.97 options/ebc options/rgba))) + (is (= sut/srm-6 (sut/convert 12.21 options/ebc options/rgba))) + (is (= sut/srm-6 (sut/convert 13.40 options/ebc options/rgba))) + (is (= sut/srm-40 (sut/convert 80.77 options/ebc options/rgba))))) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (is (= 10.0 + (precision/->2dp (sut/convert 7.94 options/lovibond options/srm)) + (precision/->2dp (sut/convert 19.69 options/ebc options/srm)))) + (is (= 10.0 + (precision/->2dp (sut/convert 7.94 :lovibond :srm)) + (precision/->2dp (sut/convert 19.69 :ebc :srm)))) + (is (= 23.2 + (precision/->2dp (sut/convert 30.66 options/srm options/lovibond)) + (precision/->2dp (sut/convert 60.38 options/ebc options/lovibond)))) + (is (= -45.53 + (precision/->2dp (sut/convert -23.11 options/srm options/ebc)) + (precision/->2dp (sut/convert -16.5 options/lovibond options/ebc))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :srm)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :srm)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "10.1 srm" + (sut/display 10.1 options/srm) + (sut/display 10.1 :srm))) + (is (= "23.2 ebc" + (sut/display 23.2 options/ebc) + (sut/display 23.2 :ebc))) + (is (= "10.1 degrees lovibond" + (sut/display 10.1 options/lovibond {options/suffix options/full}) + (sut/display 10.1 :lovibond {options/suffix options/full}))) + (is (= sut/srm-37 (sut/display sut/srm-37 options/rgba)))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "SRM color map" + (is (map? sut/srm-color-map)) + (is (not-empty sut/srm-color-map)) + (is (every? number? (keys sut/srm-color-map))) + (is (every? string? (vals sut/srm-color-map)))) + (testing "measurement->srm" + (is (map? sut/measurement->srm)) + (is (not-empty sut/measurement->srm)) + (is (every? keyword? (keys sut/measurement->srm))) + (is (every? ifn? (vals sut/measurement->srm)))) + (testing "srm->measurement" + (is (map? sut/srm->measurement)) + (is (not-empty sut/srm->measurement)) + (is (every? keyword? (keys sut/srm->measurement))) + (is (= (sort (keys sut/srm->measurement)) + (sort (keys sut/measurements->display-name)) + (sort sut/measurements))) + (is (every? ifn? (vals sut/srm->measurement)))))) diff --git a/test/brewtility/units/pressure_test.cljc b/test/brewtility/units/pressure_test.cljc new file mode 100644 index 0000000..9b37e67 --- /dev/null +++ b/test/brewtility/units/pressure_test.cljc @@ -0,0 +1,67 @@ +(ns brewtility.units.pressure-test + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units.options :as options] + [brewtility.units.pressure :as sut])) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/pascal options/pascal)) + (precision/->2dp (sut/convert 100.0 :pascal :pascal)))) + (is (= 100000.0 (precision/->2dp (sut/convert 100.0 options/kilopascal options/pascal)))) + (is (= 1.0 (precision/->2dp (sut/convert 100.0 options/kilopascal options/bar)))) + (is (= 0.99 (precision/->2dp (sut/convert 100.0 options/kilopascal options/atmosphere)))) + (is (= 750.06 (precision/->2dp (sut/convert 100.0 options/kilopascal options/torr)))) + (is (= 14.5 (precision/->2dp (sut/convert 100.0 options/kilopascal options/psi)))) + (is (= 0.1 (precision/->2dp (sut/convert 100.0 options/pascal options/kilopascal)))) + (is (= 10000.0 (precision/->2dp (sut/convert 100.0 options/bar options/kilopascal)))) + (is (= 10132.5 (precision/->2dp (sut/convert 100.0 options/atmosphere options/kilopascal)))) + (is (= 13.33 (precision/->2dp (sut/convert 100.0 options/torr options/kilopascal)))) + (is (= 689.48 (precision/->2dp (sut/convert 100.0 options/psi options/kilopascal))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "1.5 pa" + (sut/display 1.5 :pascal) + (sut/display 1.5 options/pascal))) + (is (= "1.5 pascal" + (sut/display 1.5 :pascal {options/suffix options/full})))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "measurement->kilopascal" + (is (map? sut/measurement->kilopascal)) + (is (not-empty sut/measurement->kilopascal)) + (is (every? keyword? (keys sut/measurement->kilopascal))) + (is (every? ifn? (vals sut/measurement->kilopascal)))) + (testing "kilopascal->measurement" + (is (map? sut/kilopascal->measurement)) + (is (not-empty sut/kilopascal->measurement)) + (is (every? keyword? (keys sut/kilopascal->measurement))) + (is (= (set (keys sut/measurement->kilopascal)) + (set (keys sut/kilopascal->measurement)) + (set (keys sut/measurements->display-name)) + sut/measurements)) + (is (every? ifn? (vals sut/kilopascal->measurement)))))) diff --git a/test/brewtility/units/specific_gravity_test.cljc b/test/brewtility/units/specific_gravity_test.cljc new file mode 100644 index 0000000..f386473 --- /dev/null +++ b/test/brewtility/units/specific_gravity_test.cljc @@ -0,0 +1,55 @@ +(ns brewtility.units.specific-gravity-test + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units.options :as options] + [brewtility.units.specific-gravity :as sut])) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/specific-gravity options/specific-gravity)) + (precision/->2dp (sut/convert 100.0 :specific-gravity :specific-gravity))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "1.5 sg" + (sut/display 1.5 :specific-gravity) + (sut/display 1.5 options/specific-gravity)))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "measurement->specific-gravity" + (is (map? sut/measurement->specific-gravity)) + (is (not-empty sut/measurement->specific-gravity)) + (is (every? keyword? (keys sut/measurement->specific-gravity))) + (is (every? ifn? (vals sut/measurement->specific-gravity)))) + (testing "specific-gravity->measurement" + (is (map? sut/specific-gravity->measurement)) + (is (not-empty sut/specific-gravity->measurement)) + (is (every? keyword? (keys sut/specific-gravity->measurement))) + (is (= (set (keys sut/measurement->specific-gravity)) + (set (keys sut/specific-gravity->measurement)) + (set (keys sut/measurements->display-name)) + sut/measurements)) + (is (every? ifn? (vals sut/specific-gravity->measurement)))))) diff --git a/test/brewtility/units/temperature_test.cljc b/test/brewtility/units/temperature_test.cljc new file mode 100644 index 0000000..3633b8f --- /dev/null +++ b/test/brewtility/units/temperature_test.cljc @@ -0,0 +1,103 @@ +(ns brewtility.units.temperature-test + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units.options :as options] + [brewtility.units.temperature :as sut])) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "measurement->celsius" + (is (map? sut/measurement->celsius)) + (is (not-empty sut/measurement->celsius)) + (is (every? keyword? (keys sut/measurement->celsius))) + (is (every? ifn? (vals sut/measurement->celsius)))) + (testing "celsius->measurement" + (is (map? sut/celsius->measurement)) + (is (not-empty sut/celsius->measurement)) + (is (every? keyword? (keys sut/celsius->measurement))) + (is (= (set (keys sut/measurement->celsius)) + (set (keys sut/celsius->measurement)) + (set (keys sut/measurements->display-name)) + sut/measurements)) + (is (every? ifn? (vals sut/celsius->measurement)))))) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (testing "Source and target systems are identical" + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/c options/c)) + (precision/->2dp (sut/convert 100.0 :celsius :c)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/f options/fahrenheit)) + (precision/->2dp (sut/convert 100.0 :f :f)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/k options/kelvin)) + (precision/->2dp (sut/convert 100.0 :k :k)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/centigrade options/centigrade)) + (precision/->2dp (sut/convert 100.0 :centigrade :centigrade))))) + (testing "Source and target systems differ" + (is (= 212.0 (precision/->2dp (sut/convert 100 options/celsius options/fahrenheit)))) + (is (= 363.15 (precision/->2dp (sut/convert 90 options/celsius options/kelvin)))) + (is (= 100.0 (precision/->2dp (sut/convert 100.0 options/celsius options/centigrade)))) + (is (= 32.22 (precision/->2dp (sut/convert 90 options/fahrenheit options/celsius)))) + (is (= 305.37 (precision/->2dp (sut/convert 90 options/fahrenheit options/kelvin)))) + (is (= 32.22 (precision/->2dp (sut/convert 90 options/fahrenheit options/centigrade)))) + (is (= -173.15 (precision/->2dp (sut/convert 100 options/kelvin options/celsius)))) + (is (= -297.67 (precision/->2dp (sut/convert 90 options/kelvin options/fahrenheit)))) + (is (= -183.15 (precision/->2dp (sut/convert 90 options/kelvin options/centigrade)))) + (is (= 100.0 (precision/->2dp (sut/convert 100 options/centigrade options/celsius)))) + (is (= 212.0 (precision/->2dp (sut/convert 100 options/centigrade options/fahrenheit)))) + (is (= 373.15 (precision/->2dp (sut/convert 100 options/centigrade options/kelvin)))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "1.5 c" + (sut/display 1.5 :celsius) + (sut/display 1.5 options/celsius) + (sut/display 1.5 options/c))) + (is (= "1.5 k" + (sut/display 1.5 :kelvin) + (sut/display 1.5 options/kelvin) + (sut/display 1.5 options/k))) + (is (= "1.5 f" + (sut/display 1.5 :fahrenheit) + (sut/display 1.5 options/fahrenheit) + (sut/display 1.5 options/f))) + (is (= "1.5 mg" + (sut/display 1.5 options/centigrade))) + (is (= "1.5 celsius" + (sut/display 1.5 :celsius {options/suffix options/full}) + (sut/display 1.5 options/celsius {options/suffix options/full}) + (sut/display 1.5 options/c {options/suffix options/full}))) + (is (= "1.5 kelvin" + (sut/display 1.5 :kelvin {options/suffix options/full}) + (sut/display 1.5 options/kelvin {options/suffix options/full}) + (sut/display 1.5 options/k {options/suffix options/full}))) + (is (= "1.5 fahrenheit" + (sut/display 1.5 :fahrenheit {options/suffix options/full}) + (sut/display 1.5 options/fahrenheit {options/suffix options/full}) + (sut/display 1.5 options/f {options/suffix options/full}))) + (is (= "1.5 centigrade" + (sut/display 1.5 options/centigrade {options/suffix options/full})))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) diff --git a/test/brewtility/units/time_test.cljc b/test/brewtility/units/time_test.cljc new file mode 100644 index 0000000..6306d3b --- /dev/null +++ b/test/brewtility/units/time_test.cljc @@ -0,0 +1,108 @@ +(ns brewtility.units.time-test + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units.options :as options] + [brewtility.units.time :as sut])) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "measurement->minute" + (is (map? sut/measurement->minute)) + (is (not-empty sut/measurement->minute)) + (is (every? keyword? (keys sut/measurement->minute))) + (is (every? number? (vals sut/measurement->minute)))) + (is (= (set (keys sut/measurement->minute)) + (set (keys sut/measurements->display-name)) + sut/measurements)))) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/minute options/minute)) + (precision/->2dp (sut/convert 100.0 :minute :minute)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/nanosecond options/nanosecond)) + (precision/->2dp (sut/convert 100.0 :nanosecond :nanosecond)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/second options/second)) + (precision/->2dp (sut/convert 100.0 :second :second)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/millisecond options/millisecond)) + (precision/->2dp (sut/convert 100.0 :millisecond :millisecond)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/microsecond options/microsecond)) + (precision/->2dp (sut/convert 100.0 :microsecond :microsecond)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/day options/day)) + (precision/->2dp (sut/convert 100.0 :day :day)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/hour options/hour)) + (precision/->2dp (sut/convert 100.0 :hour :hour)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/week options/week)) + (precision/->2dp (sut/convert 100.0 :week :week)))) + (is (= 6000.0 (precision/->2dp (sut/convert 100.0 options/minute options/second)))) + (is (= 6000000.0 (precision/->2dp (sut/convert 100.0 options/minute options/millisecond)))) + (is (= 60000.0 (precision/->2dp (sut/convert 0.001 options/minute options/microsecond)))) + (is (= 600000.0 (precision/->2dp (sut/convert 0.00001 options/minute options/nanosecond)))) + (is (= 0.07 (precision/->2dp (sut/convert 100.0 options/minute options/day)))) + (is (= 1.67 (precision/->2dp (sut/convert 100.0 options/minute options/hour)))) + (is (= 0.01 (precision/->2dp (sut/convert 100.0 options/minute options/week)))) + (is (= 1.67 (precision/->2dp (sut/convert 100.0 options/second options/minute)))) + (is (= 0.0 (precision/->2dp (sut/convert 100.0 options/millisecond options/minute)))) + (is (= 0.0 (precision/->2dp (sut/convert 100.0 options/microsecond options/minute)))) + (is (= 0.0 (precision/->2dp (sut/convert 100.0 options/nanosecond options/minute)))) + (is (= 144000.0 (precision/->2dp (sut/convert 100.0 options/day options/minute)))) + (is (= 6000.0 (precision/->2dp (sut/convert 100.0 options/hour options/minute)))) + (is (= 1008000.0 (precision/->2dp (sut/convert 100.0 options/week options/minute))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "1.5 m" + (sut/display 1.5 :minute) + (sut/display 1.5 options/minute))) + (is (= "1.5 d" + (sut/display 1.5 :day) + (sut/display 1.5 options/day))) + (is (= "1.5 hr" + (sut/display 1.5 :hour) + (sut/display 1.5 options/hour))) + (is (= "1.5 w" + (sut/display 1.5 :week) + (sut/display 1.5 options/week))) + (is (= "1.5 ns" + (sut/display 1.5 :nanosecond) + (sut/display 1.5 options/nanosecond))) + (is (= "1.5 ms" + (sut/display 1.5 :millisecond) + (sut/display 1.5 options/millisecond))) + (is (= "1.5 µs" + (sut/display 1.5 :microsecond) + (sut/display 1.5 options/microsecond))) + (is (= "1.5 s" + (sut/display 1.5 :second) + (sut/display 1.5 options/second))) + (is (= "1.5 minute" + (sut/display 1.5 :minute {options/suffix options/full}) + (sut/display 1.5 options/minute {options/suffix options/full})))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) diff --git a/test/brewtility/units/volume_test.cljc b/test/brewtility/units/volume_test.cljc new file mode 100644 index 0000000..753f7a8 --- /dev/null +++ b/test/brewtility/units/volume_test.cljc @@ -0,0 +1,176 @@ +(ns brewtility.units.volume-test + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units.options :as options] + [brewtility.units.volume :as sut])) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "measurement->litre" + (is (map? sut/measurement->litre)) + (is (not-empty sut/measurement->litre)) + (is (every? keyword? (keys sut/measurement->litre))) + (is (every? number? (vals sut/measurement->litre)))) + (is (= (set (keys sut/measurement->litre)) + (set (keys sut/measurements->display-name)) + sut/measurements)))) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/liter options/litre)) + (precision/->2dp (sut/convert 100.0 :litre :liter)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/milliliter options/millilitre)) + (precision/->2dp (sut/convert 100.0 :milliliter :milliliter)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/american-fluid-ounce options/american-fluid-ounce)) + (precision/->2dp (sut/convert 100.0 :american-fluid-ounce :american-fluid-ounce)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/american-gallon options/american-gallon)) + (precision/->2dp (sut/convert 100.0 :american-gallon :american-gallon)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/american-pint options/american-pint)) + (precision/->2dp (sut/convert 100.0 :american-pint :american-pint)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/american-quart options/american-quart)) + (precision/->2dp (sut/convert 100.0 :american-quart :american-quart)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/cup options/cup)) + (precision/->2dp (sut/convert 100.0 :cup :cup)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/imperial-fluid-ounce options/imperial-fluid-ounce)) + (precision/->2dp (sut/convert 100.0 :imperial-fluid-ounce :imperial-fluid-ounce)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/imperial-gallon options/imperial-gallon)) + (precision/->2dp (sut/convert 100.0 :imperial-gallon :imperial-gallon)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/imperial-pint options/imperial-pint)) + (precision/->2dp (sut/convert 100.0 :imperial-pint :imperial-pint)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/imperial-quart options/imperial-quart)) + (precision/->2dp (sut/convert 100.0 :imperial-quart :imperial-quart)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/tablespoon options/tablespoon)) + (precision/->2dp (sut/convert 100.0 :tablespoon :tablespoon)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/teaspoon options/teaspoon)) + (precision/->2dp (sut/convert 100.0 :teaspoon :teaspoon)))) + (is (= 3381.41 (precision/->2dp (sut/convert 100.0 options/liter options/american-fluid-ounce)))) + (is (= 26.42 (precision/->2dp (sut/convert 100.0 options/liter options/american-gallon)))) + (is (= 211.34 (precision/->2dp (sut/convert 100.0 options/liter options/american-pint)))) + (is (= 105.67 (precision/->2dp (sut/convert 100.0 options/liter options/american-quart)))) + (is (= 422.68 (precision/->2dp (sut/convert 100.0 options/liter options/cup)))) + (is (= 3519.5 (precision/->2dp (sut/convert 100.0 options/liter options/imperial-fluid-ounce)))) + (is (= 22.0 (precision/->2dp (sut/convert 100.0 options/liter options/imperial-gallon)))) + (is (= 175.98 (precision/->2dp (sut/convert 100.0 options/liter options/imperial-pint)))) + (is (= 87.99 (precision/->2dp (sut/convert 100.0 options/liter options/imperial-quart)))) + (is (= 6762.79 (precision/->2dp (sut/convert 100.0 options/liter options/tablespoon)))) + (is (= 20288.42 (precision/->2dp (sut/convert 100.0 options/liter options/teaspoon)))) + (is (= 2.96 (precision/->2dp (sut/convert 100.0 options/american-fluid-ounce options/liter)))) + (is (= 378.54 (precision/->2dp (sut/convert 100.0 options/american-gallon options/liter)))) + (is (= 47.32 (precision/->2dp (sut/convert 100.0 options/american-pint options/liter)))) + (is (= 94.64 (precision/->2dp (sut/convert 100.0 options/american-quart options/liter)))) + (is (= 23.66 (precision/->2dp (sut/convert 100.0 options/cup options/liter)))) + (is (= 2.84 (precision/->2dp (sut/convert 100.0 options/imperial-fluid-ounce options/liter)))) + (is (= 454.61 (precision/->2dp (sut/convert 100.0 options/imperial-gallon options/liter)))) + (is (= 56.83 (precision/->2dp (sut/convert 100.0 options/imperial-pint options/liter)))) + (is (= 113.65 (precision/->2dp (sut/convert 100.0 options/imperial-quart options/liter)))) + (is (= 1.48 (precision/->2dp (sut/convert 100.0 options/tablespoon options/liter)))) + (is (= 0.49 (precision/->2dp (sut/convert 100.0 options/teaspoon options/liter))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "1.5 l" + (sut/display 1.5 :liter) + (sut/display 1.5 options/litre))) + (is (= "1.5 liter" + (sut/display 1.5 :liter {options/suffix options/full}) + (sut/display 1.5 options/liter {options/suffix options/full}))) + (is (= "1.5 fl oz" + (sut/display 1.5 :american-fluid-ounce) + (sut/display 1.5 options/american-fluid-ounce))) + (is (= "1.5 fluid ounce" + (sut/display 1.5 :american-fluid-ounce {options/suffix options/full}) + (sut/display 1.5 options/american-fluid-ounce {options/suffix options/full}))) + (is (= "1.5 gal" + (sut/display 1.5 :american-gallon) + (sut/display 1.5 options/american-gallon))) + (is (= "1.5 US gallon" + (sut/display 1.5 :american-gallon {options/suffix options/full}) + (sut/display 1.5 options/american-gallon {options/suffix options/full}))) + (is (= "1.5 pt" + (sut/display 1.5 :american-pint) + (sut/display 1.5 options/american-pint))) + (is (= "1.5 american pint" + (sut/display 1.5 :american-pint {options/suffix options/full}) + (sut/display 1.5 options/american-pint {options/suffix options/full}))) + (is (= "1.5 qt" + (sut/display 1.5 :american-quart) + (sut/display 1.5 options/american-quart))) + (is (= "1.5 american quart" + (sut/display 1.5 :american-quart {options/suffix options/full}) + (sut/display 1.5 options/american-quart {options/suffix options/full}))) + (is (= "1.5 c" + (sut/display 1.5 :cup) + (sut/display 1.5 options/cup))) + (is (= "1.5 cup" + (sut/display 1.5 :cup {options/suffix options/full}) + (sut/display 1.5 options/cup {options/suffix options/full}))) + (is (= "1.5 imp fl oz" + (sut/display 1.5 :imperial-fluid-ounce) + (sut/display 1.5 options/imperial-fluid-ounce))) + (is (= "1.5 imperial fluid ounce" + (sut/display 1.5 :imperial-fluid-ounce {options/suffix options/full}) + (sut/display 1.5 options/imperial-fluid-ounce {options/suffix options/full}))) + (is (= "1.5 gal" + (sut/display 1.5 :imperial-gallon) + (sut/display 1.5 options/imperial-gallon))) + (is (= "1.5 imperial gallon" + (sut/display 1.5 :imperial-gallon {options/suffix options/full}) + (sut/display 1.5 options/imperial-gallon {options/suffix options/full}))) + (is (= "1.5 pt" + (sut/display 1.5 :imperial-pint) + (sut/display 1.5 options/imperial-pint))) + (is (= "1.5 imperial pint" + (sut/display 1.5 :imperial-pint {options/suffix options/full}) + (sut/display 1.5 options/imperial-pint {options/suffix options/full}))) + (is (= "1.5 qt" + (sut/display 1.5 :imperial-quart) + (sut/display 1.5 options/imperial-quart))) + (is (= "1.5 imperial quart" + (sut/display 1.5 :imperial-quart {options/suffix options/full}) + (sut/display 1.5 options/imperial-quart {options/suffix options/full}))) + (is (= "1.5 tbsp" + (sut/display 1.5 :tablespoon) + (sut/display 1.5 options/tablespoon))) + (is (= "1.5 tablespoon" + (sut/display 1.5 :tablespoon {options/suffix options/full}) + (sut/display 1.5 options/tablespoon {options/suffix options/full}))) + (is (= "1.5 tsp" + (sut/display 1.5 :teaspoon) + (sut/display 1.5 options/teaspoon))) + (is (= "1.5 teaspoon" + (sut/display 1.5 :teaspoon {options/suffix options/full}) + (sut/display 1.5 options/teaspoon {options/suffix options/full})))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) diff --git a/test/brewtility/units/weight_test.cljc b/test/brewtility/units/weight_test.cljc new file mode 100644 index 0000000..72dbae3 --- /dev/null +++ b/test/brewtility/units/weight_test.cljc @@ -0,0 +1,96 @@ +(ns brewtility.units.weight-test + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units.options :as options] + [brewtility.units.weight :as sut])) + + +(deftest code-type-tests + (testing "Ensure maps used for options are structurally correct" + (testing "Measurement types" + (is (set? sut/measurements)) + (is (every? keyword? sut/measurements)) + (is (not-empty sut/measurements))) + (testing "Measurement display names" + (is (map? sut/measurements->display-name)) + (is (not-empty sut/measurements->display-name)) + (is (every? keyword? (keys sut/measurements->display-name))) + (is (every? map? (vals sut/measurements->display-name))) + (is (every? #(contains? % options/full) (vals sut/measurements->display-name))) + (is (every? #(contains? % options/short) (vals sut/measurements->display-name)))) + (testing "measurement->kilogram" + (is (map? sut/measurement->kilogram)) + (is (not-empty sut/measurement->kilogram)) + (is (every? keyword? (keys sut/measurement->kilogram))) + (is (every? number? (vals sut/measurement->kilogram)))) + (is (= (set (keys sut/measurement->kilogram)) + (set (keys sut/measurements->display-name)) + sut/measurements)))) + + +(deftest conversion-test + (testing "Ensure various color unit conversions behave as expected" + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/gram options/gram)) + (precision/->2dp (sut/convert 100.0 :gram :gram)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/kilogram options/kilogram)) + (precision/->2dp (sut/convert 100.0 :kilogram :kilogram)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/milligram options/milligram)) + (precision/->2dp (sut/convert 100.0 :milligram :milligram)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/ounce options/ounce)) + (precision/->2dp (sut/convert 100.0 :ounce :ounce)))) + (is (= 100.0 + (precision/->2dp (sut/convert 100.0 options/pound options/pound)) + (precision/->2dp (sut/convert 100.0 :pound :pound)))) + (is (= 0.1 (precision/->2dp (sut/convert 100.0 options/gram options/kilogram)))) + (is (= 100000.0 (precision/->2dp (sut/convert 100.0 options/gram options/milligram)))) + (is (= 3.53 (precision/->2dp (sut/convert 100.0 options/gram options/ounce)))) + (is (= 0.22 (precision/->2dp (sut/convert 100.0 options/gram options/pound)))) + (is (= 100000.0 (precision/->2dp (sut/convert 100.0 options/kilogram options/gram)))) + (is (= 0.1 (precision/->2dp (sut/convert 100.0 options/milligram options/gram)))) + (is (= 2834.95 (precision/->2dp (sut/convert 100.0 options/ounce options/gram)))) + (is (= 45359.24 (precision/->2dp (sut/convert 100.0 options/pound options/gram))))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Ensure various color unit conversions behave as expected" + (is (= "1.5 g" + (sut/display 1.5 :gram) + (sut/display 1.5 options/gram))) + (is (= "1.5 kg" + (sut/display 1.5 :kilogram) + (sut/display 1.5 options/kilogram))) + (is (= "1.5 mg" + (sut/display 1.5 :milligram) + (sut/display 1.5 options/milligram))) + (is (= "1.5 oz" + (sut/display 1.5 :ounce) + (sut/display 1.5 options/ounce))) + (is (= "1.5 lb" + (sut/display 1.5 :pound) + (sut/display 1.5 options/pound))) + (is (= "1.5 gram" + (sut/display 1.5 :gram {options/suffix options/full}) + (sut/display 1.5 options/gram {options/suffix options/full}))) + (is (= "1.5 kilogram" + (sut/display 1.5 :kilogram {options/suffix options/full}) + (sut/display 1.5 options/kilogram {options/suffix options/full}))) + (is (= "1.5 milligram" + (sut/display 1.5 :milligram {options/suffix options/full}) + (sut/display 1.5 options/milligram {options/suffix options/full}))) + (is (= "1.5 ounce" + (sut/display 1.5 :ounce {options/suffix options/full}) + (sut/display 1.5 options/ounce {options/suffix options/full}))) + (is (= "1.5 pound" + (sut/display 1.5 :pound {options/suffix options/full}) + (sut/display 1.5 options/pound {options/suffix options/full})))) + (testing "Invalid options throw an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display 10.0 :invalid)))))) diff --git a/test/brewtility/units_test.cljc b/test/brewtility/units_test.cljc index 85d1c1f..991a91e 100644 --- a/test/brewtility/units_test.cljc +++ b/test/brewtility/units_test.cljc @@ -1,31 +1,428 @@ (ns brewtility.units-test - (:require #? (:clj [clojure.test :refer [deftest is testing]]) - #? (:cljs [cljs.test :refer-macros [deftest is testing]]) - [brewtility.precision :as bp] - [brewtility.units :as sut])) + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + [brewtility.precision :as precision] + [brewtility.units :as sut] + [brewtility.units.color :as color] + [brewtility.units.options :as options])) +;; These test cases existed prior to version 2.0.0 of brewtility.units +;; The tests directly against `convert` are more robust +;; These tests are left in place to ensure the new functions behave as expected (deftest convert-volume-test - (testing "Volume conversions should behave as expected" - (is (= 1.0 (sut/convert-volume 1.0 :liter :litre))) - (is (= 1.0 (sut/convert-volume 1.0 :litre :litre))) - (is (= 0.099 (bp/->3dp (sut/convert-volume 20 :teaspoon :liter)))) - (is (= 35.1 (bp/->3dp (sut/convert-volume 35.1 :tablespoon :tablespoon)))) - (is (= 12.0 (bp/->3dp (sut/convert-volume 9.99209 :imperial-pint :american-pint)))))) + (testing "Volume conversions should behave as expected." + (is (= 1.0 (sut/convert :volume 1.0 options/liter options/litre))) + (is (= 1.0 (sut/convert options/volume 1.0 options/litre options/litre))) + (is (= 0.099 (precision/->3dp (sut/convert :volume 20 options/teaspoon options/liter)))) + (is (= 35.1 (precision/->3dp (sut/convert :volume 35.1 options/tablespoon options/tablespoon)))) + (is (= 12.0 (precision/->3dp (sut/convert :volume 9.99209 options/imperial-pint options/american-pint)))))) (deftest convert-weight-test - (testing "Weight conversions should behave as expected" - (is (= 1.0 (sut/convert-weight 1.0 :kilogram :kilogram))) - (is (= 1.0 (sut/convert-weight 1.0 :pound :pound))) - (is (= 240.0 (bp/->3dp (sut/convert-weight 15.0 :pound :ounce)))) - (is (= 0.003 (bp/->3dp (sut/convert-weight 1205.5 :milligram :pound)))))) + (testing "Weight conversions should behave as expected." + (is (= 1.0 (sut/convert :weight 1.0 options/kilogram options/kilogram))) + (is (= 1.0 (sut/convert options/weight 1.0 options/pound options/pound))) + (is (= 240.0 (precision/->3dp (sut/convert :weight 15.0 options/pound options/ounce)))) + (is (= 0.003 (precision/->3dp (sut/convert :weight 1205.5 options/milligram options/pound)))))) (deftest convert-temperature-test - (testing "Temperature conversions should behave as expected" - (is (= 32.0 (sut/convert-temperature 32.0 :fahrenheit :fahrenheit))) - (is (= 32.0 (sut/convert-temperature 0.0 :celsius :fahrenheit))) - (is (= 340.706 (bp/->3dp (sut/convert-temperature 153.6008 :fahrenheit :kelvin)))) - (is (= 1743.15 (bp/->3dp (sut/convert-temperature 2016.3 :kelvin :c)))) - (is (= -40.0 (bp/->3dp (sut/convert-temperature -40.0 :f :c)))))) + (testing "Temperature conversions should behave as expected." + (is (= 32.0 (sut/convert :temperature 32.0 options/fahrenheit options/fahrenheit))) + (is (= 32.0 (sut/convert options/temperature 0.0 options/celsius options/fahrenheit))) + (is (= 340.706 (precision/->3dp (sut/convert :temperature 153.6008 options/fahrenheit options/kelvin)))) + (is (= 1743.15 (precision/->3dp (sut/convert :temperature 2016.3 options/kelvin options/c)))) + (is (= -40.0 (precision/->3dp (sut/convert :temperature -40.0 options/f options/c)))))) + + +(deftest convert-color-test + (testing "Color conversions behaves as expected." + (is (= color/srm-1 (sut/convert :color 1 options/srm options/rgba))) + (is (= color/srm-1 (sut/convert :color 1 :srm :rgba))) + (is (= color/srm-13 (sut/convert options/color 10.16 options/lovibond options/rgba))) + (is (= color/srm-13 (sut/convert :color 10.16 :lovibond :rgba))) + (is (= color/srm-13 (sut/convert :color 25.61 options/ebc options/rgba))) + (is (= 13.00988 (sut/convert :color 25.61 options/ebc options/srm))) + (is (= 25.61 (sut/convert :color 25.61 options/srm options/srm))) + (is (= 50.451699999999995 (sut/convert :color 25.61 options/srm options/ebc))) + (is (= 10.16527388158866 (sut/convert :color 25.61 options/ebc options/lovibond))) + (is (= 66.84467282 (sut/convert :color 25.61 options/lovibond options/ebc))) + (is (= color/srm-13 (sut/convert :color 25.61 :ebc :rgba))))) + + +(deftest convert-test + (testing "Supported systems can convert all units." + (testing "Color:" + (is (= 10.0 + (precision/->2dp (sut/convert options/color 7.94 options/lovibond options/srm)) + (precision/->2dp (sut/convert options/color 19.69 options/ebc options/srm)))) + (is (= 10.0 + (precision/->2dp (sut/convert options/color 7.94 :lovibond :srm)) + (precision/->2dp (sut/convert options/color 19.69 :ebc :srm)))) + (is (= 23.2 + (precision/->2dp (sut/convert options/color 30.66 options/srm options/lovibond)) + (precision/->2dp (sut/convert options/color 60.38 options/ebc options/lovibond)))) + (is (= -45.53 + (precision/->2dp (sut/convert options/color -23.11 options/srm options/ebc)) + (precision/->2dp (sut/convert options/color -16.5 options/lovibond options/ebc))))) + (testing "Pressure:" + (is (= 100.0 + (precision/->2dp (sut/convert options/pressure 100.0 options/pascal options/pascal)) + (precision/->2dp (sut/convert options/pressure 100.0 :pascal :pascal)))) + (is (= 100000.0 (precision/->2dp (sut/convert options/pressure 100.0 options/kilopascal options/pascal)))) + (is (= 1.0 (precision/->2dp (sut/convert options/pressure 100.0 options/kilopascal options/bar)))) + (is (= 0.99 (precision/->2dp (sut/convert options/pressure 100.0 options/kilopascal options/atmosphere)))) + (is (= 750.06 (precision/->2dp (sut/convert options/pressure 100.0 options/kilopascal options/torr)))) + (is (= 14.5 (precision/->2dp (sut/convert options/pressure 100.0 options/kilopascal options/psi)))) + (is (= 0.1 (precision/->2dp (sut/convert options/pressure 100.0 options/pascal options/kilopascal)))) + (is (= 10000.0 (precision/->2dp (sut/convert options/pressure 100.0 options/bar options/kilopascal)))) + (is (= 10132.5 (precision/->2dp (sut/convert options/pressure 100.0 options/atmosphere options/kilopascal)))) + (is (= 13.33 (precision/->2dp (sut/convert options/pressure 100.0 options/torr options/kilopascal)))) + (is (= 689.48 (precision/->2dp (sut/convert options/pressure 100.0 options/psi options/kilopascal))))) + (testing "Specific Gravity:" + (is (= 100.0 + (precision/->2dp (sut/convert options/specific-gravity 100.0 options/specific-gravity options/specific-gravity)) + (precision/->2dp (sut/convert options/specific-gravity 100.0 :specific-gravity :specific-gravity))))) + (testing "Temperature:" + (is (= 100.0 + (precision/->2dp (sut/convert options/temperature 100.0 options/c options/c)) + (precision/->2dp (sut/convert options/temperature 100.0 :celsius :c)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/temperature 100.0 options/f options/fahrenheit)) + (precision/->2dp (sut/convert options/temperature 100.0 :f :f)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/temperature 100.0 options/k options/kelvin)) + (precision/->2dp (sut/convert options/temperature 100.0 :k :k)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/temperature 100.0 options/centigrade options/centigrade)) + (precision/->2dp (sut/convert options/temperature 100.0 :centigrade :centigrade)))) + (is (= 212.0 (precision/->2dp (sut/convert options/temperature 100 options/celsius options/fahrenheit)))) + (is (= 363.15 (precision/->2dp (sut/convert options/temperature 90 options/celsius options/kelvin)))) + (is (= 100.0 (precision/->2dp (sut/convert options/temperature 100.0 options/celsius options/centigrade)))) + (is (= 32.22 (precision/->2dp (sut/convert options/temperature 90 options/fahrenheit options/celsius)))) + (is (= 305.37 (precision/->2dp (sut/convert options/temperature 90 options/fahrenheit options/kelvin)))) + (is (= 32.22 (precision/->2dp (sut/convert options/temperature 90 options/fahrenheit options/centigrade)))) + (is (= -173.15 (precision/->2dp (sut/convert options/temperature 100 options/kelvin options/celsius)))) + (is (= -297.67 (precision/->2dp (sut/convert options/temperature 90 options/kelvin options/fahrenheit)))) + (is (= -183.15 (precision/->2dp (sut/convert options/temperature 90 options/kelvin options/centigrade)))) + (is (= 100.0 (precision/->2dp (sut/convert options/temperature 100 options/centigrade options/celsius)))) + (is (= 212.0 (precision/->2dp (sut/convert options/temperature 100 options/centigrade options/fahrenheit)))) + (is (= 373.15 (precision/->2dp (sut/convert options/temperature 100 options/centigrade options/kelvin))))) + (testing "Time:" + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/minute options/minute)) + (precision/->2dp (sut/convert options/time 100.0 :minute :minute)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/nanosecond options/nanosecond)) + (precision/->2dp (sut/convert options/time 100.0 :nanosecond :nanosecond)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/second options/second)) + (precision/->2dp (sut/convert options/time 100.0 :second :second)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/millisecond options/millisecond)) + (precision/->2dp (sut/convert options/time 100.0 :millisecond :millisecond)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/microsecond options/microsecond)) + (precision/->2dp (sut/convert options/time 100.0 :microsecond :microsecond)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/day options/day)) + (precision/->2dp (sut/convert options/time 100.0 :day :day)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/hour options/hour)) + (precision/->2dp (sut/convert options/time 100.0 :hour :hour)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/time 100.0 options/week options/week)) + (precision/->2dp (sut/convert options/time 100.0 :week :week)))) + (is (= 6000.0 (precision/->2dp (sut/convert options/time 100.0 options/minute options/second)))) + (is (= 6000000.0 (precision/->2dp (sut/convert options/time 100.0 options/minute options/millisecond)))) + (is (= 60000.0 (precision/->2dp (sut/convert options/time 0.001 options/minute options/microsecond)))) + (is (= 600000.0 (precision/->2dp (sut/convert options/time 0.00001 options/minute options/nanosecond)))) + (is (= 0.07 (precision/->2dp (sut/convert options/time 100.0 options/minute options/day)))) + (is (= 1.67 (precision/->2dp (sut/convert options/time 100.0 options/minute options/hour)))) + (is (= 0.01 (precision/->2dp (sut/convert options/time 100.0 options/minute options/week)))) + (is (= 1.67 (precision/->2dp (sut/convert options/time 100.0 options/second options/minute)))) + (is (= 0.0 (precision/->2dp (sut/convert options/time 100.0 options/millisecond options/minute)))) + (is (= 0.0 (precision/->2dp (sut/convert options/time 100.0 options/microsecond options/minute)))) + (is (= 0.0 (precision/->2dp (sut/convert options/time 100.0 options/nanosecond options/minute)))) + (is (= 144000.0 (precision/->2dp (sut/convert options/time 100.0 options/day options/minute)))) + (is (= 6000.0 (precision/->2dp (sut/convert options/time 100.0 options/hour options/minute)))) + (is (= 1008000.0 (precision/->2dp (sut/convert options/time 100.0 options/week options/minute))))) + (testing "Volume:" + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/liter options/litre)) + (precision/->2dp (sut/convert options/volume 100.0 :litre :liter)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/milliliter options/millilitre)) + (precision/->2dp (sut/convert options/volume 100.0 :milliliter :milliliter)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/american-fluid-ounce options/american-fluid-ounce)) + (precision/->2dp (sut/convert options/volume 100.0 :american-fluid-ounce :american-fluid-ounce)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/american-gallon options/american-gallon)) + (precision/->2dp (sut/convert options/volume 100.0 :american-gallon :american-gallon)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/american-pint options/american-pint)) + (precision/->2dp (sut/convert options/volume 100.0 :american-pint :american-pint)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/american-quart options/american-quart)) + (precision/->2dp (sut/convert options/volume 100.0 :american-quart :american-quart)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/cup options/cup)) + (precision/->2dp (sut/convert options/volume 100.0 :cup :cup)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/imperial-fluid-ounce options/imperial-fluid-ounce)) + (precision/->2dp (sut/convert options/volume 100.0 :imperial-fluid-ounce :imperial-fluid-ounce)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/imperial-gallon options/imperial-gallon)) + (precision/->2dp (sut/convert options/volume 100.0 :imperial-gallon :imperial-gallon)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/imperial-pint options/imperial-pint)) + (precision/->2dp (sut/convert options/volume 100.0 :imperial-pint :imperial-pint)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/imperial-quart options/imperial-quart)) + (precision/->2dp (sut/convert options/volume 100.0 :imperial-quart :imperial-quart)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/tablespoon options/tablespoon)) + (precision/->2dp (sut/convert options/volume 100.0 :tablespoon :tablespoon)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/volume 100.0 options/teaspoon options/teaspoon)) + (precision/->2dp (sut/convert options/volume 100.0 :teaspoon :teaspoon)))) + (is (= 3381.41 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/american-fluid-ounce)))) + (is (= 26.42 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/american-gallon)))) + (is (= 211.34 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/american-pint)))) + (is (= 105.67 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/american-quart)))) + (is (= 422.68 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/cup)))) + (is (= 3519.5 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/imperial-fluid-ounce)))) + (is (= 22.0 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/imperial-gallon)))) + (is (= 175.98 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/imperial-pint)))) + (is (= 87.99 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/imperial-quart)))) + (is (= 6762.79 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/tablespoon)))) + (is (= 20288.42 (precision/->2dp (sut/convert options/volume 100.0 options/liter options/teaspoon)))) + (is (= 2.96 (precision/->2dp (sut/convert options/volume 100.0 options/american-fluid-ounce options/liter)))) + (is (= 378.54 (precision/->2dp (sut/convert options/volume 100.0 options/american-gallon options/liter)))) + (is (= 47.32 (precision/->2dp (sut/convert options/volume 100.0 options/american-pint options/liter)))) + (is (= 94.64 (precision/->2dp (sut/convert options/volume 100.0 options/american-quart options/liter)))) + (is (= 23.66 (precision/->2dp (sut/convert options/volume 100.0 options/cup options/liter)))) + (is (= 2.84 (precision/->2dp (sut/convert options/volume 100.0 options/imperial-fluid-ounce options/liter)))) + (is (= 454.61 (precision/->2dp (sut/convert options/volume 100.0 options/imperial-gallon options/liter)))) + (is (= 56.83 (precision/->2dp (sut/convert options/volume 100.0 options/imperial-pint options/liter)))) + (is (= 113.65 (precision/->2dp (sut/convert options/volume 100.0 options/imperial-quart options/liter)))) + (is (= 1.48 (precision/->2dp (sut/convert options/volume 100.0 options/tablespoon options/liter)))) + (is (= 0.49 (precision/->2dp (sut/convert options/volume 100.0 options/teaspoon options/liter))))) + (testing "Weight:" + (is (= 100.0 + (precision/->2dp (sut/convert options/weight 100.0 options/gram options/gram)) + (precision/->2dp (sut/convert options/weight 100.0 :gram :gram)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/weight 100.0 options/kilogram options/kilogram)) + (precision/->2dp (sut/convert options/weight 100.0 :kilogram :kilogram)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/weight 100.0 options/milligram options/milligram)) + (precision/->2dp (sut/convert options/weight 100.0 :milligram :milligram)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/weight 100.0 options/ounce options/ounce)) + (precision/->2dp (sut/convert options/weight 100.0 :ounce :ounce)))) + (is (= 100.0 + (precision/->2dp (sut/convert options/weight 100.0 options/pound options/pound)) + (precision/->2dp (sut/convert options/weight 100.0 :pound :pound)))) + (is (= 0.1 (precision/->2dp (sut/convert options/weight 100.0 options/gram options/kilogram)))) + (is (= 100000.0 (precision/->2dp (sut/convert options/weight 100.0 options/gram options/milligram)))) + (is (= 3.53 (precision/->2dp (sut/convert options/weight 100.0 options/gram options/ounce)))) + (is (= 0.22 (precision/->2dp (sut/convert options/weight 100.0 options/gram options/pound)))) + (is (= 100000.0 (precision/->2dp (sut/convert options/weight 100.0 options/kilogram options/gram)))) + (is (= 0.1 (precision/->2dp (sut/convert options/weight 100.0 options/milligram options/gram)))) + (is (= 2834.95 (precision/->2dp (sut/convert options/weight 100.0 options/ounce options/gram)))) + (is (= 45359.24 (precision/->2dp (sut/convert options/weight 100.0 options/pound options/gram)))))) + (testing "Invalid options generate an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/convert :ope 10.0 :invalid :broken)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/convert :mistake 10.0 :invalid :broken)))))) + + +(deftest display-test + (testing "Supported systems can cast display values" + (testing "Color:" + (is (= "10.1 srm" + (sut/display options/color 10.1 options/srm) + (sut/display options/color 10.1 :srm))) + (is (= "23.2 ebc" + (sut/display options/color 23.2 options/ebc) + (sut/display options/color 23.2 :ebc))) + (is (= "10.1 degrees lovibond" + (sut/display options/color 10.1 options/lovibond {options/suffix options/full}) + (sut/display options/color 10.1 :lovibond {options/suffix options/full}))) + (is (= color/srm-37 (sut/display options/color color/srm-37 options/rgba)))) + (testing "Pressure:" + (is (= "1.5 pa" + (sut/display options/pressure 1.5 :pascal) + (sut/display options/pressure 1.5 options/pascal))) + (is (= "1.5 pascal" + (sut/display options/pressure 1.5 :pascal {options/suffix options/full})))) + (testing "Specific Gravity:" + (is (= "1.5 sg" + (sut/display options/specific-gravity 1.5 :specific-gravity) + (sut/display options/specific-gravity 1.5 options/specific-gravity)))) + (testing "Temperature:" + (is (= "1.5 c" + (sut/display options/temperature 1.5 :celsius) + (sut/display options/temperature 1.5 options/celsius) + (sut/display options/temperature 1.5 options/c))) + (is (= "1.5 k" + (sut/display options/temperature 1.5 :kelvin) + (sut/display options/temperature 1.5 options/kelvin) + (sut/display options/temperature 1.5 options/k))) + (is (= "1.5 f" + (sut/display options/temperature 1.5 :fahrenheit) + (sut/display options/temperature 1.5 options/fahrenheit) + (sut/display options/temperature 1.5 options/f))) + (is (= "1.5 mg" + (sut/display options/temperature 1.5 options/centigrade))) + (is (= "1.5 celsius" + (sut/display options/temperature 1.5 :celsius {options/suffix options/full}) + (sut/display options/temperature 1.5 options/celsius {options/suffix options/full}) + (sut/display options/temperature 1.5 options/c {options/suffix options/full}))) + (is (= "1.5 kelvin" + (sut/display options/temperature 1.5 :kelvin {options/suffix options/full}) + (sut/display options/temperature 1.5 options/kelvin {options/suffix options/full}) + (sut/display options/temperature 1.5 options/k {options/suffix options/full}))) + (is (= "1.5 fahrenheit" + (sut/display options/temperature 1.5 :fahrenheit {options/suffix options/full}) + (sut/display options/temperature 1.5 options/fahrenheit {options/suffix options/full}) + (sut/display options/temperature 1.5 options/f {options/suffix options/full}))) + (is (= "1.5 centigrade" + (sut/display options/temperature 1.5 options/centigrade {options/suffix options/full})))) + (testing "Time:" + (is (= "1.5 m" + (sut/display options/time 1.5 :minute) + (sut/display options/time 1.5 options/minute))) + (is (= "1.5 d" + (sut/display options/time 1.5 :day) + (sut/display options/time 1.5 options/day))) + (is (= "1.5 hr" + (sut/display options/time 1.5 :hour) + (sut/display options/time 1.5 options/hour))) + (is (= "1.5 w" + (sut/display options/time 1.5 :week) + (sut/display options/time 1.5 options/week))) + (is (= "1.5 ns" + (sut/display options/time 1.5 :nanosecond) + (sut/display options/time 1.5 options/nanosecond))) + (is (= "1.5 ms" + (sut/display options/time 1.5 :millisecond) + (sut/display options/time 1.5 options/millisecond))) + (is (= "1.5 µs" + (sut/display options/time 1.5 :microsecond) + (sut/display options/time 1.5 options/microsecond))) + (is (= "1.5 s" + (sut/display options/time 1.5 :second) + (sut/display options/time 1.5 options/second))) + (is (= "1.5 minute" + (sut/display options/time 1.5 :minute {options/suffix options/full}) + (sut/display options/time 1.5 options/minute {options/suffix options/full})))) + (testing "Volume:" + (is (= "1.5 l" + (sut/display options/volume 1.5 :liter) + (sut/display options/volume 1.5 options/litre))) + (is (= "1.5 liter" + (sut/display options/volume 1.5 :liter {options/suffix options/full}) + (sut/display options/volume 1.5 options/liter {options/suffix options/full}))) + (is (= "1.5 fl oz" + (sut/display options/volume 1.5 :american-fluid-ounce) + (sut/display options/volume 1.5 options/american-fluid-ounce))) + (is (= "1.5 fluid ounce" + (sut/display options/volume 1.5 :american-fluid-ounce {options/suffix options/full}) + (sut/display options/volume 1.5 options/american-fluid-ounce {options/suffix options/full}))) + (is (= "1.5 gal" + (sut/display options/volume 1.5 :american-gallon) + (sut/display options/volume 1.5 options/american-gallon))) + (is (= "1.5 US gallon" + (sut/display options/volume 1.5 :american-gallon {options/suffix options/full}) + (sut/display options/volume 1.5 options/american-gallon {options/suffix options/full}))) + (is (= "1.5 pt" + (sut/display options/volume 1.5 :american-pint) + (sut/display options/volume 1.5 options/american-pint))) + (is (= "1.5 american pint" + (sut/display options/volume 1.5 :american-pint {options/suffix options/full}) + (sut/display options/volume 1.5 options/american-pint {options/suffix options/full}))) + (is (= "1.5 qt" + (sut/display options/volume 1.5 :american-quart) + (sut/display options/volume 1.5 options/american-quart))) + (is (= "1.5 american quart" + (sut/display options/volume 1.5 :american-quart {options/suffix options/full}) + (sut/display options/volume 1.5 options/american-quart {options/suffix options/full}))) + (is (= "1.5 c" + (sut/display options/volume 1.5 :cup) + (sut/display options/volume 1.5 options/cup))) + (is (= "1.5 cup" + (sut/display options/volume 1.5 :cup {options/suffix options/full}) + (sut/display options/volume 1.5 options/cup {options/suffix options/full}))) + (is (= "1.5 imp fl oz" + (sut/display options/volume 1.5 :imperial-fluid-ounce) + (sut/display options/volume 1.5 options/imperial-fluid-ounce))) + (is (= "1.5 imperial fluid ounce" + (sut/display options/volume 1.5 :imperial-fluid-ounce {options/suffix options/full}) + (sut/display options/volume 1.5 options/imperial-fluid-ounce {options/suffix options/full}))) + (is (= "1.5 gal" + (sut/display options/volume 1.5 :imperial-gallon) + (sut/display options/volume 1.5 options/imperial-gallon))) + (is (= "1.5 imperial gallon" + (sut/display options/volume 1.5 :imperial-gallon {options/suffix options/full}) + (sut/display options/volume 1.5 options/imperial-gallon {options/suffix options/full}))) + (is (= "1.5 pt" + (sut/display options/volume 1.5 :imperial-pint) + (sut/display options/volume 1.5 options/imperial-pint))) + (is (= "1.5 imperial pint" + (sut/display options/volume 1.5 :imperial-pint {options/suffix options/full}) + (sut/display options/volume 1.5 options/imperial-pint {options/suffix options/full}))) + (is (= "1.5 qt" + (sut/display options/volume 1.5 :imperial-quart) + (sut/display options/volume 1.5 options/imperial-quart))) + (is (= "1.5 imperial quart" + (sut/display options/volume 1.5 :imperial-quart {options/suffix options/full}) + (sut/display options/volume 1.5 options/imperial-quart {options/suffix options/full}))) + (is (= "1.5 tbsp" + (sut/display options/volume 1.5 :tablespoon) + (sut/display options/volume 1.5 options/tablespoon))) + (is (= "1.5 tablespoon" + (sut/display options/volume 1.5 :tablespoon {options/suffix options/full}) + (sut/display options/volume 1.5 options/tablespoon {options/suffix options/full}))) + (is (= "1.5 tsp" + (sut/display options/volume 1.5 :teaspoon) + (sut/display options/volume 1.5 options/teaspoon))) + (is (= "1.5 teaspoon" + (sut/display options/volume 1.5 :teaspoon {options/suffix options/full}) + (sut/display options/volume 1.5 options/teaspoon {options/suffix options/full})))) + (testing "Weight:" + (is (= "1.5 g" + (sut/display options/weight 1.5 :gram) + (sut/display options/weight 1.5 options/gram))) + (is (= "1.5 kg" + (sut/display options/weight 1.5 :kilogram) + (sut/display options/weight 1.5 options/kilogram))) + (is (= "1.5 mg" + (sut/display options/weight 1.5 :milligram) + (sut/display options/weight 1.5 options/milligram))) + (is (= "1.5 oz" + (sut/display options/weight 1.5 :ounce) + (sut/display options/weight 1.5 options/ounce))) + (is (= "1.5 lb" + (sut/display options/weight 1.5 :pound) + (sut/display options/weight 1.5 options/pound))) + (is (= "1.5 gram" + (sut/display options/weight 1.5 :gram {options/suffix options/full}) + (sut/display options/weight 1.5 options/gram {options/suffix options/full}))) + (is (= "1.5 kilogram" + (sut/display options/weight 1.5 :kilogram {options/suffix options/full}) + (sut/display options/weight 1.5 options/kilogram {options/suffix options/full}))) + (is (= "1.5 milligram" + (sut/display options/weight 1.5 :milligram {options/suffix options/full}) + (sut/display options/weight 1.5 options/milligram {options/suffix options/full}))) + (is (= "1.5 ounce" + (sut/display options/weight 1.5 :ounce {options/suffix options/full}) + (sut/display options/weight 1.5 options/ounce {options/suffix options/full}))) + (is (= "1.5 pound" + (sut/display options/weight 1.5 :pound {options/suffix options/full}) + (sut/display options/weight 1.5 options/pound {options/suffix options/full}))))) + (testing "Invalid options generate an exception" + #?(:clj (is (thrown-with-msg? Exception #"Unsupported" (sut/display :ope 10.0 :invalid)))) + #?(:cljs (is (thrown-with-msg? js/Error #"Unsupported" (sut/display :bad 10.0 :invalid)))))) diff --git a/test/brewtility/wrapping_test.cljc b/test/brewtility/wrapping_test.cljc index 7616215..eafc330 100644 --- a/test/brewtility/wrapping_test.cljc +++ b/test/brewtility/wrapping_test.cljc @@ -1,6 +1,6 @@ (ns brewtility.wrapping-test - (:require #? (:clj [clojure.test :refer [deftest is testing]]) - #? (:cljs [cljs.test :refer-macros [deftest is testing]]) + (:require #? (:clj [clojure.test :refer [deftest is testing]]) + #? (:cljs [cljs.test :refer-macros [deftest is testing]]) [brewtility.data.equipment :as equipment.data] [brewtility.data.fermentables :as fermentables.data] [brewtility.data.hops :as hops.data]