packages feed

termbox-banana 0.1.1 → 0.2.0

raw patch · 6 files changed

+611/−45 lines, 6 filesdep +aesondep +asyncdep +http-conduitdep ~basedep ~reactive-bananadep ~termboxnew-component:exe:termbox-banana-example-echonew-component:exe:termbox-banana-example-hooglePVP ok

version bump matches the API change (PVP)

Dependencies added: aeson, async, http-conduit, lens, lens-aeson, mtl, stm, tagsoup, termbox-banana

Dependency ranges changed: base, reactive-banana, termbox

API changes (from Hackage documentation)

- Termbox.Banana: main :: InputMode -> OutputMode -> (Event TermboxEvent -> Behavior (Int, Int) -> MomentIO (Behavior Scene, Event a)) -> IO a
+ Termbox.Banana: run :: InputMode -> OutputMode -> (Event TermboxEvent -> Behavior (Int, Int) -> MomentIO (Behavior Scene, Event a)) -> IO (Either InitError a)
+ Termbox.Banana: run_ :: InputMode -> OutputMode -> (Event TermboxEvent -> Behavior (Int, Int) -> MomentIO (Behavior Scene, Event a)) -> IO a

Files

.travis.yml view
@@ -4,7 +4,7 @@ # # For more information, see https://github.com/haskell-CI/haskell-ci #-# version: 0.3.20190418+# version: 0.3.20190521 # language: c dist: xenial@@ -26,12 +26,10 @@   - rm -rfv $CABALHOME/packages/head.hackage matrix:   include:-    - compiler: ghc-8.6.4-      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.4","cabal-install-2.4"]}}+    - compiler: ghc-8.6.5+      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-2.4"]}}     - compiler: ghc-8.4.4       addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-2.4"]}}-    - compiler: ghc-8.2.2-      addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-2.4"]}} before_install:   - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')   - HCPKG="$HC-pkg"@@ -77,6 +75,7 @@     echo "world-file:        $CABALHOME/world"          >> $CABALHOME/config     echo "extra-prog-path:   $CABALHOME/bin"            >> $CABALHOME/config     echo "symlink-bindir:    $CABALHOME/bin"            >> $CABALHOME/config+    echo "installdir:        $CABALHOME/bin"            >> $CABALHOME/config     echo "build-summary:     $CABALHOME/logs/build.log" >> $CABALHOME/config     echo "store-dir:         $CABALHOME/store"          >> $CABALHOME/config     echo "install-dirs user"                            >> $CABALHOME/config@@ -93,7 +92,7 @@     echo 'packages: "."' >> cabal.project   - |     echo "write-ghc-environment-files: always" >> cabal.project-  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(termbox-banana)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(termbox-banana)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"   - cat cabal.project || true   - cat cabal.project.local || true   - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi@@ -117,7 +116,7 @@     echo 'packages: "termbox-banana-*/*.cabal"' >> cabal.project   - |     echo "write-ghc-environment-files: always" >> cabal.project-  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(termbox-banana)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"+  - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(termbox-banana)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"   - cat cabal.project || true   - cat cabal.project.local || true   # Building...
CHANGELOG.md view
@@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/). +## [0.2.0] - 2019-06-28++### Added+- Added `run`++### Changed+- Renamed `main` to `run_`+- Bumped `termbox` lower bound+ ## [0.1.1] - 2019-04-19  ### Changed
+ examples/Echo.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Reactive.Banana+import Reactive.Banana.Frameworks++import qualified Termbox.Banana as Termbox++main :: IO ()+main =+  Termbox.run_+    (Termbox.InputModeEsc Termbox.MouseModeNo)+    Termbox.OutputModeNormal+    moment++moment+  :: Event Termbox.Event+  -> Behavior (Int, Int)+  -> MomentIO (Behavior Termbox.Scene, Event ())+moment eEvent _bSize = do+  let+    eQuit :: Event ()+    eQuit =+      () <$ filterE isKeyEsc eEvent++  bLatestEvent :: Behavior (Maybe Termbox.Event) <-+    stepper+      Nothing+      (Just <$> eEvent)++  let+    bCells :: Behavior Termbox.Cells+    bCells =+      maybe mempty renderEvent <$> bLatestEvent++  let+    bScene :: Behavior Termbox.Scene+    bScene =+      Termbox.Scene+        <$> bCells+        <*> pure Termbox.NoCursor++  pure (bScene, eQuit)++renderEvent :: Termbox.Event -> Termbox.Cells+renderEvent =+  foldMap (\(i, c) -> Termbox.set i 0 (Termbox.Cell c mempty mempty))+    . zip [0..]+    . show++isKeyEsc :: Termbox.Event -> Bool+isKeyEsc = \case+  Termbox.EventKey Termbox.KeyEsc _ -> True+  _ -> False
+ examples/Hoogle.hs view
@@ -0,0 +1,350 @@+{-# language LambdaCase          #-}+{-# language OverloadedStrings   #-}+{-# language RecursiveDo         #-}+{-# language ScopedTypeVariables #-}++module Main where++import Control.Concurrent+import Control.Concurrent.Async+import Control.Concurrent.STM+import Control.Exception (SomeException)+import Control.Lens+import Control.Monad+import Control.Monad.State.Strict+import Control.Monad.Writer.Strict+import Data.Aeson (Value)+import Data.Aeson.Lens+import Data.Foldable (fold, for_)+import Data.Maybe+import Data.String (fromString)+import Data.Text.Lens (unpacked)+import Network.HTTP.Simple+import Reactive.Banana+import Reactive.Banana.Frameworks+import Text.HTML.TagSoup++import qualified Termbox.Banana as Termbox++main :: IO ()+main =+  Termbox.run_+    (Termbox.InputModeEsc Termbox.MouseModeNo)+    Termbox.OutputModeNormal+    moment++moment+  :: Event Termbox.Event+  -> Behavior (Int, Int)+  -> MomentIO (Behavior Termbox.Scene, Event ())+moment eEvent bSize = mdo+  eTick :: Event () <-+    makeTickEvent++  requestQueue :: TQueue String <-+    liftIO newTQueueIO++  eSearchBox :: Event String <-+    makeSearchBoxEvent eKey++  reactimate+    ((atomically . writeTQueue requestQueue . reverse) <$> eSearchBox)++  let+    eEmptySearchBox :: Event String+    eEmptySearchBox =+      filterE null eSearchBox++  bSearchBox :: Behavior String <-+    stepper "" eSearchBox++  (eSearchResults, fireSearchResults) <-+    newEvent++  liftIO . void . forkIO $+    runHttpRequestThread+      requestQueue+      fireSearchResults++  bSearchResults :: Behavior (Either SomeException [Value]) <-+    stepper+      (Right [])+      (unionWith+        const+        (Right [] <$ eEmptySearchBox)+        eSearchResults)++  bSpinnerFrame :: Behavior (Maybe Int) <-+    accumB Nothing (unions+      [ ((\query ->+            if null query+              then const Nothing+              else const (Just 1))+          <$> eSearchBox)+      , const Nothing <$ eSearchResults+      , const Nothing <$ eEmptySearchBox+      , fmap (+1) <$ whenE (isJust <$> bSpinnerFrame) eTick+      ])++  let+    bCells :: Behavior Termbox.Cells+    bCells =+      render+        <$> bHeight+        <*> bSearchBox+        <*> bSearchResults+        <*> bSpinnerFrame++  let+    bCursor :: Behavior Termbox.Cursor+    bCursor =+      (\height searchBox -> Termbox.Cursor (length searchBox + 2) height)+        <$> bHeight+        <*> bSearchBox++  let+    bScene :: Behavior Termbox.Scene+    bScene =+      Termbox.Scene+        <$> bCells+        <*> bCursor++  pure (bScene, () <$ filterE (== Termbox.KeyEsc) eKey)++  where+    eKey :: Event Termbox.Key+    eKey =+      filterJust (eventAsKey <$> eEvent)++    bHeight :: Behavior Int+    bHeight =+      snd <$> bSize++makeTickEvent :: MomentIO (Event ())+makeTickEvent = do+  (e, f) <- newEvent+  liftIO . void . forkIO . forever $ do+    f ()+    threadDelay 100000+  pure e++makeSearchBoxEvent+  :: MonadMoment m+  => Event Termbox.Key+  -> m (Event String)+makeSearchBoxEvent eKey =+  accumE+    ""+    (unions+      [ (:) <$> filterJust (keyAsChar <$> eKey)+      , (' ' :) <$  filterE (== Termbox.KeySpace) eKey+      , safeTail <$ filterE (== Termbox.KeyBackspace2) eKey+      ])++runHttpRequestThread+  :: TQueue String -- ^ Request queue+  -> (Either SomeException [Value] -> IO ()) -- ^ Response callback+  -> IO ()+runHttpRequestThread requestQueue respond =+  loop Nothing+  where+    loop :: Maybe (Async [Value]) -> IO ()+    loop maybeInFlightRequest =+      join . atomically $+        (do+          query <- readTQueue requestQueue+          pure $ do+            for_ maybeInFlightRequest $ \inFlightRequest ->+              forkIO (cancel inFlightRequest)+            if null query+              then+                loop Nothing+              else do+                inFlightRequest <- async (performHoogleSearch query)+                loop (Just inFlightRequest))+        <|>+        (case maybeInFlightRequest of+          Nothing ->+            retry+          Just inFlightRequest -> do+            response <- waitCatchSTM inFlightRequest+            pure $ do+              respond response+              loop Nothing)++render+  :: Int+  -> String+  -> Either SomeException [Value]+  -> Maybe Int+  -> Termbox.Cells+render height searchBox searchResults spinnerFrame =+  fold+    [ case searchResults of+        Left ex ->+          renderSearchResultsError height ex+        Right results ->+          renderSearchResults height results+            & execWriterT+            & (`evalState` 0)+    , renderSearchBox height searchBox spinnerFrame+    ]++renderSearchBox :: Int -> String -> Maybe Int -> Termbox.Cells+renderSearchBox height searchBox spinnerFrame =+  renderString 0 (height-1) (promptChar : ' ' : reverse searchBox)+  where+    promptChar :: Char+    promptChar =+      case spinnerFrame of+        Nothing ->+          'λ'+        Just n  ->+          let+            cs =+              "⣧⣏⡟⠿⢻⣹⣼⣶"+          in+            cs !! (n `mod` length cs)++renderSearchResults+  :: Int+  -> [Value]+  -> WriterT Termbox.Cells (State Int) ()+renderSearchResults height = \case+  [] ->+    pure ()++  result : results -> do+    row <- get++    let+      ss = searchResultToLines result++    when (row + length ss < height-1) $ do+      for_ (zip [row..] ss) $ \(r, s) ->+        tell (renderString 0 r s)+      modify' (+ (length ss + 1))+      renderSearchResults height results++renderSearchResultsError+  :: Int+  -> SomeException+  -> Termbox.Cells+renderSearchResultsError height ex =+  ex+    & show+    & lines+    & take height+    & zip [0..]+    & foldMap (\(row, line) -> renderString 0 row line)++searchResultToLines :: Value -> [String]+searchResultToLines result =+  case result ^?! key "type" of+    "" ->+      unwords+        [ "[" ++ resultPackage result ++ "]"+        , "[" ++ resultModule result ++ "]"+        , resultItem result+        ]+        : map ("  " ++) (resultDocs result)++    "module" ->+      unwords+        [ "[" ++ resultPackage result ++ "]"+        , "[" ++ drop 7 (resultItem result) ++ "]"+        ]+        : map ("  " ++) (resultDocs result)++    "package" ->+      ("[" ++ drop 8 (resultItem result) ++ "]")+        : map ("  " ++) (resultDocs result)++    _ ->+      error (show result)++resultPackage :: Value -> String+resultPackage result =+  result+    ^?! key "package"+      . key "name"+      . _String+      . unpacked++resultModule :: Value -> String+resultModule result =+  result+    ^?! key "module"+      . key "name"+      . _String+      . unpacked++resultItem :: Value -> String+resultItem result =+  result+    ^?! key "item"+      . _String+      . unpacked+      . to htmlToText++resultDocs :: Value -> [String]+resultDocs result =+  result+    ^?! key "docs"+      . _String+      . unpacked+      . to htmlToText+      . to lines+      . to collapseLines++  where+    collapseLines :: [String] -> [String]+    collapseLines = \case+      [] ->+        []++      [""] ->+        []++      "" : ss ->+        case collapseLines ss of+          "" : ts -> "" : ts+          ts      -> "" : ts++      s : ss ->+        s : collapseLines ss++htmlToText :: String -> String+htmlToText html =+  concat $ do+    TagText text <- parseTags html+    pure text+++renderString :: Int -> Int -> String -> Termbox.Cells+renderString col row =+  foldMap (\(i, c) -> Termbox.set i row (Termbox.Cell c mempty mempty)) .+    zip [col..]++performHoogleSearch :: String -> IO [Value]+performHoogleSearch query =+  getResponseBody <$> httpJSON (fromString searchUrl)+  where+    searchUrl :: String+    searchUrl =+      "https://hoogle.haskell.org?mode=json&count=10&hoogle=" ++ query++eventAsKey :: Termbox.Event -> Maybe Termbox.Key+eventAsKey = \case+  Termbox.EventKey k _ -> Just k+  _ -> Nothing++keyAsChar :: Termbox.Key -> Maybe Char+keyAsChar = \case+  Termbox.KeyChar c -> Just c+  _ -> Nothing++safeTail :: [a] -> [a]+safeTail = \case+  [] -> []+  _:xs -> xs
src/Termbox/Banana.hs view
@@ -1,4 +1,4 @@-{-# language CPP                        #-}+{-# language DerivingStrategies         #-} {-# language GeneralizedNewtypeDeriving #-} {-# language LambdaCase                 #-} {-# language ScopedTypeVariables        #-}@@ -6,12 +6,12 @@  module Termbox.Banana   ( -- $intro-     TermboxEvent-  , main+  , run+  , run_+  , set   , Scene(..)   , Cells-  , set   , Cursor(..)     -- * Re-exports   , Termbox.black@@ -34,16 +34,29 @@   , Termbox.Mouse(..)   , Termbox.MouseMode(..)   , Termbox.OutputMode(..)+    -- * Example+    -- $example   ) where  import Control.Concurrent.MVar+import Control.Exception (throwIO) import Data.Function (fix) import Reactive.Banana import Reactive.Banana.Frameworks  import qualified Termbox + -- $intro+-- See the bottom of this module for a simple, runnable example to get started.+--+-- Here's how to run the examples with @cabal@:+--+-- @+-- cabal v2-run --constraint "termbox-banana +build-examples" termbox-banana-example-echo+-- cabal v2-run --constraint "termbox-banana +build-examples" termbox-banana-example-hoogle+-- @+-- -- This module is intended to be imported qualified. -- -- @@@ -73,15 +86,7 @@ -- @since 0.1.0 newtype Cells   = Cells (IO ())-#if MIN_VERSION_base(4,10,0)-  deriving (Monoid, Semigroup)-#else-instance Monoid Cells where-  mempty = Cells (pure ())-  mappend = (<>)-instance Semigroup Cells where-  Cells x <> Cells y = Cells (x >> y)-#endif+  deriving newtype (Monoid, Semigroup)  -- | A cursor. --@@ -113,16 +118,16 @@ -- * an event stream of arbitrary values, only the first of which is relevant, --   which ends the @termbox@ program and returns from the @main@ action. ----- @since 0.1.0-main+-- @since 0.2.0+run   :: Termbox.InputMode -- ^   -> Termbox.OutputMode -- ^   -> (  Event TermboxEvent      -> Behavior (Int, Int)      -> MomentIO (Behavior Scene, Event a))-  -> IO a-main imode omode run =-  Termbox.main $ do+  -> IO (Either Termbox.InitError a)+run imode omode program =+  Termbox.run $ do     Termbox.setInputMode imode     Termbox.setOutputMode omode @@ -146,11 +151,11 @@                 _ -> Nothing)               <$> eEvent) -        bSize :: Behavior (Int, Int) <- do+        bSize :: Behavior (Int, Int) <-           flip stepper eResize =<<-            liftIO Termbox.size+            liftIO Termbox.getSize -        moment run eEvent bSize (putMVar doneVar)+        moment program eEvent bSize (putMVar doneVar)      actuate network @@ -159,6 +164,19 @@       tryReadMVar doneVar >>=         maybe loop pure +-- | Like 'run', but throws 'Termbox.InitError's as @IO@ exceptions.+--+-- @since 0.2.0+run_+  :: Termbox.InputMode -- ^+  -> Termbox.OutputMode -- ^+  -> (  Event TermboxEvent+     -> Behavior (Int, Int)+     -> MomentIO (Behavior Scene, Event a))+  -> IO a+run_ imode omode program =+  run imode omode program >>= either throwIO pure+ moment   :: (  Event TermboxEvent      -> Behavior (Int, Int)@@ -167,9 +185,9 @@   -> Behavior (Int, Int)   -> (a -> IO ())   -> MomentIO ()-moment run eEvent bSize abort = do+moment program eEvent bSize abort = do   (bScene, eDone) :: (Behavior Scene, Event a) <--    run eEvent bSize+    program eEvent bSize    eScene :: Event (Future Scene) <-     changes bScene@@ -187,3 +205,67 @@   liftIO . render =<< valueB bScene   reactimate (abort <$> eDone)   reactimate' ((fmap.fmap) render eScene)++-- $example+--+-- Below is a sample program that simply displays the last key pressed, and+-- quits on @Esc@:+--+-- @+-- {-\# LANGUAGE LambdaCase          \#-}+-- {-\# LANGUAGE ScopedTypeVariables \#-}+--+-- module Main where+--+-- import Reactive.Banana+-- import Reactive.Banana.Frameworks+--+-- import qualified Termbox.Banana as Termbox+--+-- main :: IO ()+-- main =+--   Termbox.'run_'+--     (Termbox.'Termbox.InputModeEsc' Termbox.'Termbox.MouseModeNo')+--     Termbox.'Termbox.OutputModeNormal'+--     moment+--+-- moment+--   :: Event Termbox.'Termbox.Event'+--   -> Behavior (Int, Int)+--   -> MomentIO (Behavior Termbox.'Termbox.Scene', Event ())+-- moment eEvent _bSize = do+--   let+--     eQuit :: Event ()+--     eQuit =+--       () <$ filterE isKeyEsc eEvent+--+--   bLatestEvent :: Behavior (Maybe Termbox.'Termbox.Event') <-+--     stepper+--       Nothing+--       (Just \<$\> eEvent)+--+--   let+--     bCells :: Behavior Termbox.'Termbox.Cells'+--     bCells =+--       maybe mempty renderEvent \<$\> bLatestEvent+--+--   let+--     bScene :: Behavior Termbox.'Termbox.Scene'+--     bScene =+--       Termbox.'Termbox.Scene'+--         \<$\> bCells+--         \<*\> pure Termbox.'Termbox.NoCursor'+--+--   pure (bScene, eQuit)+--+-- renderEvent :: Termbox.'Termbox.Event' -> Termbox.'Termbox.Cells'+-- renderEvent =+--   foldMap (\\(i, c) -> Termbox.set i 0 (Termbox.'Termbox.Cell' c mempty mempty))+--     . zip [0..]+--     . show+--+-- isKeyEsc :: Termbox.'Termbox.Event' -> Bool+-- isKeyEsc = \\case+--   Termbox.'Termbox.EventKey' Termbox.'Termbox.KeyEsc' _ -> True+--   _ -> False+-- @
termbox-banana.cabal view
@@ -1,51 +1,121 @@-cabal-version: 2.0+cabal-version: 2.4  name: termbox-banana-version: 0.1.1+version: 0.2.0 category: User Interfaces description:   A @reactive-banana@-based interface to writing @termbox@ programs.+  .+  See also the <https://hackage.haskell.org/termbox termbox> package for a+  lower-level, imperative interface.+  .+  __/NOTE/__: The dependencies listed on Hackage are misleading! Most are only+  used in the examples provided, which are built only if the @build-examples@+  flag is explicitly enabled.+  .+  The actual dependencies of the @termbox-banana@ library are only:+  .+  * base+  * reactive-banana+  * termbox synopsis: reactive-banana + termbox author: Mitchell Rosen maintainer: Mitchell Rosen <mitchellwrosen@gmail.com> homepage: https://github.com/mitchellwrosen/termbox-banana bug-reports: https://github.com/mitchellwrosen/termbox-banana/issues-copyright: (c) 2018, Mitchell Rosen-license: BSD3+copyright: (c) 2018-2019, Mitchell Rosen+license: BSD-3-Clause license-file: LICENSE build-type: Simple-tested-with: GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.4+tested-with: GHC == 8.4.4, GHC == 8.6.5  extra-source-files:   .travis.yml   CHANGELOG.md   README.md +flag build-examples+  description: Build example executables+  default: False+  manual: True+ library   build-depends:-      base ^>= 4.9 || ^>= 4.10 || ^>= 4.11 || ^>= 4.12+      base ^>= 4.10 || ^>= 4.11 || ^>= 4.12     , reactive-banana ^>= 1.2-    , termbox ^>= 0.1+    , termbox ^>= 0.2+   default-language:     Haskell2010+   exposed-modules:     Termbox.Banana+   ghc-options:-    -fprint-expanded-synonyms-    -fprint-explicit-foralls-    -fprint-explicit-kinds-    -fprint-unicode-syntax     -Wall     -Wcompat     -Widentities     -Wincomplete-record-updates     -Wincomplete-uni-patterns+    -Wmissing-export-lists     -Wnoncanonical-monad-instances     -Wnoncanonical-monadfail-instances+    -Wpartial-fields     -Wredundant-constraints-  if impl(ghc >= 8.4)-    ghc-options:-      -Wmissing-export-lists-      -Wpartial-fields+    -fprint-expanded-synonyms+    -fprint-explicit-foralls+    -fprint-explicit-kinds+    -fprint-unicode-syntax+   hs-source-dirs:     src++executable termbox-banana-example-echo+  if !flag(build-examples)+    buildable: False++  build-depends:+    base,+    reactive-banana,+    termbox-banana,++  default-language:+    Haskell2010++  ghc-options:+    -Wall -threaded "-with-rtsopts=-N"++  hs-source-dirs:+    examples++  main-is:+    Echo.hs++executable termbox-banana-example-hoogle+  if !flag(build-examples)+    buildable: False++  build-depends:+    aeson,+    async,+    base,+    http-conduit,+    lens,+    lens-aeson,+    mtl,+    reactive-banana,+    stm,+    tagsoup,+    termbox-banana,++  default-language:+    Haskell2010++  ghc-options:+    -Wall -threaded "-with-rtsopts=-N"++  hs-source-dirs:+    examples++  main-is:+    Hoogle.hs