packages feed

calamity 0.1.9.1 → 0.1.9.2

raw patch · 13 files changed

+389/−200 lines, 13 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Calamity.Commands: ($sel:msg:ParserState) :: ParserState -> Text
- Calamity.Commands: ($sel:off:ParserState) :: ParserState -> Int
- Calamity.Commands: class Typeable a => Parser (a :: Type) r where {
- Calamity.Commands: data KleeneConcat a
- Calamity.Commands: data Named (s :: Symbol) a
- Calamity.Commands: name :: Parser a r => Text
- Calamity.Commands: parse :: Parser a r => Sem (ParserEffs r) (ParserResult a)
- Calamity.Commands: type ParserResult a = a;
- Calamity.Commands: type family ParserResult a;
- Calamity.Commands: }
- Calamity.Commands.Handler: addCommands :: (BotC r, Member ParsePrefix r) => Sem (DSLState r) a -> Sem r (Sem r (), CommandHandler, a)
- Calamity.Commands.Handler: buildCommands :: Member (Final IO) r => Sem (DSLState r) a -> Sem r (CommandHandler, a)
- Calamity.Commands.Handler: buildContext :: BotC r => Message -> Text -> Command -> Text -> Sem r (Maybe Context)
- Calamity.Commands.LocalWriter: [Llisten] :: m a -> LocalWriter o m (o, a)
- Calamity.Commands.LocalWriter: [Ltell] :: o -> LocalWriter o m ()
- Calamity.Commands.LocalWriter: data LocalWriter o m a
- Calamity.Commands.LocalWriter: llisten :: forall o_amon r_amRO a_Xmon. MemberWithError (LocalWriter o_amon) r_amRO => Sem r_amRO a_Xmon -> Sem r_amRO (o_amon, a_Xmon)
- Calamity.Commands.LocalWriter: ltell :: forall o_amoj r_amRM. MemberWithError (LocalWriter o_amoj) r_amRM => o_amoj -> Sem r_amRM ()
- Calamity.Commands.LocalWriter: runLocalWriter :: Monoid o => Sem (LocalWriter o : r) a -> Sem r (o, a)
- Calamity.Commands.Parser: ParserState :: Int -> Text -> ParserState
- Calamity.Commands.Parser: SpannedError :: Text -> !Int -> !Int -> SpannedError
- Calamity.Commands.Parser: [$sel:msg:ParserState] :: ParserState -> Text
- Calamity.Commands.Parser: [$sel:off:ParserState] :: ParserState -> Int
- Calamity.Commands.Parser: data ParserState
- Calamity.Commands.Parser: data SpannedError
- Calamity.Commands.Parser: name :: Parser a r => Text
+ Calamity.Commands.Command: [$sel:params:Command] :: Command -> [Text]
+ Calamity.Commands.CommandUtils: instance (Calamity.Commands.Parser.Parser x r, Calamity.Commands.CommandUtils.ParamNamesForParsers xs r) => Calamity.Commands.CommandUtils.ParamNamesForParsers (x : xs) r
+ Calamity.Commands.CommandUtils: instance forall k (r :: k). Calamity.Commands.CommandUtils.ParamNamesForParsers '[] r
+ Calamity.Commands.Group: instance GHC.Generics.Generic Calamity.Commands.Group.GroupS
+ Calamity.Commands.Group: instance GHC.Show.Show Calamity.Commands.Group.Group
+ Calamity.Commands.Group: instance GHC.Show.Show Calamity.Commands.Group.GroupS
+ Calamity.Commands.Group: instance TextShow.Classes.TextShow Calamity.Commands.Group.Group
+ Calamity.Commands.Group: instance TextShow.Classes.TextShow Calamity.Commands.Group.GroupS
+ Calamity.Commands.Handler: instance GHC.Show.Show Calamity.Commands.Handler.CommandHandler
+ Calamity.Commands.Handler: instance TextShow.Classes.TextShow Calamity.Commands.Handler.CommandHandler
+ Calamity.Commands.Help: helpCommand :: BotC r => Sem (DSLState r) Command
+ Calamity.Commands.Help: helpCommand' :: BotC r => CommandHandler -> Maybe Group -> [Check] -> Sem r Command
+ Calamity.Commands.Parser: instance Calamity.Commands.Parser.Parser (Calamity.Commands.Parser.KleeneConcat Data.Text.Internal.Text) r
+ Calamity.Commands.Parser: instance Calamity.Commands.Parser.Parser Data.Text.Internal.Text r
+ Calamity.Commands.Parser: parserName :: Parser a r => Text
+ Calamity.Commands.Parser: runCommandParser :: Context -> Text -> Sem (ParserEffs r) a -> Sem r (Either (Text, Text) a)
+ Calamity.Commands.Utils: addCommands :: (BotC r, Member ParsePrefix r) => Sem (DSLState r) a -> Sem r (Sem r (), CommandHandler, a)
+ Calamity.Commands.Utils: buildCommands :: forall r a. Member (Final IO) r => Sem (DSLState r) a -> Sem r (CommandHandler, a)
+ Calamity.Commands.Utils: buildContext :: BotC r => Message -> Text -> Command -> Text -> Sem r (Maybe Context)
+ Calamity.Internal.LocalWriter: [Llisten] :: m a -> LocalWriter o m (o, a)
+ Calamity.Internal.LocalWriter: [Ltell] :: o -> LocalWriter o m ()
+ Calamity.Internal.LocalWriter: data LocalWriter o m a
+ Calamity.Internal.LocalWriter: llisten :: forall o_aC49 r_aCgk a_XC49. MemberWithError (LocalWriter o_aC49) r_aCgk => Sem r_aCgk a_XC49 -> Sem r_aCgk (o_aC49, a_XC49)
+ Calamity.Internal.LocalWriter: ltell :: forall o_aC45 r_aCgi. MemberWithError (LocalWriter o_aC45) r_aCgi => o_aC45 -> Sem r_aCgi ()
+ Calamity.Internal.LocalWriter: runLocalWriter :: Monoid o => Sem (LocalWriter o : r) a -> Sem r (o, a)
- Calamity.Cache.Eff: delDM :: forall r_a9Joq. MemberWithError CacheEff r_a9Joq => Snowflake DMChannel -> Sem r_a9Joq ()
+ Calamity.Cache.Eff: delDM :: forall r_a9JOE. MemberWithError CacheEff r_a9JOE => Snowflake DMChannel -> Sem r_a9JOE ()
- Calamity.Cache.Eff: delGuild :: forall r_a9Joj. MemberWithError CacheEff r_a9Joj => Snowflake Guild -> Sem r_a9Joj ()
+ Calamity.Cache.Eff: delGuild :: forall r_a9JOx. MemberWithError CacheEff r_a9JOx => Snowflake Guild -> Sem r_a9JOx ()
- Calamity.Cache.Eff: delMessage :: forall r_a9JoL. MemberWithError CacheEff r_a9JoL => Snowflake Message -> Sem r_a9JoL ()
+ Calamity.Cache.Eff: delMessage :: forall r_a9JOZ. MemberWithError CacheEff r_a9JOZ => Snowflake Message -> Sem r_a9JOZ ()
- Calamity.Cache.Eff: delUnavailableGuild :: forall r_a9JoE. MemberWithError CacheEff r_a9JoE => Snowflake Guild -> Sem r_a9JoE ()
+ Calamity.Cache.Eff: delUnavailableGuild :: forall r_a9JOS. MemberWithError CacheEff r_a9JOS => Snowflake Guild -> Sem r_a9JOS ()
- Calamity.Cache.Eff: delUser :: forall r_a9Jox. MemberWithError CacheEff r_a9Jox => Snowflake User -> Sem r_a9Jox ()
+ Calamity.Cache.Eff: delUser :: forall r_a9JOL. MemberWithError CacheEff r_a9JOL => Snowflake User -> Sem r_a9JOL ()
- Calamity.Cache.Eff: getBotUser :: forall r_a9Jod. MemberWithError CacheEff r_a9Jod => Sem r_a9Jod (Maybe User)
+ Calamity.Cache.Eff: getBotUser :: forall r_a9JOr. MemberWithError CacheEff r_a9JOr => Sem r_a9JOr (Maybe User)
- Calamity.Cache.Eff: getDM :: forall r_a9Jon. MemberWithError CacheEff r_a9Jon => Snowflake DMChannel -> Sem r_a9Jon (Maybe DMChannel)
+ Calamity.Cache.Eff: getDM :: forall r_a9JOB. MemberWithError CacheEff r_a9JOB => Snowflake DMChannel -> Sem r_a9JOB (Maybe DMChannel)
- Calamity.Cache.Eff: getDMs :: forall r_a9Jop. MemberWithError CacheEff r_a9Jop => Sem r_a9Jop [DMChannel]
+ Calamity.Cache.Eff: getDMs :: forall r_a9JOD. MemberWithError CacheEff r_a9JOD => Sem r_a9JOD [DMChannel]
- Calamity.Cache.Eff: getGuild :: forall r_a9Jog. MemberWithError CacheEff r_a9Jog => Snowflake Guild -> Sem r_a9Jog (Maybe Guild)
+ Calamity.Cache.Eff: getGuild :: forall r_a9JOu. MemberWithError CacheEff r_a9JOu => Snowflake Guild -> Sem r_a9JOu (Maybe Guild)
- Calamity.Cache.Eff: getGuilds :: forall r_a9Joi. MemberWithError CacheEff r_a9Joi => Sem r_a9Joi [Guild]
+ Calamity.Cache.Eff: getGuilds :: forall r_a9JOw. MemberWithError CacheEff r_a9JOw => Sem r_a9JOw [Guild]
- Calamity.Cache.Eff: getMessage :: forall r_a9JoI. MemberWithError CacheEff r_a9JoI => Snowflake Message -> Sem r_a9JoI (Maybe Message)
+ Calamity.Cache.Eff: getMessage :: forall r_a9JOW. MemberWithError CacheEff r_a9JOW => Snowflake Message -> Sem r_a9JOW (Maybe Message)
- Calamity.Cache.Eff: getMessages :: forall r_a9JoK. MemberWithError CacheEff r_a9JoK => Sem r_a9JoK [Message]
+ Calamity.Cache.Eff: getMessages :: forall r_a9JOY. MemberWithError CacheEff r_a9JOY => Sem r_a9JOY [Message]
- Calamity.Cache.Eff: getUnavailableGuilds :: forall r_a9JoD. MemberWithError CacheEff r_a9JoD => Sem r_a9JoD [Snowflake Guild]
+ Calamity.Cache.Eff: getUnavailableGuilds :: forall r_a9JOR. MemberWithError CacheEff r_a9JOR => Sem r_a9JOR [Snowflake Guild]
- Calamity.Cache.Eff: getUser :: forall r_a9Jou. MemberWithError CacheEff r_a9Jou => Snowflake User -> Sem r_a9Jou (Maybe User)
+ Calamity.Cache.Eff: getUser :: forall r_a9JOI. MemberWithError CacheEff r_a9JOI => Snowflake User -> Sem r_a9JOI (Maybe User)
- Calamity.Cache.Eff: getUsers :: forall r_a9Jow. MemberWithError CacheEff r_a9Jow => Sem r_a9Jow [User]
+ Calamity.Cache.Eff: getUsers :: forall r_a9JOK. MemberWithError CacheEff r_a9JOK => Sem r_a9JOK [User]
- Calamity.Cache.Eff: isUnavailableGuild :: forall r_a9JoB. MemberWithError CacheEff r_a9JoB => Snowflake Guild -> Sem r_a9JoB Bool
+ Calamity.Cache.Eff: isUnavailableGuild :: forall r_a9JOP. MemberWithError CacheEff r_a9JOP => Snowflake Guild -> Sem r_a9JOP Bool
- Calamity.Cache.Eff: setBotUser :: forall r_a9Job. MemberWithError CacheEff r_a9Job => User -> Sem r_a9Job ()
+ Calamity.Cache.Eff: setBotUser :: forall r_a9JOp. MemberWithError CacheEff r_a9JOp => User -> Sem r_a9JOp ()
- Calamity.Cache.Eff: setDM :: forall r_a9Jol. MemberWithError CacheEff r_a9Jol => DMChannel -> Sem r_a9Jol ()
+ Calamity.Cache.Eff: setDM :: forall r_a9JOz. MemberWithError CacheEff r_a9JOz => DMChannel -> Sem r_a9JOz ()
- Calamity.Cache.Eff: setGuild :: forall r_a9Joe. MemberWithError CacheEff r_a9Joe => Guild -> Sem r_a9Joe ()
+ Calamity.Cache.Eff: setGuild :: forall r_a9JOs. MemberWithError CacheEff r_a9JOs => Guild -> Sem r_a9JOs ()
- Calamity.Cache.Eff: setMessage :: forall r_a9JoG. MemberWithError CacheEff r_a9JoG => Message -> Sem r_a9JoG ()
+ Calamity.Cache.Eff: setMessage :: forall r_a9JOU. MemberWithError CacheEff r_a9JOU => Message -> Sem r_a9JOU ()
- Calamity.Cache.Eff: setUnavailableGuild :: forall r_a9Joz. MemberWithError CacheEff r_a9Joz => Snowflake Guild -> Sem r_a9Joz ()
+ Calamity.Cache.Eff: setUnavailableGuild :: forall r_a9JON. MemberWithError CacheEff r_a9JON => Snowflake Guild -> Sem r_a9JON ()
- Calamity.Cache.Eff: setUser :: forall r_a9Jos. MemberWithError CacheEff r_a9Jos => User -> Sem r_a9Jos ()
+ Calamity.Cache.Eff: setUser :: forall r_a9JOG. MemberWithError CacheEff r_a9JOG => User -> Sem r_a9JOG ()
- Calamity.Commands.Command: Command :: Text -> Maybe Group -> [Check] -> (Context -> Text) -> (Context -> IO (Either CommandError a)) -> ((Context, a) -> IO (Maybe Text)) -> Command
+ Calamity.Commands.Command: Command :: Text -> Maybe Group -> [Check] -> [Text] -> (Context -> Text) -> (Context -> IO (Either CommandError a)) -> ((Context, a) -> IO (Maybe Text)) -> Command
- Calamity.Commands.CommandUtils: buildCommand :: forall ps a r. (Member (Final IO) r, TypedCommandC ps a r) => Text -> Maybe Group -> [Check] -> (Context -> Text) -> (Context -> CommandForParsers ps r) -> Sem r Command
+ Calamity.Commands.CommandUtils: buildCommand :: forall ps r. (Member (Final IO) r, TypedCommandC ps r) => Text -> Maybe Group -> [Check] -> (Context -> Text) -> (Context -> CommandForParsers ps r) -> Sem r Command
- Calamity.Commands.CommandUtils: buildCommand' :: Member (Final IO) r => Text -> Maybe Group -> [Check] -> (Context -> Text) -> (Context -> Sem r (Either CommandError a)) -> ((Context, a) -> Sem (Fail : r) ()) -> Sem r Command
+ Calamity.Commands.CommandUtils: buildCommand' :: Member (Final IO) r => Text -> Maybe Group -> [Check] -> [Text] -> (Context -> Text) -> (Context -> Sem r (Either CommandError a)) -> ((Context, a) -> Sem (Fail : r) ()) -> Sem r Command
- Calamity.Commands.CommandUtils: type TypedCommandC ps a r = (ApplyTupRes a (CommandSemType r) ~ CommandForParsers ps r, a ~ ParserResult (ListToTup ps), Parser (ListToTup ps) r, ApplyTup a (CommandSemType r))
+ Calamity.Commands.CommandUtils: type TypedCommandC ps r = (ApplyTupRes (ParserResult (ListToTup ps)) (CommandSemType r) ~ CommandForParsers ps r, Parser (ListToTup ps) r, ApplyTup (ParserResult (ListToTup ps)) (CommandSemType r), ParamNamesForParsers ps r)
- Calamity.Commands.Dsl: command :: forall ps a r. (Member (Final IO) r, TypedCommandC ps a r) => Text -> (Context -> CommandForParsers ps r) -> Sem (DSLState r) Command
+ Calamity.Commands.Dsl: command :: forall ps r. (Member (Final IO) r, TypedCommandC ps r) => Text -> (Context -> CommandForParsers ps r) -> Sem (DSLState r) Command
- Calamity.Commands.Dsl: command' :: Member (Final IO) r => Text -> (Context -> Sem r (Either CommandError a)) -> ((Context, a) -> Sem (Fail : r) ()) -> Sem (DSLState r) Command
+ Calamity.Commands.Dsl: command' :: Member (Final IO) r => Text -> [Text] -> (Context -> Sem r (Either CommandError a)) -> ((Context, a) -> Sem (Fail : r) ()) -> Sem (DSLState r) Command
- Calamity.Commands.Dsl: type DSLState r = (LocalWriter (HashMap Text Command) : LocalWriter (HashMap Text Group) : Reader (Maybe Group) : Reader (Context -> Text) : Reader [Check] : Fixpoint : r)
+ Calamity.Commands.Dsl: type DSLState r = (LocalWriter (HashMap Text Command) : LocalWriter (HashMap Text Group) : Reader (Maybe Group) : Reader (Context -> Text) : Reader [Check] : Reader CommandHandler : Fixpoint : r)
- Calamity.Commands.ParsePrefix: parsePrefix :: forall r_a6EvC. MemberWithError ParsePrefix r_a6EvC => Message -> Sem r_a6EvC (Maybe (Text, Text))
+ Calamity.Commands.ParsePrefix: parsePrefix :: forall r_a6EVW. MemberWithError ParsePrefix r_a6EVW => Message -> Sem r_a6EVW (Maybe (Text, Text))

