packages feed

currencies (empty) → 0.1.0.0

raw patch · 9 files changed

+417/−0 lines, 9 filesdep +basedep +currenciesdep +hspecsetup-changed

Dependencies added: base, currencies, hspec, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Daniel Campoverde [alx741] (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Daniel Campoverde [alx741] nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,3 @@+[![CircleCI](https://circleci.com/gh/alx741/currencies.svg?style=svg)](https://circleci.com/gh/alx741/currencies)++# currencies
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ currencies.cabal view
@@ -0,0 +1,45 @@+name:                currencies+version:             0.1.0.0+synopsis:            Currencies representation, pretty printing and conversion+description:         ISO 4217 Currencies representation, pretty printing and conversion:+    .+    * Represent monetary amouts of a particular currency in a type-safe manner+    .+    * Convert amounts between different currencies+    .+    * Print human readable amouts+homepage:            https://github.com/alx741/currencies#readme+license:             BSD3+license-file:        LICENSE+author:              Daniel Campoverde [alx741]+maintainer:          alx@sillybytes.net+copyright:           2017 Daniel Campoverde [alx741]+category:            Data+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Data.Currency.Currencies+                     , Data.Currency.Amounts+                     , Data.Currency.Pretty+  build-depends:       base >= 4.7 && < 5+                     , text >= 1.2 && < 2+  default-language:    Haskell2010++test-suite currencies-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Spec.hs+  other-modules:       Data.Currency.PrettySpec+  build-depends:       base+                     , currencies+                     , hspec >= 2.0 && < 3+                     , text >= 1.2 && < 2+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/alx741/currencies
+ src/Data/Currency/Amounts.hs view
@@ -0,0 +1,53 @@+-- | Operations upon monetary 'Amount's+--+-- Converting amounts+--+-- >>> convert USD (Amount EUR 23482.34)+-- Amount USD 27709.1612+--+-- >>>  prettyPrint $ convert USD (Amount EUR 23482.34)+-- "USD 27,709.16"+--+-- Comparing amounts+--+-- >>> compareAmounts  (Amount EUR 1000) (Amount BTC 1)+-- LT++module Data.Currency.Amounts+    ( -- * Monetary Amount+      Amount(..)++      -- * Converting+    , toUSD+    , fromUSD+    , convert++      -- * Comparing+    , compareAmounts++    , module Data.Currency.Currencies+    ) where++import Data.Currency.Currencies++-- | Monetary amounts+data Amount c = Amount c Double deriving (Show, Read, Eq)++instance (Currency c) => Ord (Amount c) where+    (Amount _ v1) <= (Amount _ v2) = v1 <= v2++-- | Convert an 'Amount' of an arbitrary currency to 'USD'+toUSD :: (Currency c) => Amount c -> Amount USD+toUSD (Amount c amount) = Amount USD $ amount * exchangeUSD c++-- | Convert an 'Amount' from 'USD' to a given 'Currency'+fromUSD :: (Currency c) => c -> Amount USD -> Amount c+fromUSD c (Amount USD amount) = Amount c $ amount / exchangeUSD c++-- | Convert an 'Amount' to a given 'Currency'+convert :: (Currency c', Currency c) => c' -> Amount c -> Amount c'+convert c' amount = fromUSD c' $ toUSD amount++-- | Compare two 'Amount's of arbitrary currencies+compareAmounts :: (Currency c1, Currency c2) => Amount c1 -> Amount c2 -> Ordering+compareAmounts a1 a2 = compare (toUSD a1) (toUSD a2)
+ src/Data/Currency/Currencies.hs view
@@ -0,0 +1,89 @@+-- | ISO 4217 compliant and other currencies++module Data.Currency.Currencies+    ( -- * Currency Class+      Currency(..)+    , CurrencyType(..)++      -- * Currencies+    , BTC(..)+    , CLP(..)+    , EUR(..)+    , USD(..)+    ) where++class (Show c, Eq c) => Currency c where+    currencyType :: c -> CurrencyType+    -- | ISO 4217 Currency Code+    isoCode :: c -> String+    -- | ISO 4217 Currency Numeric Code+    isoNumericCode :: c -> String+    -- | Number of digits after the decimal separator+    decimalDigits :: c -> Int+    -- | Currency UTF-8 symbol+    symbol :: c -> String+    -- | Exchange rate with US Dollar (USD)+    exchangeUSD :: c -> Double+    -- | ISO 3166-1 alpha-2 Country codes where the currency is used+    countries :: c -> [String]++data CurrencyType+  = Circulating -- ^ Currencies recognized as legal tender+  | Local -- ^ Currencies with validity only in particular geographical localities+  | Supranational -- ^ Currencies for procedural purposes and precious metals (X currencies)+  | Cryptocurrency -- ^ Digital, cryptography based currencies+  | Fictional -- ^ Currencies used in games, movies, novels, and other fictional setups+  deriving (Show, Read, Eq)+++-- | Bitcoin+data BTC = BTC deriving (Show, Read, Eq)++-- | Chilean Peso+data CLP = CLP deriving (Show, Read, Eq)++-- | European Union Euro+data EUR = EUR deriving (Show, Read, Eq)++-- | US Dollar+data USD = USD deriving (Show, Read, Eq)+++instance Currency BTC where+    currencyType _ = Cryptocurrency+    isoCode = show+    isoNumericCode _ = ""+    decimalDigits _ = 8+    symbol _ = "B"+    exchangeUSD _ = 4237.88+    countries _ = []++instance Currency CLP where+    currencyType _ = Circulating+    isoCode = show+    isoNumericCode _ = "152"+    decimalDigits _ = 0+    symbol _ = "$"+    exchangeUSD _ = 0.0015+    countries _ = ["CL"]++instance Currency EUR where+    currencyType _ = Circulating+    isoCode = show+    isoNumericCode _ = "978"+    decimalDigits _ = 2+    symbol _ = "€"+    exchangeUSD _ = 1.18+    countries _ = ["AD", "AT", "BE", "CY", "EE", "FI", "FR", "DE", "GR"+        , "GP", "IE", "IT", "LV", "LT", "LU", "MT", "MQ", "YT", "MC"+        , "ME", "NL", "PT", "RE", "BL", "PM", "SM", "SK", "SI", "ES"]++instance Currency USD where+    currencyType _ = Circulating+    isoCode = show+    isoNumericCode _ = "840"+    decimalDigits _ = 2+    symbol _ = "$"+    exchangeUSD _ = 1.0+    countries _ = ["US", "AS", "BB", "BM", "IO", "VG", "BQ", "EC", "MH"+        , "FM", "MP", "PW", "PA", "PR", "TL", "TC", "VI"]
+ src/Data/Currency/Pretty.hs view
@@ -0,0 +1,132 @@+-- | Pretty print monetary amounts like:+--+-- * /$ 34.50/+--+-- * /USD 3,456.29/+--+-- * /€ 32 433 938.23/+--+-- Using default printing settings+--+-- >>> prettyPrint (Amount USD 2342.2)+-- "USD 2,342.20"+-- >>> prettyPrint (Amount EUR 45827.346)+-- "EUR 45,827.35"+--+-- Using custom printing settings+--+-- >>> prettyPrintWith (defaultConfig { useCurrencySymbol = True }) (Amount USD 2342.2)+-- "$ 2,342.20"+-- >>> prettyPrintWith (defaultConfig { useCurrencySymbol = True }) (Amount EUR 2342.2)+-- "€ 2,342.20"+-- >>> prettyPrintWith (defaultConfig { showDecimals = False }) (Amount USD 25.50)+-- "USD 25"+--+-- For more printing settings see 'PrettyConfig'++module Data.Currency.Pretty+    ( -- * Pretty printing+      prettyPrint+    , prettyPrintWith++      -- * Configuration+    , PrettyConfig(..)+    , defaultConfig++    , module Data.Currency.Amounts+    ) where++import Text.Printf+import Data.Currency.Amounts+import Data.Monoid ((<>))++-- | Pretty print a monetary amount using 'defaultConfig'+prettyPrint :: (Currency c) => Amount c -> String+prettyPrint = prettyPrintWith defaultConfig++-- | Pretty print a monetary amount with a custom 'PrettyConfig' configuration+prettyPrintWith :: (Currency c) => PrettyConfig -> Amount c -> String+prettyPrintWith cnf (Amount currency amount) =+    prefixSymbol currency cnf+    $ prefixCode currency cnf+    $ changeDecimalSep currency cnf+    $ largeAmountSeparate currency cnf+    $ toDecimalString currency cnf amount++prefixSymbol :: (Currency c) => c -> PrettyConfig -> String -> String+prefixSymbol currency cnf val+    | useCurrencySymbol cnf = symbol currency <> " " <> val+    | otherwise = val++prefixCode :: (Currency c) => c -> PrettyConfig -> String -> String+prefixCode currency cnf val+    | useCurrencySymbol cnf = val+    | suffixIsoCode cnf = val <> " " <> isoCode currency+    | otherwise = isoCode currency <> " " <> val++changeDecimalSep :: (Currency c) => c -> PrettyConfig -> String -> String+changeDecimalSep currency cnf = replaceFst '.' (decimalSeparator cnf)+    where+        replaceFst :: Char -> Char -> String -> String+        replaceFst c c' [] = []+        replaceFst c c' (s:ss)+            | s == c = c' : ss+            | otherwise = s : replaceFst c c' ss++largeAmountSeparate :: (Currency c) => c -> PrettyConfig -> String -> String+largeAmountSeparate currency cnf amount+    | compactFourDigitAmounts cnf = if length integer <= 4 then amount else separated ++ decimal+    | otherwise = separated ++ decimal+    where+        (integer, decimal) = span (/= '.') amount+        separated = reverse $ intersperseN 3 (largeAmountSeparator cnf) $ reverse integer++toDecimalString :: (Currency c) => c -> PrettyConfig -> Double -> String+toDecimalString currency cnf amount+    | showDecimals cnf = printf format amount+    | otherwise = takeWhile (/= '.') $ printf "%.1f" amount+    where format = "%." <> show (decimalDigits currency) <> "f"++intersperseN :: Eq a => Int -> a -> [a] -> [a]+intersperseN n s ss+    | null remainder = ss+    | otherwise = (take ++ [s]) ++ intersperseN n s remainder+    where (take, remainder) = splitAt n ss+++data PrettyConfig = PrettyConfig+    { showDecimals :: Bool+    -- | Print four digits amounts as+    -- /USD 1000,00/ instead of /USD 1,000.00/+    , compactFourDigitAmounts :: Bool+    -- | Replace the currency ISO code with its symbol to produce+    -- /$ 23.50/ instead of /USD 23.50/+    , useCurrencySymbol :: Bool+    -- | Use the currency ISO code as suffix to produce+    -- /23.50 USD/ instead of /USD 23.50/+    , suffixIsoCode :: Bool+    , largeAmountSeparator :: Char+    , decimalSeparator :: Char+    } deriving (Show)+++-- | Default 'PrettyConfig' used in 'prettyPrint'+--+-- * Show decimals+--+-- * Compact four digit amounts+--+-- * Use ISO code+--+-- * Separate large amounts with comma+--+-- * Separate decimals with dot+defaultConfig :: PrettyConfig+defaultConfig = PrettyConfig+    { showDecimals = True+    , compactFourDigitAmounts = True+    , useCurrencySymbol = False+    , suffixIsoCode = False+    , largeAmountSeparator = ','+    , decimalSeparator = '.'+    }
+ test/Data/Currency/PrettySpec.hs view
@@ -0,0 +1,62 @@+module Data.Currency.PrettySpec where++import Test.Hspec+import Data.Currency.Pretty+import Data.Currency.Amounts++spec :: Spec+spec = do+    describe "prettyPrint" $ do+        it "prints human readable monetary amounts" $ do+            prettyPrint (Amount USD 23.50)  `shouldBe` "USD 23.50"+            prettyPrint (Amount USD 540.256)  `shouldBe` "USD 540.26"+            prettyPrint (Amount USD 300.251)  `shouldBe` "USD 300.25"+            prettyPrint (Amount USD 3.4)  `shouldBe` "USD 3.40"+            prettyPrint (Amount EUR 15.589)  `shouldBe` "EUR 15.59"++        it "respects currency decimal digits" $ do+            prettyPrint (Amount CLP 345.35)  `shouldBe` "CLP 345"+            prettyPrint (Amount CLP 23.53)  `shouldBe` "CLP 24"++        it "can use the currency code as suffix" $ do+            let config = defaultConfig { suffixIsoCode = True }+            prettyPrintWith config (Amount USD 23.50)  `shouldBe` "23.50 USD"+            prettyPrintWith config (Amount CLP 345.35)  `shouldBe` "345 CLP"++        it "can omit decimals" $ do+            let config = defaultConfig { showDecimals = False }+            prettyPrintWith config (Amount USD 23.50)  `shouldBe` "USD 23"+            prettyPrintWith config (Amount USD 534.25)  `shouldBe` "USD 534"+            prettyPrintWith config (Amount EUR 15.589)  `shouldBe` "EUR 15"++        it "can use the currency symbol" $ do+            let config = defaultConfig { useCurrencySymbol = True }+            prettyPrintWith config (Amount USD 23.50)  `shouldBe` "$ 23.50"+            prettyPrintWith config (Amount EUR 15.589)  `shouldBe` "€ 15.59"+            prettyPrintWith config (Amount BTC 0.5898)  `shouldBe` "B 0.58980000"++        it "can use a custom decimal separator" $ do+            let config = defaultConfig { decimalSeparator = ',' }+            prettyPrintWith config (Amount USD 23.50)  `shouldBe` "USD 23,50"+            prettyPrintWith config (Amount EUR 15.589)  `shouldBe` "EUR 15,59"+            prettyPrintWith config (Amount USD 23.0)  `shouldBe` "USD 23,00"++        it "uses large amounts separators" $ do+            prettyPrint (Amount USD 32323.50)  `shouldBe` "USD 32,323.50"+            prettyPrint (Amount EUR 3827115.259)  `shouldBe` "EUR 3,827,115.26"++        it "can use a custom large amounts separator" $ do+            let config = defaultConfig { largeAmountSeparator = ' ' }+            prettyPrintWith config (Amount USD 32323.50)  `shouldBe` "USD 32 323.50"+            prettyPrintWith config (Amount EUR 3827115.259)  `shouldBe` "EUR 3 827 115.26"++        it "can avoid separating 4 digit amounts" $ do+            prettyPrint (Amount USD 2323.50)  `shouldBe` "USD 2323.50"+            prettyPrint (Amount EUR 4629.25)  `shouldBe` "EUR 4629.25"+            prettyPrint (Amount USD 23875.00)  `shouldBe` "USD 23,875.00"++        it "can force 4-digit amounts separation" $ do+            let config = defaultConfig { compactFourDigitAmounts = False}+            prettyPrintWith config (Amount USD 2323.50)  `shouldBe` "USD 2,323.50"+            prettyPrintWith config (Amount EUR 4629.25)  `shouldBe` "EUR 4,629.25"+            prettyPrintWith config (Amount USD 23875.00)  `shouldBe` "USD 23,875.00"
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}