packages feed

lambdabot-telegram-plugins-0.2.0: src/Lambdabot/Plugin/Telegram/Bot/Generic.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE EmptyCase #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
module Lambdabot.Plugin.Telegram.Bot.Generic where

import Data.Char
import Data.Text (Text)
import Data.Proxy
import qualified Data.Text as Text
import GHC.Generics
import GHC.TypeLits

import Lambdabot.Plugin.Telegram.Shared

-- | Helper type class used to derive 'FromCommand' via DeriveGeneric extension.
class GFromCommand command where
  gGetMessage :: command proxy -> Msg
  gGetPrefix :: command proxy -> Text

-- Empty data decl
instance GFromCommand V1 where
  gGetMessage x = case x of { }
  gGetPrefix  x = case x of { } 

instance (FromCommand c) => GFromCommand (K1 i c) where
  gGetMessage (K1 x) = getMessage x
  gGetPrefix (K1 x) = getPrefix x

instance (Constructor t, GFromCommand f) => GFromCommand (M1 C t f) where
  gGetMessage (M1 x) = gGetMessage x
  gGetPrefix m@(M1 _) = Text.cons '@' $ toKebabCase $ Text.pack $ conName m

instance (GFromCommand f) => GFromCommand (M1 S t f) where
  gGetMessage (M1 x) = gGetMessage x
  gGetPrefix (M1 x) = gGetPrefix x

instance (GFromCommand f) => GFromCommand (M1 D t f) where
  gGetMessage (M1 x) = gGetMessage x
  gGetPrefix (M1 x) = gGetPrefix x

instance (GFromCommand f, GFromCommand g) => GFromCommand (f :+: g) where
  gGetMessage (L1 x) = gGetMessage x
  gGetMessage (R1 x) = gGetMessage x

  gGetPrefix (L1 x) = gGetPrefix x
  gGetPrefix (R1 x) = gGetPrefix x

instance (GFromCommand f, GFromCommand g) => GFromCommand (f :*: g) where
  gGetMessage (x :*: _y) = gGetMessage x
  gGetPrefix  (x :*: _y) = gGetPrefix  x

class FromCommand command where
  getMessage :: command -> Msg

  default getMessage :: (Generic command, GFromCommand (Rep command)) => command -> Msg
  getMessage x = gGetMessage (from x)

  getPrefix :: command -> Text

  default getPrefix :: (Generic command, GFromCommand (Rep command)) => command -> Text
  getPrefix x = gGetPrefix (from x)

-- | Type class to identify the essence of incoming command and transform it to 'Msg' transport type.
instance FromCommand Msg where
  getMessage = id
  getPrefix = const ""

-- | Transform incoming telegram command into 'Msg'.
fromCommand :: FromCommand command => command -> Msg
fromCommand cmd = old { msgMessage = getPrefix cmd <> " " <> msgMessage old }
    where
      old = getMessage cmd

-- ** Helpers

-- | Helper to transform text into kebab case.
toKebabCase :: Text -> Text
toKebabCase txt =
  let str = Text.unpack txt
      uppers = isUpper <$> str
      indices = zip [0..] uppers :: [(Int, Bool)]
      onlyUpperIndices = filter (/= 0) $ fmap fst $ filter snd indices
      go ix txt' =
        let (begin, end) = Text.splitAt ix txt'
        in Text.concat [ begin, "-", Text.toLower end ]
  in Text.toLower $ foldr go txt onlyUpperIndices

data MaybeWith (modifier :: Modifier) a = MaybeWith a

data Modifier = AtEnd Symbol

instance (KnownSymbol postfix, FromCommand a, modifier ~ 'AtEnd postfix) =>
  FromCommand (MaybeWith modifier a) where

    getMessage (MaybeWith x) = getMessage x
    getPrefix (MaybeWith x) = getPrefix x <> Text.pack (symbolVal (Proxy @postfix))