packages feed

bbcode-0.1.0.0: lib/Text/BBCode/Internal/Types.hs

{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}

module Text.BBCode.Internal.Types where

import Control.Lens hiding (List)
import Data.String (IsString (..))
import Data.Text (Text)

-- |BBCode AST
data BBCode where
  -- |Element that has neither closing part nor arguments
  --
  -- @'ElVoid' 'HR'@ represents "[hr]"
  ElVoid :: El -> BBCode
  -- |Element that has closing part but no arguments
  --
  -- @'ElSimple' 'Bold' ('ElText' "abc")@ represents @"[b]abc[/b]"@
  ElSimple :: El -> BBCode -> BBCode
  -- |Element that has closing part and exactly one argument
  --
  -- @'ElArg' 'Color' "red" ('ElText' "I am red")@ represents @"[color=red]I am red[/color]"@
  ElArg :: El -> Text -> BBCode -> BBCode
  -- |Plain text
  --
  -- @'ElText' "I am text" represents @"I am text"@
  ElText :: Text -> BBCode
  -- |Just a list of BBCode elements, just as type signature says.
  --
  -- @'ElDocument' ['ElSimple' 'Bold' ('ElText' "Bold not bald"), 'ElText' "legit text"]@
  -- represents @"[b]Bold not bald[/b]legit text"@
  ElDocument :: [BBCode] -> BBCode
  deriving
    ( Eq
    , -- |Don't use it to render BBCode!
      Show
    )

instance Semigroup BBCode where
  (<>) :: BBCode -> BBCode -> BBCode
  (<>) (ElText x) (ElText y) = ElText $ x <> y
  (<>) (ElText "") y = y
  (<>) x (ElText "") = x
  (<>) (ElDocument xs) (ElDocument ys) = ElDocument $ xs <> ys
  (<>) (ElDocument xs) y = ElDocument $ xs <> [y]
  (<>) x (ElDocument ys) = ElDocument $ [x] <> ys
  (<>) x y = ElDocument [x, y]
  {-# INLINEABLE (<>) #-}

{- |
Identity is defined as zero-width Text

>>> mempty :: BBCode
ElText ""
-}
instance Monoid BBCode where
  mempty :: BBCode
  mempty = ElText ""
  {-# INLINE mempty #-}

{- |
Allows easier BBCode construction

>>> ElSimple Bold "I am bold"
ElSimple Bold (ElText "I am bold")

Recall that 'ElSimple' takes 'El' and 'BBCode', but not 'Text'
-}
instance IsString BBCode where
  fromString :: String -> BBCode
  fromString = ElText . fromString
  {-# INLINE fromString #-}

{- ORMOLU_DISABLE -}
{- |
Type of an element. 'BBCode' declares three constructors for elements: 'ElVoid', 'ElSimple', 'ElArg'. But 'El' can be split into four cateogories:
1) void elements,
2) simple elements,
3) elements with optional argument,
4) element with one argument

There is no enforcement of combining 'BBCode' constuctors and 'El' values, that means you can create @ElSimple HR (ElText "smth")@ but that would make no sense. 'Text.BBCode.pretty' would emit a runtime error if you try to pass such value.
-}
data El
  = HR            -- ^ void element
  | BR            -- ^ void element
  | Clear         -- ^ void element
  | ListElement   -- ^ void element

  | Bold          -- ^ simple element
  | Italic        -- ^ simple element
  | Underline     -- ^ simple element
  | Strikethrough -- ^ simple element
  | Indent        -- ^ simple element
  | NFO           -- ^ simple element
  | Oneline       -- ^ simple element
  | Code          -- ^ simple element
  | Preformatted  -- ^ simple element

  | Box           -- ^ element with optional argument
  | Image         -- ^ element with optional argument
  | Quote         -- ^ element with optional argument
  | Spoiler       -- ^ element with optional argument
  | List          -- ^ element with optional argument

  | Color         -- ^ element with one argument
  | URL           -- ^ element with one argument
  | Size          -- ^ element with one argument
  | Align         -- ^ element with one argument
  | Font          -- ^ element with one argument
  deriving
    ( Eq
    , Ord -- ^ Allows having 'Data.Map.Map' with key type 'El' (used in Parser)
    , Show
    , Enum -- ^ Allows internal module to quickly list elements of different categories (Void, Simple, Optional, One)
    , Bounded -- ^ Addition to Enum instance
    )
{- ORMOLU_ENABLE -}

{- |
Used for building BBCode safely. Instead of passing 'Text' to builder functions,
you pass values of types that implement 'IsArgument'. Then 'toArgument' is used
to convert that value to Text.
-}
class IsArgument a where
  toArgument :: a -> Text

-- | Argument to 'Text.BBCode.align'
data AlignPosition where
  AlignLeft :: AlignPosition
  AlignRight :: AlignPosition
  AlignCenter :: AlignPosition
  AlignJustify :: AlignPosition
  deriving (Eq, Show)

instance IsArgument AlignPosition where
  toArgument :: AlignPosition -> Text
  toArgument AlignLeft = "left"
  toArgument AlignRight = "right"
  toArgument AlignCenter = "center"
  toArgument AlignJustify = "justify"
  {-# INLINEABLE toArgument #-}

-- | Argument to 'Text.BBCode.listFlavor'
data ListFlavor where
  -- | Numeric 1, 2, 3,..
  Roman :: ListFlavor
  -- | Arabic I, II, III,..
  ArabicUpper :: ListFlavor
  -- | Arabic i, ii, iii,..
  ArabicLower :: ListFlavor
  -- | Alphabetical A, B, C,..
  LatinUpper :: ListFlavor
  -- | Alphabetical a, b, c,..
  LatinLower :: ListFlavor
  deriving (Eq, Show)

instance IsArgument ListFlavor where
  toArgument :: ListFlavor -> Text
  toArgument Roman = "1"
  toArgument ArabicUpper = "I"
  toArgument ArabicLower = "i"
  toArgument LatinUpper = "A"
  toArgument LatinLower = "a"
  {-# INLINEABLE toArgument #-}

-- | Argument to 'Text.BBCode.imageAlign'
data ImagePosition where
  ImageLeft :: ImagePosition
  ImageRight :: ImagePosition
  deriving (Eq, Show)

instance IsArgument ImagePosition where
  toArgument :: ImagePosition -> Text
  toArgument ImageLeft = "left"
  toArgument ImageRight = "right"
  {-# INLINEABLE toArgument #-}

-- | Argument to 'Text.BBCode.boxAlign'
data BoxPosition where
  BoxLeft :: BoxPosition
  BoxCenter :: BoxPosition
  BoxRight :: BoxPosition
  deriving (Eq, Show)

instance IsArgument BoxPosition where
  toArgument :: BoxPosition -> Text
  toArgument BoxLeft = "left"
  toArgument BoxCenter = "center"
  toArgument BoxRight = "right"
  {-# INLINEABLE toArgument #-}

makePrisms ''BBCode
makePrisms ''AlignPosition
makePrisms ''ListFlavor
makePrisms ''ImagePosition
makePrisms ''El