diff options
| -rw-r--r-- | default.nix | 56 | ||||
| -rw-r--r-- | sequence.cabal | 17 | ||||
| -rw-r--r-- | sequence.nix | 11 | ||||
| -rw-r--r-- | src/Main.hs | 77 | ||||
| -rw-r--r-- | src/Sequence/Types.hs | 101 | ||||
| -rw-r--r-- | src/Sequence/Utils.hs | 46 |
6 files changed, 302 insertions, 6 deletions
diff --git a/default.nix b/default.nix index ac19354..c96e43f 100644 --- a/default.nix +++ b/default.nix | |||
| @@ -3,6 +3,60 @@ | |||
| 3 | }: | 3 | }: |
| 4 | 4 | ||
| 5 | rec { | 5 | rec { |
| 6 | haskellPackages = pkgs.haskell.packages."${compilerName}"; | 6 | haskellPackages = pkgs.haskell.packages."${compilerName}".override { |
| 7 | overrides = self: super: { | ||
| 8 | Shellac = pkgs.haskell.lib.appendPatch super.Shellac (pkgs.writeText "build.patch" '' | ||
| 9 | From 29c78ed6549525fefb04ae6f7cab8316ac59f3c4 Mon Sep 17 00:00:00 2001 | ||
| 10 | From: Gregor Kleen <gkleen@yggdrasil.li> | ||
| 11 | Date: Thu, 2 Jun 2016 17:14:15 +0200 | ||
| 12 | Subject: [PATCH 1/2] Hiding <$> provided by newer versions of Prelude | ||
| 13 | |||
| 14 | --- | ||
| 15 | src/System/Console/Shell/PPrint.hs | 1 + | ||
| 16 | 1 file changed, 1 insertion(+) | ||
| 17 | |||
| 18 | diff --git a/src/System/Console/Shell/PPrint.hs b/src/System/Console/Shell/PPrint.hs | ||
| 19 | index e8ec520..8d271e1 100644 | ||
| 20 | --- a/src/System/Console/Shell/PPrint.hs | ||
| 21 | +++ b/src/System/Console/Shell/PPrint.hs | ||
| 22 | @@ -48,6 +48,7 @@ module System.Console.Shell.PPrint | ||
| 23 | ) where | ||
| 24 | |||
| 25 | import System.IO (Handle,hPutStr,hPutChar,stdout) | ||
| 26 | +import Prelude hiding ((<$>)) | ||
| 27 | |||
| 28 | infixr 5 </>,<//>,<$>,<$$> | ||
| 29 | infixr 6 <>,<+> | ||
| 30 | -- | ||
| 31 | 2.8.0 | ||
| 32 | |||
| 33 | |||
| 34 | From 74cb07ccfa92fdcdd6eb3c5871289796ea4981d5 Mon Sep 17 00:00:00 2001 | ||
| 35 | From: Gregor Kleen <gkleen@yggdrasil.li> | ||
| 36 | Date: Thu, 2 Jun 2016 17:28:17 +0200 | ||
| 37 | Subject: [PATCH 2/2] Applicative instance for Sh | ||
| 38 | |||
| 39 | --- | ||
| 40 | src/System/Console/Shell/Types.hs | 2 +- | ||
| 41 | 1 file changed, 1 insertion(+), 1 deletion(-) | ||
| 42 | |||
| 43 | diff --git a/src/System/Console/Shell/Types.hs b/src/System/Console/Shell/Types.hs | ||
| 44 | index 4ec47a9..9efe4b4 100644 | ||
| 45 | --- a/src/System/Console/Shell/Types.hs | ||
| 46 | +++ b/src/System/Console/Shell/Types.hs | ||
| 47 | @@ -74,7 +74,7 @@ type OutputCommand = BackendOutput -> IO () | ||
| 48 | -- The type parameter @st@ allows the monad to carry around a package of | ||
| 49 | -- user-defined state. | ||
| 50 | newtype Sh st a = Sh { unSh :: StateT (CommandResult st) (ReaderT OutputCommand IO) a } | ||
| 51 | - deriving (Monad, MonadIO, MonadFix, Functor) | ||
| 52 | + deriving (Monad, MonadIO, MonadFix, Functor, Applicative) | ||
| 53 | |||
| 54 | ------------------------------------------------------------------------ | ||
| 55 | -- The shell description and utility functions | ||
| 56 | -- | ||
| 57 | 2.8.0 | ||
| 58 | ''); | ||
| 59 | }; | ||
| 60 | }; | ||
| 7 | sequence = haskellPackages.callPackage ./sequence.nix {}; | 61 | sequence = haskellPackages.callPackage ./sequence.nix {}; |
| 8 | } | 62 | } |
diff --git a/sequence.cabal b/sequence.cabal index 0cea160..3d9e66f 100644 --- a/sequence.cabal +++ b/sequence.cabal | |||
| @@ -17,8 +17,21 @@ cabal-version: >=1.10 | |||
| 17 | 17 | ||
| 18 | executable sequence | 18 | executable sequence |
| 19 | main-is: Main.hs | 19 | main-is: Main.hs |
| 20 | -- other-modules: | 20 | other-modules: Sequence.Types |
| 21 | -- other-extensions: | 21 | -- other-extensions: |
| 22 | build-depends: base >=4.8 && <4.9 | 22 | build-depends: base >=4.8 && <5 |
| 23 | , Shellac | ||
| 24 | , Shellac-haskeline | ||
| 25 | , data-default | ||
| 26 | , xdg-basedir | ||
| 27 | , filepath | ||
| 28 | , directory | ||
| 29 | , containers | ||
| 30 | , case-insensitive | ||
| 31 | , lens | ||
| 32 | , bimap | ||
| 33 | , mtl | ||
| 34 | , table-layout | ||
| 35 | , game-probability | ||
| 23 | hs-source-dirs: src | 36 | hs-source-dirs: src |
| 24 | default-language: Haskell2010 \ No newline at end of file | 37 | default-language: Haskell2010 \ No newline at end of file |
diff --git a/sequence.nix b/sequence.nix index 612eb30..4533ad2 100644 --- a/sequence.nix +++ b/sequence.nix | |||
| @@ -1,10 +1,17 @@ | |||
| 1 | { mkDerivation, base, stdenv }: | 1 | { mkDerivation, base, bimap, case-insensitive, containers |
| 2 | , data-default, directory, filepath, game-probability, lens, mtl | ||
| 3 | , Shellac, Shellac-haskeline, stdenv, table-layout, xdg-basedir | ||
| 4 | }: | ||
| 2 | mkDerivation { | 5 | mkDerivation { |
| 3 | pname = "sequence"; | 6 | pname = "sequence"; |
| 4 | version = "0.0.0"; | 7 | version = "0.0.0"; |
| 5 | src = ./.; | 8 | src = ./.; |
| 6 | isLibrary = false; | 9 | isLibrary = false; |
| 7 | isExecutable = true; | 10 | isExecutable = true; |
| 8 | executableHaskellDepends = [ base ]; | 11 | executableHaskellDepends = [ |
| 12 | base bimap case-insensitive containers data-default directory | ||
| 13 | filepath game-probability lens mtl Shellac Shellac-haskeline | ||
| 14 | table-layout xdg-basedir | ||
| 15 | ]; | ||
| 9 | license = stdenv.lib.licenses.mit; | 16 | license = stdenv.lib.licenses.mit; |
| 10 | } | 17 | } |
diff --git a/src/Main.hs b/src/Main.hs index e9e1deb..639673c 100644 --- a/src/Main.hs +++ b/src/Main.hs | |||
| @@ -1,2 +1,77 @@ | |||
| 1 | {-# LANGUAGE ViewPatterns #-} | ||
| 2 | |||
| 3 | import Control.Monad | ||
| 4 | |||
| 5 | import Control.Lens | ||
| 6 | |||
| 7 | import System.Console.Shell | ||
| 8 | import System.Console.Shell.ShellMonad | ||
| 9 | import System.Console.Shell.Backend.Haskeline | ||
| 10 | |||
| 11 | import System.Environment.XDG.BaseDir | ||
| 12 | import System.FilePath | ||
| 13 | import System.Directory | ||
| 14 | |||
| 15 | import Data.Default | ||
| 16 | import Data.CaseInsensitive (CI) | ||
| 17 | import qualified Data.CaseInsensitive | ||
| 18 | |||
| 19 | import Data.Map.Strict (Map) | ||
| 20 | import qualified Data.Map.Strict as Map | ||
| 21 | |||
| 22 | import Data.List | ||
| 23 | import Data.Maybe | ||
| 24 | |||
| 25 | import Data.Function | ||
| 26 | |||
| 27 | import Control.Monad.State.Strict | ||
| 28 | |||
| 29 | import Sequence.Types | ||
| 30 | import Sequence.Utils | ||
| 31 | |||
| 32 | import Text.Layout.Table | ||
| 33 | |||
| 1 | main :: IO () | 34 | main :: IO () |
| 2 | main = undefined | 35 | main = do |
| 36 | historyFile <- getUserCacheFile "sequence" "history" | ||
| 37 | createDirectoryIfMissing True $ takeDirectory historyFile | ||
| 38 | let | ||
| 39 | description = initialShellDescription | ||
| 40 | { historyFile = Just historyFile | ||
| 41 | , prompt = \st -> return $ maybe "" (++ " ") ((evalState ?? st) . toName . snd <$> (listToMaybe $ view priorityQueue st)) ++ "→ " | ||
| 42 | , beforePrompt = gets stateOutline >>= (\str -> if null str then return () else shellPutStrLn str) | ||
| 43 | , commandStyle = OnlyCommands | ||
| 44 | , shellCommands = [ exitCommand "exit" | ||
| 45 | , helpCommand "help" | ||
| 46 | , cmd "factions" listFactions "List all inhabited factions" | ||
| 47 | , cmd "members" listFaction "List all members of a faction" | ||
| 48 | , cmd "entities" listEntities "List all entities" | ||
| 49 | ] | ||
| 50 | } | ||
| 51 | void $ runShell description haskelineBackend (def :: GameState) | ||
| 52 | |||
| 53 | stateOutline :: GameState -> String | ||
| 54 | stateOutline st | ||
| 55 | | null pQueue = "" | ||
| 56 | | otherwise = layoutTableToString rowGs (Just ("" : factions, repeat def)) (repeat def) unicodeBoldHeaderS | ||
| 57 | where | ||
| 58 | factions = map (view faction') $ st ^. inhabitedFactions | ||
| 59 | pQueue = st ^. priorityQueue | ||
| 60 | protoRows = groupBy ((==) `on` fst) pQueue | ||
| 61 | faction id = fromJust $ view eFaction <$> Map.lookup id (st ^. gEntities) | ||
| 62 | factionIndex id = fromJust $ elemIndex (view faction' $ faction id) factions | ||
| 63 | rowGs = do | ||
| 64 | rowGroup'@((seqVal', _):_) <- protoRows | ||
| 65 | let | ||
| 66 | rowGroup = map snd rowGroup' | ||
| 67 | factionColumn i = [evalState ?? st $ toName x | x <- rowGroup, factionIndex x == i ] | ||
| 68 | return . colsAllG top $ [show (seqVal' ^. seqVal)] : map factionColumn [0..(length factions - 1)] | ||
| 69 | |||
| 70 | listFaction :: Completable Faction -> Sh GameState () | ||
| 71 | listFaction (Completable (flip (set faction') def -> qFaction)) = use gEntities >>= mapM_ (shellPutStrLn <=< toName) . Map.keys . Map.filter ((==) qFaction . view eFaction) | ||
| 72 | |||
| 73 | listFactions :: Sh GameState () | ||
| 74 | listFactions = use inhabitedFactions >>= mapM_ (shellPutStrLn . view faction') | ||
| 75 | |||
| 76 | listEntities :: Sh GameState () | ||
| 77 | listEntities = use (gEntities . to Map.keys) >>= mapM_ (shellPutStrLn <=< toName) | ||
diff --git a/src/Sequence/Types.hs b/src/Sequence/Types.hs new file mode 100644 index 0000000..afe1060 --- /dev/null +++ b/src/Sequence/Types.hs | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, ViewPatterns, OverloadedStrings, TemplateHaskell, GeneralizedNewtypeDeriving #-} | ||
| 2 | |||
| 3 | module Sequence.Types | ||
| 4 | ( GameState(..), gEntities, gEntityNames | ||
| 5 | , Faction, faction, faction' | ||
| 6 | , SeqVal(..), seqVal | ||
| 7 | , Entity(..), eFaction, eSeqVal | ||
| 8 | , EntityName(..), entityName | ||
| 9 | , EntityIdentifier(..), entityId | ||
| 10 | , inhabitedFactions, priorityQueue | ||
| 11 | ) where | ||
| 12 | |||
| 13 | import Control.Lens | ||
| 14 | |||
| 15 | import System.Console.Shell | ||
| 16 | |||
| 17 | import Data.Default | ||
| 18 | import Data.CaseInsensitive (CI) | ||
| 19 | import qualified Data.CaseInsensitive as CI | ||
| 20 | |||
| 21 | import Data.Map.Strict (Map) | ||
| 22 | import qualified Data.Map.Strict as Map | ||
| 23 | |||
| 24 | import Data.Bimap (Bimap) | ||
| 25 | import qualified Data.Bimap as Bimap | ||
| 26 | |||
| 27 | import Data.List | ||
| 28 | import Data.Maybe | ||
| 29 | import Data.Tuple | ||
| 30 | import Data.Ord | ||
| 31 | |||
| 32 | |||
| 33 | newtype Faction = Faction { _lFaction :: Maybe (CI String) } | ||
| 34 | deriving (Show, Eq, Ord) | ||
| 35 | makeLenses ''Faction | ||
| 36 | |||
| 37 | instance Default Faction where | ||
| 38 | def = Faction Nothing | ||
| 39 | |||
| 40 | unaligned :: CI String | ||
| 41 | unaligned = "Unaligned" | ||
| 42 | |||
| 43 | faction :: Getter Faction (Maybe (CI String)) | ||
| 44 | faction = lFaction | ||
| 45 | |||
| 46 | faction' :: Lens' Faction String | ||
| 47 | faction' = lens (CI.original . fromMaybe unaligned . view faction) (\s a -> s { _lFaction = parseFaction a }) | ||
| 48 | where | ||
| 49 | parseFaction str@(CI.mk -> str') | ||
| 50 | | str' == unaligned = Nothing | ||
| 51 | | null str = Nothing | ||
| 52 | | otherwise = Just str' | ||
| 53 | |||
| 54 | |||
| 55 | newtype SeqVal = SeqVal { _seqVal :: Integer } | ||
| 56 | deriving (Show, Ord, Eq, Num, Integral, Enum, Real) | ||
| 57 | makeLenses ''SeqVal | ||
| 58 | |||
| 59 | |||
| 60 | data Entity = Entity | ||
| 61 | { _eFaction :: Faction | ||
| 62 | , _eSeqVal :: Maybe SeqVal | ||
| 63 | } | ||
| 64 | deriving (Show) | ||
| 65 | makeLenses ''Entity | ||
| 66 | |||
| 67 | instance Default Entity where | ||
| 68 | def = Entity | ||
| 69 | { _eFaction = def | ||
| 70 | , _eSeqVal = def | ||
| 71 | } | ||
| 72 | |||
| 73 | newtype EntityName = EntityName { _entityName :: CI String } | ||
| 74 | deriving (Show, Eq, Ord) | ||
| 75 | makeLenses ''EntityName | ||
| 76 | |||
| 77 | newtype EntityIdentifier = EntityIdentifier { _entityId :: Integer } | ||
| 78 | deriving (Show, Eq, Ord) | ||
| 79 | makeLenses ''EntityIdentifier | ||
| 80 | |||
| 81 | data GameState = GameState | ||
| 82 | { _gEntities :: Map EntityIdentifier Entity | ||
| 83 | , _gEntityNames :: Bimap EntityIdentifier EntityName | ||
| 84 | } | ||
| 85 | makeLenses ''GameState | ||
| 86 | |||
| 87 | instance Default GameState where | ||
| 88 | def = GameState | ||
| 89 | { _gEntities = def | ||
| 90 | , _gEntityNames = Bimap.empty | ||
| 91 | } | ||
| 92 | |||
| 93 | inhabitedFactions :: Getter GameState [Faction] | ||
| 94 | inhabitedFactions = to $ nub . sort . Map.elems . fmap (view eFaction) . view gEntities | ||
| 95 | |||
| 96 | priorityQueue :: Getter GameState [(SeqVal, EntityIdentifier)] | ||
| 97 | priorityQueue = to priorityQueue' | ||
| 98 | where | ||
| 99 | priorityQueue' (Map.toAscList . view gEntities -> entities) = sortBy (comparing $ Down . snd) . concat . map filter . map (over _1 $ view eSeqVal) . map swap $ entities | ||
| 100 | filter (Nothing, _) = mempty | ||
| 101 | filter (Just val, id) = pure (val, id) | ||
diff --git a/src/Sequence/Utils.hs b/src/Sequence/Utils.hs new file mode 100644 index 0000000..7d28b83 --- /dev/null +++ b/src/Sequence/Utils.hs | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | {-# LANGUAGE ViewPatterns, FlexibleContexts, MultiParamTypeClasses #-} | ||
| 2 | |||
| 3 | module Sequence.Utils | ||
| 4 | ( toName, fromName | ||
| 5 | ) where | ||
| 6 | |||
| 7 | import Sequence.Types | ||
| 8 | |||
| 9 | import Control.Monad.State.Strict | ||
| 10 | |||
| 11 | import Control.Lens | ||
| 12 | |||
| 13 | import Data.Bimap (Bimap) | ||
| 14 | import qualified Data.Bimap as Bimap | ||
| 15 | |||
| 16 | import Data.CaseInsensitive (CI) | ||
| 17 | import qualified Data.CaseInsensitive as CI | ||
| 18 | |||
| 19 | import Data.Map.Strict (Map) | ||
| 20 | import qualified Data.Map.Strict as Map | ||
| 21 | |||
| 22 | import Data.Function | ||
| 23 | import Data.Default | ||
| 24 | import Data.Maybe | ||
| 25 | import Text.Read | ||
| 26 | |||
| 27 | import Data.List | ||
| 28 | |||
| 29 | import System.Console.Shell | ||
| 30 | |||
| 31 | unaligned = view faction' def | ||
| 32 | |||
| 33 | toName :: MonadState GameState m => EntityIdentifier -> m String | ||
| 34 | toName id = fromMaybe (show $ id ^. entityId) . fmap (CI.original . view entityName) . Bimap.lookup id <$> use gEntityNames | ||
| 35 | |||
| 36 | fromName :: MonadState GameState m => String -> m (Maybe EntityIdentifier) | ||
| 37 | fromName (readMaybe -> n@(Just _)) = return $ EntityIdentifier <$> n | ||
| 38 | fromName (EntityName . CI.mk -> name) = Bimap.lookupR name <$> use gEntityNames | ||
| 39 | |||
| 40 | instance Completion EntityIdentifier GameState where | ||
| 41 | completableLabel _ = "<entity>" | ||
| 42 | complete _ st prefix = return . filter ((isPrefixOf `on` CI.foldCase) prefix) . map ((evalState ?? st) . toName) . Map.keys $ st ^. gEntities | ||
| 43 | |||
| 44 | instance Completion Faction GameState where | ||
| 45 | completableLabel _ = "<faction>" | ||
| 46 | complete _ st prefix = return . filter ((isPrefixOf `on` CI.foldCase) prefix) . nub . sort $ unaligned : map (view faction') (st ^. inhabitedFactions) | ||
