streamly-process 0.4.0 → 0.4.1
raw patch · 5 files changed
+75/−62 lines, 5 filesdep ~QuickCheckdep ~tasty-benchdep ~unix
Dependency ranges changed: QuickCheck, tasty-bench, unix
Files
- CHANGELOG.md +4/−0
- src/Streamly/Internal/System/Command.hs +20/−9
- src/Streamly/System/Command.hs +11/−20
- streamly-process.cabal +29/−22
- test/Streamly/System/Process.hs +11/−11
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Changelog +## 0.4.1 (Apr 2026)++* Rename quotedWord to shellWord+ ## 0.4.0 (Sep 2025) * Remove buffering from the pipe chunked APIs.
src/Streamly/Internal/System/Command.hs view
@@ -35,11 +35,14 @@ , foreground , daemon - -- * Helpers- , quotedWord+ -- * Low-level Functions+ , shellWord , runWith , streamWith , pipeWith++ -- * Deprecated+ , quotedWord ) where @@ -63,8 +66,11 @@ #include "DocTestCommand.hs" --- | Posix compliant quote escaping:+-- | Posix compliant quoted shell command string parsing with escaping: --+-- Quotes and escapes are parsed as in a standard POSIX shell.+-- Examples (using shell @echo@ command):+-- -- $ echo 'hello\\"world' -- hello\\"world --@@ -73,9 +79,9 @@ -- -- $ echo 'hello\' -- hello\-{-# INLINE quotedWord #-}-quotedWord :: MonadCatch m => Parser Char m String-quotedWord =+{-# INLINE shellWord #-}+shellWord :: MonadCatch m => Parser Char m String+shellWord = let toRQuote x = case x of '"' -> Just x@@ -92,6 +98,11 @@ trEsc _ _ = Nothing in Parser.wordWithQuotes False trEsc '\\' toRQuote isSpace Fold.toList +{-# DEPRECATED quotedWord "Use shellWord instead." #-}+{-# INLINE quotedWord #-}+quotedWord :: MonadCatch m => Parser Char m String+quotedWord = shellWord+ -- | A modifier for stream generation APIs in "Streamly.System.Process" to -- generate streams from command strings. --@@ -110,7 +121,7 @@ Stream.concatEffect $ do xs <- Stream.fold Fold.toList $ Stream.catRights- $ Stream.parseMany quotedWord+ $ Stream.parseMany shellWord $ Stream.fromList cmd case xs of y:ys -> return $ f y ys@@ -133,7 +144,7 @@ runWith f cmd = do xs <- Stream.fold Fold.toList $ Stream.catRights- $ Stream.parseMany quotedWord+ $ Stream.parseMany shellWord $ Stream.fromList cmd case xs of y:ys -> f y ys@@ -161,7 +172,7 @@ Stream.concatEffect $ do xs <- Stream.fold Fold.toList $ Stream.catRights- $ Stream.parseMany quotedWord+ $ Stream.parseMany shellWord $ Stream.fromList cmd case xs of y:ys -> return $ f y ys input
src/Streamly/System/Command.hs view
@@ -38,10 +38,9 @@ -- With the "Streamly.System.Command" module you can write the examples in the -- "Streamly.System.Process" module more conveniently. ----- = Executables as functions+-- = Executables as Functions ----- The shell command @echo "hello world" | tr [a-z] [A-Z]@ can be written as--- follows using this module:+-- The shell command @echo "hello world" | tr [a-z] [A-Z]@ can be expressed as: -- -- >>> :{ -- Command.toBytes [str|echo "hello world"|]@@ -50,12 +49,17 @@ -- :} -- HELLO WORLD ----- = Shell commands as functions+-- In the example above, system commands are run as independent processes and+-- composed directly in Haskell, without invoking a shell. This offers a+-- powerful, type-safe, and efficient alternative to shell scripting, using+-- Streamly’s streaming APIs for composition. ----- We recommend using streamly to compose commands natively in Haskell rather--- than using the shell as shown in the previous example. However, if for some--- reason you want to execute commands using the shell:+-- = Shell Commands as Functions --+-- Prefer running commands directly and composing their output in Haskell (see+-- previous example) instead of using shell pipes. But if you really want to+-- run a command using shell as interpreter, you can do that too like this:+-- -- >>> :{ -- Command.toBytes [str|sh "-c" "echo 'hello world' | tr [a-z] [A-Z]"|] -- & Stream.fold Stdio.write@@ -168,16 +172,3 @@ -- Keep it synced with the Internal module #include "DocTestCommand.hs"--- Note: Commands are not executed using shell------ You can use this module to execute system commands and compose them with--- Haskell. It does not use the system shell to execute commands, they are--- executed as independent processes. This provides a convenient and powerful--- way to replace shell scripting with Haskell. Instead of composing commands--- using shell you can use Haskell Streamly streaming APIs to compose them with--- better efficiency and type safety.------ Normally, you should not need the system shell but if you want to use shell--- scripts in your program then you can take a look at the @streamly-shell@--- package which provides convenient wrapper over "Streamly.System.Process" to--- execute shell scripts, commands.
streamly-process.cabal view
@@ -1,14 +1,21 @@ cabal-version: 2.2 name: streamly-process-version: 0.4.0-synopsis: Use OS processes as stream transformation functions+version: 0.4.1+synopsis: Write shell-like command pipelines in Haskell description:- Use operating system (OS) commands in Haskell programs as if they were- native Haskell functions, by treating their inputs and outputs as- Haskell streams. This allows you to write high-level Haskell scripts- that can perform tasks similar to shell scripts, but with C-like- performance, and with strong safety guarantees, refactorability, and- modularity.+ Treat operating system (OS) processes as first-class stream+ transformations in Haskell. This package lets you compose external+ commands as if they were native Haskell functions, connecting+ their inputs and outputs through typed streams and perform tasks+ concurrently. It let's you do your scripting tasks in Haskell with+ near-native performance.+ .+ The API is designed to be concise and ergonomic, making it easy+ to express complex pipelines with minimal boilerplate. You can+ build high-level, declarative workflows similar to shell scripts+ while retaining Haskell’s strengths: type safety, composability,+ refactorability, and modular design. Moreover, you get the concurrent+ compositions and C-like performance of streamly. homepage: https://streamly.composewell.com bug-reports: https://github.com/composewell/streamly-process/issues license: Apache-2.0@@ -18,21 +25,21 @@ copyright: Composewell Technologies category: Streamly, Streaming, System stability: Experimental-tested-with: GHC==9.10.1- , GHC==9.8.1- , GHC==9.6.3- , GHC==9.4.4- , GHC==9.2.7- , GHC==9.0.2- , GHC==8.10.7- , GHC==8.8.4- , GHC==8.6.5-build-type: Simple-extra-source-files:+tested-with:+ GHC==9.14.1+ , GHC==9.12.4+ , GHC==9.10.3+ , GHC==9.8.4+ , GHC==9.6.3+ , GHC==9.4.8+ , GHC==8.10.7+build-type: Simple+extra-doc-files: CHANGELOG.md NOTICE README.md design/proposal.md+extra-source-files: src/DocTestCommand.hs src/DocTestProcess.hs test/data/failExec.bat@@ -107,7 +114,7 @@ else if !os(windows) build-depends:- unix >= 2.5 && < 2.8+ unix >= 2.5 && < 2.9 ------------------------------------------------------------------------------- -- Benchmarks@@ -130,7 +137,7 @@ , directory >= 1.2.2 && < 1.4 -- Uses internal APIs , streamly-core- , tasty-bench >= 0.2.5 && < 0.5+ , tasty-bench >= 0.2.5 && < 0.6 if flag(fusion-plugin) && !impl(ghc < 8.6) build-depends:@@ -150,6 +157,6 @@ , directory >= 1.2.2 && < 1.4 , exceptions >= 0.8 && < 0.11 , hspec >= 2.0 && < 3- , QuickCheck >= 2.10 && < 2.16+ , QuickCheck >= 2.10 && < 2.19 -- Uses internal APIs , streamly-core
test/Streamly/System/Process.hs view
@@ -38,7 +38,7 @@ import qualified Streamly.Data.Stream as Stream import qualified Streamly.Internal.FileSystem.Handle as FH (putBytes, read)-import qualified Streamly.Internal.System.Command as Cmd (quotedWord)+import qualified Streamly.Internal.System.Command as Cmd (shellWord) ------------------------------------------------------------------------------- -- Compatibility@@ -486,12 +486,12 @@ checkFailAction = catch action failAction -quotedWordTest :: String -> [String] -> IO ()-quotedWordTest inp expected = do+shellWordTest :: String -> [String] -> IO ()+shellWordTest inp expected = do res <- Stream.fold Fold.toList $ Stream.catRights- $ Stream.parseMany Cmd.quotedWord $ Stream.fromList inp+ $ Stream.parseMany Cmd.shellWord $ Stream.fromList inp res `shouldBe` expected main :: IO ()@@ -551,14 +551,14 @@ prop "toBytesEither cat = FH.toBytes" toBytes1 prop "toBytesEither on failing executable" toBytes2 - describe "quotedWord" $ do+ describe "shellWord" $ do it "Single quote test" $- quotedWordTest "'hello\\\\\"world'" ["hello\\\\\"world"]+ shellWordTest "'hello\\\\\"world'" ["hello\\\\\"world"] it "Double quote test" $- quotedWordTest+ shellWordTest "\"hello\\\"\\\\w\\'orld\"" ["hello\"\\w\\'orld"]- -- TODO: We need to let the escape character be at the end- -- "wordWithQuotes" needs to be fixed!- -- it "Double quote test" $- -- quotedWordTest "'hello\'" ["hello\\"]+#if MIN_VERSION_streamly_core(0,3,0)+ it "Double quote test, escape at end" $+ shellWordTest "'hello\\'" ["hello\\"]+#endif