packages feed

casa-reoc-0.1.0.0: src/ReOC/Primitive/PhoneNumber.hs

{-# LANGUAGE DeriveLift #-}
{-# OPTIONS_GHC -Wall #-}

{- | An Australian phone number: 10 digits, starting with 0 (a mobile or
landline number with its area code), or with 13 or 18 (a 1300 or 1800
number).

The constructor is not exported (see "ReOC.Primitive.Text1"). A
@PhoneNumber@ is built with the prism '_PhoneNumber' from its digits, with
'parsePhoneNumber', which also accepts spaces, or with 'phoneNumberLiteral'.
-}
module ReOC.Primitive.PhoneNumber (
  PhoneNumber,
  _PhoneNumber,
  parsePhoneNumber,
  phoneNumberText,
  phoneNumberFormatted,
  phoneNumberLiteral,
) where

import Control.Lens (Prism', preview, prism')
import Data.Char (isDigit, isSpace)
import Data.Text (Text)
import qualified Data.Text as Text
import Language.Haskell.TH.Syntax (Code (..), Lift, Q, liftTyped)

{- $setup
>>> :set -XOverloadedStrings
>>> import Control.Lens ((^?))
-}

-- | An Australian phone number.
newtype PhoneNumber
  = PhoneNumber Text
  deriving (Eq, Ord, Show, Lift)

{- | A phone number from its 10 digits, with no spaces.

>>> "0491570156" ^? _PhoneNumber
Just (PhoneNumber "0491570156")

>>> "491570156" ^? _PhoneNumber
Nothing
-}
_PhoneNumber :: Prism' Text PhoneNumber
_PhoneNumber =
  prism'
    phoneNumberText
    ( \t ->
        let ds = Text.unpack t
         in if length ds == 10 && all isDigit ds && (take 1 ds == "0" || take 2 ds `elem` ["13", "18"])
              then Just (PhoneNumber t)
              else Nothing
    )

{- | A phone number from text that may have spaces between the digits.

>>> parsePhoneNumber "0491 570 156"
Just (PhoneNumber "0491570156")
-}
parsePhoneNumber :: Text -> Maybe PhoneNumber
parsePhoneNumber =
  preview _PhoneNumber . Text.filter (not . isSpace)

-- | The 10 digits of a phone number.
phoneNumberText :: PhoneNumber -> Text
phoneNumberText (PhoneNumber t) =
  t

{- | A phone number as it is usually written, with spaces: a mobile or a 1300
or 1800 number in groups of 4, 3 and 3 digits, and a landline as its area
code and two groups of 4 digits.

>>> phoneNumberFormatted <$> parsePhoneNumber "0491570156"
Just "0491 570 156"

>>> phoneNumberFormatted <$> parsePhoneNumber "0732101234"
Just "07 3210 1234"
-}
phoneNumberFormatted :: PhoneNumber -> Text
phoneNumberFormatted (PhoneNumber t) =
  let groups ns s =
        case ns of
          [] -> [s]
          n : rest -> let (a, b) = Text.splitAt n s in a : groups rest b
   in Text.unwords
        ( if Text.take 2 t `elem` map Text.pack ["02", "03", "07", "08"]
            then groups [2, 4] t
            else groups [4, 3] t
        )

{- | A @PhoneNumber@ literal, checked when the program is compiled. Spaces
are allowed:

> $$(phoneNumberLiteral "0491 570 156")
-}
phoneNumberLiteral :: String -> Code Q PhoneNumber
phoneNumberLiteral s =
  maybe
    (Code (fail ("phoneNumberLiteral: not an Australian phone number: " <> show s)))
    liftTyped
    (parsePhoneNumber (Text.pack s))