diff options
author | Alexander Foremny <aforemny@posteo.de> | 2024-03-08 05:00:53 +0100 |
---|---|---|
committer | Alexander Foremny <aforemny@posteo.de> | 2024-03-08 05:00:53 +0100 |
commit | b0f442b78a6b2c7d8978ae958bcadcc23ff82199 (patch) | |
tree | 179b9a8a85134456a4385cb6d07265bf56587925 | |
parent | 64d495b4cfbbeb3fcc72b5b8771874793d8fc6a3 (diff) |
chore: use `${{..}}` for impure interpolation
-rw-r--r-- | sh.cabal | 1 | ||||
-rw-r--r-- | src/Process/Shell.hs | 113 | ||||
-rw-r--r-- | test/Main.hs | 6 |
3 files changed, 63 insertions, 57 deletions
@@ -34,5 +34,6 @@ test-suite sh-test base, bytestring, hspec, + mtl, sh, text diff --git a/src/Process/Shell.hs b/src/Process/Shell.hs index 06c9dbe..87d22df 100644 --- a/src/Process/Shell.hs +++ b/src/Process/Shell.hs @@ -1,8 +1,10 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE DefaultSignatures #-} -{-# LANGUAGE OverlappingInstances #-} +{-# LANGUAGE IncoherentInstances #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UndecidableInstances #-} module Process.Shell ( sh, @@ -11,7 +13,7 @@ module Process.Shell where import Control.Monad -import Control.Monad.Trans +import Control.Monad.Reader import Data.ByteString qualified as B import Data.ByteString.Lazy qualified as LB import Data.ByteString.Lazy.UTF8 qualified as LB @@ -25,33 +27,27 @@ import Data.Text.Lazy qualified as LT import Data.Text.Lazy.Encoding qualified as LT import Data.Void import Language.Haskell.Meta.Parse -import Language.Haskell.TH +import Language.Haskell.TH hiding (Type) import Language.Haskell.TH.Quote import System.Process.Typed import Text.Megaparsec import Text.Megaparsec.Char import Prelude hiding (exp) -class Processable r where - sh_ :: String -> r +class Processable m r where + sh_ :: m String -> m r -instance (MonadIO m) => Processable (m ()) where - sh_ s = do - liftIO (runProcess_ (fromString s)) +instance (MonadIO m) => Processable m () where + sh_ = (liftIO . runProcess_ . fromString =<<) -instance (MonadIO m, Outputable a) => Processable (m a) where - sh_ s = do - fromLBS <$> liftIO (readProcessInterleaved_ (fromString s)) +instance (MonadIO m, Outputable a) => Processable m a where + sh_ = (fmap fromLBS . liftIO . readProcessInterleaved_ . fromString =<<) -instance (MonadIO m, Outputable stdout, Outputable stderr) => Processable (m (stdout, stderr)) where - sh_ s = do - (\(out, err) -> (fromLBS out, fromLBS err)) - <$> liftIO (readProcess_ (fromString s)) +instance (MonadIO m, Outputable stdout, Outputable stderr) => Processable m (stdout, stderr) where + sh_ = (fmap (\(out, err) -> (fromLBS out, fromLBS err)) . liftIO . readProcess_ . fromString =<<) -instance (MonadIO m, Outputable stdout, Outputable stderr) => Processable (m (ExitCode, stdout, stderr)) where - sh_ s = do - (\(exitCode, out, err) -> (exitCode, fromLBS out, fromLBS err)) - <$> liftIO (readProcess (fromString s)) +instance (MonadIO m, Outputable stdout, Outputable stderr) => Processable m (ExitCode, stdout, stderr) where + sh_ = (fmap (\(exitCode, out, err) -> (exitCode, fromLBS out, fromLBS err)) . liftIO . readProcess . fromString =<<) class Outputable a where fromLBS :: LB.ByteString -> a @@ -71,30 +67,27 @@ instance Outputable T.Text where instance Outputable LT.Text where fromLBS = LT.decodeUtf8 -class Quotable m a where - toStringM :: (Monad m) => a -> m String - default toStringM :: (Show a, Monad m) => a -> m String - toStringM = pure . show +class Quotable a where + toString :: a -> String + default toString :: (Show a) => a -> String + toString = show -instance Quotable m String where - toStringM = pure +instance Quotable String where + toString = id -instance (Monad m, Quotable m a) => Quotable m (m a) where - toStringM mkA = toStringM =<< mkA +instance Quotable Int -instance Quotable m Int +instance Quotable B.ByteString where + toString = B.toString -instance Quotable m B.ByteString where - toStringM = pure . B.toString +instance Quotable LB.ByteString where + toString = LB.toString -instance Quotable m LB.ByteString where - toStringM = pure . LB.toString +instance Quotable T.Text where + toString = T.unpack -instance Quotable m T.Text where - toStringM = pure . T.unpack - -instance Quotable m LT.Text where - toStringM = pure . LT.unpack +instance Quotable LT.Text where + toString = LT.unpack squote :: String -> String squote s = "'" <> quote' s <> "'" @@ -128,12 +121,24 @@ sh = QuasiQuoter quoteExp undefined undefined undefined many . choice $ [ do Var False . either fail pure . parseExp + <$> ( string "#{{" + *> takeWhile1P Nothing (/= '}') + <* string "}}" + ), + do + Var True . either fail pure . parseExp + <$> ( string "'#{{" + *> takeWhile1P Nothing (/= '}') + <* string "}}'" + ), + do + Var False . either fail (appE [|pure|] . pure) . parseExp <$> ( string "#{" *> takeWhile1P Nothing (/= '}') <* string "}" ), do - Var True . either fail pure . parseExp + Var True . either fail (appE [|pure|] . pure) . parseExp <$> ( string "'#{" *> takeWhile1P Nothing (/= '}') <* string "}'" @@ -144,31 +149,27 @@ sh = QuasiQuoter quoteExp undefined undefined undefined Lit . (: []) <$> satisfy ((||) <$> (== '\'') <*> (== '#')) ] + makeExp :: [Expr (Q Exp)] -> Q Exp makeExp exprs = do - exprs' <- + exprs' :: [(Expr Name)] <- sequence [ case expr of Lit s -> pure (Lit s) Var q _ -> Var q <$> newName "arg" | expr <- exprs ] - doE $ - [ BindS <$> (varP nam) <*> [|toStringM $exp|] - | (Var _ exp, Var _ nam) <- zip exprs exprs' - ] - ++ [ fmap - NoBindS - [| - sh_ - $( foldr - (\a b -> [|$a ++ $b|]) - [|""|] - (map toExp exprs') - ) - |] - ] + [| + sh_ $ + $( doE $ + [ BindS <$> (varP nam) <*> [|toString <$> $exp|] + | (Var _ exp, Var _ nam) <- zip exprs exprs' + ] + ++ [ NoBindS <$> [|pure $(foldr (\a b -> [|$a ++ $b|]) [|""|] (map toExp exprs'))|] + ] + ) + |] toExp :: Expr Name -> Q Exp toExp (Lit s) = [|s|] - toExp (Var q nam) = - [|(if q then squote else dquote) $(varE nam)|] + toExp (Var True nam) = [|squote $(varE nam)|] + toExp (Var False nam) = [|dquote $(varE nam)|] diff --git a/test/Main.hs b/test/Main.hs index 71723c3..e00ccf6 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -1,8 +1,10 @@ {-# LANGUAGE BlockArguments #-} +{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE QuasiQuotes #-} module Main (main) where +import Control.Monad.Reader import Data.ByteString.Char8 qualified as B import Data.ByteString.Lazy.Char8 qualified as LB import Data.Text qualified as T @@ -41,7 +43,9 @@ main = hspec do it "interpolates expressions" do (`shouldBe` "1") =<< let x = 1 :: Int in [sh|echo -n '#{show x}'|] it "interpolates monadic expressions" do - (`shouldBe` "1") =<< let x = 1 :: Int in [sh|echo -n '#{pure @IO x}'|] + (`shouldBe` "1") =<< let x = 1 :: Int in [sh|echo -n '#{{pure @IO x}}'|] + it "interpolates monadic expressions" do + (`shouldBe` "1") =<< runReaderT [sh|echo -n '#{{asks fst}}'|] (1 :: Int, 2 :: Int) it "preserves argument order" do (`shouldBe` "1 2") =<< let x = "1"; y = "2" in [sh|echo -n '#{x}' '#{y}'|] describe "quoting" do |