packages feed

shqq (empty) → 0.1

raw patch · 6 files changed

+338/−0 lines, 6 filesdep +basedep +parsecdep +posix-escapesetup-changed

Dependencies added: base, parsec, posix-escape, process, template-haskell, unix

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) Keegan McAllister 2011++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README view
@@ -0,0 +1,12 @@+This library provides a quasiquoter for executing shell commands in Haskell,+somewhat similar to Perl's backtick operator.  Shell commands are IO actions,+and they capture the command's standard output as a String result.  You can+also interpolate Haskell variables into a command.++Documentation is hosted at http://hackage.haskell.org/package/shqq++To build the documentation yourself, run++  $ cabal configure && cabal haddock --hyperlink-source++This will produce HTML documentation under dist/doc/html/shqq
+ Setup.hs view
@@ -0,0 +1,4 @@+#! /usr/bin/runhaskell++import Distribution.Simple+main = defaultMain
+ System/ShQQ.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE+    TemplateHaskell+  , CPP #-}++-- | Embed shell commands with interpolated Haskell+-- variables, and capture output.+module System.ShQQ+    ( -- * Quasiquoters+      sh+    , shc++      -- * Helper functions+      --+      -- | These functions are used in the implementation of+      -- @'sh'@, and may be useful on their own.+    , readShell+    , readShellWithCode+    , showNonString+    ) where++import Language.Haskell.TH+import Language.Haskell.TH.Quote+import Control.Applicative+import Control.Exception ( evaluate, throwIO )+import Data.Char+import Data.Foldable ( asum )+import Data.Typeable ( Typeable, cast )+import Text.Parsec hiding ( (<|>), many )+import Text.Parsec.String+import System.IO+import System.Exit++import qualified System.Posix.Escape.Unicode as E+import qualified System.Process as P++#if defined(mingw32_HOST_OS)+#error shqq is not supported on Windows.+#endif++-- | Acts like the identity function on @'String'@, and+-- like @'show'@ on other types.+showNonString :: (Typeable a, Show a) => a -> String+showNonString x = case cast x of+    Just y  -> y+    Nothing -> show x++data Tok+    = Lit String+    | VarOne  String+    | VarMany String+    deriving (Show)++parseToks :: Parser [Tok]+parseToks = many part where+    isIdent '_' = True+    isIdent x   = isAlphaNum x+    -- NB:  '\'' excluded++    ident = some (satisfy isIdent)+    var =     VarOne  <$> ident+          <|> VarMany <$  char '+' <*> ident+    part = asum [+        char '\\' *> ( Lit "\\" <$ char '\\'+                  <|>  Lit "$"  <$ char '$' )+      , char '$' *>+            ( var <|> between (char '{') (char '}') var )+      , Lit <$> some (noneOf "$\\") ]++-- | Execute a shell command, capturing output and exit code.+--+-- Used in the implementation of @'shc'@.+readShellWithCode :: String -> IO (ExitCode, String)+readShellWithCode cmd = do+    (Nothing, Just hOut, Nothing, hProc) <- P.createProcess $+        (P.shell cmd) { P.std_out = P.CreatePipe }+    out <- hGetContents hOut+    _   <- evaluate (length out)+    hClose hOut+    ec  <- P.waitForProcess hProc+    return (ec, out)++-- | Execute a shell command, capturing output.+--+-- Used in the implementation of @'sh'@.+readShell :: String -> IO String+readShell cmd = do+    (ec, out) <- readShellWithCode cmd+    case ec of+        ExitSuccess -> return out+        _ -> throwIO ec++mkExp :: Q Exp -> [Tok] -> Q Exp+mkExp reader toks = [| $reader (concat $strs) |] where+    strs = listE (map f toks)+    var  = varE . mkName+    f (Lit     x) = [| x |]+    f (VarOne  v) = [| E.escape (showNonString $(var v)) |]+    f (VarMany v) = [| showNonString $(var v) |]++shExp :: Q Exp -> String -> Q Exp+shExp reader xs = case parse parseToks "System.ShQQ expression" xs of+    Left  e -> error ('\n' : show e)+    Right t -> mkExp reader t++baseQQ :: QuasiQuoter+baseQQ = QuasiQuoter+    { quoteExp  = error "internal error in System.ShQQ"+    , quotePat  = const (error "no pattern quote for System.ShQQ")+#if MIN_VERSION_template_haskell(2,5,0)+    , quoteType = const (error "no type quote for System.ShQQ")+    , quoteDec  = const (error "no decl quote for System.ShQQ")+#endif+    }+++{- | Execute a shell command, capturing output.++This requires the @QuasiQuotes@ extension.++The expression @[sh| ... |]@ has type @'IO' 'String'@.+Executing this IO action will invoke the quoted shell+command and produce its standard output as a @'String'@.++>>> [sh| sha1sum /proc/uptime |]+"ebe14a88cf9be69d2192dcd7bec395e3f00ca7a4  /proc/uptime\n"++You can interpolate Haskell @'String'@ variables using the+syntax @$x@.  Special characters are escaped, so that the+program invoked by the shell will see each interpolated+variable as a single argument.++>>> let x = "foo bar" in [sh| cat $x |]+cat: foo bar: No such file or directory+*** Exception: ExitFailure 1++You can also write @${x}@ to separate the variable name from+adjacent characters.++>>> let x = "b" in [sh| echo a${x}c |]+"abc\n"++Be careful: the automatic escaping means that @[sh| cat '$x'+|]@ is /less safe/ than @[sh| cat $x |]@, though it will+work \"by accident\" in common cases.++To interpolate /without/ escaping special characters, use+the syntax @$+x@ .++>>> let x = "foo bar" in [sh| cat $+x |]+cat: foo: No such file or directory+cat: bar: No such file or directory+*** Exception: ExitFailure 1++You can pass a literal @$@ to the shell as @\\$@, or a+literal @\\@ as @\\\\@.++As demonstrated above, a non-zero exit code from the+subprocess will raise an exception in your Haskell program.++Variables of type other than @'String'@ are interpolated via+@'show'@.++>>> let x = Just (2 + 2) in [sh| touch $x; ls -l J* |]+"-rw-r--r-- 1 keegan keegan 0 Oct  7 23:28 Just 4\n"++The interpolated variable's type must be an instance of+@'Show'@ and of @'Typeable'@.++-}++sh :: QuasiQuoter+sh = baseQQ { quoteExp = shExp [| readShell |] }+++{- | Execute a shell command, capturing output and exit code.++The expression @[shc| ... |]@ has type @'IO' ('ExitCode',+'String')@.  A non-zero exit code does not raise an+exception your the Haskell program.++Otherwise, @'shc'@ acts like @'sh'@.++-}++shc :: QuasiQuoter+shc = baseQQ { quoteExp = shExp [| readShellWithCode |] }
+ examples/dupe.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE QuasiQuotes, TupleSections #-}++{- dupe.hs: find duplicate files++This program finds duplicate files in the directory tree rooted at the+current working directory.++It demonstrates the goal of the shqq library: to combine the easy+command execution of traditional shell scripting with the strengths of+Haskell, such as concurrency and non-trivial data structures.++Depends on: shqq-0.1, spawn-0.3, split-0.1++Run it with:+  $ ghc -O -threaded -rtsopts dupe.hs+  $ ./dupe +RTS -N++-}++import Control.Concurrent.Spawn+import Data.List.Split+import System.Posix.Files+import System.ShQQ++import qualified Data.Map as M++-- Find potentially duplicate files based on some key, either size+-- or a checksum.+dupes :: (Ord k) => [(FilePath,k)] -> [[FilePath]]+dupes = filter (not . null . drop 1) . M.elems+      . foldr (\(v,k) -> M.insertWith (++) k [v]) M.empty++-- We want to collect data in parallel, but the OS will complain if we+-- have too many open files.  We limit each pass to have at most 256+-- tests in progress at once.+inParallel :: (a -> IO b) -> [a] -> IO [b]+inParallel f xs = do p <- pool 256; parMapIO (p . f) xs++main :: IO ()+main = do+    files <- endBy "\0" `fmap` [sh| find -type f -print0 |]++    -- Get the size of every file, as a first pass.+    let getSize f = ((f,) . fileSize) `fmap` getFileStatus f+    sizeDupes <- dupes `fmap` inParallel getSize files++    -- Checksum the files which have duplicated sizes.+    let getSha f = ((f,) . head . words) `fmap` [sh| sha1sum $f |]+    shaDupes  <- dupes `fmap` inParallel getSha (concat sizeDupes)++    -- Print duplicated file names, one per line, with a blank line+    -- after each group of duplicates.+    mapM_ (mapM_ putStrLn . (++[""])) shaDupes
+ shqq.cabal view
@@ -0,0 +1,57 @@+name:                shqq+version:             0.1+license:             BSD3+license-file:        LICENSE+synopsis:            Embed shell commands with interpolated Haskell variables, and capture output+category:            System+author:              Keegan McAllister <mcallister.keegan@gmail.com>+maintainer:          Keegan McAllister <mcallister.keegan@gmail.com>+build-type:          Simple+cabal-version:       >=1.6+description:+    This library provides a quasiquoter for executing shell commands, somewhat+    similar to Perl's backtick operator.  Shell commands are IO actions, and+    they capture the command's standard output as a @String@ result.+    .+    You can use Haskell variables in a shell command.  A string representation+    of the contents will be interpolated.  The shell will see each interpolated+    variable as a single token without interpreting special characters, unless+    you choose otherwise.+    .+    Note: The shell escaping is not correct for the Windows shell.  This library+    should fail to build on Windows, as well.+    .+    Examples of using this library are included in @examples/@.++extra-source-files:+    README+  , examples/dupe.hs++-- Our shell escaping is not correct on Windows.  We make the project Unix-only+-- in two ways: the 'unix' dependency, and a CPP macro check in ShQQ.hs.++library+  exposed-modules:+      System.ShQQ+  ghc-options:      -Wall+  build-depends:+      base             >= 3 && < 5+    , template-haskell >= 2.3+    , posix-escape     >= 0.1+    , parsec           >= 3.1+    , unix++    -- NB: process-1.0 has a Unicode handling bug which makes shell escaping+    -- ineffective, compromising security.  See:+    --+    -- http://hackage.haskell.org/trac/ghc/ticket/4006+    -- http://hackage.haskell.org/trac/ghc/ticket/1414+    , process          >= 1.1++  other-extensions:+      TemplateHaskell+    , CPP++source-repository head+    type:     git+    location: git://github.com/kmcallister/shqq