dojang-0.2.0: src/Dojang/Types/MonikerName.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedRecordUpdate #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use guards" #-}
module Dojang.Types.MonikerName
( MonikerName
, MonikerNameError (..)
, parseMonikerName
) where
import Data.CaseInsensitive (CI, mk)
import Data.Char (isAlpha, isAlphaNum)
import Data.Hashable (Hashable)
import Data.Text (Text, findIndex, head, null)
import GHC.Records (HasField (getField))
import Prelude hiding (head, null)
-- $setup
-- >>> :seti -XOverloadedStrings -XOverloadedRecordDot
-- | A moniker name. The name must start with a letter and contain only
-- letters, digits, hyphens, and underscores (no spaces, no special characters).
-- Non-Latin letters and digits are allowed too. The name must not be empty.
--
-- >>> parseMonikerName "foo-bar"
-- Right (MonikerName "foo-bar")
-- >>> parseMonikerName "甲乙_丙丁"
-- Right (MonikerName "甲乙_丙丁")
--
-- Case insensitive, but the original case is preserved:
--
-- >>> parseMonikerName "FOO-BAR" == parseMonikerName "foo-bar"
-- True
-- >>> parseMonikerName "GIÁP_ẤT" == parseMonikerName "giáp_ất" -- cSpell:disable-line
-- True
newtype MonikerName = MonikerName (CI Text) deriving (Eq, Ord, Hashable, Show)
-- | A record field named @name@ for 'MonikerName':
--
-- >>> let (Right monikerName) = parseMonikerName "foo-bar"
-- >>> monikerName.name
-- "foo-bar"
instance HasField "name" MonikerName (CI Text) where
getField (MonikerName name) = name
-- | Errors that can occur when parsing a moniker name.
data MonikerNameError
= -- | The moniker name is empty.
Empty
| -- | The moniker name starts with a non-letter character.
StartingWithNonLetter
| -- | The moniker name contains an invalid character at the given index.
HavingInvalidCharacter Int
deriving (Eq, Show, Read)
-- | Parse a moniker name.
--
-- >>> parseMonikerName "foo-bar"
-- Right (MonikerName "foo-bar")
-- >>> parseMonikerName "1"
-- Left StartingWithNonLetter
-- >>> parseMonikerName "foo bar"
-- Left (HavingInvalidCharacter 3)
-- >>> parseMonikerName "甲乙_丙丁"
-- Right (MonikerName "甲乙_丙丁")
-- >>> parseMonikerName ""
-- Left Empty
parseMonikerName :: Text -> Either MonikerNameError MonikerName
parseMonikerName name =
if null name
then Left Empty
else
if isAlpha $ head name
then case findIndex isInvalidChar name of
Nothing -> Right $ MonikerName $ mk name
Just i -> Left $ HavingInvalidCharacter i
else Left StartingWithNonLetter
where
isInvalidChar :: Char -> Bool
isInvalidChar c = not $ isAlphaNum c || c == '-' || c == '_'