@hackage calamity-commands0.4.1.0

A library for declaring, parsing, and invoking text-input based commands

Calamity Commands

Hackage Build Status License Hackage-Deps Discord Invite

Calamity Commands is a Haskell library for constructing text-based commands, it uses Polysemy as the core library for handling effects, allowing you to pick and choose how to handle certain features of the library.

Docs

You can find documentation on hackage at: https://hackage.haskell.org/package/calamity-commands

Examples

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

{-# LANGUAGE TypeOperators #-}

module Main where

import Polysemy
import CalamityCommands.Commands
import CalamityCommands.Commands.Help
import CalamityCommands.Commands.Context
import CalamityCommands.Commands.ParsePrefix
import Data.Functor.Identity
import qualified Data.Text as T
import qualified Data.Text.IO as T

-- Make a command handler, we don't actually use the context and therefore this
-- handler is generic over the context used
h' :: CommandContext Identity c (Either T.Text Int) => CommandHandler Identity c (Either T.Text Int)
h' = runIdentity . runFinal $ do
  (h, _) <- buildCommands $ do
        command @'[Int, Int] "add" $ \ctx a b -> pure $ Right (a + b)
        command @'[Int, Int] "mul" $ \ctx a b -> pure $ Right (a * b)
        helpCommand (pure . Left)
  pure h

-- To use the commands we need to provide the interpreters for
-- 'ConstructContext' and 'ParsePrefix', the default provided ones are being
-- used here: 'useBasicContext' which makes @ctx ~ 'BasicContext'@, and
-- @'useConstantPrefix' "!"@ which treats any input starting with @!@ as a
-- command.
--
--
-- The 'processCommands' function can then be used to parse and invoke commands,
-- since commands are generic over the monad they run in we use @'runIdentity' .
-- 'runFinal' . 'embedToFinal'@ to have the commands interpreted purely.
--
--
-- This function 'r' takes an input string such as "!add 1 2", and then looks up
-- the invoked command and runs it, returning the result.
r :: T.Text -> Maybe (Either
                       (CmdInvokeFailReason (BasicContext Identity (Either T.Text Int)))
                       (BasicContext Identity (Either T.Text Int), Either T.Text Int))
r = runIdentity . runFinal . embedToFinal . useBasicContext . useConstantPrefix "!" . processCommands h'

-- Then to display the result of processing the command nicely, we can use a
-- something like this function, which prints the result of a command if one was
-- invoked successfully, and prints the error nicely if not.
rm :: T.Text -> IO ()
rm s = case r s of
            Just (Right (_, Right r)) ->
              print r

            Just (Right (_, Left h)) ->
              T.putStrLn h

            Just (Left (CommandInvokeError _ (ParseError t r))) ->
              T.putStrLn ("Parsing parameter " <> t <> " failed with reason: " <> r)

            _ -> pure ()