An open API service indexing awesome lists of open source software.

https://github.com/sernamar/dinero

A Clojure library for working with money
https://github.com/sernamar/dinero

bitcoin clojure currencies money

Last synced: 3 months ago
JSON representation

A Clojure library for working with money

Awesome Lists containing this project

README

          

** dinero :noexport:
[[https://clojars.org/com.sernamar/dinero][file:https://img.shields.io/clojars/v/com.sernamar/dinero.svg]]

/dinero/ is a Clojure library designed for managing monetary amounts and currencies. It offers support for precise arithmetic operations, formatting, and parsing, all tailored to different locales and currencies.

This library is heavily inspired by [[https://github.com/JavaMoney/jsr354-ri][Moneta]], which is part of the [[https://javamoney.github.io/][JavaMoney]] project.

It supports three types of monetary amounts:
- =Money=: represents monetary amounts with arbitrary precision and scale, ensuring accurate numerical computations during arithmetic operations.
- =FastMoney=: represents monetary amounts using a fixed scale, offering a balance between performance and accuracy while being precise enough for most applications.
- =RoundedMoney=: represents monetary amounts that are automatically rounded after each arithmetic operation, ensuring consistent precision.

It also offers functionality to format monetary amounts and parse strings representing monetary amounts. It takes care of different formats and currencies based on the user's locale settings, ensuring accurate and locale-sensitive representations.

In terms of currencies, it supports all ISO 4217 currencies by default, allowing for seamless handling of all internationally recognized currencies like the Euro (=EUR=) and British Pound (=GBP=). You can also extend support to non-ISO currencies, such as cryptocurrencies like Bitcoin (=BTC=), or even custom currencies specific to your domain, through an EDN configuration file.
** Table of contents :TOC_3:
- [[#installation][Installation]]
- [[#configuration][Configuration]]
- [[#usage][Usage]]
- [[#monetary-amounts][Monetary amounts]]
- [[#currencies][Currencies]]
- [[#formatting][Formatting]]
- [[#parsing][Parsing]]
- [[#equality-and-comparison][Equality and comparison]]
- [[#arithmetic-operations][Arithmetic operations]]
- [[#rounding][Rounding]]
- [[#currency-conversion][Currency conversion]]
- [[#license][License]]

** Installation
Clojure CLI/deps.edn:
#+begin_src shell
com.sernamar/dinero {:mvn/version "0.3.0"}
#+end_src
Leiningen/Boot:
#+begin_src shell
[com.sernamar/dinero "0.3.0"]
#+end_src
** Configuration
Before using this library, you can configure the default currency and default rounding mode using a configuration file named =config.edn=, which should be located in the root directory of the project.

For example, to set /euros/ as the default currency and the /half even/ method as the default rounding method, the =config.edn= file should have the following content:
#+begin_src clojure
{:default-currency :eur
:default-rounding-mode :half-even}
#+end_src
** Usage
*** Monetary amounts
This library support three types of monetary amounts:
- =Money=: represents monetary amounts with arbitrary precision and scale, ensuring accurate numerical computations during arithmetic operations.
- =FastMoney=: represents monetary amounts using a fixed scale, offering a balance between performance and accuracy while being precise enough for most applications.
- =RoundedMoney=: represents monetary amounts that are automatically rounded after each arithmetic operation.
**** Monetary amounts of type =Money=
You can use =money-of= to create monetary amounts with arbitrary precision and scale. It accepts two arguments:
- =amount=: can be either an integer, a floating-point number, a =BigDecimal= or a =String=.
- =currency=: can be a keyword, a string, or a symbol, in either uppercase or lowercase.
If the =currency= is not specified, the =money-of= function will use the default currency set in the configuration file. If a currency is provided, it will override the default and use the specified currency instead:
#+begin_src clojure
(require '[dinero.core :as dinero])

(dinero/money-of 1)
;; => {:amount 1M, :currency :eur}

(dinero/money-of 1 :gbp)
;; => {:amount 1M, :currency :gbp}
#+end_src
For convenience, you can use the =with-currency= macro to bind the default currency in the macro's body. This way, if the default currency isn’t set in the configuration file, you won’t need to specify the currency when creating monetary amounts:
#+begin_src clojure
(dinero/with-currency :gbp
(dinero/money-of 1))
;; => {:amount 1M, :currency :gbp}
#+end_src
If none the =amount= nor the =currency= is specified, it uses =0= as the amount, and the default currency as the currency:
#+begin_src clojure
(dinero/money-of)
;; => {:amount 0M, :currency :eur}
#+end_src
Although you can use either an integer, a floating-point number, a =BigDecimal= or a =String= as the =amount= argument, they are all parsed and stored as =BigDecimals=:
#+begin_src clojure
(dinero/money-of 1 :eur)
;; => {:amount 1M, :currency :eur}

(dinero/money-of 1.23 :eur)
;; => {:amount 1.23M, :currency :eur}

(dinero/money-of 1.23M :eur)
;; => {:amount 1.23M, :currency :eur}

(dinero/money-of "1.23" :eur)
;; => {:amount 1.23M, :currency :eur}
#+end_src
/WARNING/:
#+begin_quote
Be aware that using floating-point numbers can lead to rounding errors in certain calculations, as Clojure reads them as =Doubles=, which are 64-bit IEEE 754 floating point numbers. This means you get double precision, with about 15-17 significant decimal digits of precision.

So for accurate numerical computations, especially in financial applications, don't use floating-point numbers; use =BigDecimals= or =Strings= instead, as they will be parsed accurately.
#+begin_src clojure
;;; Not safe, as it uses a floating-point number, losing precision when parsing
(dinero/money-of 0.123456789123456789123456789 :eur)
;; => {:amount 0.12345678912345678M, :currency :eur}

;;; Safe, as it uses BigDecimal or String
(dinero/money-of 0.123456789123456789123456789M :eur)
;; => {:amount 0.123456789123456789123456789M, :currency :eur}
(dinero/money-of "0.123456789123456789123456789" :eur)
;; => {:amount 0.123456789123456789123456789M, :currency :eur}
#+end_src
#+end_quote
**** Monetary amounts of type =FastMoney=
You can use =fast-money-of= to create monetary amounts optimized for performance while maintaining sufficient accuracy. It accepts up to two arguments:
- =amount=: can be an integer, a floating-point number, a =BigDecimal=, or a =String=.
- =currency=: can be a keyword, a string, or a symbol, in either uppercase or lowercase.

If the =currency= is not specified, the =fast-money-of= function will use the default currency from the configuration file. This type of monetary amount uses a fixed scale for calculations, ensuring fast operations while remaining precise enough for most applications:
#+begin_src clojure
(dinero/fast-money-of 1 :eur)
;; => {:amount 1M, :currency :eur}
#+end_src
**** Monetary amounts of type =RoundedMoney=
You can use =rounded-money-of= to create monetary amounts that are automatically rounded after each arithmetic operation. This function accepts up to 4 arguments:
- =amount=: can be an integer, a floating-point number, a =BigDecimal= or a =String=.
- =currency=: can be a keyword, a string, or a symbol, in either uppercase or lowercase.
- =scale=: the number of decimal places to which the amount will be rounded.
- =rounding-mode=: the rounding mode to use when rounding the amount.
If =currency= is not specified, the =rounded-money-of= function will use the default currency from the configuration file. If =scale= is not provided, the minor units of the given currency will be used. If =rounding-mode= is not specified, the default rounding mode will be applied (or =:half-even= if the default rounding mode is not set in the configuration file):
#+begin_src clojure
(dinero/rounded-money-of 1234.5678 :eur)
;; => {:amount 1234.57M, :currency :eur, :scale 2, :rounding-mode :half-even}

(dinero/rounded-money-of 1234.5678 :eur 0)
;; => {:amount 1235M, :currency :eur, :scale 0, :rounding-mode :half-even}

(dinero/rounded-money-of 1234.5678 :eur 0 :down)
;; => {:amount 1234M, :currency :eur, :scale 0, :rounding-mode :down}
#+end_src
**** Amount, currency, and rounding information
Given a monetary amount, you can get its amount and currency using the =get-amount= and =get-currency= functions:
#+begin_src clojure
(let [money (dinero/money-of 1 :eur)]
(dinero/get-amount money))
;; => 1M
(let [money (dinero/money-of 1 :eur)]
(dinero/get-currency money))
;; => :eur
#+end_src
For rounded monetary amounts, you can also use the =get-scale= and =get-rounding-mode= functions to retrieve the scale and rounding mode applied during the rounding process:
#+begin_src clojure
(let [money (dinero/rounded-money-of 1 :eur)]
(dinero/get-scale money))
;; => 2

(let [money (dinero/rounded-money-of 1 :eur)]
(dinero/get-rounding-mode money))
;; => :half-even
#+end_src
*** Currencies
This library supports all ISO 4217 currencies by default, providing seamless handling of all internationally recognized currencies like the Euro (=EUR=) and British Pound (=GBP=). Additionally, you can extend support to non-ISO currencies, such as cryptocurrencies like Bitcoin (=BTC=), or even custom currencies specific to your domain, by editing the =resources/currencies.edn= file. The format for defining currencies is as follows:
#+begin_src clojure
{:eur {:type :iso-4217, :currency-code "EUR", :minor-units 2},
:gbp {:type :iso-4217, :currency-code "GBP", :minor-units 2},
:btc {:type :crypto, :currency-code "BTC", :symbol "₿", :minor-units 8}}
#+end_src
For ISO 4217 currencies, the =:symbol= key should not be used, as the library automatically relies on the symbol defined by the locale. For example, the British Pound (=GBP=) is represented as =£= in =java.util.Locale/UK=, while in =java.util.Locale/FRANCE=, it appears as =£GB=. This ensures that the correct symbol is displayed based on the user's locale settings.

For non-ISO currencies, such as Bitcoin, the =:symbol= key is required because they are not supported by =java.util.Locale=. Since their symbols are not locale-specific, we define a single symbol in the =resources/currencies.edn= file, which is used consistently across all locales.

This approach provides flexibility in handling both standardized and custom currencies, allowing your application to adapt to a wide range of monetary systems.
*** Formatting
As already mentioned, monetary amounts could be stored internally with more decimal places than the smallest unit of the currency. Although this may be important for accurate numerical computations, you might be interested in displaying amounts in a user-friendly format.

To display monetary amounts in a user-friendly format, you can use the =format-money= function. This function will convert the internal representation of the monetary amount into a string with a more readable format.

The =format-money= function accepts a map of configuration options as its second argument. The available options are:
- locale
- rounding-mode
- decimal-places
- symbol-style: accepts either =:symbol= (default) or =:code=.
For example:
#+begin_src clojure
(require '[dinero.core :as dinero]
'[dinero.format :as format])

(let [m1 (dinero/money-of 1234.5678 :eur)
germany java.util.Locale/GERMANY]
(println (format/format-money m1 {:locale germany}))
(println (format/format-money m1 {:locale germany :symbol-style :code}))
(println (format/format-money m1 {:locale germany :rounding-mode :down :symbol-style :code}))
(println (format/format-money m1 {:locale germany :rounding-mode :down :decimal-places 0 :symbol-style :code})))
;; 1.234,57 €
;; 1.234,57 EUR
;; 1.234,56 EUR
;; 1.234 EUR
#+end_src
You can also use the =format-money-with-pattern= function, which uses the given formatting pattern to format the monetary amount. This function also accepts a map of configuration options as its third argument, supporting these options:
- locale
- rounding-mode
For example:
#+begin_src clojure
(let [m1 (dinero/money-of 1234.5678 :eur)
germany java.util.Locale/GERMANY]
(println (format/format-money-with-pattern m1 "#,##0.00 ¤" {:locale germany}))
(println (format/format-money-with-pattern m1 "#,##0.00 ¤¤" {:locale germany}))
(println (format/format-money-with-pattern m1 "#,##0.00 euros" {:locale germany}))
(println (format/format-money-with-pattern m1 "#,##0.000 ¤" {:locale germany}))
(println (format/format-money-with-pattern m1 "#,##0 ¤" {:locale germany}))
(println (format/format-money-with-pattern m1 "#,##0 ¤" {:locale germany :rounding-mode :down})))
;; 1.234,57 €
;; 1.234,57 EUR
;; 1.234,57 euros
;; 1.234,568 €
;; 1.235 €
;; 1.234 €
#+end_src
*** Parsing
This library supports parsing strings with both ISO 4217 currencies (e.g., Euro) and non-ISO 4217 currencies (e.g., Bitcoin), whether they use currency symbols (e.g., =€= or =₿=) or currency codes (e.g., =EUR= or =BTC=).

To parse a string representing a monetary amount, use the =parse-string= function, which accepts a map of configuration options as its second argument. The available options are:
- =:locale=: a =java.util.Locale= object used for parsing. If =NIL=, the default locale is used.
- =:currencies=: a sequence of currencies to attempt during parsing. If =NIL=, it defaults to either the configured currency or the locale's default currency.
- =:try-all-currencies?=: a boolean flag. If =TRUE=, the function will attempt to parse the string using all currencies available in =resources/currencies.edn= if the provided currencies fail. Defaults to =FALSE=.
#+begin_src clojure
(require '[dinero.parse :as parse])

(parse/parse-string "1.234,56 €" {:locale java.util.Locale/GERMANY})
;; => {:amount 1234.56M, :currency :eur}

(parse/parse-string "1.234,56 EUR" {:locale java.util.Locale/GERMANY})
;; => {:amount 1234.56M, :currency :eur}

(parse/parse-string "1.234,56 £" {:locale java.util.Locale/GERMANY :currencies [:eur :gbp]})
;; => {:amount 1234.56M, :currency :gbp}

(parse/parse-string "1.234,56 GBP" {:locale java.util.Locale/GERMANY :currencies [:eur :gbp]})
;; => {:amount 1234.56M, :currency :gbp}

(parse/parse-string "1.234,56 £" {:locale java.util.Locale/GERMANY :try-all-currencies? true})
;; => {:amount 1234.56M, :currency :gbp}

(parse/parse-string "1.234,56 ₿" {:locale java.util.Locale/GERMANY :currencies [:btc]})
;; => {:amount 1234.56M, :currency :btc}

(parse/parse-string "1.234,56 BTC" {:locale java.util.Locale/GERMANY :currencies [:btc]})
;; => {:amount 1234.56M, :currency :btc}

(parse/parse-string "1.234,56 ₿" {:locale java.util.Locale/GERMANY :try-all-currencies? true})
;; => {:amount 1234.56M, :currency :btc}

(parse/parse-string "1.234,56 ₿" {:locale java.util.Locale/GERMANY :currencies [:eur :gbp] :try-all-currencies? true})
;; => {:amount 1234.56M, :currency :btc}
#+end_src
The =parse-string= function is capable of differentiating between the same currency symbol used in different locales. For example, the dollar sign (=$=) represents both US dollars (=USD=) and Canadian dollars (=CAD=), depending on the locale:
#+begin_src clojure
(parse/parse-string "$1,234.56" {:locale java.util.Locale/US})
;; => {:amount 1234.56M, :currency :usd}

(parse/parse-string "$1,234.56" {:locale java.util.Locale/CANADA})
;; => {:amount 1234.56M, :currency :cad}
#+end_src
If =parse-string= cannot recognize the format or the currency in the string, it throws a =java.text.ParseException=:
#+begin_src clojure
;; unrecognized format for java.util.Locale/GERMANY
(parse/parse-string "1,234.56 €" {:locale java.util.Locale/GERMANY})
;; Unhandled java.text.ParseException
;; Unparseable number: "1,234.56 €"

;; unrecognized currency for java.util.Locale/GERMANY
(parse/parse-string "1.234,56 £" {:locale java.util.Locale/GERMANY})
;; Unhandled java.text.ParseException
;; Unparseable number: "1.234,56 £"

;; unrecognized currency for any java.util.Locale
(parse/parse-string "1.234,56 ₿" {:locale java.util.Locale/GERMANY})
;; Unhandled java.text.ParseException
;; Unparseable number: "1.234,56 ₿"
#+end_src
*** Equality and comparison
You could use the following functions to do equality and comparison operations on monetary amounts: ~=~, =not==, =money<=, =money<==, =money>=, =money>==, =money-zero?=, =money-pos?=, and =money-neg?=.

For example:
#+begin_src clojure
(require '[dinero.core :as dinero]
'[dinero.math :as math])

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 1 :eur)]
(= m1 m2))
;; => true

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 1 :gbp)]
(= m1 m2))
;; => false

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 2 :eur)]
(not= m1 m2))
;; => true

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 2 :eur)]
(math/money< m1 m2))
;; => true

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 2 :eur)]
(math/money> m1 m2))
;; => false

(let [money (dinero/money-of 0 :eur)]
(math/money-zero? money))
;; => true

(let [money (dinero/money-of -1 :eur)]
(math/money-pos? money))
;; => false

(let [money (dinero/money-of -1 :eur)]
(math/money-neg? money))
;; => true
#+end_src
*** Arithmetic operations
You can use =add=, =substract=, =multiply=, and =divide= to perform arithmetic operations on monetary amounts:
#+begin_src clojure
(require '[dinero.core :as dinero]
'[dinero.math :as math])

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 1 :eur)]
(math/add m1 m2))
;; => {:amount 2M, :currency :eur}

(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 1 :eur)]
(math/subtract m1 m2))
;; => {:amount 0M, :currency :eur}

(let [money (dinero/money-of 1 :eur)
factor 2]
(math/multiply money factor))
;; => {:amount 2M, :currency :eur}

(let [money (dinero/money-of 2 :eur)
divisor 2]
(math/divide money divisor))
;; => {:amount 1M, :currency :eur}
#+end_src
Note that =add= and =substract= can be used to add and substract more than two monetary amounts:
#+begin_src clojure
(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 2 :eur)
m3 (dinero/money-of 3 :eur)]
(math/add m1 m2 m3))
;; => {:amount 6M, :currency :eur}

(let [m1 (dinero/money-of 3 :eur)
m2 (dinero/money-of 2 :eur)
m3 (dinero/money-of 1 :eur)]
(math/subtract m1 m2 m3))
;; => {:amount 0M, :currency :eur}
#+end_src
Adding or substracting monetary amounts with different currencies throws an =ExceptionInfo= exception:
#+begin_src clojure
(let [m1 (dinero/money-of 1 :eur)
m2 (dinero/money-of 1 :gbp)]
(math/add m1 m2))
;; clojure.lang.ExceptionInfo
;; Currencies do not match
;; {:currencies (:eur :gbp)}
#+end_src
*** Rounding
As previously mentioned, money amounts could be stored internally with more decimal places than the smallest unit of the currency. But some applications might require operating with amounts rounded to the smallest unit of currency. In such cases, you can use a monetary amount of type rounded, but you can also use the =round= function to adjust the monetary amount accordingly.

By default, the =round= function rounds amounts to the smallest unit of the currency, using the default rounding mode specified in the configuration file (if no rounding mode is configured, it defaults to =:half-even=):
#+begin_src clojure
(require '[dinero.core :as dinero]
'[dinero.math :as math]
'[dinero.rounding :as rounding])

(let [m1 (dinero/money-of 1.555 :eur)
m2 (dinero/money-of 1.555 :eur)]
(math/add m1 m2))
;; => {:amount 3.110M, :currency :eur}

(let [m1 (dinero/money-of 1.555 :eur)
m2 (dinero/money-of 1.555 :eur)
m1-rounded (rounding/round m1)
m2-rounded (rounding/round m2)]
(math/add m1-rounded m2-rounded))
;; => {:amount 3.12M, :currency :eur}
#+end_src
But you can also speficy the number of decimal places and the rounding mode you want to use when rounding. For example:
#+begin_src clojure
(let [m1 (dinero/money-of 1.555 :eur)
m2 (dinero/money-of 1.555 :eur)
m1-rounded (rounding/round m1 0 :half-even)
m2-rounded (rounding/round m2 0 :half-even)]
(math/add m1-rounded m2-rounded))
;; => {:amount 4M, :currency :eur}
#+end_src
If necessary, you can also call =round= with two arguments, which are the monetary amount and a custom rounding funtion to use to round the monetary amount. This allows you to specify different rounding rules for certain cases.

For example, the Swiss Franc (=CHF=) uses unique rounding rules because the smallest unit of currency in Switzerland is the 5-centime (=0.05 CHF=) coin. To handle the specific rounding requirements for Swiss Francs, you can use the =chf-rounding-fn= function, which containins a rounding function tailored to =CHF=:
#+begin_src clojure
(let [money (dinero/money-of 1.024 :chf)]
(rounding/round money rounding/chf-rounding-fn))
;; => {:amount 1.00M, :currency :chf}

(let [money (dinero/money-of 1.025 :chf)]
(rounding/round money rounding/chf-rounding-fn))
;; => {:amount 1.05M, :currency :chf}
#+end_src
This approach is also useful when formatting currencies with special rounding requirements. For instance, when formatting Swiss Francs, you might want to round the amount before using the =format= function to ensure the displayed value matches the currency's rounding conventions:
#+begin_src clojure
(let [money (dinero/money-of 1.025 :chf)]
(format/format-money money {:locale (java.util.Locale. "de" "CH")}))
;; => "CHF 1.02"

(let [money (dinero/money-of 1.025 :chf)
rounded-money (rounding/round money rounding/chf-rounding-fn)]
(format/format-money rounded-money {:locale (java.util.Locale. "de" "CH")}))
;; => "CHF 1.05"
#+end_src
*** Currency conversion
This library provides several functions to convert monetary amounts between currencies using various sources for exchange rates.

The simplest function is =convert-using-exchange-rate=, where you provide the exchange rate for the conversion:
#+begin_src clojure
(require '[dinero.core :as dinero]
'[dinero.conversion.core :as conversion])

(let [money (dinero/money-of 1 :eur)]
(conversion/convert-using-exchange-rate money :gbp 0.8))
;; => {:amount 0.8M, :currency :gbp}
#+end_src
In addition to this, you can use other functions designed for specific use cases, whether you're retrieving exchange rates from external providers or custom databases.

For example, to perform currency conversion using a database, use =convert-using-db=. This function requires, besides the monetary amount and target currency for the conversion (and optionally, the date), a database connection along with schema details (such as the table name, fields for the base and target currencies, the exchange rate field, and the date field if needed):
#+begin_src clojure
(require '[next.jdbc :as jdbc])

(defonce db (jdbc/get-datasource {:dbtype "h2:mem" :dbname "readme-db"}))
(jdbc/execute-one! db ["CREATE TABLE exchange_rate (from_currency VARCHAR(3), to_currency VARCHAR(3), rate DOUBLE, date DATE)"])
(jdbc/execute-one! db ["INSERT INTO exchange_rate (from_currency, to_currency, rate, date) VALUES ('EUR', 'GBP', 0.80, '2024-09-08')"])

(let [money (dinero/money-of 1 :eur)]
(conversion/convert-using-db money :gbp db "exchange_rate" "from_currency" "to_currency" "rate"))
;; => {:amount 0.8M, :currency :gbp}

(let [money (dinero/money-of 1 :eur)
date (java.time.LocalDate/parse "2024-09-08")]
(conversion/convert-using-db money :gbp date db "exchange_rate" "from_currency" "to_currency" "rate" "date"))
;; => {:amount 0.8M, :currency :gbp}
#+end_src
Additionally, you can retrieve exchange rates from external providers. Currently, the library supports exchange rates from the European Central Bank (ECB) for both current and historical (up to 90 days) data, as well as from Coinbase for both current and historical Bitcoin exchange rates:
#+begin_src clojure
(let [money (dinero/money-of 1 :eur)]
(conversion/convert-using-ecb money :gbp))
;; => {:amount 0.84375M, :currency :gbp}

(let [money (dinero/money-of 1 :eur)
date (java.time.LocalDate/of 2024 9 11)]
(conversion/convert-using-ecb money :gbp date))
;; => {:amount 0.84375M, :currency :gbp}

(let [money (dinero/money-of 1 :btc)]
(conversion/convert-using-coinbase money :eur))
;; => {:amount 52394.05M, :currency :eur}

(let [money (dinero/money-of 1 :btc)
date (java.time.LocalDate/of 2024 9 11)]
(conversion/convert-using-coinbase money :eur date))
;; => {:amount 52314.756527447545254192M, :currency :eur}
#+end_src
** License
Copyright © 2025 Sergio Navarro

Distributed under the [[https://www.mozilla.org/en-US/MPL/2.0/][Mozilla Public License, version 2.0]].