packages feed

typed-process-effectful (empty) → 1.0.0.0

raw patch · 7 files changed

+556/−0 lines, 7 filesdep +basedep +bytestringdep +effectful

Dependencies added: base, bytestring, effectful, effectful-core, tasty, tasty-hunit, typed-process, typed-process-effectful

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# CHANGELOG++## v1.0.0.0 (2023-08-23)+* Initial release
+ LICENSE.md view
@@ -0,0 +1,26 @@+Copyright (c) 2021-2022, Dominik Peteler++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer.++2. 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.++3. Neither the name of the copyright holder nor the names of its+   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 HOLDER 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.
+ README.md view
@@ -0,0 +1,30 @@+# typed-process-effectful++## Description++An alternative `Process` effect for the [`effectful`][effectful] ecosystem.+While to the `Process` effect shipped with the `effectful` library is based on+the [`process`][process] package this implementation relies on+[`typed-process`][typed-process] instead.++## How to use++The functions exposed by the `Effectful.Process.Typed` module are those from+[`System.Process.Typed`](https://hackage.haskell.org/package/typed-process-0.2.6.1/docs/System-Process-Typed.html)+with the notable difference that they have a `TypedProcess :> es` constraint.+Use `runTypedProcess` to handle the effect and eliminate the constraint.++```haskell+import Effectful.Monad+import Effectful.Process.Typed++main :: IO ()+main = runEff . runTypedProcess $ true++true :: TypedProcess :> es => Eff es ()+true = Effectful.Process.Typed.runProcess_ $ shell "true"+```++[effectful]: https://github.com/haskell-effectful/effectful+[process]: https://hackage.haskell.org/package/process+[typed-process]: https://hackage.haskell.org/package/typed-process
+ src/Effectful/Process/Typed.hs view
@@ -0,0 +1,246 @@+{-|+Module      : Effectful.Process.Typed+Description : effectful bindings for typed-process+Copyright   : (c) 2022 Dominik Peteler+License     : BSD-3-Clause+Stability   : stable++This module provides [effectful](https://hackage.haskell.org/package/effectful)+bindings for [typed-process](https://hackage.haskell.org/package/typed-process).+-}+{-# OPTIONS_GHC -fno-warn-dodgy-imports #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeFamilies #-}+module Effectful.Process.Typed+  ( -- * Process effect+    TypedProcess+  , runTypedProcess++  -- * Launch a process+  , startProcess+  , stopProcess+  , withProcessWait+  , withProcessWait_+  , withProcessTerm+  , withProcessTerm_+  , readProcess+  , readProcess_+  , runProcess+  , runProcess_+  , readProcessStdout+  , readProcessStdout_+  , readProcessStderr+  , readProcessStderr_+  , readProcessInterleaved+  , readProcessInterleaved_++  -- * Process exit code+  , waitExitCode+  , getExitCode+  , checkExitCode++  -- * Re-exports from "System.Process.Typed"+  , module Reexport+#if ! MIN_VERSION_typed_process(0,2,8)+  , ExitCode(..)+#endif+  ) where++import System.Process.Typed as Reexport hiding+  ( startProcess+  , stopProcess+  , withProcessWait+  , withProcessWait_+  , withProcessTerm+  , withProcessTerm_+  , readProcess+  , readProcess_+  , runProcess+  , runProcess_+  , readProcessStdout+  , readProcessStdout_+  , readProcessStderr+  , readProcessStderr_+  , readProcessInterleaved+  , readProcessInterleaved_+  , waitExitCode+  , getExitCode+  , checkExitCode+  )++import Data.ByteString.Lazy (ByteString)+import qualified System.Process.Typed as PT++import Effectful+import qualified Effectful.Process+import Effectful.Dispatch.Static++#if ! MIN_VERSION_typed_process(0,2,8)+import System.Exit (ExitCode(..))+#endif++----------------------------------------+-- Effect & Handler++-- | We provide a type synonym for the 'Effectful.Process.Process' effect since+-- it clashes with 'PT.Process' type of @typed-process@.+type TypedProcess = Effectful.Process.Process++-- | This is merely an alias for 'Effectful.Process.runProcess' since that name+-- clashes with 'runProcess', i.e.:+--+-- > runTypedProcess = Effectful.Process.runProcess+runTypedProcess :: IOE :> es => Eff (TypedProcess : es) a -> Eff es a+runTypedProcess = Effectful.Process.runProcess++----------------------------------------+-- Launch a process++-- | Lifted 'PT.startProcess'.+startProcess :: TypedProcess :> es+             => PT.ProcessConfig stdin stdout stderr+             -> Eff es (PT.Process stdin stdout stderr)+startProcess = unsafeEff_ . PT.startProcess++-- | Lifted 'PT.stopProcess'.+stopProcess :: TypedProcess :> es => PT.Process stdin stdout stderr -> Eff es ()+stopProcess = unsafeEff_ . PT.stopProcess++-- | Lifted 'PT.withProcessWait'.+withProcessWait :: TypedProcess :> es+                => PT.ProcessConfig stdin stdout stderr+                -> (PT.Process stdin stdout stderr -> Eff es a)+                -> Eff es a+withProcessWait = liftWithProcess PT.withProcessWait++-- | Lifted 'PT.withProcessWait_'.+withProcessWait_ :: TypedProcess :> es+                 => PT.ProcessConfig stdin stdout stderr+                 -> (PT.Process stdin stdout stderr -> Eff es a)+                 -> Eff es a+withProcessWait_ = liftWithProcess PT.withProcessWait_++-- | Lifted 'PT.withProcessTerm'.+withProcessTerm :: TypedProcess :> es+                => PT.ProcessConfig stdin stdout stderr+                -> (PT.Process stdin stdout stderr -> Eff es a)+                -> Eff es a+withProcessTerm = liftWithProcess PT.withProcessTerm++-- | Lifted 'PT.withProcessTerm_'.+withProcessTerm_ :: TypedProcess :> es+                 => PT.ProcessConfig stdin stdout stderr+                 -> (PT.Process stdin stdout stderr -> Eff es a)+                 -> Eff es a+withProcessTerm_ = liftWithProcess PT.withProcessTerm_++-- | Lifted 'PT.readProcess'.+readProcess :: TypedProcess :> es+            => PT.ProcessConfig stdin stdoutIgnored stderrIgnored+            -> Eff es (ExitCode, ByteString, ByteString)+readProcess = unsafeEff_ . PT.readProcess++-- | Lifted 'PT.readProcess_'.+readProcess_ :: TypedProcess :> es+             => PT.ProcessConfig stdin stdoutIgnored stderrIgnored+             -> Eff es (ByteString, ByteString)+readProcess_ = unsafeEff_ . PT.readProcess_++-- | Lifted 'PT.runProcess'.+runProcess :: TypedProcess :> es+           => PT.ProcessConfig stdin stdout stderr+           -> Eff es ExitCode+runProcess = unsafeEff_ . PT.runProcess++-- | Lifted 'PT.runProcess_'.+runProcess_ :: TypedProcess :> es+            => PT.ProcessConfig stdin stdout stderr+            -> Eff es ()+runProcess_ = unsafeEff_ . PT.runProcess_++-- | Lifted 'PT.readProcessStdout'.+readProcessStdout :: TypedProcess :> es+                 => PT.ProcessConfig stdin stdoutIgnored stderr+                 -> Eff es (ExitCode, ByteString)+readProcessStdout = unsafeEff_ . PT.readProcessStdout++-- | Lifted 'PT.readProcessStdout_'.+readProcessStdout_ :: TypedProcess :> es+                  => PT.ProcessConfig stdin stdoutIgnored stderr+                  -> Eff es ByteString+readProcessStdout_ = unsafeEff_ . PT.readProcessStdout_++-- | Lifted 'PT.readProcessStderr'.+readProcessStderr :: TypedProcess :> es+                 => PT.ProcessConfig stdin stdout stderrIgnored+                 -> Eff es (ExitCode, ByteString)+readProcessStderr = unsafeEff_ . PT.readProcessStderr++-- | Lifted 'PT.readProcessStderr_'.+readProcessStderr_ :: TypedProcess :> es+                  => PT.ProcessConfig stdin stdout stderrIgnored+                  -> Eff es ByteString+readProcessStderr_ = unsafeEff_ . PT.readProcessStderr_++-- | Lifted 'PT.readProcessInterleaved'.+readProcessInterleaved :: TypedProcess :> es+                      => PT.ProcessConfig stdin stdoutIgnored stderrIgnored+                      -> Eff es (ExitCode, ByteString)+readProcessInterleaved = unsafeEff_ . PT.readProcessInterleaved++-- | Lifted 'PT.readProcessInterleaved_'.+readProcessInterleaved_ :: TypedProcess :> es+                       => PT.ProcessConfig stdin stdoutIgnored stderrIgnored+                       -> Eff es ByteString+readProcessInterleaved_ = unsafeEff_ . PT.readProcessInterleaved_++----------------------------------------+-- Process exit code++-- | Lifted 'PT.waitExitCode'.+waitExitCode :: TypedProcess :> es+             => PT.Process stdin stdout stderr+             -> Eff es ExitCode+waitExitCode = unsafeEff_ . PT.waitExitCode++---- | Lifted 'PT.waitExitCodeSTM'.+--waitExitCodeSTM :: TypedProcess :> es+--                => PT.Process stdin stdout stderr+--                -> Eff es ExitCode+--waitExitCodeSTM = unsafeEff_ . PT.waitExitCode++-- | Lifted 'PT.getExitCode'.+getExitCode :: TypedProcess :> es+            => PT.Process stdin stdout stderr+            -> Eff es (Maybe ExitCode)+getExitCode = unsafeEff_ . PT.getExitCode++---- | Lifted 'PT.getExitCodeSTM'.+--getExitCodeSTM :: TypedProcess :> es+--               => PT.Process stdin stdout stderr+--               -> Eff es (Maybe ExitCode)+--getExitCodeSTM = unsafeEff_ . PT.getExitCodeSTM++-- | Lifted 'PT.checkExitCode'.+checkExitCode :: TypedProcess :> es+              => PT.Process stdin stdout stderr+              -> Eff es ()+checkExitCode = unsafeEff_ . PT.checkExitCode++---- | Lifted 'PT.checkExitCodeSTM'.+--checkExitCodeSTM :: TypedProcess :> es+--                 => PT.Process stdin stdout stderr+--                 -> Eff es ()+--checkExitCodeSTM = unsafeEff_ . PT.checkExitCodeSTM++----------------------------------------+-- Helpers++liftWithProcess :: TypedProcess :> es+                => (PT.ProcessConfig stdin stdout stderr -> (PT.Process stdin stdout stderr -> IO a) -> IO a)+                -> PT.ProcessConfig stdin stdout stderr+                -> (PT.Process stdin stdout stderr -> Eff es a)+                -> Eff es a+liftWithProcess k pc f = unsafeEff $ \es ->+  seqUnliftIO es $ \runInIO ->+    k pc (runInIO . f)
+ test/Main.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import Effectful+import Effectful.Temporary+import System.IO.Error (isDoesNotExistError)+import qualified Utils as U++import Effectful.Process.Typed+import Test.Tasty.HUnit (Assertion, testCase)+import Test.Tasty (defaultMain, testGroup)++main :: IO ()+main = defaultMain $ testGroup "typed-process-effectful"+  [ testCase "Existing executable" testExistingExecutable+  , testCase "Non-existent executable" testNonExistentExecutable+  , testCase "Continue process execution" testContinueProcessExecution+  , testCase "Terminate process" testTerminateProcess+  , testCase "Wait for process" testWaitForProcess+  , testCase "Helper functions" testHelperFunctions+  , testCase "Exit Codes" testExitCodes+  ]++testExistingExecutable :: Assertion+testExistingExecutable = runEff $ do+  let pc = proc "/usr/bin/env" ["true"]+      action =  runTypedProcess $ do+        p <- startProcess pc+        waitExitCode p+  result <- action+  U.assertEqual "Executable exists" ExitSuccess result++testNonExistentExecutable :: Assertion+testNonExistentExecutable = runEff $ do+  let pc = proc "/bin/doesnotexist" []+  let action = runTypedProcess $ do+        p <- startProcess pc+        waitExitCode p+  U.assertException (runEff action) isDoesNotExistError++testContinueProcessExecution :: Assertion+testContinueProcessExecution = runEff $ do+  let pc = shell "sleep 3"+      action =  runTypedProcess $ do+        p <- startProcess pc+        getExitCode p+  result <- action+  U.assertEqual "" result Nothing++testTerminateProcess :: Assertion+testTerminateProcess = runEff $ do+  let action =  runTemporary . runTypedProcess $ do+        withSystemTempFile "typed-process-effectful-test" $ \fp h -> do+          let pc = setStdout (useHandleClose h)+                 $ shell "sleep 1; printf 'Output'"+          withProcessTerm pc (const $ pure ())+          liftIO $ readFile fp+  result <- action+  U.assertEqual "" "" result++testWaitForProcess :: Assertion+testWaitForProcess = runEff $ do+  let action =  runTemporary . runTypedProcess $ do+        withSystemTempFile "typed-process-effectful-test" $ \fp h -> do+          let pc = setStdout (useHandleClose h)+                 $ shell "sleep 1; printf 'Output'"+          withProcessWait pc (const $ pure ())+          liftIO $ readFile fp+  result <- action+  U.assertEqual "" result "Output"++testHelperFunctions :: Assertion+testHelperFunctions = runEff $ do+  let pc = proc "/usr/bin/env" ["true"]+      action = runTypedProcess $ do+        runProcess pc+  result <- action+  U.assertEqual "" result ExitSuccess++  let pc2 = proc "/usr/bin/env" ["true"]+  result2 <- runTypedProcess $ do+        runProcess_ pc2+  U.assertEqual "" result2  ()++  let pc3 = shell "printf 'stdout'; printf 'stderr' >&2"+  result3 <- runTypedProcess $ do+        readProcess pc3+  U.assertEqual "" result3  (ExitSuccess, "stdout", "stderr")++  let pc4 = shell "printf 'stdout'; printf 'stderr' >&2"+  result4 <- runTypedProcess $ do+        readProcess_ pc4+  U.assertEqual "" result4 ("stdout", "stderr")++  let pc5 = shell "printf 'Output'"+  result5 <- runTypedProcess $ do+        readProcessStdout pc5+  U.assertEqual "" result5 (ExitSuccess, "Output")++  let pc6 = shell "printf 'Output'"+  result6 <- runTypedProcess $ do+        readProcessStdout pc6+  U.assertEqual "" result6 (ExitSuccess, "Output")++  let pc7 = shell "printf 'Output'"+  result7 <- runTypedProcess $ do+        readProcessStdout_ pc7+  U.assertEqual "" result7 "Output"++  let pc8 = shell "printf 'Output' >&2"+  result8 <- runTypedProcess $ do+        readProcessStderr pc8+  U.assertEqual "" result8 (ExitSuccess, "Output")++testExitCodes :: Assertion+testExitCodes = runEff $ do+  let pc = proc "/usr/bin/env" ["false"]+      action = runEff . runTypedProcess $ do+          runProcess_ pc+  U.assertException action (const @_ @ExitCodeException True)++  let pc2 = proc "/usr/bin/env" ["false"]+      action2 = runEff . runTypedProcess $ do+        readProcess_ pc2+  U.assertException action2 (const @_ @ExitCodeException True)++  let pc3 = proc "/usr/bin/env" ["false"]+      action3 = runEff . runTypedProcess $ do+        readProcessStdout_ pc3+  U.assertException action3 (const @_ @ExitCodeException True)++  let pc4 = proc "/usr/bin/env" ["false"]+      action4 = runEff . runTypedProcess $ do+        readProcessStderr_ pc4+  U.assertException action4 (const @_ @ExitCodeException True)
+ test/Utils.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE RankNTypes #-}+module Utils+  ( assertBool+  , assertEqual+  , assertFailure+  , assertException+  ) where++import GHC.Stack+import qualified Test.Tasty.HUnit as T++import Effectful+import Control.Exception++assertBool :: (HasCallStack, IOE :> es) => String -> Bool -> Eff es ()+assertBool msg p = liftIO $ T.assertBool msg p++assertEqual+  :: (HasCallStack, Eq a, Show a, IOE :> es)+  => String+  -> a+  -> a+  -> Eff es ()+assertEqual msg expected given = liftIO $ T.assertEqual msg expected given++assertFailure :: (HasCallStack, IOE :> es) => String -> Eff es a+assertFailure msg = liftIO $ T.assertFailure msg++assertException :: forall e es a+                . (HasCallStack, Exception e, IOE :> es)+                => IO a+                -> (e -> Bool)+                -> Eff es ()+assertException action expectedClassifier = do+  r <- liftIO $ try action+  case r of+      Right _ ->+        assertFailure "The action did not raise an exception"+      Left e ->+        assertBool "The expected exception doesn't match what was given" (expectedClassifier e)
+ typed-process-effectful.cabal view
@@ -0,0 +1,73 @@+cabal-version:   3.0+name:            typed-process-effectful+version:         1.0.0.0+synopsis:+  A binding of the @typed-process@ library for the @effectful@ effect system.++description:+  This library provides an alternative `Process` effect for the+  [effectful](https://github.com/haskell-effectful/effectful) ecosystem.+  While to the `Process` effect shipped with the `effectful` library is based on+  the [process](https://hackage.haskell.org/package/process) package this+  implementation relies on+  [typed-process](https://hackage.haskell.org/package/typed-process) instead.++category:        System+homepage:+  https://github.com/haskell-effectful/typed-process-effectful#readme++bug-reports:+  https://github.com/haskell-effectful/typed-process-effectful/issues++author:          Dominik Peteler+maintainer:      hackage+typed-process-effectful@with-h.at+license:         BSD-3-Clause+build-type:      Simple+extra-doc-files:+  CHANGELOG.md+  LICENSE.md+  README.md++tested-with:     GHC ==8.10.7 || ==9.0.2 || ==9.2.7 || ==9.4.4 || ==9.6.1++source-repository head+  type:     git+  location:+    https://github.com/haskell-effectful/typed-process-effectful++common language+  ghc-options:        -Wall -Wcompat+  default-language:   Haskell2010+  default-extensions:+    DataKinds+    FlexibleContexts+    GADTs+    KindSignatures+    TypeOperators++library+  import:          language+  hs-source-dirs:  src+  exposed-modules: Effectful.Process.Typed+  build-depends:+    , base            >=4.14  && <5+    , bytestring      <0.12+    , effectful       >=2.0   && <2.3+    , effectful-core  >=2.0   && <2.3+    , typed-process   >=0.2.5 && <0.3++test-suite typed-process-effectful-test+  import:         language+  ghc-options:    -rtsopts -threaded -with-rtsopts=-N+  build-depends:+    , base+    , effectful+    , effectful-core+    , tasty                    >=1.4  && <1.5+    , tasty-hunit              >=0.10 && <0.11+    , typed-process-effectful++  hs-source-dirs: test+  type:           exitcode-stdio-1.0+  main-is:        Main.hs+  other-modules:  Utils