packages feed

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 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+[![Hackage](https://budueba.com/hackage/command-qq)](http://hackage.haskell.org/package/command-qq)+[![Build Status](https://secure.travis-ci.org/biegunka/command-qq.png?branch=master)](http://travis-ci.org/biegunka/command-qq)+[![Build Status](https://drone.io/github.com/biegunka/command-qq/status.png)](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