diff --git a/CHANGELOG.md b/CHANGELOG.md index 4706fde..17abb28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,14 @@ available [on GitHub][2]. ### Added + +[#107] (https://github.com/chshersh/iris/issues/107): +Implement 'out', 'outLn', 'err' and 'errLn' functions for outputting 'Text' to corresponding handlers + + * Add `out`, `outLn`, `err` and `errLn` functions for outputting 'Text' to corresponding handlers + + (by [@martinhelmer]) + [#9](https://github.com/chshersh/iris/issues/9): Implement Yes/No reading functions: diff --git a/iris.cabal b/iris.cabal index 24c34db..9d3e0af 100644 --- a/iris.cabal +++ b/iris.cabal @@ -96,6 +96,7 @@ library Iris.Tool Iris.Interactive Iris.Interactive.Question + Iris.IO build-depends: @@ -126,13 +127,15 @@ test-suite iris-test Test.Iris.Tool Test.Iris.Interactive Test.Iris.Interactive.Question + Test.Iris.IO build-depends: , iris , hspec >= 2.9.7 && < 2.11 , text , optparse-applicative - + , silently + ghc-options: -threaded -rtsopts diff --git a/src/Iris.hs b/src/Iris.hs index bb623c2..b1406eb 100644 --- a/src/Iris.hs +++ b/src/Iris.hs @@ -49,6 +49,8 @@ module Iris ( -- $tool module Iris.Tool, module Iris.Interactive, + module Iris.IO, + -- $io ) where import Iris.App @@ -56,6 +58,7 @@ import Iris.Browse import Iris.Cli import Iris.Colour import Iris.Env +import Iris.IO import Iris.Interactive import Iris.Settings import Iris.Tool @@ -80,6 +83,10 @@ Functions to detect terminal support for colouring and print coloured output. Global environment for a CLI application and CLI app settings. -} +{- $io +Output Text to stdout/ stderr +-} + {- $settings Settings for the environment. -} diff --git a/src/Iris/Colour/Formatting.hs b/src/Iris/Colour/Formatting.hs index db36016..d7b7d14 100644 --- a/src/Iris/Colour/Formatting.hs +++ b/src/Iris/Colour/Formatting.hs @@ -22,13 +22,10 @@ module Iris.Colour.Formatting ( import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad.Reader (MonadReader) import Data.Text (Text) -import qualified Data.Text.IO as T -import System.IO (stderr) import Iris.Colour.Mode (ColourMode (..)) import Iris.Env (CliEnv (..), asksCliEnv) - -import qualified Data.Text.IO as TIO +import qualified Iris.IO as IO {- | Print 'Text' to 'System.IO.stdout' by providing a custom formatting function. @@ -52,7 +49,7 @@ putStdoutColouredLn -> m () putStdoutColouredLn formatWithColour str = do colourMode <- asksCliEnv cliEnvStdoutColourMode - liftIO $ T.putStrLn $ case colourMode of + IO.outLn $ case colourMode of DisableColour -> str EnableColour -> formatWithColour str @@ -78,7 +75,7 @@ putStderrColouredLn -> m () putStderrColouredLn formatWithColour str = do colourMode <- asksCliEnv cliEnvStderrColourMode - liftIO $ T.hPutStrLn stderr $ case colourMode of + IO.errLn $ case colourMode of DisableColour -> str EnableColour -> formatWithColour str @@ -105,7 +102,7 @@ putStdoutColoured -> m () putStdoutColoured formatWithColour str = do colourMode <- asksCliEnv cliEnvStdoutColourMode - liftIO $ TIO.putStr $ case colourMode of + IO.out $ case colourMode of DisableColour -> str EnableColour -> formatWithColour str @@ -132,6 +129,6 @@ putStderrColoured -> m () putStderrColoured formatWithColour str = do colourMode <- asksCliEnv cliEnvStderrColourMode - liftIO $ TIO.hPutStr stderr $ case colourMode of + IO.err $ case colourMode of DisableColour -> str EnableColour -> formatWithColour str diff --git a/src/Iris/IO.hs b/src/Iris/IO.hs new file mode 100644 index 0000000..0e30913 --- /dev/null +++ b/src/Iris/IO.hs @@ -0,0 +1,97 @@ +{- | +Module : Iris.IO +Copyright : (c) 2023 Dmitrii Kovanikov +SPDX-License-Identifier : MPL-2.0 +Maintainer : Dmitrii Kovanikov +Stability : Experimental +Portability : Portable + +Functions for IO, such as writing Text to stdout and stderr. + + +Usage example: + +@ +import qualified Iris + +main = do + Iris.outLn "This goes to stdout" + Iris.errLn "This goes to stderr" +@ + +Results in: + +@ +\$ ./app +This goes to stdout +This goes to stderr +@ + +@since x.x.x.x +-} +module Iris.IO ( + out, + outLn, + err, + errLn, +) where + +import Control.Monad.IO.Class (MonadIO (..)) +import Data.Text (Text) +import qualified Data.Text.IO as Text +import System.IO (stderr, stdout) + +{- | Write the given Text to stdout. +No linefeed at the end. + +@ +ghci> do Iris.out "foo" >> Iris.out "bar" +foobarghci> + +@ +@since x.x.x.x +-} +out :: MonadIO m => Text -> m () +out = do + liftIO . Text.hPutStr stdout + +{- | Write the given Text to stdout with linefeed at the end. + +@ +ghci> Iris.outLn "foo" >> Iris.outLn "bar" +foo +bar +ghci> + +@ +@since x.x.x.x +-} +outLn :: MonadIO m => Text -> m () +outLn = liftIO . Text.hPutStrLn stdout + +{- | Write the given Text to stderr. +No linefeed at the end. + +@ +ghci> Iris.err "foo" >> Iris.err "bar" +foobarghci> + +@ +@since x.x.x.x +-} +err :: MonadIO m => Text -> m () +err = liftIO . Text.hPutStr stderr + +{- | Write the given Text to stderr with linefeed at the end. + +@ +ghci> Iris.errLn "foo" >> Iris.errLn "bar" +foo +bar +ghci> + +@ +@since x.x.x.x +-} +errLn :: MonadIO m => Text -> m () +errLn = liftIO . Text.hPutStrLn stderr diff --git a/src/Iris/Interactive/Question.hs b/src/Iris/Interactive/Question.hs index 96fd796..5654d40 100644 --- a/src/Iris/Interactive/Question.hs +++ b/src/Iris/Interactive/Question.hs @@ -28,6 +28,7 @@ import System.IO (hFlush, stdout) import Iris.Cli.Interactive (InteractiveMode (..)) import Iris.Env (CliEnv (..), asksCliEnv) +import qualified Iris.IO as IO {- @since x.x.x.x @@ -107,12 +108,12 @@ yesno question defaultAnswer = do where loop :: IO YesNo loop = do - Text.putStr $ question <> " (yes/no) " + IO.out $ question <> " (yes/no) " hFlush stdout input <- Text.getLine case parseYesNo input of Just answer -> pure answer Nothing -> do - Text.putStrLn $ "I don't understand your answer: '" <> input <> "'" - Text.putStrLn "Please, answer yes or no (or y, or n)" + IO.out $ "I don't understand your answer: '" <> input <> "'" + IO.out "Please, answer yes or no (or y, or n)" loop diff --git a/test/Test/Iris.hs b/test/Test/Iris.hs index 4793bd6..369be2c 100644 --- a/test/Test/Iris.hs +++ b/test/Test/Iris.hs @@ -4,6 +4,7 @@ import Test.Hspec (Spec, describe) import Test.Iris.Cli (cliSpec, cliSpecParserConflicts) import Test.Iris.Colour (colourSpec) +import Test.Iris.IO (ioSpec) import Test.Iris.Interactive (interactiveSpec) import Test.Iris.Tool (toolSpec) @@ -14,3 +15,4 @@ irisSpec = describe "Iris" $ do colourSpec toolSpec interactiveSpec + ioSpec diff --git a/test/Test/Iris/IO.hs b/test/Test/Iris/IO.hs new file mode 100644 index 0000000..9910459 --- /dev/null +++ b/test/Test/Iris/IO.hs @@ -0,0 +1,34 @@ +module Test.Iris.IO (ioSpec) +where + +import Test.Hspec (Spec, describe, it, shouldReturn) + +-- Silently has side effect: writes file to tmp (or current dir) and then deletes it. + +import System.IO (stderr, stdout) +import System.IO.Silently (hCapture_, hSilence) + +import qualified Iris.IO as IO (err, errLn, out, outLn) + +checkStdErr :: IO a -> IO String +checkStdErr = hCapture_ [stderr] . hSilence [stdout] + +checkStdOut :: IO a -> IO String +checkStdOut = hCapture_ [stdout] . hSilence [stderr] + +ioSpec :: Spec +ioSpec = + describe "IO" $ do + describe "out" $ do + -- we need `flip` to get the expectation reported correctly in case of a failure + it "writes to stdout, no LF " $ checkStdOut (IO.out "TEXT") `shouldReturn` "TEXT" + it "does not write to stderr " $ checkStdErr (IO.out "TEXT") `shouldReturn` "" + describe "outLn" $ do + it "writes to stdout, LF " $ checkStdOut (IO.outLn "TEXT") `shouldReturn` "TEXT\n" + it "does not write to stderr " $ checkStdErr (IO.outLn "TEXT") `shouldReturn` "" + describe "err" $ do + it "writes to stderr, no LF " $ checkStdErr (IO.err "TEXT") `shouldReturn` "TEXT" + it "does not write to stdout " $ checkStdOut (IO.err "TEXT") `shouldReturn` "" + describe "errLn" $ do + it "writes to stderr, LF " $ checkStdErr (IO.errLn "TEXT") `shouldReturn` "TEXT\n" + it "does not write to stdout " $ checkStdOut (IO.errLn "TEXT") `shouldReturn` ""