diff --git a/flake.nix b/flake.nix index 7048ce76..26f843cf 100644 --- a/flake.nix +++ b/flake.nix @@ -33,6 +33,6 @@ outputs = { self, nixpkgs, flake-utils, haskell-flake-utils, flake-compat, ... } ]; name = "rhine"; - packageNames = [ "rhine-gloss" "rhine-examples" ]; + packageNames = [ "rhine-gloss" "rhine-terminal" "rhine-examples" ]; }; } diff --git a/rhine-terminal/ChangeLog.md b/rhine-terminal/ChangeLog.md new file mode 100644 index 00000000..caed8f27 --- /dev/null +++ b/rhine-terminal/ChangeLog.md @@ -0,0 +1,5 @@ +# Revision history for rhine-terminal + +## 0.8.0.1 -- 2022-05-21 + +* First version. Version numbers follow rhine. diff --git a/rhine-terminal/LICENSE b/rhine-terminal/LICENSE new file mode 100644 index 00000000..c1ba552a --- /dev/null +++ b/rhine-terminal/LICENSE @@ -0,0 +1,30 @@ +Copyright (c) 2017, Manuel Bärenz, Jun Matsushita + +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 Manuel Bärenz 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. diff --git a/rhine-terminal/README.md b/rhine-terminal/README.md new file mode 100644 index 00000000..b524670e --- /dev/null +++ b/rhine-terminal/README.md @@ -0,0 +1,9 @@ +# README + +This package provides an interface for the [`haskell-terminal` library](https://github.com/lpeterse/haskell-terminal), enabling you to write `terminal` applications as signal functions. + +It consists of a `TerminalEventClock` which provides terminal events, a `flowTerminal` allowing you to run `Rhine`s which can receive terminal events and display to a terminal, as well as a `terminalConcurrently` schedule to coordinate multiple `Rhine`s. + +It also probides a simple example program, +which you can run as `cabal run rhine-terminal-simple` +or `nix build .#rhine-terminal && result/bin/rhine-terminal-simple`. diff --git a/rhine-terminal/Setup.hs b/rhine-terminal/Setup.hs new file mode 100644 index 00000000..9a994af6 --- /dev/null +++ b/rhine-terminal/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/rhine-terminal/TerminalSimple.hs b/rhine-terminal/TerminalSimple.hs new file mode 100644 index 00000000..8070cf5b --- /dev/null +++ b/rhine-terminal/TerminalSimple.hs @@ -0,0 +1,110 @@ +{- | Example application for the @rhine-terminal@ library. -} + +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- base +import Prelude hiding (putChar) +import System.Exit (exitSuccess) +import System.IO hiding (putChar) + +-- text +import Data.Text (Text) +import qualified Data.Text as T + +-- terminal +import System.Terminal +import System.Terminal.Internal + +-- rhine +import FRP.Rhine + +-- rhine-terminal +import FRP.Rhine.Terminal + +type App = TerminalT LocalTerminal IO + +-- Clocks + +data Input + = Char Char Modifiers + | Space + | Backspace + | Enter + | Exit + +type InputClock = SelectClock TerminalEventClock Input + +inputClock :: InputClock +inputClock = SelectClock + { mainClock = TerminalEventClock + , select = \case + Right (KeyEvent (CharKey k) m) + -- Don't display Ctrl-J https://github.com/lpeterse/haskell-terminal/issues/17 + | k /= 'J' || m /= ctrlKey -> Just (Char k m) + Right (KeyEvent SpaceKey _) -> Just Space + Right (KeyEvent BackspaceKey _) -> Just Backspace + Right (KeyEvent EnterKey _) -> Just Enter + Left _ -> Just Exit + _ -> Nothing + } + +type PromptClock = LiftClock IO (TerminalT LocalTerminal) (Millisecond 1000) + +type AppClock = ParallelClock App InputClock PromptClock + +-- ClSFs + +inputSource :: ClSF App InputClock () Input +inputSource = tagS + +promptSource :: ClSF App PromptClock () Text +promptSource = flip T.cons " > " . (cycle " ." !!) <$> count + +inputSink :: ClSF App cl Input () +inputSink = arrMCl $ \case + Char c _ -> putChar c >> flush + Space -> putChar ' ' >> flush + Backspace -> moveCursorBackward 1 >> deleteChars 1 >> flush + Enter -> putLn >> changePrompt " > " >> flush + Exit -> do + putLn + putStringLn "Exiting program." + flush + liftIO exitSuccess + +changePrompt :: MonadScreen m => Text -> m () +changePrompt prmpt = do + Position _ column <- getCursorPosition + if column /= 0 then do + moveCursorBackward column + putText prmpt + setCursorColumn column + else putText prmpt + flush + +promptSink :: ClSF App cl Text () +promptSink = arrMCl changePrompt + +-- Rhines + +mainRhine :: Rhine App AppClock () () +mainRhine = inputRhine ||@ terminalConcurrently @|| promptRhine + where + inputRhine :: Rhine App InputClock () () + inputRhine = inputSource >-> inputSink @@ inputClock + + promptRhine :: Rhine App PromptClock () () + promptRhine = promptSource >-> promptSink @@ liftClock waitClock + +-- Main + +main :: IO () +main = do + hSetBuffering stdin NoBuffering + hSetBuffering stdout NoBuffering + withTerminal $ \term -> flowTerminal term mainRhine diff --git a/rhine-terminal/rhine-terminal.cabal b/rhine-terminal/rhine-terminal.cabal new file mode 100644 index 00000000..bcf1d686 --- /dev/null +++ b/rhine-terminal/rhine-terminal.cabal @@ -0,0 +1,83 @@ +-- Initial rhine-gloss.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/ + +name: rhine-terminal +version: 0.8.0.1 +synopsis: Terminal backend for Rhine +description: + This package provides an example of a `terminal` based program using rhine. +license: BSD3 +license-file: LICENSE +author: Manuel Bärenz, Jun Matsushita +maintainer: programming@manuelbaerenz.de, jun@iilab.org +-- copyright: +category: FRP +build-type: Simple +extra-source-files: ChangeLog.md +extra-doc-files: README.md +cabal-version: 1.18 + +source-repository head + type: git + location: git@github.com:turion/rhine.git + +source-repository this + type: git + location: git@github.com:turion/rhine.git + tag: v0.8.0.1 + +library + exposed-modules: + FRP.Rhine.Terminal + build-depends: base >= 4.11 && < 4.16 + , exceptions >= 0.10.4 + , transformers >= 0.5 + , rhine == 0.8.0.1 + , dunai >= 0.6 + , terminal >= 0.2.0.0 + , time >= 1.9.3 + hs-source-dirs: src + default-language: Haskell2010 + ghc-options: -W + if flag(dev) + ghc-options: -Werror + +executable rhine-terminal-simple + main-is: TerminalSimple.hs + ghc-options: -threaded + build-depends: base >= 4.11 && < 4.16 + , rhine == 0.8.0.1 + , rhine-terminal + , terminal >= 0.2.0.0 + , text >= 1.2.5.0 + , time >= 1.9.3 + + default-language: Haskell2010 + ghc-options: -W -threaded -rtsopts -with-rtsopts=-N + if flag(dev) + ghc-options: -Werror + +test-suite rhine-terminal-tests + type: exitcode-stdio-1.0 + main-is: tests/Main.hs + ghc-options: -threaded + build-depends: base >= 4.11 && < 4.16 + , rhine == 0.8.0.1 + , rhine-terminal + , exceptions >= 0.10.4 + , transformers >= 0.5 + , terminal >= 0.2.0.0 + , text >= 1.2.5.0 + , time >= 1.9.3 + , stm >= 2.5.0 + , hspec + + default-language: Haskell2010 + ghc-options: -W -threaded -rtsopts -with-rtsopts=-N + if flag(dev) + ghc-options: -Werror + +flag dev + description: Enable warnings as errors. Active on ci. + default: False + manual: True diff --git a/rhine-terminal/src/FRP/Rhine/Terminal.hs b/rhine-terminal/src/FRP/Rhine/Terminal.hs new file mode 100644 index 00000000..8585a81a --- /dev/null +++ b/rhine-terminal/src/FRP/Rhine/Terminal.hs @@ -0,0 +1,118 @@ +{- | Wrapper to write @terminal@ applications in Rhine, using concurrency. +-} + +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE RecordWildCards #-} +module FRP.Rhine.Terminal + ( TerminalEventClock (..) + , flowTerminal + , terminalConcurrently + ) where + +-- base +import Prelude hiding (putChar) +import Unsafe.Coerce (unsafeCoerce) + +-- exceptions +import Control.Monad.Catch (MonadMask) + +-- time +import Data.Time.Clock ( getCurrentTime ) + +-- terminal +import System.Terminal ( awaitEvent, runTerminalT, Event, Interrupt, TerminalT, MonadInput ) +import System.Terminal.Internal ( Terminal ) + +-- transformers +import Control.Monad.Trans.Reader +import Control.Monad.Trans.Class (lift) + +-- rhine +import FRP.Rhine + +-- | A clock that ticks whenever events or interrupts on the terminal arrive. +data TerminalEventClock = TerminalEventClock + +instance (MonadInput m, MonadIO m) => Clock m TerminalEventClock + where + type Time TerminalEventClock = UTCTime + type Tag TerminalEventClock = Either Interrupt Event + + initClock TerminalEventClock = do + initialTime <- liftIO getCurrentTime + return + ( constM $ do + event <- awaitEvent + time <- liftIO getCurrentTime + return (time, event) + , initialTime + ) + +instance GetClockProxy TerminalEventClock + +instance Semigroup TerminalEventClock where + t <> _ = t + +-- | A function wrapping `flow` to use at the top level +-- in order to run a `Rhine (TerminalT t m) cl ()` +-- +-- Example: +-- +-- @ +-- mainRhine :: MonadIO m => Rhine (TerminalT LocalTerminal m) TerminalEventClock () () +-- mainRhine = tagS >-> arrMCl (liftIO . print) @@ TerminalEventClock +-- +-- main :: IO () +-- main = withTerminal $ \term -> `flowTerminal` term mainRhine +-- @ + +flowTerminal + :: ( MonadIO m + , MonadMask m + , Terminal t + , Clock (TerminalT t m) cl + , GetClockProxy cl + , Time cl ~ Time (In cl) + , Time cl ~ Time (Out cl) + ) + => t + -> Rhine (TerminalT t m) cl () () + -> m () +flowTerminal term clsf = flip runTerminalT term $ flow clsf + +-- | A schedule in the 'TerminalT LocalTerminal' transformer, +-- supplying the same backend connection to its scheduled clocks. +terminalConcurrently + :: forall t cl1 cl2. ( + Terminal t + , Clock (TerminalT t IO) cl1 + , Clock (TerminalT t IO) cl2 + , Time cl1 ~ Time cl2 + ) + => Schedule (TerminalT t IO) cl1 cl2 +terminalConcurrently + = Schedule $ \cl1 cl2 -> do + term <- terminalT ask + lift $ first liftTransS <$> + initSchedule concurrently (runTerminalClock term cl1) (runTerminalClock term cl2) + +-- Workaround TerminalT constructor not being exported. Should be safe in practice. +-- See PR upstream https://github.com/lpeterse/haskell-terminal/pull/18 +terminalT :: ReaderT t m a -> TerminalT t m a +terminalT = unsafeCoerce + +type RunTerminalClock m t cl = HoistClock (TerminalT t m) m cl + +runTerminalClock + :: Terminal t + => t + -> cl + -> RunTerminalClock IO t cl +runTerminalClock term unhoistedClock = HoistClock + { monadMorphism = flip runTerminalT term + , .. + } diff --git a/rhine-terminal/tests/Main.hs b/rhine-terminal/tests/Main.hs new file mode 100644 index 00000000..740b7eb4 --- /dev/null +++ b/rhine-terminal/tests/Main.hs @@ -0,0 +1,72 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NamedFieldPuns #-} +module Main where + +-- base +import Prelude hiding (putChar) +import GHC.Conc (retry, readTVarIO, atomically) +import Control.Concurrent (forkIO, threadDelay) +import Control.Monad (void) + +-- rhine +import FRP.Rhine +import FRP.Rhine.Terminal + +-- terminal +import System.Terminal +import System.Terminal.Internal + +-- stm +import Control.Concurrent.STM.TQueue + +-- hspec +import Test.Hspec + +type KeyClock = SelectClock TerminalEventClock Char + +keyClock :: KeyClock +keyClock = SelectClock { mainClock = TerminalEventClock , select } + where + select :: Tag TerminalEventClock -> Maybe Char + select (Right (KeyEvent (CharKey k) _)) = Just k + select _ = Nothing + +defaultSettings :: TQueue Event -> VirtualTerminalSettings +defaultSettings eventQueue = VirtualTerminalSettings + { virtualType = "xterm" + , virtualWindowSize = pure (Size 3 10) + , virtualEvent = readTQueue eventQueue + , virtualInterrupt = retry + } + +displayDot :: MonadScreen m => ClSF m KeyClock () () +displayDot = constMCl $ do + putChar '.' + flush + +testRhine :: Terminal t => Rhine (TerminalT t IO) KeyClock () () +testRhine = displayDot @@ keyClock + +charEvent :: TQueue Event -> t -> Char -> IO () +charEvent eventQueue _ char = do + atomically $ writeTQueue eventQueue $ KeyEvent (CharKey char) mempty + +main :: IO () +main = hspec $ do + describe "rhine-terminal with VirtualTerminal" $ do + it "reaplces virtual inputs by dots" $ do + eventQueue <- newTQueueIO + withVirtualTerminal (defaultSettings eventQueue) $ \t -> do + void $ liftIO $ forkIO $ flowTerminal t testRhine + charEvent eventQueue t '1' + threadDelay $ 200 * 1000 + charEvent eventQueue t '2' + threadDelay $ 200 * 1000 + charEvent eventQueue t '3' + threadDelay $ 200 * 1000 + readTVarIO (virtualWindow t) `shouldReturn` expWindow + where + expWindow = + [ "... " + , " " + , " " ] diff --git a/stack.8.10.7.yaml b/stack.8.10.7.yaml index 641b1ec9..183dcc9d 100644 --- a/stack.8.10.7.yaml +++ b/stack.8.10.7.yaml @@ -4,7 +4,10 @@ packages: - rhine - rhine-examples - rhine-gloss +- rhine-terminal extra-deps: - time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 - dunai-0.8.3@sha256:06f75101fc87566f00e09b6c941384a98e1e1393eac721976e5b714d9c4a1f68,6260 +- terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 +- text-1.2.5.0@sha256:791f0f6c97ed96113f17ab520cf0efe1a3a4f883a8c85910a5660567c8241c40,7895 diff --git a/stack.8.10.7.yaml.lock b/stack.8.10.7.yaml.lock index 57c9e6a2..3d7ce6f2 100644 --- a/stack.8.10.7.yaml.lock +++ b/stack.8.10.7.yaml.lock @@ -5,15 +5,36 @@ packages: - completed: - hackage: time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 pantry-tree: - size: 223 sha256: afa0bf300d4ebf038bdea9d3990c0075743c9d20ffc9cb43db862a93342f7ba4 + size: 223 + hackage: time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 original: hackage: time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 +- completed: + pantry-tree: + sha256: 674e02bdeb768ba1839077cbba24ec418f1cd82fb3e083a1b2fe900c4cdc0939 + size: 2172 + hackage: dunai-0.8.3@sha256:06f75101fc87566f00e09b6c941384a98e1e1393eac721976e5b714d9c4a1f68,6260 + original: + hackage: dunai-0.8.3@sha256:06f75101fc87566f00e09b6c941384a98e1e1393eac721976e5b714d9c4a1f68,6260 +- completed: + pantry-tree: + sha256: 54160663bf0cbcd1d9fa52e136740faaf91a82d1ca1efeb08e8e58575a7446e7 + size: 1775 + hackage: terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 + original: + hackage: terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 +- completed: + pantry-tree: + sha256: f41504ec5c04a3f3358ef104362f02fdef29cbce4e5e4e6dbd6b6db70c40d4bf + size: 7395 + hackage: text-1.2.5.0@sha256:791f0f6c97ed96113f17ab520cf0efe1a3a4f883a8c85910a5660567c8241c40,7895 + original: + hackage: text-1.2.5.0@sha256:791f0f6c97ed96113f17ab520cf0efe1a3a4f883a8c85910a5660567c8241c40,7895 snapshots: - completed: + sha256: ce4fb8d44f3c6c6032060a02e0ebb1bd29937c9a70101c1517b92a87d9515160 size: 586110 url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/18/21.yaml - sha256: ce4fb8d44f3c6c6032060a02e0ebb1bd29937c9a70101c1517b92a87d9515160 original: lts-18.21 diff --git a/stack.8.8.4.yaml b/stack.8.8.4.yaml index 7972bfe8..bd0a91db 100644 --- a/stack.8.8.4.yaml +++ b/stack.8.8.4.yaml @@ -4,7 +4,12 @@ packages: - rhine - rhine-examples - rhine-gloss +- rhine-terminal extra-deps: - time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 - dunai-0.8.3@sha256:06f75101fc87566f00e09b6c941384a98e1e1393eac721976e5b714d9c4a1f68,6260 +- terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 +- text-1.2.5.0@sha256:791f0f6c97ed96113f17ab520cf0efe1a3a4f883a8c85910a5660567c8241c40,7895 +- Cabal-3.4.1.0@sha256:48e64c97688149fac15445803830177248f15a9b1a783389efed5e375d70d2d0,31402 +- parsec-3.1.15.1@sha256:8c7a36aaadff12a38817fc3c4ff6c87e3352cffd1a58df640de7ed7a97ad8fa3,4601 diff --git a/stack.8.8.4.yaml.lock b/stack.8.8.4.yaml.lock index 44414abb..b02e7ff2 100644 --- a/stack.8.8.4.yaml.lock +++ b/stack.8.8.4.yaml.lock @@ -5,15 +5,50 @@ packages: - completed: - hackage: time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 pantry-tree: - size: 223 sha256: afa0bf300d4ebf038bdea9d3990c0075743c9d20ffc9cb43db862a93342f7ba4 + size: 223 + hackage: time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 original: hackage: time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 +- completed: + pantry-tree: + sha256: 674e02bdeb768ba1839077cbba24ec418f1cd82fb3e083a1b2fe900c4cdc0939 + size: 2172 + hackage: dunai-0.8.3@sha256:06f75101fc87566f00e09b6c941384a98e1e1393eac721976e5b714d9c4a1f68,6260 + original: + hackage: dunai-0.8.3@sha256:06f75101fc87566f00e09b6c941384a98e1e1393eac721976e5b714d9c4a1f68,6260 +- completed: + pantry-tree: + sha256: 54160663bf0cbcd1d9fa52e136740faaf91a82d1ca1efeb08e8e58575a7446e7 + size: 1775 + hackage: terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 + original: + hackage: terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977 +- completed: + pantry-tree: + sha256: f41504ec5c04a3f3358ef104362f02fdef29cbce4e5e4e6dbd6b6db70c40d4bf + size: 7395 + hackage: text-1.2.5.0@sha256:791f0f6c97ed96113f17ab520cf0efe1a3a4f883a8c85910a5660567c8241c40,7895 + original: + hackage: text-1.2.5.0@sha256:791f0f6c97ed96113f17ab520cf0efe1a3a4f883a8c85910a5660567c8241c40,7895 +- completed: + pantry-tree: + sha256: f05b577b5b2b6f8805ec5b2c0af9d8c42005319522b20db72ce6de90b7cbfe52 + size: 45845 + hackage: Cabal-3.4.1.0@sha256:48e64c97688149fac15445803830177248f15a9b1a783389efed5e375d70d2d0,31402 + original: + hackage: Cabal-3.4.1.0@sha256:48e64c97688149fac15445803830177248f15a9b1a783389efed5e375d70d2d0,31402 +- completed: + pantry-tree: + sha256: 147ad21b8aa90273721903a6b294cc4ecd660d229d88c4e84c6275bc5d630ae6 + size: 2630 + hackage: parsec-3.1.15.1@sha256:8c7a36aaadff12a38817fc3c4ff6c87e3352cffd1a58df640de7ed7a97ad8fa3,4601 + original: + hackage: parsec-3.1.15.1@sha256:8c7a36aaadff12a38817fc3c4ff6c87e3352cffd1a58df640de7ed7a97ad8fa3,4601 snapshots: - completed: + sha256: 637fb77049b25560622a224845b7acfe81a09fdb6a96a3c75997a10b651667f6 size: 534126 url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/31.yaml - sha256: 637fb77049b25560622a224845b7acfe81a09fdb6a96a3c75997a10b651667f6 original: lts-16.31 diff --git a/stack.9.0.2.yaml b/stack.9.0.2.yaml index 2f5187ca..1e257e8a 100644 --- a/stack.9.0.2.yaml +++ b/stack.9.0.2.yaml @@ -4,7 +4,8 @@ packages: - rhine - rhine-examples - rhine-gloss +- rhine-terminal extra-deps: - time-domain-0.1.0.0@sha256:48146872e85f46399e8b8d7ff7c3d3b7f530e2de167b6893ba3ce649ba2e2e75,800 - +- terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977