shh 0.1.0.1 → 0.1.1.0
raw patch · 7 files changed
+74/−25 lines, 7 filesdep ~asyncdep ~basedep ~deepseqPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: async, base, deepseq, directory, filepath, mtl, process, split, template-haskell, unix
API changes (from Hackage documentation)
+ Shh: load :: [String] -> Q [Dec]
+ Shh: loadAnnotated :: (String -> String) -> [String] -> Q [Dec]
+ Shh.Internal: checkExecutable :: FilePath -> IO Bool
+ Shh.Internal: load :: [String] -> Q [Dec]
+ Shh.Internal: loadAnnotated :: (String -> String) -> [String] -> Q [Dec]
Files
- ChangeLog.md +6/−0
- README.md +9/−1
- app/Example.hs +7/−1
- shh.cabal +12/−11
- src/Shh.hs +2/−0
- src/Shh/Internal.hs +37/−11
- test/Test.hs +1/−1
ChangeLog.md view
@@ -1,5 +1,11 @@ # Revision history for execpath +## 0.1.1.0 -- 2019-01-22++* Generate an IO action to check for missing dependencies at runtime.++* Add ability to use TH to load a list of specific executables.+ ## 0.1.0.1 -- 2019-01-22 * Force output of lazy read functions to normal form to prevent
README.md view
@@ -1,12 +1,16 @@ # Shh +[](http://hackage.haskell.org/package/shh)+[](https://travis-ci.org/luke-clifton/shh)+ Shh is a library to enable convinient shell-like programming in Haskell. It works well in scripts, and from GHCi, allowing you to use GHCi as a shell. It supports * Automatically defining a function for each executable on your `$PATH`- using template Haskell.+ using template Haskell, as well as a runtime check to ensure they all+ exist on startup. * Redirction of stdout and stderr @@ -104,6 +108,10 @@ You now have all your executables available as simple to read Haskell functions.++If you want to check that all the dependenies still exist, you can use+`missingExecutables :: IO [String]`, which will tell you if anything is+missing. ### Usage in GHCi
app/Example.hs view
@@ -11,5 +11,11 @@ $(loadEnv) +-- We could also have been a little more explicit about it.+-- load ["sleep", "echo", "cat"]+ main :: IO ()-main = (sleep 1 >> echo "Hello" >> sleep 2) |> cat+main = do+ -- Crash the program if we are missing any executables.+ [] <- missingExecutables+ (sleep 1 >> echo "Hello" >> sleep 2) |> cat
shh.cabal view
@@ -1,5 +1,5 @@ name: shh-version: 0.1.0.1+version: 0.1.1.0 synopsis: Simple shell scripting from Haskell description: Provides a shell scripting environment for Haskell. It helps you all external binaries, and allows you to@@ -22,16 +22,17 @@ library exposed-modules: Shh , Shh.Internal- build-depends: base >=4.9 && <4.13- , async- , deepseq- , directory- , filepath- , mtl- , process- , split- , template-haskell- , unix+ build-depends:+ base >= 4.11 && < 4.13,+ async >= 2.2.1 && < 2.3,+ deepseq >= 1.4.3 && < 1.5,+ directory >= 1.3.1 && < 1.4,+ filepath >= 1.4.2 && < 1.5,+ mtl >= 2.2.2 && < 2.3,+ process >= 1.6.3 && < 1.7,+ split >= 0.2.3 && < 0.3,+ template-haskell >= 2.13.0 && < 2.15,+ unix >= 2.7.2 && < 2.8 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall
src/Shh.hs view
@@ -53,7 +53,9 @@ -- | == Constructing Arguments , ExecArg(..) -- | == Template Haskell helpers+ , load , loadEnv+ , loadAnnotated , loadAnnotatedEnv , loadExe , loadExeAs
src/Shh/Internal.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GADTs #-} @@ -19,9 +20,9 @@ import Data.Char (isLower, isSpace, isAlphaNum) import Data.List (nub, dropWhileEnd, intercalate) import Data.List.Split (endBy, splitOn)-import Data.Maybe (mapMaybe)+import Data.Maybe (mapMaybe, isJust) import Language.Haskell.TH-import System.Directory (doesDirectoryExist, getDirectoryContents)+import qualified System.Directory as Dir import System.Environment (getEnv) import System.Exit (ExitCode(..)) import System.IO@@ -379,8 +380,9 @@ pathBins :: IO [FilePath] pathBins = do pathsVar <- splitOn ":" <$> getEnv "PATH"- paths <- filterM doesDirectoryExist pathsVar- nub . concat <$> mapM getDirectoryContents paths+ paths <- filterM Dir.doesDirectoryExist pathsVar+ ps <- nub . concat <$> mapM Dir.getDirectoryContents paths+ filterM checkExecutable ps -- | Execute the given command. Further arguments can be passed in. --@@ -409,6 +411,8 @@ typn = mkName "a" typ = SigD name (ForallT [PlainTV typn] [AppT (ConT ''Unit) (VarT typn), AppT (ConT ''ExecArgs) (VarT typn)] (VarT typn)) in do+ isExe <- runIO $ checkExecutable executable+ when (not isExe) $ error $ "Attempted to load '" ++ executable ++ "', but it isn't executable" i <- impl return $ [typ,i] @@ -428,23 +432,45 @@ -- | Scans your '$PATH' environment variable and creates a function for each -- executable found. Binaries that would not create valid Haskell identifiers--- are ignored.+-- are ignored. It also creates the IO action @missingExecutables@ which will+-- do a runtime check to ensure all the executables that were found at+-- compile time still exist. loadEnv :: Q [Dec] loadEnv = loadAnnotatedEnv id --- | Like `loadEnv`, but allows you to modify the function name that would--- be generated.-loadAnnotatedEnv :: (String -> String) -> Q [Dec]-loadAnnotatedEnv f = do- bins <- runIO pathBins+-- | Test to see if an executable can be found either on the $PATH or absolute.+checkExecutable :: FilePath -> IO Bool+checkExecutable = fmap isJust . Dir.findExecutable++-- | Load the given executables into the program, checking their executability+-- and creating a function @missingExecutables@ to do a runtime check for their+-- availability.+load :: [String] -> Q [Dec]+load = loadAnnotated id++-- | Same as `load`, but allows you to modify the function names.+loadAnnotated :: (String -> String) -> [String] -> Q [Dec]+loadAnnotated f bins = do let pairs = mapMaybe getAnnotation bins- fmap join $ mapM (uncurry loadExeAs) pairs+ ds <- fmap join $ mapM (uncurry loadExeAs) pairs+ d <- valD (varP (mkName "missingExecutables")) (normalB [|+ filterM (fmap not . checkExecutable) bins+ |]) [] + pure (d:ds)+ where getAnnotation :: String -> Maybe (String,String) getAnnotation s | validIdentifier (f s) = Just (f s, s) | otherwise = Nothing++-- | Like `loadEnv`, but allows you to modify the function name that would+-- be generated.+loadAnnotatedEnv :: (String -> String) -> Q [Dec]+loadAnnotatedEnv f = do+ bins <- runIO pathBins+ loadAnnotated f bins -- | Function that splits '\0' seperated list of strings. Useful in conjuction -- with @find . "-print0"@.
test/Test.hs view
@@ -55,7 +55,7 @@ r @?= "test\n" , testCase "Lazy read" $ do withRead (cat "/dev/urandom" |> xxd) $ \s -> do- take 10 s @?= "00000000: "+ take 6 s @?= "000000" , testCase "Multiple outputs" $ do l <- readProc $ (echo (1 :: Int) >> echo (2 :: Int)) |> cat l @?= "1\n2\n"