command-qq 0.1.0.0 → 0.2.0.0
raw patch · 11 files changed
+603/−267 lines, 11 filesdep +textdep −unix
Dependencies added: text
Dependencies removed: unix
Files
- CHANGELOG.md +7/−0
- README.md +77/−0
- command-qq.cabal +39/−34
- examples/CommandT.hs +11/−21
- src/System/Command/QQ.hs +72/−161
- src/System/Command/QQ/Embed.hs +78/−0
- src/System/Command/QQ/Eval.hs +118/−0
- tests/Doctests.hs +2/−0
- tests/EmbeddingSpec.hs +0/−51
- tests/System/Command/QQ/EmbedSpec.hs +105/−0
- tests/System/Command/QQ/EvalSpec.hs +94/−0
+ CHANGELOG.md view
@@ -0,0 +1,7 @@+0.2.0.0+=======++ * Added `sh_` quasiquoter to avoid type annotations for trivial quotes+ * Moved `Eval` onto `Text` to speed I/O up.+ * Quasiquoters support literals and constructors with no arguments+ * More `Embed` instances
+ README.md view
@@ -0,0 +1,77 @@+# command-qq+[](http://hackage.haskell.org/package/command-qq)+[](http://travis-ci.org/biegunka/command-qq)+[](https://drone.io/github.com/biegunka/command-qq/latest)++```+>>> import System.Command.QQ+>>> putStr =<< unlines . reverse . lines <$> [sh|cowsay "Hello, I am command-qq!"|]+ || ||+ ||----w |+ (__)\ )\/\+ \ (oo)\_______+ \ ^__^+ -------------------------+< Hello, I am command-qq! >+ _________________________+```++## Install++```+% cabal install command-qq+```++## Features++### Quasiquotation syntax for external interpreters++```+>>> [sh_| echo hello world! |]+hello world!+```++### Custom quasiquoters++```+ghci = quoter $ callCommand "ghc" ["-ignore-dot-ghci", "-e"]+```++Then you can use `ghci` in ghci!++```+>>> [ghci| putStrLn "hello world!" |] :: IO ()+hello world!+```++For more examples, see [`examples/CustomQQ.hs`][0]++### Haskell values embedding++Let's define `Embed` instance for a custom data type:++```haskell+data Bang = Bang++instance Embed Bang where+ embed Bang = "!"+```++Then you can use variables of `Bang` type in quoted strings!++```+>>> [sh_| echo hello#{Bang} |]+hello!+>>> let bang = Bang in [sh_| echo hello#{bang} |]+hello!+```++Note, `command-qq` does not support full Haskell in embeddings,+only variables/constructors names and literals++### DSLs++See [`examples/CommandT.hs`][1]++ [0]: https://github.com/biegunka/command-qq/blob/master/examples/CustomQQ.hs+ [1]: https://github.com/biegunka/command-qq/blob/master/examples/CommandT.hs
command-qq.cabal view
@@ -1,36 +1,33 @@ name: command-qq-version: 0.1.0.0+version: 0.2.0.0 synopsis: Quasiquoters for external commands description: Features: .- * Quasiquotation syntax for external commands:+ * Quasiquotation syntax for external interpreters .- > [sh| echo hello world! |]+ > >>> [sh_| echo hello world! |]+ > hello world! .- * Easy way to define custom quasiquoters (see @examples/CustomQQ.hs@)+ * Custom quasiquoters .- > ghci = QQ.quoter $ QQ.callCommand "ghc" ["-ignore-dot-ghci", "-e"]+ > ghci = quoter $ callCommand "ghc" ["-ignore-dot-ghci", "-e"] .- * Can embed Haskell variables in scripts+ Then you can use @ghci@ in ghci! .- > class Embed a where- > embed :: a -> String- >- > instance Embed String- > embed = id- >- > main :: IO ()- > main = let excl = replicate 3 '!' in -- I'd love to show an example but haddock markup fails here+ > >>> [ghci| putStrLn "hello world!" |] :: IO ()+ > hello world! .- * Can embed quasiquoters in DSLs (see @examples/CommandT.hs@)+ For more examples, see @examples/CustomQQ.hs@ .- > class Eval r where- > eval :: String -> [String] -> r- >- > instance Eval (IO ExitCode) where- > eval command args = System.Process.rawSystem command args+ * Haskell values embedding .+ See README.md for an example+ .+ * DSLs+ .+ See @examples/CommandT.hs@+ . homepage: http://biegunka.github.io/command-qq/ license: BSD3 license-file: LICENSE@@ -40,6 +37,8 @@ build-type: Simple cabal-version: >= 1.10 extra-source-files:+ CHANGELOG.md+ README.md examples/CommandT.hs examples/CustomQQ.hs @@ -47,19 +46,22 @@ library default-language: Haskell2010 build-depends:- base == 4.*,- process,- template-haskell,- unix+ base == 4.*+ , process+ , template-haskell+ , text hs-source-dirs: src- exposed-modules: System.Command.QQ+ exposed-modules:+ System.Command.QQ+ System.Command.QQ.Embed+ System.Command.QQ.Eval test-suite doctests default-language: Haskell2010 type: exitcode-stdio-1.0 build-depends:- base == 4.*,- doctest+ base == 4.*+ , doctest hs-source-dirs: tests main-is: Doctests.hs @@ -67,15 +69,18 @@ default-language: Haskell2010 type: exitcode-stdio-1.0 build-depends:- base == 4.*,- hspec,- command-qq,- template-haskell+ base == 4.*+ , hspec+ , command-qq+ , template-haskell+ , text hs-source-dirs:- tests,- examples+ tests+ , examples main-is: Spec.hs- other-modules: EmbeddingSpec+ other-modules:+ System.Command.QQ.EmbedSpec+ System.Command.QQ.EvalSpec source-repository head
examples/CommandT.hs view
@@ -15,17 +15,16 @@ import Control.Monad.IO.Class (MonadIO(..)) -- transformers import Control.Monad.Trans.Class (MonadTrans(..)) -- transformers import Control.Monad.Trans.Either -- either-import Data.Functor.Alt (Alt(..)) -- semigroupoids-import Data.Functor.Apply (Apply(..)) -- semigroupoids-import Data.Functor.Bind (Bind(..)) -- semigroupoids import Data.Monoid (Last(..)) -- base+import Data.Text.Lazy (Text) -- text import System.Exit (ExitCode(..)) -- base import System.Command.QQ (Eval(..)) -- command-qq -- $setup -- >>> :set -XQuasiQuotes -- >>> import System.Command.QQ--- >>> let lengths = [sh|while read line; do echo ${#line}; done|] :: String -> CommandT IO String+-- >>> import qualified Data.Text.Lazy as T+-- >>> let lengths = [sh|while read line; do echo ${#line}; done|] :: Text -> CommandT IO Text infixl 1 >>! -- same as >>=@@ -53,7 +52,7 @@ newtype CommandT m a = CommandT { unCommandT :: EitherT (Last Failure) m a } -- | Failed command with exit code and @stderr@-data Failure = Failure Command Int String+data Failure = Failure Command Int Text deriving (Show, Read) -- | Command name and arguments@@ -67,26 +66,17 @@ instance Monad m => Functor (CommandT m) where fmap f (CommandT x) = CommandT (fmap f x) -instance Monad m => Apply (CommandT m) where- CommandT f <.> CommandT x = CommandT (f <.> x)- instance Monad m => Applicative (CommandT m) where pure = CommandT . pure- (<*>) = (<.>)--instance Monad m => Bind (CommandT m) where- CommandT x >>- k = CommandT (x >>- unCommandT . k)+ CommandT f <*> CommandT x = CommandT (f <*> x) instance Monad m => Monad (CommandT m) where return = pure- (>>=) = (>>-)--instance Monad m => Alt (CommandT m) where- CommandT f <!> CommandT x = CommandT (f <!> x)+ CommandT x >>= k = CommandT (x >>= unCommandT . k) instance Monad m => Alternative (CommandT m) where empty = CommandT empty- (<|>) = (<!>)+ CommandT f <|> CommandT x = CommandT (f <|> x) instance Monad m => MonadPlus (CommandT m) where mzero = empty@@ -98,14 +88,14 @@ instance MonadIO m => MonadIO (CommandT m) where liftIO = lift . liftIO -instance (o ~ String, MonadIO m) => Eval (CommandT m o) where+instance (o ~ Text, MonadIO m) => Eval (CommandT m o) where eval command args = CommandT . EitherT $ do (status, out, err) <- liftIO $ eval command args return $ case status of ExitSuccess -> Right out ExitFailure i -> Left (Last (Just (Failure (Command command args) i err))) -instance (i ~ String, o ~ String, MonadIO m) => Eval (i -> CommandT m o) where+instance (i ~ Text, o ~ Text, MonadIO m) => Eval (i -> CommandT m o) where eval command args input = CommandT . EitherT $ do (status, out, err) <- liftIO $ eval command args input return $ case status of@@ -127,9 +117,9 @@ -- -- And playing may involve arbitrary Haskell functions, of course: ----- >>> runCommandT $ [sh|echo -e "hello\nworld!!!">&2; exit 1|] >>! lengths . unlines . reverse . lines+-- >>> runCommandT $ [sh|echo -e "hello\nworld!!!">&2; exit 1|] >>! lengths . T.unlines . reverse . T.lines -- Right "8\n5\n"-(>>!) :: Monad m => CommandT m a -> (String -> CommandT m b) -> CommandT m b+(>>!) :: Monad m => CommandT m a -> (Text -> CommandT m b) -> CommandT m b x >>! k = CommandT . EitherT $ do t <- runCommandT x case t of
src/System/Command/QQ.hs view
@@ -1,86 +1,93 @@-{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -- | Quasiquoters for external commands module System.Command.QQ ( -- * Quasiquoters- sh, shell, interpreter+ -- ** Default shell+ sh_, sh+ -- ** Constructors+ , shell, interpreter -- * Customizations , quoter, callCommand- , Eval(..), Embed(..)+ , module System.Command.QQ.Embed+ , module System.Command.QQ.Eval ) where -import Control.Applicative ((<$), pure)-import Data.Int-import Data.Word-import Language.Haskell.TH-import Language.Haskell.TH.Quote-import System.Exit (ExitCode)-import System.Posix.Env (getEnvDefault)-import qualified System.Process as P+import Control.Applicative+import Data.Char (isLower, isUpper)+import Data.Maybe (fromMaybe)+import Language.Haskell.TH+import Language.Haskell.TH.Quote+import System.Environment (lookupEnv)+import Text.Read (readMaybe) +import System.Command.QQ.Embed+import System.Command.QQ.Eval+ -- $setup -- >>> :set -XQuasiQuotes+-- >>> :set -XOverloadedStrings+-- >>> import System.Exit+-- >>> import Data.Text.Lazy (Text) -- | Quasiquoter for the default shell ----- \"default\" here means it uses value of @SHELL@ environment variable--- or @\/bin\/sh@ if it is not set.+-- Constructs polymorphic action of type @Eval a => a@ from passed string. ----- >>> [sh|echo "hi!"|] :: IO ExitCode--- hi!+-- Uses @SHELL@ environment variable as path to shell executable+-- or @\/bin\/sh@ if it is unset.+--+-- >>> [sh|echo "hello, world!"|] :: IO ExitCode -- ExitSuccess--- >>> [sh|echo "hi!"|] :: IO String--- "hi!\n"+-- >>> [sh|echo "hello, world!"|] :: IO Text+-- "hello, world!\n" -- -- Haskell values can be embedded with Ruby-like syntax: -- -- >>> let apples = 7--- >>> [sh|echo "#{apples} apples!"|] :: IO String+-- >>> [sh|echo "#{apples} apples!"|] :: IO Text -- "7 apples!\n"------ Works only for expressions (obviously):------ >>> return 3 :: IO [sh|blah|]--- <BLANKLINE>--- <interactive>:116:16:--- Exception when trying to run compile-time code:--- this quasiquoter does not support splicing types--- Code: quoteType sh "blah" sh :: QuasiQuoter sh = quoter $ \string -> do- shellEx <- runIO $ getEnvDefault "SHELL" "/bin/sh"+ shellEx <- runIO $ getEnvDefault "/bin/sh" "SHELL" callCommand shellEx ["-c"] string --- | Shell commands quasiquoter maker+-- | Simple quasiquoter for the default shell ----- \"Shell\" here means something that implements the following interface:+-- 'sh' analog that always constructs an action of type+-- @IO ()@ and so can always be used without type annotations --+-- >>> [sh_|echo "hello, world!"|]+-- hello, world!+sh_ :: QuasiQuoter+sh_ = quoter $ \string -> do+ shellEx <- runIO $ getEnvDefault "/bin/sh" "SHELL"+ callCommand_ shellEx ["-c"] string++-- | Shell's quasiquoter constructor+--+-- \"Shell\" here means executable that has the following API:+-- -- @ -- \<SHELL\> -c \<COMMAND\> -- @ -- -- /e.g./ @sh@, @bash@, @zsh@, @ksh@, @tcsh@, @python@, etc------ Everything that applies to 'sh' applies to 'shell' shell :: FilePath -> QuasiQuoter shell path = quoter (callCommand path ["-c"]) --- | Interpreter commands quasiquoter maker+-- | Interpreter's quasiquoter constructor ----- \"Interpreter\" here means something that implements the following interface:+-- \"Interpreter\" here means executable that has the following API: -- -- @ -- \<INTERPRETER\> -e \<COMMAND\> -- @ -- -- /e.g./ @perl@, @ruby@, @ghc@, etc------ Everything that applies to 'sh' applies to 'interpreter' interpreter :: FilePath -> QuasiQuoter interpreter path = quoter (callCommand path ["-e"]) @@ -110,139 +117,43 @@ callCommand path args string = [e| eval path (args ++ [$(string2exp string)]) |] +-- | Construct Haskell expression for external command call+callCommand_+ :: FilePath -- ^ Command path+ -> [String] -- ^ Arguments that go to command before quasiquoter contents+ -> String -- ^ Quasiquoter contents+ -> Q Exp+callCommand_ path args string =+ [e| eval path (args ++ [$(string2exp string)]) :: IO () |]+ -- | Parse references to Haskell variables string2exp :: String -> Q Exp string2exp = raw where+ raw, var :: String -> Q Exp raw (break (== '#') -> parts) = case parts of (before, '#':'{' :after) -> [e| before ++ $(var after) |] (before, '#':'\\':after) -> [e| before ++ '#' : $(raw after) |] (before, '#':after) -> [e| before ++ '#' : $(raw after) |] (before, []) -> [e| before |]- _ -> fail $ "Should never happen"+ _ -> fail "Should never happen" var (break (== '}') -> parts) = case parts of- (before, '}':after) -> [e| embed $(return (VarE (mkName before))) ++ $(raw after) |]- (before, _) -> fail $ "Bad variable pattern: #{" ++ before----- | Different interesting return types for quasiquoters------ Instances here mostly resemble the types of things in "System.Process"-class Eval r where- eval :: String -> [String] -> r---- | Most basic instance: nothing is known about what happened in external command------ >>> [sh|echo hello world|] :: IO ()--- hello world-instance Eval (IO ()) where- eval command args = () <$ P.rawSystem command args---- | Return only exit code of external process------ >>> [sh|echo hello world|] :: IO ExitCode--- hello world--- ExitSuccess------ >>> [sh|exit 1|] :: IO ExitCode--- ExitFailure 1-instance Eval (IO ExitCode) where- eval command args = P.rawSystem command args---- | Return only stdout of external process------ Does not care if external process failed.------ >>> [sh|echo hello world|] :: IO String--- "hello world\n"------ >>> [sh|echo hello world; return 1|] :: IO String--- "hello world\n"-instance Eval (IO String) where- eval command args = do- (_, out, _) <- eval command args- return out---- | Return exit code, stdout, and stderr of external process------ >>> [sh|echo hello world; echo bye world >&2; exit 1|] :: IO (ExitCode, String, String)--- (ExitFailure 1,"hello world\n","bye world\n")-instance- ( s ~ ExitCode- , o ~ String- , e ~ String- ) => Eval (IO (s, o, e)) where- eval command args = P.readProcessWithExitCode command args ""---- | Return exit code, stdout, and stderr of external process--- and consume stdin from supplied 'String'------ >>> [sh|while read line; do echo ${#line}; done|] "hello\nworld!\n"--- (ExitSuccess,"5\n6\n","")-instance- ( i ~ String- , o ~ (ExitCode, String, String)- ) => Eval (i -> IO o) where- eval command args stdin = P.readProcessWithExitCode command args stdin----- | Embed haskell values into external commands------ I recommend using @-XExtendedDefaultRules@ for modules--- where you want to embed values, it would save for annoying--- type annotations for numeric literals------ @--- embed . embed = embed--- @-class Embed a where- embed :: a -> String- default embed :: Show a => a -> String- embed = show---- |--- >>> embed 4--- "4"------ >>> embed (7 :: Integer)--- "7"-instance Embed Integer---- |--- >>> embed (7 :: Int)--- "7"-instance Embed Int-instance Embed Int8-instance Embed Int16-instance Embed Int32-instance Embed Int64-instance Embed Word-instance Embed Word8-instance Embed Word16-instance Embed Word32-instance Embed Word64---- |--- >>> embed (7 :: Float)--- "7.0"-instance Embed Float---- |--- >>> embed 4.0--- "4.0"------ >>> embed (7 :: Double)--- "7.0"-instance Embed Double+ (b:efore, '}':after)+ | isLower b -> external (VarE (mkName (b:efore))) after+ | isUpper b -> external (ConE (mkName (b:efore))) after+ | isUpper b -> external (ConE (mkName (b:efore))) after+ | Just i <- readMaybe (b:efore) -> external (LitE (IntegerL i)) after+ | Just d <- readMaybe (b:efore) -> external (LitE (RationalL (toRational (d :: Double)))) after+ | Just c <- readMaybe (b:efore) -> external (LitE (CharL c)) after+ | Just s <- readMaybe (b:efore) -> external (LitE (StringL s)) after+ (before, _) -> fail $ "Invalid name: " ++ before --- |--- >>> embed 'c'--- "c"-instance Embed Char where- embed = pure+ external :: Exp -> String -> Q Exp+ external e after = [e| embed $(return e) ++ $(raw after) |] --- |--- >>> embed "hi"--- "hi"-instance Embed String where- embed = id+-- | Get environment variable or default value if it's unset+getEnvDefault+ :: String -- ^ The default vefault+ -> String -- ^ Environment variable+ -> IO String+getEnvDefault def query = fromMaybe def <$> lookupEnv query
+ src/System/Command/QQ/Embed.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+-- | Haskell values embedding+module System.Command.QQ.Embed+ ( Embed(..)+ ) where++import Control.Applicative+import Data.Int+import Data.Ratio (Ratio)+import Data.Word+import Foreign.C.Types++-- $setup+-- >>> import Data.Ratio+++-- | Embed haskell values into external commands+--+-- I recommend using @-XExtendedDefaultRules@ for modules+-- where you want to embed values, it would save for annoying+-- type annotations for numeric literals+--+-- @+-- embed . embed = embed+-- @+class Embed a where+ embed :: a -> String+ default embed :: Show a => a -> String+ embed = show++instance Embed Integer+instance Embed Int+instance Embed Int8+instance Embed Int16+instance Embed Int32+instance Embed Int64+instance Embed Word+instance Embed Word8+instance Embed Word16+instance Embed Word32+instance Embed Word64+instance Embed Float+instance Embed Double++instance Embed CChar+instance Embed CSChar+instance Embed CUChar+instance Embed CShort+instance Embed CUShort+instance Embed CInt+instance Embed CUInt+instance Embed CLong+instance Embed CULong+instance Embed CSize+instance Embed CLLong+instance Embed CULLong+instance Embed CFloat+instance Embed CDouble++-- |+-- >>> embed (3 % 5)+-- "0.6"+instance a ~ Integer => Embed (Ratio a) where+ embed = embed . (fromRational :: Rational -> Double)++-- |+-- >>> embed 'c'+-- "c"+instance Embed Char where+ embed = pure++-- |+-- >>> embed "hi"+-- "hi"+instance Embed String where+ embed = id
+ src/System/Command/QQ/Eval.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+-- | Evalute passed arguments with external interpreter+module System.Command.QQ.Eval+ ( Eval(..)+ ) where++import Control.Applicative+import Control.Concurrent+import Control.Exception (evaluate)+import Control.Monad+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.IO as T+import System.Exit (ExitCode)+import qualified System.Process as P+import System.IO (hFlush, hClose)++-- $setup+-- >>> import System.Command.QQ+++-- | Different interesting return types for quasiquoters+--+-- Instances here mostly resemble the types of things in "System.Process"+class Eval r where+ eval :: String -> [String] -> r++-- | Most basic instance: nothing is known about what happened in external command+--+-- >>> [sh|echo hello world|] :: IO ()+-- hello world+instance Eval (IO ()) where+ eval command args = () <$ P.rawSystem command args++-- | Return exit code of the external process+--+-- >>> [sh|exit 0|] :: IO ExitCode+-- ExitSuccess+--+-- >>> [sh|exit 7|] :: IO ExitCode+-- ExitFailure 7+instance Eval (IO ExitCode) where+ eval command args = do+ (s, _, _) <- eval command args (T.pack "")+ return s++-- | Return stdout of the external process as 'Text'+--+-- Does not care whether external process has failed or not.+--+-- >>> [sh|echo -n hello world|] :: IO Text+-- "hello world"+instance Eval (IO Text) where+ eval command args = do+ (_, o, _) <- eval command args+ return o++-- | Return stdout of external process as 'String'+--+-- Does not care whether external process has failed or not.+--+-- >>> [sh|echo -n hello world|] :: IO String+-- "hello world"+instance Eval (IO String) where+ eval command args = T.unpack <$> eval command args++-- | Return exit code, stdout, and stderr of external process+--+-- >>> [sh|echo hello world; echo bye world >&2; exit 1|] :: IO (ExitCode, Text, Text)+-- (ExitFailure 1,"hello world\n","bye world\n")+instance+ ( s ~ ExitCode+ , o ~ Text+ , e ~ Text+ ) => Eval (IO (s, o, e)) where+ eval command args = eval command args (T.pack "")++-- | Return exit code, stdout, and stderr of the external process+-- and pass supplied 'Text' to its stdin+--+-- >>> [sh|while read line; do echo ${#line}; done|] "hello\nworld!\n"+-- (ExitSuccess,"5\n6\n","")+instance+ ( i ~ Text+ , o ~ (ExitCode, Text, Text)+ ) => Eval (i -> IO o) where+ eval = readProcessWithExitCode++readProcessWithExitCode :: String -> [String] -> Text -> IO (ExitCode, Text, Text)+readProcessWithExitCode cmd args input = do+ (Just ih, Just oh, Just eh, p) <-+ P.createProcess (P.proc cmd args)+ { P.std_in = P.CreatePipe+ , P.std_out = P.CreatePipe+ , P.std_err = P.CreatePipe+ }++ m <- newEmptyMVar+ o <- T.hGetContents oh+ e <- T.hGetContents eh++ forkFinally (evaluate (T.length o)) (\_ -> putMVar m ())+ forkFinally (evaluate (T.length e)) (\_ -> putMVar m ())++ unless (T.null input) $ do+ T.hPutStr ih input+ hFlush ih+ hClose ih++ takeMVar m+ takeMVar m+ hClose oh+ hClose eh++ s <- P.waitForProcess p++ return (s, o, e)
tests/Doctests.hs view
@@ -6,6 +6,8 @@ main :: IO () main = doctest [ "src/System/Command/QQ.hs"+ , "src/System/Command/QQ/Embed.hs"+ , "src/System/Command/QQ/Eval.hs" , "examples/CustomQQ.hs" , "examples/CommandT.hs" ]
− tests/EmbeddingSpec.hs
@@ -1,51 +0,0 @@-{-# LANGUAGE ExtendedDefaultRules #-}-{-# LANGUAGE QuasiQuotes #-}-{-# OPTIONS_GHC -fno-warn-unused-binds #-}-module EmbeddingSpec where--import System.Command.QQ-import Test.Hspec---spec :: Spec-spec = do- describe "variable embeddings" $ do-- it "can embed integers" $- let foo = 7 in [sh|echo #{foo}|] `shouldReturn` "7\n"-- it "can embed doubles" $- let foo = 7.0 in [sh|echo #{foo}|] `shouldReturn` "7.0\n"-- it "can embed characters" $- let foo = 'z' in [sh|echo #{foo}|] `shouldReturn` "z\n"-- it "can embed strings" $- let foo = "hello" in [sh|echo #{foo}|] `shouldReturn` "hello\n"-- describe "multi-line embeddings" $ do-- it "supports multiline commands" $- [sh|- echo hello- echo world- echo !!!- |] `shouldReturn` "hello\nworld\n!!!\n"-- it "supports embeddings in multiline commands" $- let foo = 4- bar = 7- in [sh|- echo #{foo}- echo #{bar}- |] `shouldReturn` "4\n7\n"-- describe "escapings" $ do-- it "is possible to write #{} in scripts still (as a comment)" $ do- [sh|echo #\{foo}|] `shouldReturn` "\n"- [sh|echo #\\{foo}|] `shouldReturn` "\n"-- it "is possible to write #{} in scripts still (as a string)" $ do- [sh|echo "#\{foo}"|] `shouldReturn` "#{foo}\n"- [sh|echo "#\\{foo}"|] `shouldReturn` "#\\{foo}\n"
+ tests/System/Command/QQ/EmbedSpec.hs view
@@ -0,0 +1,105 @@+module System.Command.QQ.EmbedSpec (spec) where++import Data.Int+import Data.Ratio+import Data.Word+import Foreign.C.Types+import System.Command.QQ+import Test.Hspec+++spec :: Spec+spec =+ describe "embedding" $ do+ describe "Haskell types" $ do+ describe "numeric types" $ do+ it "embeds Integer" $+ embed (4 :: Integer) `shouldBe` "4"++ it "embeds Int" $+ embed (4 :: Int) `shouldBe` "4"++ it "embeds Int8" $+ embed (4 :: Int8) `shouldBe` "4"++ it "embeds Int16" $+ embed (4 :: Int16) `shouldBe` "4"++ it "embeds Int32" $+ embed (4 :: Int32) `shouldBe` "4"++ it "embeds Int64" $+ embed (4 :: Int64) `shouldBe` "4"++ it "embeds Word" $+ embed (4 :: Word) `shouldBe` "4"++ it "embeds Word8" $+ embed (4 :: Word8) `shouldBe` "4"++ it "embeds Word16" $+ embed (4 :: Word16) `shouldBe` "4"++ it "embeds Word32" $+ embed (4 :: Word32) `shouldBe` "4"++ it "embeds Word64" $+ embed (4 :: Word64) `shouldBe` "4"++ it "embeds Float" $+ embed (7 :: Float) `shouldBe` "7.0"++ it "embeds Double" $+ embed (7 :: Double) `shouldBe` "7.0"++ it "embeds Rational" $+ embed (3 % 5) `shouldBe` "0.6"++ it "embeds Char" $+ embed 'e' `shouldBe` "e"++ it "embeds String" $+ embed "foo" `shouldBe` "foo"++ describe "C types" $ do+ it "embeds CChar" $+ embed (4 :: CChar) `shouldBe` "4"++ it "embeds CSChar" $+ embed (4 :: CSChar) `shouldBe` "4"++ it "embeds CUChar" $+ embed (4 :: CUChar) `shouldBe` "4"++ it "embeds CShort" $+ embed (4 :: CShort) `shouldBe` "4"++ it "embeds CUShort" $+ embed (4 :: CUShort) `shouldBe` "4"++ it "embeds CInt" $+ embed (4 :: CInt) `shouldBe` "4"++ it "embeds CUInt" $+ embed (4 :: CUInt) `shouldBe` "4"++ it "embeds CLong" $+ embed (4 :: CLong) `shouldBe` "4"++ it "embeds CULong" $+ embed (4 :: CULong) `shouldBe` "4"++ it "embeds CSize" $+ embed (4 :: CSize) `shouldBe` "4"++ it "embeds CLLong" $+ embed (4 :: CLLong) `shouldBe` "4"++ it "embeds CULLong" $+ embed (4 :: CULLong) `shouldBe` "4"++ it "embeds CFloat" $+ embed (7 :: CFloat) `shouldBe` "7.0"++ it "embeds CDouble" $+ embed (7 :: CDouble) `shouldBe` "7.0"
+ tests/System/Command/QQ/EvalSpec.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE ExtendedDefaultRules #-}+{-# LANGUAGE QuasiQuotes #-}+module System.Command.QQ.EvalSpec (spec) where++import Data.Text.Lazy (Text, pack)+import System.Command.QQ+import System.Exit (ExitCode(..))+import Test.Hspec+++spec :: Spec+spec = do+ describe "quasiquotation" $ do+ context "unicode" $ do+ it "works with unicode symbols in the String output" $+ [sh|echo -n "ДМИТРИЙ МАЛИКОВ"|] `shouldReturn` "ДМИТРИЙ МАЛИКОВ"++ it "works with unicode symbols in the Text output" $+ [sh|echo -n "ЕГОР ЛЕТОВ"|] `shouldReturn` text "ЕГОР ЛЕТОВ"++ context "exit code" $ do+ it "is possible to get successful exit code" $+ [sh|exit 0|] `shouldReturn` ExitSuccess++ it "is possible to get unsuccessful exit code" $+ [sh|exit 4|] `shouldReturn` ExitFailure 4++ it "is possible to get all of exitcode, stdout, and stderr" $+ [sh|echo -n hello; echo -n world >&2; exit 4|] `shouldReturn`+ (ExitFailure 4, text "hello", text "world")++ describe "embedding" $ do+ describe "variable embeddings" $ do++ it "can embed integers" $+ let foo = 7 in [sh|echo #{foo}|] `shouldReturn` "7\n"++ it "can embed doubles" $+ let foo = 7.0 in [sh|echo #{foo}|] `shouldReturn` "7.0\n"++ it "can embed characters" $+ let foo = 'z' in [sh|echo #{foo}|] `shouldReturn` "z\n"++ it "can embed strings" $+ let foo = "hello" in [sh|echo #{foo}|] `shouldReturn` "hello\n"++ describe "multi-line embeddings" $ do+ it "supports multiline commands" $+ [sh|+ echo hello+ echo world+ echo !!!+ |] `shouldReturn` "hello\nworld\n!!!\n"++ it "supports embeddings in multiline commands" $+ let foo = 4+ bar = 7+ in [sh|+ echo #{foo}+ echo #{bar}+ |] `shouldReturn` "4\n7\n"++ describe "escapings" $ do+ it "is possible to write #{} literally as a comment" $ do+ [sh|echo #\{foo}|] `shouldReturn` "\n"+ [sh|echo #\\{foo}|] `shouldReturn` "\n"++ it "is possible to write #{} literally as a string" $ do+ [sh|echo "#\{foo}"|] `shouldReturn` "#{foo}\n"+ [sh|echo "#\\{foo}"|] `shouldReturn` "#\\{foo}\n"++ describe "literals" $ do+ it "is possible to embed integer literals" $+ [sh|echo -n #{4}|] `shouldReturn` "4"++ it "is possible to embed rational literals" $+ [sh|echo -n #{4.0}|] `shouldReturn` "4.0"++ it "is possible to embed char literals" $+ [sh|echo -n #{'q'}|] `shouldReturn` "q"++ it "is possible to embed string literals" $+ [sh|echo -n #{"hello"}|] `shouldReturn` "hello"++ describe "custom data types" $+ it "is possible to embed custom data types" $+ [sh|echo -n hello#{Bang}|] `shouldReturn` "hello!"++data Bang = Bang++instance Embed Bang where embed Bang = "!"++text :: String -> Text+text = pack