diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2016-06-02 23:29:36 +0200 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2016-06-02 23:29:36 +0200 |
commit | 1b7f21ba636e4034f76495deafd0ac6ca9758a4e (patch) | |
tree | f8cdffa91a9c6643df51d851d367de0daafb6d5d | |
parent | e4fe9710287960438856fa78b697ccae64b7e2eb (diff) | |
download | 2017-01-16_17:13:37-1b7f21ba636e4034f76495deafd0ac6ca9758a4e.tar 2017-01-16_17:13:37-1b7f21ba636e4034f76495deafd0ac6ca9758a4e.tar.gz 2017-01-16_17:13:37-1b7f21ba636e4034f76495deafd0ac6ca9758a4e.tar.bz2 2017-01-16_17:13:37-1b7f21ba636e4034f76495deafd0ac6ca9758a4e.tar.xz 2017-01-16_17:13:37-1b7f21ba636e4034f76495deafd0ac6ca9758a4e.zip |
rudimentary state
-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) | ||