Initial commit
This commit is contained in:
commit
ad7290e705
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
dist/
|
5
ChangeLog.md
Normal file
5
ChangeLog.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Revision history for demobot
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
30
LICENSE
Normal file
30
LICENSE
Normal file
@ -0,0 +1,30 @@
|
||||
Copyright (c) 2018, Mats Rauhala
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Mats Rauhala nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
3
default.nix
Normal file
3
default.nix
Normal file
@ -0,0 +1,3 @@
|
||||
{ haskellPackages }:
|
||||
|
||||
haskellPackages.callCabal2nix "demobot" ./. {}
|
51
demobot.cabal
Normal file
51
demobot.cabal
Normal file
@ -0,0 +1,51 @@
|
||||
-- Initial demobot.cabal generated by cabal init. For further
|
||||
-- documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: demobot
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Mats Rauhala
|
||||
maintainer: mats.rauhala@iki.fi
|
||||
-- copyright:
|
||||
category: Web
|
||||
build-type: Simple
|
||||
extra-source-files: ChangeLog.md
|
||||
cabal-version: >=1.10
|
||||
|
||||
library
|
||||
exposed-modules: Bot.DSL
|
||||
, Bot.Extension
|
||||
, Bot.DSL.Network
|
||||
, Bot.DSL.State
|
||||
, Bot.DSL.Time
|
||||
, Bot.Lib
|
||||
, Bot.Log
|
||||
-- other-modules:
|
||||
-- other-extensions:
|
||||
build-depends: base >=4.11 && <4.12
|
||||
, text
|
||||
, mtl
|
||||
, bytestring
|
||||
, time
|
||||
, monad-logger
|
||||
, exceptions
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
|
||||
executable demobot
|
||||
main-is: Main.hs
|
||||
other-modules: Extensions
|
||||
-- other-extensions:
|
||||
build-depends: base >=4.11 && <4.12
|
||||
, demobot
|
||||
, text
|
||||
, mtl
|
||||
, bytestring
|
||||
, time
|
||||
, monad-logger
|
||||
, exceptions
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
21
release.nix
Normal file
21
release.nix
Normal file
@ -0,0 +1,21 @@
|
||||
{ nixpkgs ? import <nixpkgs> {} }:
|
||||
|
||||
let
|
||||
demobot = nixpkgs.callPackage ./default.nix {};
|
||||
shell = nixpkgs.buildEnv {
|
||||
name = "demobot-shell";
|
||||
paths = [];
|
||||
buildInputs = with nixpkgs.haskellPackages; [
|
||||
ghcid
|
||||
cabal-install
|
||||
hasktags
|
||||
(ghcWithPackages (_: demobot.buildInputs ++ demobot.propagatedBuildInputs))
|
||||
nixpkgs.binutils-unwrapped
|
||||
];
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
inherit demobot shell;
|
||||
}
|
3
shell.nix
Normal file
3
shell.nix
Normal file
@ -0,0 +1,3 @@
|
||||
{ nixpkgs ? import <nixpkgs> {} }:
|
||||
|
||||
(nixpkgs.callPackage ./release.nix {}).shell
|
33
src/AppM.hs
Normal file
33
src/AppM.hs
Normal file
@ -0,0 +1,33 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
module AppM where
|
||||
|
||||
import Bot.DSL
|
||||
import Control.Monad.Catch (MonadCatch, MonadThrow)
|
||||
import Control.Monad.Logger
|
||||
import Control.Monad.Trans (MonadIO, liftIO)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as T
|
||||
import qualified Data.Time as Time
|
||||
|
||||
newtype AppM a = AppM (LoggingT IO a)
|
||||
deriving (Functor, Applicative, Monad, MonadCatch, MonadThrow, MonadLogger, MonadIO)
|
||||
|
||||
-- The tagless final interpreter.
|
||||
instance MonadNetwork AppM where
|
||||
type Meta AppM = ()
|
||||
recvMsg = Request <$> liftIO T.getLine <*> pure ()
|
||||
putMsg Response{..} = liftIO . T.putStrLn $ content
|
||||
|
||||
instance MonadData AppM where
|
||||
putData key val = liftIO $ T.putStrLn $ "Would put " <> T.pack key <> " = " <> (T.pack $ show val)
|
||||
getData key = liftIO (T.putStrLn $ "Would fetch " <> T.pack key) >> return Nothing
|
||||
|
||||
instance MonadTime AppM where
|
||||
getCurrentTime = liftIO Time.getCurrentTime
|
||||
|
||||
runAppM :: AppM a -> IO a
|
||||
runAppM (AppM f) = runStdoutLoggingT f
|
18
src/Bot/DSL.hs
Normal file
18
src/Bot/DSL.hs
Normal file
@ -0,0 +1,18 @@
|
||||
{-# LANGUAGE ConstraintKinds #-}
|
||||
{-# LANGUAGE Safe #-}
|
||||
module Bot.DSL
|
||||
( module Bot.DSL.Network
|
||||
, module Bot.DSL.State
|
||||
, module Bot.DSL.Time
|
||||
, MonadBot
|
||||
, MonadExtension
|
||||
) where
|
||||
|
||||
import Control.Monad.Logger
|
||||
|
||||
import Bot.DSL.Network
|
||||
import Bot.DSL.State
|
||||
import Bot.DSL.Time
|
||||
|
||||
type MonadBot m = (MonadNetwork m, MonadData m, MonadLogger m, MonadTime m)
|
||||
type MonadExtension m = (MonadData m, MonadLogger m, MonadTime m)
|
17
src/Bot/DSL/Network.hs
Normal file
17
src/Bot/DSL/Network.hs
Normal file
@ -0,0 +1,17 @@
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE Safe #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
module Bot.DSL.Network where
|
||||
|
||||
import Data.Text (Text)
|
||||
|
||||
data Request meta = Request { content :: Text
|
||||
, meta :: meta }
|
||||
data Response meta = Response { content :: Text
|
||||
, meta :: meta }
|
||||
|
||||
class Monad m => MonadNetwork m where
|
||||
-- Meta for stuff like event types, or source / target information
|
||||
type Meta m :: *
|
||||
recvMsg :: m (Request (Meta m))
|
||||
putMsg :: Response (Meta m) -> m ()
|
5
src/Bot/DSL/State.hs
Normal file
5
src/Bot/DSL/State.hs
Normal file
@ -0,0 +1,5 @@
|
||||
module Bot.DSL.State where
|
||||
|
||||
class Monad m => MonadData m where
|
||||
getData :: Read a => String -> m (Maybe a)
|
||||
putData :: Show a => String -> a -> m ()
|
6
src/Bot/DSL/Time.hs
Normal file
6
src/Bot/DSL/Time.hs
Normal file
@ -0,0 +1,6 @@
|
||||
module Bot.DSL.Time where
|
||||
|
||||
import qualified Data.Time as Time
|
||||
|
||||
class Monad m => MonadTime m where
|
||||
getCurrentTime :: m Time.UTCTime
|
9
src/Bot/Extension.hs
Normal file
9
src/Bot/Extension.hs
Normal file
@ -0,0 +1,9 @@
|
||||
{-# Language Safe #-}
|
||||
{-# Language RankNTypes #-}
|
||||
{-# Language GADTs #-}
|
||||
module Bot.Extension where
|
||||
|
||||
import Bot.DSL
|
||||
|
||||
data Extension meta = Extension { act :: forall m. (meta ~ Meta m, MonadExtension m) => Request meta -> m (Maybe (Response meta))
|
||||
, name :: String }
|
29
src/Bot/Lib.hs
Normal file
29
src/Bot/Lib.hs
Normal file
@ -0,0 +1,29 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE Safe #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
|
||||
module Bot.Lib where
|
||||
|
||||
import Bot.DSL
|
||||
import Bot.Extension
|
||||
import Bot.Log
|
||||
import Control.Monad (forever)
|
||||
import Control.Monad.Catch (MonadCatch, SomeException, catch, try)
|
||||
import Data.Maybe (catMaybes)
|
||||
import qualified Data.Text as T
|
||||
import GHC.Stack
|
||||
|
||||
tshow :: Show a => a -> T.Text
|
||||
tshow = T.pack . show
|
||||
|
||||
mainLoop :: forall m. (MonadCatch m, MonadBot m) => [Extension (Meta m)] -> m ()
|
||||
mainLoop extensions = forever $ catch go handleFail
|
||||
where
|
||||
handleFail :: SomeException -> m ()
|
||||
handleFail e = logError $ tshow e
|
||||
go :: m ()
|
||||
go = do
|
||||
msg <- recvMsg
|
||||
responses <- catMaybes <$> mapM (\ext -> (act ext) msg) extensions
|
||||
mapM_ putMsg responses
|
14
src/Bot/Log.hs
Normal file
14
src/Bot/Log.hs
Normal file
@ -0,0 +1,14 @@
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE Safe #-}
|
||||
module Bot.Log where
|
||||
|
||||
import Control.Monad.Logger (MonadLogger)
|
||||
import qualified Control.Monad.Logger as Log
|
||||
import Data.Text (Text)
|
||||
import GHC.Stack
|
||||
|
||||
logInfo :: (HasCallStack, MonadLogger m) => Text -> m ()
|
||||
logInfo = Log.logInfoCS callStack
|
||||
|
||||
logError :: (HasCallStack, MonadLogger m) => Text -> m ()
|
||||
logError = Log.logErrorCS callStack
|
15
src/Extension/Hello.hs
Normal file
15
src/Extension/Hello.hs
Normal file
@ -0,0 +1,15 @@
|
||||
{-# Language RecordWildCards #-}
|
||||
{-# Language OverloadedStrings #-}
|
||||
{-# Language ScopedTypeVariables #-}
|
||||
module Extension.Hello where
|
||||
|
||||
import Bot.Extension
|
||||
import Bot.DSL
|
||||
import qualified Data.Text as T
|
||||
|
||||
extension :: Extension ()
|
||||
extension = Extension{..}
|
||||
where
|
||||
name = "hello world"
|
||||
act Request{..} | "hello" `T.isPrefixOf` content = return $ Just $ Response "Hello to you" ()
|
||||
| otherwise = return Nothing
|
10
src/Extensions.hs
Normal file
10
src/Extensions.hs
Normal file
@ -0,0 +1,10 @@
|
||||
{-# LANGUAGE Safe #-}
|
||||
|
||||
module Extensions where
|
||||
|
||||
import Bot.DSL
|
||||
import Bot.Extension
|
||||
import qualified Extension.Hello as Hello
|
||||
|
||||
extensions :: [Extension ()]
|
||||
extensions = [Hello.extension]
|
9
src/Main.hs
Normal file
9
src/Main.hs
Normal file
@ -0,0 +1,9 @@
|
||||
module Main where
|
||||
|
||||
import AppM
|
||||
import Bot.Lib
|
||||
import Extensions
|
||||
|
||||
|
||||
main :: IO ()
|
||||
main = runAppM (mainLoop extensions)
|
Loading…
Reference in New Issue
Block a user