Files

ChangeLog.md view
@@ -1,5 +1,13 @@ # Changelog for Calamity +## 0.1.9.2++*2020-05-23*++* Added a default help command, located in `Calamity.Commands.Help`.++* Commands now have the list of parameters they take+ ## 0.1.9.1  *2020-05-23*
calamity.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 3c7ebdf28eac3707c7b4322aa4cfcd2c315be4e5a0d442d83e1ae4ad4bb5fcab+-- hash: ab08a364b5d3e67e015dfa8b1e5a96b8255ea898856a72811e1d1462bb984147  name:           calamity-version:        0.1.9.1+version:        0.1.9.2 synopsis:       A library for writing discord bots description:    Please see the README on GitHub at <https://github.com/nitros12/calamity#readme> category:       Network, Web@@ -49,9 +49,10 @@       Calamity.Commands.Error       Calamity.Commands.Group       Calamity.Commands.Handler-      Calamity.Commands.LocalWriter+      Calamity.Commands.Help       Calamity.Commands.ParsePrefix       Calamity.Commands.Parser+      Calamity.Commands.Utils       Calamity.Gateway       Calamity.Gateway.DispatchEvents       Calamity.Gateway.Shard@@ -73,6 +74,7 @@       Calamity.Internal.AesonThings       Calamity.Internal.BoundedStore       Calamity.Internal.GenericCurry+      Calamity.Internal.LocalWriter       Calamity.Internal.RunIntoIO       Calamity.Internal.SnowflakeMap       Calamity.Internal.Updateable
src/Calamity/Commands.hs view
@@ -4,11 +4,15 @@     ( module Calamity.Commands.Dsl     , module Calamity.Commands.Error     , module Calamity.Commands.Handler+    , module Calamity.Commands.Utils     , module Calamity.Commands.ParsePrefix-    , module Calamity.Commands.Parser ) where+    , module Calamity.Commands.Parser+    , module Calamity.Commands.Help ) where  import           Calamity.Commands.Dsl import           Calamity.Commands.Error import           Calamity.Commands.Handler+import           Calamity.Commands.Help import           Calamity.Commands.ParsePrefix-import           Calamity.Commands.Parser hiding ( ParserState, SpannedError )+import           Calamity.Commands.Parser+import           Calamity.Commands.Utils
src/Calamity/Commands/Command.hs view
@@ -21,6 +21,7 @@   { name     :: S.Text   , parent   :: Maybe Group   , checks   :: [Check]+  , params   :: [S.Text]   , help     :: Context -> L.Text   , parser   :: Context -> IO (Either CommandError a)   , callback :: (Context, a) -> IO (Maybe L.Text)@@ -28,6 +29,7 @@  data CommandS = CommandS   { name   :: S.Text+  , params :: [S.Text]   , parent :: Maybe S.Text   , checks :: [S.Text]   }@@ -35,9 +37,9 @@   deriving ( TextShow ) via TSG.FromGeneric CommandS  instance Show Command where-  showsPrec d Command { name, parent, checks } = showsPrec d $ CommandS name (parent ^? _Just . #name)+  showsPrec d Command { name, params, parent, checks } = showsPrec d $ CommandS name params (parent ^? _Just . #name)     (checks ^.. traverse . #name)  instance TextShow Command where-  showbPrec d Command { name, parent, checks } = showbPrec d $ CommandS name (parent ^? _Just . #name)+  showbPrec d Command { name, params, parent, checks } = showbPrec d $ CommandS name params (parent ^? _Just . #name)     (checks ^.. traverse . #name)
src/Calamity/Commands/CommandUtils.hs view
@@ -30,24 +30,23 @@ import qualified Polysemy                    as P import qualified Polysemy.Error              as P import qualified Polysemy.Fail               as P-import qualified Polysemy.Reader             as P-import qualified Polysemy.State              as P  buildCommand' :: P.Member (P.Final IO) r               => S.Text               -> Maybe Group               -> [Check]+              -> [S.Text]               -> (Context -> L.Text)               -> (Context -> P.Sem r (Either CommandError a))               -> ((Context, a) -> P.Sem (P.Fail ': r) ())               -> P.Sem r Command-buildCommand' name parent checks help parser cb = do+buildCommand' name parent checks params help parser cb = do   cb' <- buildCallback cb   parser' <- buildParser name parser-  pure $ Command name parent checks help parser' cb'+  pure $ Command name parent checks params help parser' cb' -buildCommand :: forall ps a r.-             (P.Member (P.Final IO) r, TypedCommandC ps a r)+buildCommand :: forall ps r.+             (P.Member (P.Final IO) r, TypedCommandC ps r)              => S.Text              -> Maybe Group              -> [Check]@@ -55,7 +54,7 @@              -> (Context -> CommandForParsers ps r)              -> P.Sem r Command buildCommand name parent checks help command = let (parser, cb) = buildTypedCommand @ps command-                                               in buildCommand' name parent checks help parser cb+                                               in buildCommand' name parent checks (paramNames @ps @r) help parser cb  buildParser :: P.Member (P.Final IO) r             => S.Text@@ -87,22 +86,34 @@  type CommandSemType r = P.Sem (P.Fail ': r) () -type TypedCommandC ps a r =-  ( ApplyTupRes a (CommandSemType r) ~ CommandForParsers ps r-  , a ~ ParserResult (ListToTup ps)+type TypedCommandC ps r =+  ( ApplyTupRes (ParserResult (ListToTup ps)) (CommandSemType r) ~ CommandForParsers ps r   , Parser (ListToTup ps) r-  , ApplyTup a (CommandSemType r))+  , ApplyTup (ParserResult (ListToTup ps)) (CommandSemType r)+  , ParamNamesForParsers ps r+  )  buildTypedCommand   :: forall (ps :: [Type]) a r.-  TypedCommandC ps a r+  (TypedCommandC ps r, a ~ ParserResult (ListToTup ps))   => (Context -> CommandForParsers ps r)-  -> ( Context -> P.Sem r (Either CommandError a)-     , (Context, a) -> P.Sem (P.Fail ': r) ())-buildTypedCommand cmd = let parser ctx = buildTypedCommandParser @ps (ctx, ctx ^. #unparsedParams)+  -> ( Context+         -> P.Sem r (Either CommandError a)+     , (Context, a)+         -> P.Sem (P.Fail ': r) ())+buildTypedCommand cmd = let parser ctx = buildTypedCommandParser @ps ctx (ctx ^. #unparsedParams)                             consumer (ctx, r) = applyTup (cmd ctx) r                         in (parser, consumer) +class ParamNamesForParsers (ps :: [Type]) r where+  paramNames :: [S.Text]++instance ParamNamesForParsers '[] r where+  paramNames = []++instance (Parser x r, ParamNamesForParsers xs r) => ParamNamesForParsers (x : xs) r where+  paramNames = (parserName @x @r : paramNames @xs @r)+ class ApplyTup a b where   type ApplyTupRes a b @@ -118,8 +129,8 @@    applyTup r () = r -buildTypedCommandParser :: forall (ps :: [Type]) r. Parser (ListToTup ps) r => (Context, L.Text) -> P.Sem r (Either CommandError (ParserResult (ListToTup ps)))-buildTypedCommandParser (ctx, t) = (P.runReader ctx . P.runError . P.evalState (ParserState 0 t) $ parse @(ListToTup ps)) <&> \case+buildTypedCommandParser :: forall (ps :: [Type]) r. Parser (ListToTup ps) r => Context -> L.Text -> P.Sem r (Either CommandError (ParserResult (ListToTup ps)))+buildTypedCommandParser ctx t = (runCommandParser ctx t $ parse @(ListToTup ps) @r) <&> \case   Right r -> Right r   Left (n, e)  -> Left $ ParseError n e 
src/Calamity/Commands/Dsl.hs view
@@ -18,7 +18,8 @@ import           Calamity.Commands.Context     hiding ( command ) import           Calamity.Commands.Error import           Calamity.Commands.Group       hiding ( help )-import           Calamity.Commands.LocalWriter+import           Calamity.Commands.Handler+import           Calamity.Internal.LocalWriter  import qualified Data.HashMap.Lazy             as LH import qualified Data.Text                     as S@@ -34,29 +35,31 @@                        P.Reader (Maybe Group) ':                        P.Reader (Context -> L.Text) ':                        P.Reader [Check] ':+                       P.Reader CommandHandler ':                        P.Fixpoint ': r)  raiseDSL :: P.Sem r a -> P.Sem (DSLState r) a-raiseDSL = P.raise . P.raise . P.raise . P.raise . P.raise . P.raise+raiseDSL = P.raise . P.raise . P.raise . P.raise . P.raise . P.raise . P.raise  -- | Build a command with an already prepared invokation action command'   :: P.Member (P.Final IO) r   => S.Text+  -> [S.Text]   -> (Context -> P.Sem r (Either CommandError a))   -> ((Context, a) -> P.Sem (P.Fail ': r) ())   -> P.Sem (DSLState r) Command-command' name parser cb = do+command' name params parser cb = do   parent <- P.ask @(Maybe Group)   checks <- P.ask @[Check]   help' <- P.ask @(Context -> L.Text)-  cmd <- raiseDSL $ buildCommand' name parent checks help' parser cb+  cmd <- raiseDSL $ buildCommand' name parent checks params help' parser cb   ltell $ LH.singleton name cmd   pure cmd -command :: forall ps a r.+command :: forall ps r.         ( P.Member (P.Final IO) r,-          TypedCommandC ps a r)+          TypedCommandC ps r)         => S.Text         -> (Context -> CommandForParsers ps r)         -> P.Sem (DSLState r) Command@@ -102,7 +105,9 @@   checks <- P.ask @[Check]   help'  <- P.ask @(Context -> L.Text)   let group' = Group name parent commands children help' checks-  (children, (commands, res)) <- llisten @(LH.HashMap S.Text Group) $ llisten @(LH.HashMap S.Text Command) $ P.local (const $ Just group') m+  (children, (commands, res)) <- llisten @(LH.HashMap S.Text Group) $+                                 llisten @(LH.HashMap S.Text Command) $+                                 P.local @(Maybe Group) (const $ Just group') m   ltell $ LH.singleton name group'   pure res 
src/Calamity/Commands/Group.hs view
@@ -6,12 +6,17 @@ import {-# SOURCE #-} Calamity.Commands.Command import {-# SOURCE #-} Calamity.Commands.Context +import           Control.Lens              hiding ( (<.>), Context )+ import qualified Data.HashMap.Lazy         as LH import qualified Data.Text                 as S import qualified Data.Text.Lazy            as L  import           GHC.Generics +import           TextShow+import qualified TextShow.Generic          as TSG+ data Group = Group   { name     :: S.Text   , parent   :: Maybe Group@@ -21,3 +26,18 @@   , checks   :: [Check]   }   deriving ( Generic )++data GroupS = GroupS+  { name     :: S.Text+  , parent   :: Maybe S.Text+  , commands :: LH.HashMap S.Text Command+  , children :: LH.HashMap S.Text Group+  }+  deriving ( Generic, Show )+  deriving ( TextShow ) via TSG.FromGeneric GroupS++instance Show Group where+  showsPrec d Group { name, parent, commands, children } = showsPrec d $ GroupS name (parent ^? _Just . #name) commands children++instance TextShow Group where+  showbPrec d Group { name, parent, commands, children } = showbPrec d $ GroupS name (parent ^? _Just . #name) commands children
src/Calamity/Commands/Handler.hs view
@@ -1,41 +1,17 @@ -- | A command handler module Calamity.Commands.Handler-    ( CommandHandler(..)-    , addCommands-    , buildCommands-    , buildContext ) where+    ( CommandHandler(..) ) where -import           Calamity.Cache.Eff-import           Calamity.Client.Client-import           Calamity.Client.Types import           Calamity.Commands.Command-import           Calamity.Commands.CommandUtils-import           Calamity.Commands.Context-import           Calamity.Commands.Dsl-import           Calamity.Commands.Error import           Calamity.Commands.Group-import           Calamity.Commands.LocalWriter-import           Calamity.Commands.ParsePrefix-import           Calamity.Internal.Utils-import           Calamity.Types.Model.Channel-import           Calamity.Types.Model.User-import           Calamity.Types.Snowflake -import           Control.Lens                   hiding ( Context )-import           Control.Monad--import           Data.Char                      ( isSpace )-import qualified Data.HashMap.Lazy              as LH-import qualified Data.Text                      as S-import qualified Data.Text.Lazy                 as L+import qualified Data.HashMap.Lazy         as LH+import qualified Data.Text                 as S  import           GHC.Generics -import qualified Polysemy                       as P-import qualified Polysemy.Error                 as P-import qualified Polysemy.Fail                  as P-import qualified Polysemy.Fixpoint              as P-import qualified Polysemy.Reader                as P+import           TextShow+import qualified TextShow.Generic          as TSG  data CommandHandler = CommandHandler   { groups   :: LH.HashMap S.Text Group@@ -43,82 +19,5 @@   , commands :: LH.HashMap S.Text Command     -- ^ Top level commands   }-  deriving ( Generic )--mapLeft :: (e -> e') -> Either e a -> Either e' a-mapLeft f (Left x)  = Left $ f x-mapLeft _ (Right x) = Right x--data FailReason-  = NoPrefix-  | NoCtx-  | NF [L.Text]-  | ERR Context CommandError--addCommands :: (BotC r, P.Member ParsePrefix r) => P.Sem (DSLState r) a -> P.Sem r (P.Sem r (), CommandHandler, a)-addCommands m = do-  (handler, res) <- buildCommands m-  remove <- react @'MessageCreateEvt $ \msg -> do-    err <- P.runError $ do-      (prefix, rest) <- P.note NoPrefix =<< parsePrefix msg-      (command, unparsedParams) <- P.fromEither $ mapLeft NF $ findCommand handler rest-      ctx <- P.note NoCtx =<< buildContext msg prefix command unparsedParams-      P.fromEither . mapLeft (ERR ctx) =<< invokeCommand ctx (ctx ^. #command)-      pure ctx-    case err of-      Left (ERR ctx e) -> fire $ customEvt @"command-error" (ctx, e)-      Left (NF path)   -> fire $ customEvt @"command-not-found" path-      Left _           -> pure () -- "ignore if no prefix or if context couldn't be built"-      Right ctx        -> fire $ customEvt @"command-run" ctx-  pure (remove, handler, res)---buildCommands :: P.Member (P.Final IO) r-              => P.Sem (DSLState r) a-              -> P.Sem r (CommandHandler, a)-buildCommands =-  ((\(groups, (cmds, a)) -> (CommandHandler groups cmds, a)) <$>) .-  P.fixpointToFinal .-  P.runReader [] .-  P.runReader (const "This command or group has no help.") .-  P.runReader Nothing .-  runLocalWriter @(LH.HashMap S.Text Group) .-  runLocalWriter @(LH.HashMap S.Text Command)--buildContext :: BotC r => Message -> L.Text -> Command -> L.Text -> P.Sem r (Maybe Context)-buildContext msg prefix command unparsed = (rightToMaybe <$>) . P.runFail $ do-  guild <- join <$> getGuild `traverse` (msg ^. #guildID)-  let member = guild ^? _Just . #members . ix (coerceSnowflake $ getID @User msg)-  let gchan = guild ^? _Just . #channels . ix (coerceSnowflake $ getID @Channel msg)-  Just channel <- case gchan of-    Just chan -> pure . pure $ GuildChannel' chan-    _         -> DMChannel' <<$>> getDM (coerceSnowflake $ getID @Channel msg)-  Just user <- getUser $ getID msg--  pure $ Context msg guild member channel user command prefix unparsed--nextWord :: L.Text -> (L.Text, L.Text)-nextWord = L.break isSpace . L.stripStart--findCommand :: CommandHandler -> L.Text -> Either [L.Text] (Command, L.Text)-findCommand handler msg = goH $ nextWord msg-  where-    goH :: (L.Text, L.Text) -> Either [L.Text] (Command, L.Text)-    goH ("", _) = Left []-    goH (x, xs) = attachSoFar x-      (((, xs) <$> attachInitial (LH.lookup (L.toStrict x) (handler ^. #commands)))-       <> (attachInitial (LH.lookup (L.toStrict x) (handler ^. #groups)) >>= goG (nextWord xs)))--    goG :: (L.Text, L.Text) -> Group -> Either [L.Text] (Command, L.Text)-    goG ("", _) _ = Left []-    goG (x, xs) g = attachSoFar x-      (((, xs) <$> attachInitial (LH.lookup (L.toStrict x) (g ^. #commands)))-       <> (attachInitial (LH.lookup (L.toStrict x) (g ^. #children)) >>= goG (nextWord xs)))--    attachInitial :: Maybe a -> Either [L.Text] a-    attachInitial (Just a) = Right a-    attachInitial Nothing = Left []--    attachSoFar :: L.Text -> Either [L.Text] a -> Either [L.Text] a-    attachSoFar cmd (Left xs) = Left (cmd:xs)-    attachSoFar _ r = r+  deriving ( Show, Generic )+  deriving ( TextShow ) via TSG.FromGeneric CommandHandler
+ src/Calamity/Commands/Help.hs view
@@ -0,0 +1,105 @@+-- | A default help command implementation+module Calamity.Commands.Help+    ( helpCommand'+    , helpCommand ) where++import           Calamity.Client.Types+import           Calamity.Commands.Check+import           Calamity.Commands.Command+import           Calamity.Commands.CommandUtils+import           Calamity.Commands.Context+import           Calamity.Commands.Dsl+import           Calamity.Commands.Group+import           Calamity.Commands.Handler+import           Calamity.Internal.LocalWriter+import           Calamity.Types.Tellable++import           Control.Applicative+import           Control.Lens hiding ( Context(..) )+import           Control.Monad++import qualified Data.HashMap.Lazy              as LH+import qualified Data.Text                      as S+import qualified Data.Text.Lazy                 as L++import qualified Polysemy                       as P+import qualified Polysemy.Fail                  as P+import qualified Polysemy.Reader                as P++data CommandOrGroup+  = Command' Command+  | Group' Group [S.Text]++helpCommandHelp :: Context -> L.Text+helpCommandHelp _ = "Show help for a command or group."++groupPath :: Group -> [S.Text]+groupPath grp = maybe [] groupPath (grp ^. #parent) ++ [grp ^. #name]++commandParams :: Command -> L.Text+commandParams Command { params } = L.fromStrict $ S.intercalate " " params++helpForCommand :: Context -> Command -> L.Text+helpForCommand ctx (cmd@Command { name, parent, help }) = "```\nUsage: " <> prefix' <> path' <> " " <> params' <> "\n\n" <> help ctx <> "\n```"+  where prefix' = ctx ^. #prefix+        path'   = L.fromStrict . S.intercalate " " $ maybe [] groupPath parent ++ [name]+        params' = commandParams cmd++fmtCommandWithParams :: Command -> L.Text+fmtCommandWithParams cmd@Command { name } = L.fromStrict name <> " " <> commandParams cmd++helpForGroup :: Context -> Group -> L.Text+helpForGroup ctx grp = "```\nGroup: " <> path' <> "\n\n" <> (grp ^. #help) ctx <> "\n" <> groupsMsg <> commandsMsg <> "\n```"+  where path' = L.fromStrict . S.intercalate " " $ groupPath grp+        groups = LH.keys $ grp ^. #children+        commands = LH.elems $ grp ^. #commands+        groupsMsg = if null groups then "" else "The following child groups exist:\n" <> L.fromStrict (S.unlines . map ("- " <>) $ groups)+        commandsMsg = if null commands then "" else "\nThe following child commands exist:\n" <> (L.unlines . map ("- " <>) . map fmtCommandWithParams $ commands)++rootHelp :: CommandHandler -> L.Text+rootHelp handler = "```\n" <> groupsMsg <> commandsMsg <> "\n```"+  where groups = LH.keys $ handler ^. #groups+        commands = LH.elems $ handler ^. #commands+        groupsMsg = if null groups then "" else "The following groups exist:\n" <> L.fromStrict (S.unlines . map ("- " <>) $ groups)+        commandsMsg = if null commands then "" else "\nThe following commands exist:\n" <> (L.unlines . map ("- " <>) . map fmtCommandWithParams $ commands)++-- TODO: process checks++helpCommandCallback :: BotC r => CommandHandler -> Context -> [S.Text] -> P.Sem (P.Fail ': r) ()+helpCommandCallback handler ctx path = do+  case findCommandOrGroup handler path of+    Just (Command' cmd@Command { name }) ->+      void $ tell @L.Text ctx $ "Help for command `" <> L.fromStrict name <> "`: \n" <> helpForCommand ctx cmd+    Just (Group' grp@Group { name } remainingPath) ->+      let failedMsg = if null remainingPath+            then ""+            else "No command or group with the path: `" <> L.fromStrict (S.intercalate " " path) <> "` exists for the group: `" <> L.fromStrict name <> "`\n"+      in void $ tell @L.Text ctx $ failedMsg <> "Help for group `" <> L.fromStrict name <> "`: \n" <> helpForGroup ctx grp+    Nothing -> let failedMsg = if null path+                     then ""+                     else "No command or group with the path: `" <> L.fromStrict (S.intercalate " " path) <> "` was found.\n"+               in void $ tell @L.Text ctx $ failedMsg <> rootHelp handler++helpCommand' :: BotC r => CommandHandler -> Maybe Group -> [Check] -> P.Sem r Command+helpCommand' handler parent checks = buildCommand @'[[S.Text]] "help" parent checks helpCommandHelp+  (helpCommandCallback handler)++helpCommand :: BotC r => P.Sem (DSLState r) Command+helpCommand = do+  handler <- P.ask @CommandHandler+  parent <- P.ask @(Maybe Group)+  checks <- P.ask @[Check]+  cmd <- raiseDSL $ helpCommand' handler parent checks+  ltell $ LH.singleton "help" cmd+  pure cmd++findCommandOrGroup :: CommandHandler -> [S.Text] -> Maybe CommandOrGroup+findCommandOrGroup handler path = go (handler ^. #commands, handler ^. #groups) path+  where go :: (LH.HashMap S.Text Command, LH.HashMap S.Text Group) -> [S.Text] -> Maybe CommandOrGroup+        go (commands, groups) (x : xs) =+          case LH.lookup x commands of+            Just cmd -> Just (Command' cmd)+            Nothing  -> case LH.lookup x groups of+              Just group -> go (group ^. #commands, group ^. #children) xs <|> Just (Group' group xs)+              Nothing    -> Nothing+        go _ [] = Nothing
− src/Calamity/Commands/LocalWriter.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}---- | A Writer monad that supports local writing, reverse reader I guess?-module Calamity.Commands.LocalWriter-    ( LocalWriter(..)-    , ltell-    , llisten-    , runLocalWriter ) where--import qualified Polysemy       as P-import qualified Polysemy.State as P--data LocalWriter o m a where-  Ltell :: o -> LocalWriter o m ()-  Llisten :: m a -> LocalWriter o m (o, a)--P.makeSem ''LocalWriter--runLocalWriter :: Monoid o => P.Sem (LocalWriter o ': r) a -> P.Sem r (o, a)-runLocalWriter = P.runState mempty . P.reinterpretH-  (\case-     Ltell o   -> do-       P.modify' (<> o) >>= P.pureT-     Llisten m -> do-       mm <- P.runT m-       (o, fa) <- P.raise $ runLocalWriter mm-       pure $ fmap (o, ) fa)
src/Calamity/Commands/Parser.hs view
@@ -3,8 +3,7 @@     ( Parser(..)     , Named     , KleeneConcat-    , ParserState(..)-    , SpannedError(..) ) where+    , runCommandParser ) where  import           Calamity.Cache.Eff import           Calamity.Commands.Context@@ -22,7 +21,6 @@ import           Data.List.NonEmpty            ( NonEmpty(..) ) import qualified Data.Text                     as S import qualified Data.Text.Lazy                as L-import           Data.Text.Lazy                ( Text ) import           Data.Typeable  import           GHC.Generics                  ( Generic )@@ -37,7 +35,7 @@ import           Text.Megaparsec.Char import           Text.Megaparsec.Error.Builder ( errFancy, fancy ) -data SpannedError = SpannedError Text !Int !Int+data SpannedError = SpannedError L.Text !Int !Int   deriving ( Show, Eq, Ord )  showTypeOf :: forall a. Typeable a => String@@ -45,21 +43,24 @@  data ParserState = ParserState   { off :: Int-  , msg :: Text+  , msg :: L.Text   }   deriving ( Show, Generic ) -type ParserEffs r = P.State ParserState ': P.Error (S.Text, Text) ': P.Reader Context ': r+type ParserEffs r = P.State ParserState ': P.Error (S.Text, L.Text) ': P.Reader Context ': r type ParserCtxE r = P.Reader Context ': r +runCommandParser :: Context -> L.Text -> P.Sem (ParserEffs r) a -> P.Sem r (Either (S.Text, L.Text) a)+runCommandParser ctx t = P.runReader ctx . P.runError . P.evalState (ParserState 0 t)+ class Typeable a => Parser (a :: Type) r where   type ParserResult a    type ParserResult a = a -  name :: S.Text-  default name :: S.Text-  name = ":" <> S.pack (showTypeOf @a)+  parserName :: S.Text+  default parserName :: S.Text+  parserName = ":" <> S.pack (showTypeOf @a)    parse :: P.Sem (ParserEffs r) (ParserResult a) @@ -68,14 +69,14 @@ instance (KnownSymbol s, Parser a r) => Parser (Named s a) r where   type ParserResult (Named s a) = ParserResult a -  name = (S.pack . symbolVal $ Proxy @s) <> name @a @r+  parserName = (S.pack . symbolVal $ Proxy @s) <> parserName @a @r -  parse = mapE (_1 .~ name @(Named s a) @r) $ parse @a @r+  parse = mapE (_1 .~ parserName @(Named s a) @r) $ parse @a @r  mapE :: P.Member (P.Error e) r => (e -> e) -> P.Sem r a -> P.Sem r a mapE f m = P.catch m (P.throw . f) -parseMP :: S.Text -> ParsecT SpannedError Text (P.Sem (ParserCtxE r)) a -> P.Sem (ParserEffs r) a+parseMP :: S.Text -> ParsecT SpannedError L.Text (P.Sem (ParserCtxE r)) a -> P.Sem (ParserEffs r) a parseMP n m = do   s <- P.get   res <- P.raise . P.raise $ runParserT (skipN (s ^. #off) *> trackOffsets (space *> m)) "" (s ^. #msg)@@ -85,9 +86,12 @@       pure a     Left s  -> P.throw (n, L.pack $ errorBundlePretty s) -instance Parser Text r where-  parse = parseMP (name @Text) item+instance Parser L.Text r where+  parse = parseMP (parserName @L.Text) item +instance Parser S.Text r where+  parse = parseMP (parserName @S.Text) (L.toStrict <$> item)+ instance Parser a r => Parser (Maybe a) r where   type ParserResult (Maybe a) = Maybe (ParserResult a) @@ -98,7 +102,9 @@    parse = go []     where go :: [ParserResult a] -> P.Sem (ParserEffs r) [ParserResult a]-          go l = P.catch ((: []) <$> parse @a) (const $ pure []) >>= go . (l ++)+          go l = P.catch (Just <$> parse @a) (const $ pure Nothing) >>= \case+            Just a -> go $ l ++ [a]+            Nothing -> pure l  instance (Parser a r, Typeable a) => Parser (NonEmpty a) r where   type ParserResult (NonEmpty a) = NonEmpty (ParserResult a)@@ -115,29 +121,35 @@    parse = mconcat <$> parse @[a] -instance {-# OVERLAPS #-}Parser (KleeneConcat Text) r where-  type ParserResult (KleeneConcat Text) = ParserResult Text+instance {-# OVERLAPS #-}Parser (KleeneConcat L.Text) r where+  type ParserResult (KleeneConcat L.Text) = ParserResult L.Text    -- consume rest on text just takes everything remaining-  parse = parseMP (name @(KleeneConcat Text)) someSingle+  parse = parseMP (parserName @(KleeneConcat L.Text)) someSingle +instance {-# OVERLAPS #-}Parser (KleeneConcat S.Text) r where+  type ParserResult (KleeneConcat S.Text) = ParserResult S.Text++  -- consume rest on text just takes everything remaining+  parse = parseMP (parserName @(KleeneConcat S.Text)) (L.toStrict <$> someSingle)+ instance Typeable (Snowflake a) => Parser (Snowflake a) r where-  parse = parseMP (name @(Snowflake a)) snowflake+  parse = parseMP (parserName @(Snowflake a)) snowflake  instance {-# OVERLAPS #-}Parser (Snowflake User) r where-  parse = parseMP (name @(Snowflake User)) (try (ping "@") <|> snowflake)+  parse = parseMP (parserName @(Snowflake User)) (try (ping "@") <|> snowflake)  instance {-# OVERLAPS #-}Parser (Snowflake Member) r where-  parse = parseMP (name @(Snowflake Member)) (try (ping "@") <|> snowflake)+  parse = parseMP (parserName @(Snowflake Member)) (try (ping "@") <|> snowflake)  instance {-# OVERLAPS #-}Parser (Snowflake Channel) r where-  parse = parseMP (name @(Snowflake Channel)) (try (ping "#") <|> snowflake)+  parse = parseMP (parserName @(Snowflake Channel)) (try (ping "#") <|> snowflake)  instance {-# OVERLAPS #-}Parser (Snowflake Role) r where-  parse = parseMP (name @(Snowflake Role)) (try (ping "@&") <|> snowflake)+  parse = parseMP (parserName @(Snowflake Role)) (try (ping "@&") <|> snowflake)  instance {-# OVERLAPS #-}Parser (Snowflake Emoji) r where-  parse = parseMP (name @(Snowflake Emoji)) (try emoji <|> snowflake)+  parse = parseMP (parserName @(Snowflake Emoji)) (try emoji <|> snowflake)  -- mapParserMaybe :: Stream s => ParsecT SpannedError s m a -> Text -> (a -> Maybe b) -> ParsecT SpannedError s m b -- mapParserMaybe m e f = do@@ -148,7 +160,7 @@ --     Just r' -> pure r' --     _       -> parseError . errFancy offs . fancy . ErrorCustom $ SpannedError e offs offe -mapParserMaybeM :: (Monad m, Stream s) => ParsecT SpannedError s m a -> Text -> (a -> m (Maybe b)) -> ParsecT SpannedError s m b+mapParserMaybeM :: (Monad m, Stream s) => ParsecT SpannedError s m a -> L.Text -> (a -> m (Maybe b)) -> ParsecT SpannedError s m b mapParserMaybeM m e f = do   offs <- getOffset   r <- m >>= lift . f@@ -158,14 +170,14 @@     _       -> parseError . errFancy offs . fancy . ErrorCustom $ SpannedError e offs offe  instance Parser Member r where-  parse = parseMP (name @Member) $ mapParserMaybeM (try (ping "@") <|> snowflake)+  parse = parseMP (parserName @Member) $ mapParserMaybeM (try (ping "@") <|> snowflake)           "Couldn't find a Member with this id"           (\mid -> do               ctx <- P.ask               pure $ ctx ^? #guild . _Just . #members . ix mid)  instance P.Member CacheEff r => Parser User r where-  parse = parseMP (name @User @r) $ mapParserMaybeM (try (ping "@") <|> snowflake)+  parse = parseMP (parserName @User @r) $ mapParserMaybeM (try (ping "@") <|> snowflake)           "Couldn't find a User with this id"           getUser @@ -178,7 +190,7 @@     pure (a, b)  instance Parser () r where-  parse = parseMP (name @()) space+  parse = parseMP (parserName @()) space  instance ShowErrorComponent SpannedError where   showErrorComponent (SpannedError t _ _) = L.unpack t@@ -187,16 +199,16 @@ skipN :: (Stream s, Ord e) => Int -> ParsecT e s m () skipN n = void $ takeP Nothing n -ping :: MonadParsec e Text m => Text -> m (Snowflake a)+ping :: MonadParsec e L.Text m => L.Text -> m (Snowflake a) ping c = chunk ("<" <> c) *> optional (chunk "!") *> snowflake <* chunk ">" -ping' :: MonadParsec e Text m => m () -> m (Snowflake a)+ping' :: MonadParsec e L.Text m => m () -> m (Snowflake a) ping' m = chunk "<" *> m *> snowflake <* chunk ">" -snowflake :: MonadParsec e Text m => m (Snowflake a)+snowflake :: MonadParsec e L.Text m => m (Snowflake a) snowflake = (Snowflake . read) <$> some digitChar -emoji :: MonadParsec e Text m => m (Snowflake a)+emoji :: MonadParsec e L.Text m => m (Snowflake a) emoji = ping' (optional (chunk "a") *> between (chunk ":") (chunk ":") (void $ takeWhileP Nothing $ not . (== ':')))  trackOffsets :: MonadParsec e s m => m a -> m (a, Int)@@ -206,7 +218,7 @@   offe <- getOffset   pure (a, offe - offs) -item :: MonadParsec e Text m => m Text+item :: MonadParsec e L.Text m => m L.Text item = try quotedString <|> someNonWS  -- manySingle :: MonadParsec e s m => m (Tokens s)@@ -215,7 +227,7 @@ someSingle :: MonadParsec e s m => m (Tokens s) someSingle = takeWhile1P (Just "any character") (const True) -quotedString :: MonadParsec e Text m => m Text+quotedString :: MonadParsec e L.Text m => m L.Text quotedString = try (between (chunk "'") (chunk "'") (takeWhileP (Just "any character") $ not . (== '\''))) <|>                between (chunk "\"") (chunk "\"") (takeWhileP (Just "any character") $ not . (== '"')) 
+ src/Calamity/Commands/Utils.hs view
@@ -0,0 +1,121 @@+{-# LANGUAGE RecursiveDo #-}+-- | Command handler utilities+module Calamity.Commands.Utils+    ( addCommands+    , buildCommands+    , buildContext ) where++import           Calamity.Cache.Eff+import           Calamity.Client.Client+import           Calamity.Client.Types+import           Calamity.Commands.Command+import           Calamity.Commands.CommandUtils+import           Calamity.Commands.Context+import           Calamity.Commands.Dsl+import           Calamity.Commands.Handler+import           Calamity.Commands.Error+import           Calamity.Commands.Group+import           Calamity.Commands.ParsePrefix+import           Calamity.Internal.LocalWriter+import           Calamity.Internal.Utils+import           Calamity.Types.Model.Channel+import           Calamity.Types.Model.User+import           Calamity.Types.Snowflake++import           Control.Lens                   hiding ( Context )+import           Control.Monad++import           Data.Char                      ( isSpace )+import qualified Data.HashMap.Lazy              as LH+import qualified Data.Text                      as S+import qualified Data.Text.Lazy                 as L++import qualified Polysemy                       as P+import qualified Polysemy.Error                 as P+import qualified Polysemy.Fail                  as P+import qualified Polysemy.Fixpoint              as P+import qualified Polysemy.Reader                as P++mapLeft :: (e -> e') -> Either e a -> Either e' a+mapLeft f (Left x)  = Left $ f x+mapLeft _ (Right x) = Right x++data FailReason+  = NoPrefix+  | NoCtx+  | NF [L.Text]+  | ERR Context CommandError++addCommands :: (BotC r, P.Member ParsePrefix r) => P.Sem (DSLState r) a -> P.Sem r (P.Sem r (), CommandHandler, a)+addCommands m = do+  (handler, res) <- buildCommands m+  remove <- react @'MessageCreateEvt $ \msg -> do+    err <- P.runError $ do+      (prefix, rest) <- P.note NoPrefix =<< parsePrefix msg+      (command, unparsedParams) <- P.fromEither $ mapLeft NF $ findCommand handler rest+      ctx <- P.note NoCtx =<< buildContext msg prefix command unparsedParams+      P.fromEither . mapLeft (ERR ctx) =<< invokeCommand ctx (ctx ^. #command)+      pure ctx+    case err of+      Left (ERR ctx e) -> fire $ customEvt @"command-error" (ctx, e)+      Left (NF path)   -> fire $ customEvt @"command-not-found" path+      Left _           -> pure () -- "ignore if no prefix or if context couldn't be built"+      Right ctx        -> fire $ customEvt @"command-run" ctx+  pure (remove, handler, res)+++buildCommands :: forall r a. P.Member (P.Final IO) r+              => P.Sem (DSLState r) a+              -> P.Sem r (CommandHandler, a)+buildCommands m = P.fixpointToFinal $ mdo+  (groups, (cmds, a)) <- inner handler m+  let handler = CommandHandler groups cmds+  pure (handler, a)++  where inner :: CommandHandler -> P.Sem (DSLState r) a -> P.Sem (P.Fixpoint ': r) (LH.HashMap S.Text Group, (LH.HashMap S.Text Command, a))+        inner h =+          P.runReader h .+          P.runReader [] .+          P.runReader (const "This command or group has no help.") .+          P.runReader Nothing .+          runLocalWriter @(LH.HashMap S.Text Group) .+          runLocalWriter @(LH.HashMap S.Text Command)+++buildContext :: BotC r => Message -> L.Text -> Command -> L.Text -> P.Sem r (Maybe Context)+buildContext msg prefix command unparsed = (rightToMaybe <$>) . P.runFail $ do+  guild <- join <$> getGuild `traverse` (msg ^. #guildID)+  let member = guild ^? _Just . #members . ix (coerceSnowflake $ getID @User msg)+  let gchan = guild ^? _Just . #channels . ix (coerceSnowflake $ getID @Channel msg)+  Just channel <- case gchan of+    Just chan -> pure . pure $ GuildChannel' chan+    _         -> DMChannel' <<$>> getDM (coerceSnowflake $ getID @Channel msg)+  Just user <- getUser $ getID msg++  pure $ Context msg guild member channel user command prefix unparsed++nextWord :: L.Text -> (L.Text, L.Text)+nextWord = L.break isSpace . L.stripStart++findCommand :: CommandHandler -> L.Text -> Either [L.Text] (Command, L.Text)+findCommand handler msg = goH $ nextWord msg+  where+    goH :: (L.Text, L.Text) -> Either [L.Text] (Command, L.Text)+    goH ("", _) = Left []+    goH (x, xs) = attachSoFar x+      (((, xs) <$> attachInitial (LH.lookup (L.toStrict x) (handler ^. #commands)))+       <> (attachInitial (LH.lookup (L.toStrict x) (handler ^. #groups)) >>= goG (nextWord xs)))++    goG :: (L.Text, L.Text) -> Group -> Either [L.Text] (Command, L.Text)+    goG ("", _) _ = Left []+    goG (x, xs) g = attachSoFar x+      (((, xs) <$> attachInitial (LH.lookup (L.toStrict x) (g ^. #commands)))+       <> (attachInitial (LH.lookup (L.toStrict x) (g ^. #children)) >>= goG (nextWord xs)))++    attachInitial :: Maybe a -> Either [L.Text] a+    attachInitial (Just a) = Right a+    attachInitial Nothing = Left []++    attachSoFar :: L.Text -> Either [L.Text] a -> Either [L.Text] a+    attachSoFar cmd (Left xs) = Left (cmd:xs)+    attachSoFar _ r = r
+ src/Calamity/Internal/LocalWriter.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE TemplateHaskell #-}++-- | A Writer monad that supports local writing, reverse reader I guess?+module Calamity.Internal.LocalWriter+    ( LocalWriter(..)+    , ltell+    , llisten+    , runLocalWriter ) where++import qualified Polysemy       as P+import qualified Polysemy.State as P++data LocalWriter o m a where+  Ltell :: o -> LocalWriter o m ()+  Llisten :: m a -> LocalWriter o m (o, a)++P.makeSem ''LocalWriter++runLocalWriter :: Monoid o => P.Sem (LocalWriter o ': r) a -> P.Sem r (o, a)+runLocalWriter = P.runState mempty . P.reinterpretH+  (\case+     Ltell o   -> do+       P.modify' (<> o) >>= P.pureT+     Llisten m -> do+       mm <- P.runT m+       (o, fa) <- P.raise $ runLocalWriter mm+       pure $ fmap (o, ) fa)