diff --git a/README.md b/README.md index 4d591955..d93f3ba3 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 00fa132e..00000000 --- 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 b417c2f7..5416bf34 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: + +- [clesius](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 251a3833..f363c4be 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 00000000..4cba6cf6 --- /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 fulfil 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.