shh 0.3.0.1 → 0.3.1.0
raw patch · 4 files changed
+70/−53 lines, 4 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Shh.Internal: validIdentifier :: String -> Bool
+ Shh: encodeIdentifier :: String -> String
+ Shh: pathBins :: IO [FilePath]
+ Shh: pathBinsAbs :: IO [FilePath]
+ Shh.Internal: encodeIdentifier :: String -> String
+ Shh.Internal: rawExe :: String -> String -> Q [Dec]
Files
- shh.cabal +1/−1
- src/Shh.hs +3/−0
- src/Shh/Internal.hs +64/−52
- test/Test.hs +2/−0
shh.cabal view
@@ -1,5 +1,5 @@ name: shh-version: 0.3.0.1+version: 0.3.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
src/Shh.hs view
@@ -54,6 +54,7 @@ , ExecArgs() , Unit() -- | == Template Haskell helpers+ , encodeIdentifier , ExecReference(..) , load , loadEnv@@ -61,6 +62,8 @@ , loadAnnotatedEnv , loadExe , loadExeAs+ , pathBins+ , pathBinsAbs -- | = Builtins , cd ) where
src/Shh/Internal.hs view
@@ -18,11 +18,11 @@ import Control.Exception as C import Control.Monad import Control.Monad.IO.Class-import Data.Char (isLower, isSpace, isAlphaNum)+import Data.Char (isLower, isSpace, isAlphaNum, isUpper, toLower) import Data.List (dropWhileEnd, intercalate) import Data.List.Split (endBy, splitOn) import qualified Data.Map as Map-import Data.Maybe (mapMaybe, isJust)+import Data.Maybe (isJust) import Language.Haskell.TH import qualified System.Directory as Dir import System.Environment (getEnv, setEnv)@@ -32,7 +32,7 @@ import System.Posix.Signals import System.Process --- | This function needs to be called in order to use the library succesfully+-- | This function needs to be called in order to use the library successfully -- from GHCi. initInteractive :: IO () initInteractive = do@@ -407,8 +407,6 @@ ordNubOn :: Ord b => (a -> b) -> [a] -> [a] ordNubOn f as = map snd . Map.toList . Map.fromListWith const $ zip (map f as) as -- -- | Execute the given command. Further arguments can be passed in. -- -- > exe "ls" "-l"@@ -426,52 +424,67 @@ = Absolute -- ^ Find executables on PATH, but store their absolute path | SearchPath -- ^ Always search on PATH --- | @$(loadExeAs fnName executable)@ defines a function called @fnName@--- which executes the path in @executable@.-loadExeAs :: ExecReference -> String -> String -> Q [Dec]-loadExeAs ref fnName executable =- -- TODO: Can we place haddock markup in TH generated functions.- -- TODO: Can we palce the man page for each function in there xD- -- https://ghc.haskell.org/trac/ghc/ticket/5467+-- | Template Haskell function to create a function from a path that will be+-- called. This does not check for executability at compile time.+rawExe :: String -> String -> Q [Dec]+rawExe fnName executable = do let name = mkName $ fnName- impl executableRef = valD (varP name) (normalB [|- exe executableRef+ impl = valD (varP name) (normalB [|+ exe executable |]) [] typn = mkName "a" typ = SigD name (ForallT [PlainTV typn] [AppT (ConT ''Unit) (VarT typn), AppT (ConT ''ExecArgs) (VarT typn)] (VarT typn))- in do- runIO (Dir.findExecutable executable) >>= \case- Nothing -> error $ "Attempted to load '" ++ executable ++ "', but it is not executable"- Just absExe -> do- i <- impl (case ref of { Absolute -> absExe; SearchPath -> executable })- return $ [typ,i]+ i <- impl+ return $ [typ,i] --- | Checks if a String is a valid Haskell identifier.-validIdentifier :: String -> Bool-validIdentifier "" = False-validIdentifier ident = isValidInit (head ident) && all isValidC ident && isNotIdent- where- isValidInit c = isLower c || c `elem` "_"- isValidC c = isAlphaNum c || c `elem` "_'"- isNotIdent = not $ ident `elem`- [ "import", "if", "else", "then", "do", "in", "let", "type"+-- | @$(loadExeAs ref fnName executable)@ defines a function called @fnName@+-- which executes the path in @executable@. If @executable@ is an absolute path+-- it is used directly. If it is just an executable name, then it is searched+-- for in the PATH environment variable. If @ref@ is @SearchPath@, the short+-- name is retained, and your PATH will be searched at runtime. If @ref@+-- is @Absolute@, a executable name will be turned into an absolute path, which+-- will be used at runtime.+loadExeAs :: ExecReference -> String -> String -> Q [Dec]+loadExeAs ref fnName executable = do+ -- TODO: Can we place haddock markup in TH generated functions.+ -- TODO: Can we place the man page for each function in there xD+ -- https://ghc.haskell.org/trac/ghc/ticket/5467+ runIO (Dir.findExecutable executable) >>= \case+ Nothing -> error $ "Attempted to load '" ++ executable ++ "', but it is not executable"+ Just absExe ->+ rawExe fnName (case ref of { Absolute -> absExe; SearchPath -> executable })++-- | Takes a string, and makes a Haskell identifier out of it. There+-- is some chance of overlap. If the string is a path, the filename portion+-- is used. The transformation replaces all non-alphanumeric characters+-- with @'_'@. If the first character is uppercase it is forced into lowercase.+encodeIdentifier :: String -> String+encodeIdentifier ident =+ let+ i = go (takeFileName ident)+ go (c:cs)+ | isLower c = c : go' cs+ | isUpper c = toLower c : go' cs+ | otherwise = go' (c:cs)+ go [] = "_"+ go' (c:cs)+ | isAlphaNum c = c : go' cs+ | otherwise = '_' : go' cs+ go' [] = []+ -- Includes cd, which has to be a built-in+ reserved = [ "import", "if", "else", "then", "do", "in", "let", "type" , "as", "case", "of", "class", "data", "default", "deriving" , "instance", "forall", "foreign", "hiding", "infix", "infixl" , "infixr", "mdo", "module", "newtype", "proc", "qualified"- , "rec", "type", "where"]+ , "rec", "where", "cd"]+ in if i `elem` reserved then i ++ "_" else i -- | Scans your '$PATH' environment variable and creates a function for each -- executable found. Binaries that would not create valid Haskell identifiers--- 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.------ Note: If an executable named @cd@ is discovered, this will load it as @cd'@+-- are encoded using the @'encodeIdentifier'@ function. loadEnv :: ExecReference -> Q [Dec]-loadEnv ref = loadAnnotatedEnv ref $ \case- "cd" -> "cd'"- x -> x+loadEnv ref = loadAnnotatedEnv ref encodeIdentifier -- | Test to see if an executable can be found either on the $PATH or absolute. checkExecutable :: FilePath -> IO Bool@@ -479,14 +492,15 @@ -- | Load the given executables into the program, checking their executability -- and creating a function @missingExecutables@ to do a runtime check for their--- availability.+-- availability. Uses the @'encodeIdentifier'@ function to create function+-- names. load :: ExecReference -> [String] -> Q [Dec]-load ref = loadAnnotated ref id+load ref = loadAnnotated ref encodeIdentifier -- | Same as `load`, but allows you to modify the function names. loadAnnotated :: ExecReference -> (String -> String) -> [String] -> Q [Dec] loadAnnotated ref f bins = do- let pairs = mapMaybe getAnnotation bins+ let pairs = zip (map f bins) bins ds <- fmap join $ mapM (uncurry (loadExeAs ref)) pairs d <- valD (varP (mkName "missingExecutables")) (normalB [| filterM (fmap not . checkExecutable) bins@@ -494,32 +508,30 @@ 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 :: ExecReference -> (String -> String) -> Q [Dec] loadAnnotatedEnv ref f = do- bins <- runIO pathBins- loadAnnotated ref f bins+ bins <- runIO $ case ref of+ Absolute -> pathBinsAbs+ SearchPath -> pathBins+ i <- forM bins $ \bin -> do+ rawExe (f $ takeFileName bin) bin+ pure (concat i) --- | Function that splits '\0' seperated list of strings. Useful in conjuction+-- | Function that splits '\0' separated list of strings. Useful in conjunction -- with @find . "-print0"@. split0 :: String -> [String] split0 = endBy "\0" --- | A convinience function for reading in a @"\\NUL"@ seperated list of+-- | A convenience function for reading in a @"\\NUL"@ separated list of -- strings. This is commonly used when dealing with paths. -- -- > readSplit0 $ find "-print0" readSplit0 :: Proc () -> IO [String] readSplit0 p = withReadSplit0 p pure --- | A convinience function for reading the output lines of a `Proc`.+-- | A convenience function for reading the output lines of a `Proc`. -- -- Note: Please consider using @'readSplit0'@ instead if you can. readLines :: Proc () -> IO [String]
test/Test.hs view
@@ -26,6 +26,8 @@ properties :: TestTree properties = testGroup "Properties" [ testProperty "trim = trim . trim" $ \l -> trim l == trim (trim l)+ , testProperty "encodeIdentifier = encodeIdentifier . encodeIdentifier"+ $ \l -> encodeIdentifier l == encodeIdentifier (encodeIdentifier l) ] withTmp :: (FilePath -> IO a) -> IO a