packages feed

turtle (empty) → 1.0.0

raw patch · 10 files changed

+3270/−0 lines, 10 filesdep +Win32dep +asyncdep +basesetup-changed

Dependencies added: Win32, async, base, clock, directory, doctest, foldl, managed, process, system-fileio, system-filepath, temporary, text, time, transformers, unix

Files

+ LICENSE view
@@ -0,0 +1,24 @@+Copyright (c) 2015 Gabriel Gonzalez+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:+    * Redistributions of source code must retain the above copyright notice,+      this list of conditions and the following disclaimer.+    * 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.+    * Neither the name of Gabriel Gonzalez nor the names of other 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 COPYRIGHT OWNER 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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Turtle.hs view
@@ -0,0 +1,143 @@+-- | See "Turtle.Tutorial" to learn how to use this library or "Turtle.Prelude"+--  for a quick-start guide.+--+--  Here is the recommended way to import this library:+--+--  > {-# LANGUAGE OverloadedStrings #-}+--  >+--  > import Turtle+--  > import Prelude hiding (FilePath)+--+--  This module re-exports the rest of the library and also re-exports useful+--  modules from @base@:+--+--  "Turtle.Format" provides type-safe string formatting+--+--  "Turtle.Pattern" provides `Pattern`s, which are like more powerful regular+--  expressions+--+--  "Turtle.Shell" provides a `Shell` abstraction for building streaming,+--  exception-safe pipelines+--+--  "Turtle.Prelude" provides a library of Unix-like utilities to get you+--  started with basic shell-like programming within Haskell+--+--  "Control.Applicative" provides two classes:+--+--  * `Applicative`, which works with `Fold`, `Pattern`, `Managed`, and `Shell`+--+--  * `Alternative`, which works with `Pattern` and `Shell`+--+--  "Control.Monad" provides two classes:+--+--  * `Monad`, which works with `Pattern`, `Managed` and `Shell`+--+--  * `MonadPlus`, which works with `Pattern` and `Shell`+--+--  "Control.Monad.IO.Class" provides one class:+--+--  * `MonadIO`, which works with `Managed` and `Shell`+--+--  "Data.Monoid" provides one class:+--+--  * `Monoid`, which works with `Fold`, `Pattern`, `Managed`, and `Shell`+--+--  "Control.Monad.Managed.Safe" provides `Managed` resources+--+--  "Filesystem.Path.CurrentOS" provides `FilePath`-manipulation utilities+--+--  Additionally, you might also want to import the following modules qualified:+--+--  * "Options.Applicative" from @optparse-applicative@ for command-line option+--     parsing+--+--  * "Control.Foldl" (for predefined folds)+--+--  * "Control.Foldl.Text" (for `Text`-specific folds)+--+--  * "Data.Text" (for `Text`-manipulation utilities)+--+--  * "Data.Text.IO" (for reading and writing `Text`)+--+--  * "Filesystem.Path.CurrentOS" (for the remaining `FilePath` utilities)++module Turtle (+    -- * Modules+      module Turtle.Format+    , module Turtle.Pattern+    , module Turtle.Shell+    , module Turtle.Prelude+    , module Control.Applicative+    , module Control.Monad+    , module Control.Monad.IO.Class+    , module Data.Monoid+    , module Control.Monad.Managed.Safe+    , module Filesystem.Path.CurrentOS+    , Fold(..)+    , FoldM(..)+    , Text+    , UTCTime+    , NominalDiffTime+    , Handle+    , ExitCode(..)+    , IsString(..)+    ) where++import Turtle.Format+import Turtle.Pattern+import Turtle.Shell+import Turtle.Prelude+import Control.Applicative+    ( Applicative(..)+    , Alternative(..)+    , (<$>)+    , liftA2+    , optional+    )+import Control.Monad+    ( MonadPlus(..)+    , forever+    , void+    , (>=>)+    , (<=<)+    , join+    , msum+    , mfilter+    , replicateM_+    , guard+    , when+    , unless+    )+import Control.Monad.IO.Class (MonadIO(..))+import Data.Monoid (Monoid(..), (<>))+import Data.String (IsString(..))+import Filesystem.Path.CurrentOS+    ( FilePath+    , root+    , directory+    , parent+    , filename+    , dirname+    , basename+    , absolute+    , relative+    , (</>)+    , commonPrefix+    , stripPrefix+    , collapse+    , splitDirectories+    , extension+    , hasExtension+    , (<.>)+    , dropExtension+    , splitExtension+    , toText+    , fromText+    )+import Control.Monad.Managed.Safe (Managed, managed, runManaged)+import Control.Foldl (Fold(..), FoldM(..))+import Data.Text (Text)+import Data.Time (NominalDiffTime, UTCTime)+import System.IO (Handle)+import System.Exit (ExitCode(..))+import Prelude hiding (FilePath)
+ src/Turtle/Format.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE OverloadedStrings #-}++{-| Minimalist implementation of type-safe formatted strings, borrowing heavily+    from the implementation of the @formatting@ package.++    Example use of this module:++>>> :set -XOverloadedStrings+>>> import Turtle.Format+>>> format ("This is a "%s%" string that takes "%d%" arguments") "format" 2+"This is a format string that takes 2 arguments"++    A `Format` string that takes no arguments has this type:++> "I take 0 arguments" :: Format r r+>+> format "I take 0 arguments" :: Text++>>> format "I take 0 arguments"+"I take 0 arguments"++    A `Format` string that takes one argument has this type:++> "I take "%d%" arguments" :: Format r (Int -> r)+>+> format ("I take "%d%" argument") :: Int -> Text++>>> format ("I take "%d%" argument") 1+"I take 1 argument"++    A `Format` string that takes two arguments has this type:++> "I "%s%" "%d%" arguments" :: Format r (Text -> Int -> r)+>+> format ("I "%s%" "%d%" arguments") :: Text -> Int -> Text++>>> format ("I "%s%" "%d%" arguments") "take" 2+"I take 2 arguments"+-}++{-# LANGUAGE TypeFamilies #-}++module Turtle.Format (+    -- * Format+      Format+    , (%)+    , format+    , makeFormat++    -- * Parameters+    , w+    , d+    , u+    , o+    , x+    , f+    , e+    , g+    , s++    -- * Utilities+    , repr+    ) where++import Control.Category (Category(..))+import Data.Monoid ((<>))+import Data.String (IsString(..))+import Data.Text (Text, pack)+import Data.Word (Word)+import Numeric (showEFloat, showFFloat, showGFloat, showHex, showOct)+import Prelude hiding ((.), id, FilePath)++-- | A `Format` string+newtype Format a b = Format { (>>-) :: (Text -> a) -> b }++instance Category Format where+    id = Format (\return_ -> return_ "")++    fmt1 . fmt2 = Format (\return_ ->+        fmt1 >>- \str1 ->+        fmt2 >>- \str2 ->+        return_ (str1 <> str2) )++-- | Concatenate two `Format` strings+(%) :: Format b c -> Format a b -> Format a c+(%) = (.)++instance (a ~ b) => IsString (Format a b) where+    fromString str = Format (\return_ -> return_ (pack str))++{-| Convert a `Format` string to a print function that takes zero or more typed+    arguments and returns a `Text` string+-}+format :: Format Text r -> r+format fmt = fmt >>- id++-- | Create your own format specifier+makeFormat :: (a -> Text) -> Format r (a -> r)+makeFormat k = Format (\return_ -> \a -> return_ (k a))++{-| `Format` any `Show`able value++>>> format w True+"True"+-}+w :: Show a => Format r (a -> r)+w = makeFormat (pack . show)++{-| `Format` an `Int` value as a signed decimal++>>> format d 25+"25"+>>> format d (-25)+"-25"+-}+d :: Format r (Int -> r)+d = w++{-| `Format` a `Word` value as an unsigned decimal++>>> format u 25+"25"+-}+u :: Format r (Word -> r)+u = w++{-| `Format` a `Word` value as an unsigned octal number++>>> format o 25+"31"+-}+o :: Format r (Word -> r)+o = makeFormat (\n -> pack (showOct n ""))++{-| `Format` a `Word` value as an unsigned hexadecimal number (without a+    leading \"0x\")++>>> format x 25+"19"+-}+x :: Format r (Word -> r)+x = makeFormat (\n -> pack (showHex n ""))++{-| `Format` a `Double` using decimal notation with 6 digits of precision++>>> format f 25.1+"25.100000"+-}+f :: Format r (Double -> r)+f = makeFormat (\n -> pack (showFFloat (Just 6) n ""))++{-| `Format` a `Double` using scientific notation with 6 digits of precision++>>> format e 25.1+"2.510000e1"+-}+e :: Format r (Double -> r)+e = makeFormat (\n -> pack (showEFloat (Just 6) n ""))++{-| `Format` a `Double` using decimal notation for small exponents and+    scientific notation for large exponents++>>> format g 25.1+"25.100000"+>>> format g 123456789+"1.234568e8"+>>> format g 0.00000000001+"1.000000e-11"+-}+g :: Format r (Double -> r)+g = makeFormat (\n -> pack (showGFloat (Just 6) n ""))++{-| `Format` that inserts `Text`++>>> format s "ABC"+"ABC"+-}+s :: Format r (Text -> r)+s = makeFormat id++{-| Convert a `Show`able value to `Text`++    Short-hand for @(format w)@++>>> repr (1,2)+"(1,2)"+-}+repr :: Show a => a -> Text+repr = format w
+ src/Turtle/Pattern.hs view
@@ -0,0 +1,614 @@+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies               #-}++{-| Use this module to either:++    * match `Text` with light-weight backtracking patterns, or:++    * parse structured values from `Text`.++    Example usage:++>>> :set -XOverloadedStrings+>>> match ("can" <|> "cat") "cat"+["cat"]+>>> match ("can" <|> "cat") "dog"+[]+>>> match (decimal `sepBy` ",") "1,2,3"+[[1,2,3]]++    This pattern has unlimited backtracking, and will return as many solutions+    as possible:++>>> match (prefix (star anyChar)) "123"+["123","12","1",""]++    Use @do@ notation to structure more complex patterns:++>>> :{+let bit = ("0" *> pure False) <|> ("1" *> pure True);+    portableBitMap = do+        { "P1"+        ; width  <- spaces1 *> decimal+        ; height <- spaces1 *> decimal+        ; count width (count height (spaces1 *> bit))+        };+in  match (prefix portableBitMap) "P1\n2 2\n0 0\n1 0\n"+:}+[[[False,False],[True,False]]]++-}++module Turtle.Pattern (+    -- * Pattern+      Pattern+    , match++    -- * Primitive patterns+    , anyChar+    , eof++    -- * Character patterns+    , dot+    , satisfy+    , char+    , notChar+    , text+    , oneOf+    , noneOf+    , space+    , spaces+    , spaces1+    , tab+    , newline+    , crlf+    , upper+    , lower+    , alphaNum+    , letter+    , digit+    , hexDigit+    , octDigit++    -- * Numbers+    , decimal+    , signed++    -- * Combinators+    , prefix+    , suffix+    , has+    , once+    , star+    , plus+    , selfless+    , choice+    , count+    , option+    , between+    , skip+    , within+    , fixed+    , sepBy+    , sepBy1+    ) where++import Control.Applicative+import Control.Monad+import Control.Monad.Trans.State+import Data.Char+import Data.List (foldl')+import Data.Monoid (Monoid(..), (<>))+import Data.String (IsString(..))+import Data.Text (Text)+import qualified Data.Text as Text++-- | A fully backtracking pattern that parses an @\'a\'@ from some `Text`+newtype Pattern a = Pattern { runPattern :: StateT Text [] a }+    deriving (Functor, Applicative, Monad, Alternative, MonadPlus)++instance Monoid a => Monoid (Pattern a) where+    mempty  = pure mempty+    mappend = liftA2 mappend++instance Num a => Num (Pattern a) where+    fromInteger n = pure (fromInteger n)++    (+) = liftA2 (+)+    (*) = liftA2 (*)+    (-) = liftA2 (-)++    abs    = fmap abs+    signum = fmap signum+    negate = fmap negate++instance Fractional a => Fractional (Pattern a) where+    fromRational n = pure (fromRational n)++    recip = fmap recip++    (/) = liftA2 (/)++instance Floating a => Floating (Pattern a) where+    pi = pure pi++    exp   = fmap exp+    sqrt  = fmap sqrt+    log   = fmap log+    sin   = fmap sin+    tan   = fmap tan+    cos   = fmap cos+    asin  = fmap sin+    atan  = fmap atan+    acos  = fmap acos+    sinh  = fmap sinh+    tanh  = fmap tanh+    cosh  = fmap cosh+    asinh = fmap asinh+    atanh = fmap atanh+    acosh = fmap acosh++    (**)    = liftA2 (**)+    logBase = liftA2 logBase++instance (a ~ Text) => IsString (Pattern a) where+    fromString str = text (Text.pack str)++{-| Match a `Pattern` against a `Text` input, returning all possible solutions++    The `Pattern` must match the entire `Text`+-}+match :: Pattern a -> Text -> [a]+match p = evalStateT (runPattern (p <* eof))++{-| Match any character++>>> match anyChar "1"+"1"+>>> match anyChar ""+""+-}+anyChar :: Pattern Char+anyChar = Pattern (do+    Just (c, cs) <- fmap Text.uncons get+    put cs+    return c )++{-| Matches the end of input++>>> match eof "1"+[]+>>> match eof ""+[()]+-}+eof :: Pattern ()+eof = Pattern (do+    True <- fmap Text.null get+    return () )++-- | Synonym for `anyChar`+dot :: Pattern Char+dot = anyChar++{-| Match any character that satisfies the given predicate++>>> match (satisfy (== '1')) "1"+"1"+>>> match (satisfy (== '2')) "1"+""+-}+satisfy :: (Char -> Bool) -> Pattern Char+satisfy predicate = do+    c <- anyChar+    guard (predicate c)+    return c++{-| Match a specific character++>>> match (char '1') "1"+"1"+>>> match (char '2') "1"+""+-}+char :: Char -> Pattern Char+char c = satisfy (== c)++{-| Match any character except the given one++>>> match (notChar '2') "1"+"1"+>>> match (notChar '1') "1"+""+-}+notChar :: Char -> Pattern Char+notChar c = satisfy (/= c)++{-| Match a specific string++>>> match (text "123") "123"+["123"]++    You can also omit the `text` function if you enable the @OverloadedStrings@+    extension:++>>> match "123" "123"+["123"]+-}+text :: Text -> Pattern Text+text before' = Pattern (do+    txt <- get+    let (before, after) = Text.splitAt (Text.length before') txt+    guard (before == before')+    put after+    return before)++{-| Match any one of the given characters++>>> match (oneOf "1a") "1"+"1"+>>> match (oneOf "2a") "1"+""+-}+oneOf :: [Char] -> Pattern Char+oneOf cs = satisfy (`elem` cs)++{-| Match anything other than the given characters++>>> match (noneOf "2a") "1"+"1"+>>> match (noneOf "1a") "1"+""+-}+noneOf :: [Char] -> Pattern Char+noneOf cs = satisfy (`notElem` cs)++{-| Match a whitespace character++>>> match space " "+" "+>>> match space "1"+""+-}+space :: Pattern Char+space = satisfy isSpace++{-| Match zero or more whitespace characters++>>> match spaces "  "+["  "]+>>> match spaces ""+[""]+-}+spaces :: Pattern Text+spaces = star space++{-| Match one or more whitespace characters++>>> match spaces1 "  "+["  "]+>>> match spaces1 ""+[]+-}+spaces1 :: Pattern Text+spaces1 = plus space++{-| Match the tab character (@\'\t\'@)++>>> match tab "\t"+"\t"+>>> match tab " "+""+-}+tab :: Pattern Char+tab = char '\t'++{-| Match the newline character (@\'\n\'@)++>>> match newline "\n"+"\n"+>>> match newline " "+""+-}+newline :: Pattern Char+newline = char '\n'++{-| Matches a carriage return (@\'\r\'@) followed by a newline (@\'\n\'@)++>>> match crlf "\r\n"+["\r\n"]+>>> match crlf "\n\r"+[]+-}+crlf :: Pattern Text+crlf = text "\r\n"++{-| Match an uppercase letter++>>> match upper "A"+"A"+>>> match upper "a"+""+-}+upper :: Pattern Char+upper = satisfy isUpper++{-| Match a lowercase letter++>>> match lower "a"+"a"+>>> match lower "A"+""+-}+lower :: Pattern Char+lower = satisfy isLower++{-| Match a letter or digit++>>> match alphaNum "1"+"1"+>>> match alphaNum "a"+"a"+>>> match alphaNum "A"+"A"+>>> match alphaNum "."+""+-}+alphaNum :: Pattern Char+alphaNum = satisfy isAlphaNum++{-| Match a letter++>>> match letter "A"+"A"+>>> match letter "a"+"a"+>>> match letter "1"+""+-}+letter :: Pattern Char+letter = satisfy isLetter++{-| Match a digit++>>> match digit "1"+"1"+>>> match digit "a"+""+-}+digit :: Pattern Char+digit = satisfy isDigit++{-| Match a hexadecimal digit++>>> match hexDigit "1"+"1"+>>> match hexDigit "A"+"A"+>>> match hexDigit "a"+"a"+>>> match hexDigit "g"+""+-}+hexDigit :: Pattern Char+hexDigit = satisfy isHexDigit++{-| Match an octal digit++>>> match octDigit "1"+"1"+>>> match octDigit "9"+""+-}+octDigit :: Pattern Char+octDigit = satisfy isOctDigit++{-| Match an unsigned decimal number++>>> match decimal  "123"+[123]+>>> match decimal "-123"+[]+-}+decimal :: Num n => Pattern n+decimal = do+    ds <- some digit+    return (foldl' step 0 ds)+  where+    step n d = n * 10 + fromIntegral (ord d - ord '0')++{-| Transform a numeric parser to accept an optional leading @\'+\'@ or @\'-\'@+    sign++>>> match (signed decimal) "+123"+[123]+>>> match (signed decimal) "-123"+[-123]+>>> match (signed decimal)  "123"+[123]+-}+signed :: Num a => Pattern a -> Pattern a+signed p = do+    sign <- (char '+' *> pure id) <|> (char '-' *> pure negate) <|> (pure id)+    fmap sign p++{-| Match a `Char`, but return `Text`++>>> match (once (char '1')) "1"+["1"]+>>> match (once (char '1')) ""+[]+-}+once :: Pattern Char -> Pattern Text+once p = fmap Text.singleton p++{-| Use this to match the prefix of a string++>>> match         "A"  "ABC"+[]+>>> match (prefix "A") "ABC"+["A"]+-}+prefix :: Pattern a -> Pattern a+prefix p = p <* star anyChar++{-| Use this to match the suffix of a string++>>> match         "C"  "ABC"+[]+>>> match (suffix "C") "ABC"+["C"]+-}+suffix :: Pattern a -> Pattern a+suffix p = star anyChar *> p++{-| Use this to match the interior of a string++>>> match      "B"  "ABC"+[]+>>> match (has "B") "ABC"+["B"]+-}+has :: Pattern a -> Pattern a+has p = star anyChar *> p <* star anyChar++{-| Parse 0 or more occurrences of the given character++>>> match (star anyChar) "123"+["123"]+>>> match (star anyChar) ""+[""]+-}+star :: Pattern Char -> Pattern Text+star p = fmap Text.pack (many p)++{-| Parse 1 or more occurrences of the given character++>>> match (plus anyChar) "123"+["123"]+>>> match (plus anyChar) ""+[]+-}+plus :: Pattern Char -> Pattern Text+plus p = fmap Text.pack (some p)++{-| Patterns that match multiple times are greedy by default, meaning that they+    try to match as many times as possible.  The `selfless` combinator makes a+    pattern match as few times as possible++    This only changes the order in which solutions are returned, by prioritizing+    less greedy solutions++>>> match (prefix (selfless (some anyChar))) "123"+["1","12","123"]+>>> match (prefix           (some anyChar) ) "123"+["123","12","1"]+-}+selfless :: Pattern a -> Pattern a+selfless p = Pattern (StateT (\s -> reverse (runStateT (runPattern p) s)))++{-| Apply the patterns in the list in order, until one of them succeeds++>>> match (choice ["cat", "dog", "egg"]) "egg"+["egg"]+>>> match (choice ["cat", "dog", "egg"]) "cat"+["cat"]+>>> match (choice ["cat", "dog", "egg"]) "fan"+[]+-}+choice :: [Pattern a] -> Pattern a+choice = msum++{-| Apply the given pattern a fixed number of times, collecting the results++>>> match (count 3 anyChar) "123"+["123"]+>>> match (count 4 anyChar) "123"+[]+-}+count :: Int -> Pattern a -> Pattern [a]+count = replicateM++{-| Transform a parser to a succeed with an empty value instead of failing++    See also: `optional`++>>> match (option "1" <> "2") "12"+["12"]+>>> match (option "1" <> "2") "2"+["2"]+-}+option :: Monoid a => Pattern a -> Pattern a+option p = p <|> mempty++{-| @(between open close p)@ matches @\'p\'@ in between @\'open\'@ and+    @\'close\'@++>>> match (between (char '(') (char ')') (star anyChar)) "(123)"+["123"]+>>> match (between (char '(') (char ')') (star anyChar)) "(123"+[]+-}+between :: Pattern a -> Pattern b -> Pattern c -> Pattern c+between open close p = open *> p <* close++{-| Discard the pattern's result++>>> match (skip anyChar) "1"+[()]+>>> match (skip anyChar) ""+[]+-}+skip :: Pattern a -> Pattern ()+skip = void++{-| Restrict the pattern to consume no more than the given number of characters++>>> match (within 2 decimal) "12"+[12]+>>> match (within 2 decimal) "1"+[1]+>>> match (within 2 decimal) "123"+[]+-}+within :: Int -> Pattern a -> Pattern a+within n p = Pattern (do+    txt <- get+    let (before, after) = Text.splitAt n txt+    put before+    a <- runPattern p+    modify (<> after)+    return a )++{-| Require the pattern to consume exactly the given number of characters++>>> match (fixed 2 decimal) "12"+[12]+>>> match (fixed 2 decimal) "1"+[]+-}+fixed :: Int -> Pattern a -> Pattern a+fixed n p = do+    txt <- Pattern get+    guard (Text.length txt >= n)+    within n (p <* eof)++{-| @p `sepBy` sep@ matches zero or more occurrences of @p@ separated by @sep@++>>> match (decimal `sepBy` char ',') "1,2,3"+[[1,2,3]]+>>> match (decimal `sepBy` char ',') ""+[[]]+-}+sepBy :: Pattern a -> Pattern b -> Pattern [a]+p `sepBy` sep = (p `sepBy1` sep) <|> pure []++{-| @p `sepBy1` sep@ matches one or more occurrences of @p@ separated by @sep@++>>> match (decimal `sepBy1` ",") "1,2,3"+[[1,2,3]]+>>> match (decimal `sepBy1` ",") ""+[]+-}+sepBy1 :: Pattern a -> Pattern b -> Pattern [a]+p `sepBy1` sep = (:) <$> p <*> many (sep *> p) 
+ src/Turtle/Prelude.hs view
@@ -0,0 +1,705 @@+{-# LANGUAGE CPP               #-}+{-# LANGUAGE OverloadedStrings #-}++-- | This module provides a large suite of utilities that resemble Unix+--  utilities.+--+--  Many of these commands are just existing Haskell commands renamed to match+--  their Unix counterparts:+--+-- >>> :set -XOverloadedStrings+-- >>> cd "/tmp"+-- >>> pwd+-- FilePath "/tmp"+--+-- Some commands are `Shell`s that emit streams of values.  `view` prints all+-- values in a `Shell` stream:+--+-- >>> view (ls "/usr")+-- FilePath "/usr/lib"+-- FilePath "/usr/src"+-- FilePath "/usr/sbin"+-- FilePath "/usr/include"+-- FilePath "/usr/share"+-- FilePath "/usr/games"+-- FilePath "/usr/local"+-- FilePath "/usr/bin"+-- >>> view (find "Browser.py" "/usr/lib")+-- FilePath "lib/python3.2/idlelib/ObjectBrowser.py"+-- FilePath "lib/python3.2/idlelib/PathBrowser.py"+-- FilePath "lib/python3.2/idlelib/RemoteObjectBrowser.py"+-- FilePath "lib/python3.2/idlelib/ClassBrowser.py"+--+-- Use `fold` to reduce the output of a `Shell` stream:+--+-- >>> import qualified Control.Foldl as Fold+-- >>> fold (ls "/usr") Fold.length+-- 8+-- >>> fold (find "Browser.py" "/usr/lib") Fold.head+-- Just (FilePath "/usr/lib/python3.2/idlelib/ObjectBrowser.py")+--+-- Create files using `output`:+--+-- >>> output "foo.txt" ("123" <|> "456" <|> "ABC")+-- >>> realpath "foo.txt"+-- FilePath "/tmp/foo.txt"+--+-- Read in files using `input`:+--+-- >>> stdout (input "foo.txt")+-- 123+-- 456+-- ABC+--+-- Commands like `grep`, `sed` and `find` accept arbitrary `Pattern`s+--+-- >>> stdout (grep ("123" <|> "ABC") (input "foo.txt"))+-- 123+-- ABC+-- >>> let exclaim = fmap (<> "!") (plus digit)+-- >>> stdout (sed exclaim (input "foo.txt"))+-- 123!+-- 456!+-- ABC+--+-- Note that `grep` and `find` differ from their Unix counterparts by requiring+-- that the `Pattern` matches the entire line or file name by default.  However,+-- you can optionally match the prefix, suffix, or interior of a line:+--+-- >>> stdout (grep (has    "2") (input "foo.txt"))+-- 123+-- >>> stdout (grep (prefix "1") (input "foo.txt"))+-- 123+-- >>> stdout (grep (suffix "3") (input "foo.txt"))+-- 123+--+--  You can also build up more sophisticated `Shell` programs using `sh` in+--  conjunction with @do@ notation:+--+-- >{-# LANGUAGE OverloadedStrings #-}+-- >+-- >import Turtle+-- >+-- >main = sh example+-- >+-- >example = do+-- >    -- Read in file names from "files1.txt" and "files2.txt"+-- >    file <- fmap fromText (input "files1.txt" <|> input "files2.txt")+-- >+-- >    -- Stream each file to standard output only if the file exists+-- >    True <- liftIO (testfile file)+-- >    line <- input file+-- >    liftIO (echo line)+--+-- See "Turtle.Tutorial" for an extended tutorial explaining how to use this+-- library in greater detail.++module Turtle.Prelude (+    -- * IO+      proc+    , shell+    , echo+    , err+    , readline+#if MIN_VERSION_base(4,7,0)+    , export+    , unset+#endif+#if MIN_VERSION_base(4,6,0)+    , need+#endif+    , env+    , cd+    , pwd+    , home+    , realpath+    , mv+    , mkdir+    , mktree+    , cp+    , rm+    , rmdir+    , rmtree+    , du+    , testfile+    , testdir+    , date+    , datefile+    , touch+    , time+    , sleep+    , exit+    , die++    -- * Managed+    , readonly+    , writeonly+    , appendonly+    , mktemp+    , mktempdir+    , fork+    , wait++    -- * Shell+    , inproc+    , inshell+    , stdin+    , input+    , inhandle+    , stdout+    , stderr+    , output+    , append+    , ls+    , lstree+    , cat+    , grep+    , sed+    , find+    , yes+    , limit+    , limitWhile+    ) where++import Control.Applicative (Alternative(..))+import Control.Concurrent.Async (Async, withAsync, wait)+import Control.Concurrent (threadDelay)+import Control.Exception (bracket, throwIO)+import Control.Foldl (FoldM(..))+import Control.Monad (msum)+import Control.Monad.Managed (Managed, managed)+#ifdef mingw32_HOST_OS+import Data.Bits ((.&.))+#endif+import Data.IORef (newIORef, readIORef, writeIORef)+import Data.Text (Text, pack, unpack)+import Data.Time (NominalDiffTime, UTCTime, getCurrentTime)+import qualified Data.Text    as Text+import qualified Data.Text.IO as Text+import qualified Filesystem+import Filesystem.Path.CurrentOS (FilePath, (</>))+import qualified Filesystem.Path.CurrentOS as Filesystem+import System.Clock (Clock(..), TimeSpec(..), getTime)+import System.Environment (+#if MIN_VERSION_base(4,7,0)+    setEnv,+    unsetEnv,+#endif+#if MIN_VERSION_base(4,6,0)+    lookupEnv,+#endif+    getEnvironment )+import System.Directory (getPermissions, readable)+import System.Exit (ExitCode(..), exitWith)+import System.IO (Handle)+import qualified System.IO as IO+import System.IO.Temp (withTempDirectory, withTempFile)+import qualified System.Process as Process+#ifdef mingw32_HOST_OS+import qualified System.Win32 as Win32+#else+import System.Posix (openDirStream, readDirStream, closeDirStream, touchFile)+#endif+import Prelude hiding (FilePath)++import Turtle.Pattern (Pattern, anyChar, match)+import Turtle.Shell++{-| Run a command using @execvp@, retrieving the exit code++    The command inherits @stdout@ and @stderr@ for the current process+-}+proc+    :: Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell Text+    -- ^ Lines of standard input+    -> IO ExitCode+    -- ^ Exit code+proc cmd args = system (Process.proc (unpack cmd) (map unpack args))++{-| Run a command line using the shell, retrieving the exit code++    This command is more powerful than `proc`, but highly vulnerable to code+    injection if you template the command line with untrusted input++    The command inherits @stdout@ and @stderr@ for the current process+-}+shell+    :: Text+    -- ^ Command line+    -> Shell Text+    -- ^ Lines of standard input+    -> IO ExitCode+    -- ^ Exit code+shell cmdLine = system (Process.shell (unpack cmdLine))++system+    :: Process.CreateProcess+    -- ^ Command+    -> Shell Text+    -- ^ Lines of standard input+    -> IO ExitCode+    -- ^ Exit code+system p s = do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.Inherit+            , Process.std_err = Process.Inherit+            }+    (Just hIn, Nothing, Nothing, ph) <- liftIO (Process.createProcess p')+    let feedIn = sh (do+            txt <- s+            liftIO (Text.hPutStrLn hIn txt) )+    withAsync feedIn (\_ -> liftIO (Process.waitForProcess ph) )++{-| Run a command using @execvp@, streaming @stdout@ as lines of `Text`++    The command inherits @stderr@ for the current process+-}+inproc+    :: Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell Text+    -- ^ Lines of standard input+    -> Shell Text+    -- ^ Lines of standard output+inproc cmd args = stream (Process.proc (unpack cmd) (map unpack args))++{-| Run a command line using the shell, streaming @stdout@ as lines of `Text`++    This command is more powerful than `inproc`, but highly vulnerable to code+    injection if you template the command line with untrusted input++    The command inherits @stderr@ for the current process+-}+inshell+    :: Text+    -- ^ Command line+    -> Shell Text+    -- ^ Lines of standard input+    -> Shell Text+    -- ^ Lines of standard output+inshell cmd = stream (Process.shell (unpack cmd))++stream+    :: Process.CreateProcess+    -- ^ Command+    -> Shell Text+    -- ^ Lines of standard input+    -> Shell Text+    -- ^ Lines of standard output+stream p s = do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.Inherit+            }+    (Just hIn, Just hOut, Nothing, _) <- liftIO (Process.createProcess p')+    let feedIn = sh (do+            txt <- s+            liftIO (Text.hPutStrLn hIn txt) )+    _ <- using (fork feedIn)+    inhandle hOut++-- | Print to @stdout@+echo :: Text -> IO ()+echo = Text.putStrLn++-- | Print to @stderr@+err :: Text -> IO ()+err = Text.hPutStrLn IO.stderr++{-| Read in a line from @stdin@++    Returns `Nothing` if at end of input+-}+readline :: IO (Maybe Text)+readline = do+    eof <- IO.isEOF+    if eof+        then return Nothing+        else fmap (Just . pack) getLine++#if MIN_VERSION_base(4,7,0)+-- | Set or modify an environment variable+export :: Text -> Text -> IO ()+export key val = setEnv (unpack key) (unpack val)++-- | Delete an environment variable+unset :: Text -> IO ()+unset key = unsetEnv (unpack key)+#endif++#if MIN_VERSION_base(4,6,0)+-- | Look up an environment variable+need :: Text -> IO (Maybe Text)+need key = fmap (fmap pack) (lookupEnv (unpack key))+#endif++-- | Retrieve all environment variables+env :: IO [(Text, Text)]+env = fmap (fmap toTexts) getEnvironment+  where+    toTexts (key, val) = (pack key, pack val)++-- | Change the current directory+cd :: FilePath -> IO ()+cd = Filesystem.setWorkingDirectory++-- | Get the current directory+pwd :: IO FilePath+pwd = Filesystem.getWorkingDirectory++-- | Get the home directory+home :: IO FilePath+home = Filesystem.getHomeDirectory++-- | Canonicalize a path+realpath :: FilePath -> IO FilePath+realpath = Filesystem.canonicalizePath++#ifdef mingw32_HOST_OS+fILE_ATTRIBUTE_REPARSE_POINT :: Win32.FileAttributeOrFlag+fILE_ATTRIBUTE_REPARSE_POINT = 1024++reparsePoint :: Win32.FileAttributeOrFlag -> Bool+reparsePoint attr = fILE_ATTRIBUTE_REPARSE_POINT .&. attr /= 0+#endif++{-| Stream all immediate children of the given directory, excluding @\".\"@ and+    @\"..\"@+-}+ls :: FilePath -> Shell FilePath+ls path = Shell (\(FoldM step begin done) -> do+    x0 <- begin+    let path' = Filesystem.encodeString path+    canRead <- fmap readable (getPermissions path')+#ifdef mingw32_HOST_OS+    reparse <- fmap reparsePoint (Win32.getFileAttributes path')+    if (canRead && not reparse)+        then bracket+            (Win32.findFirstFile (Filesystem.encodeString (path </> "*")))+            (\(h, _) -> Win32.findClose h)+            (\(h, fdat) -> do+                let loop x = do+                        file' <- Win32.getFindDataFileName fdat+                        let file = Filesystem.decodeString file'+                        x' <- if (file' /= "." && file' /= "..")+                            then step x (path </> file)+                            else return x+                        more <- Win32.findNextFile h fdat+                        if more then loop $! x' else done x'+                loop $! x0 )+        else done x0 )+#else+    if canRead+        then bracket (openDirStream path') closeDirStream (\dirp -> do+            let loop x = do+                    file' <- readDirStream dirp+                    case file' of+                        "" -> done x+                        _  -> do+                            let file = Filesystem.decodeString file'+                            x' <- if (file' /= "." && file' /= "..")+                                then step x (path </> file)+                                else return x+                            loop $! x'+            loop $! x0 )+        else done x0 )+#endif++-- | Stream all recursive descendents of the given directory+lstree :: FilePath -> Shell FilePath+lstree path = do+    child <- ls path+    isDir <- liftIO (testdir child)+    if isDir+        then return child <|> lstree child+        else return child++-- | Move a file or directory+mv :: FilePath -> FilePath -> IO ()+mv = Filesystem.rename++{-| Create a directory++    Fails if the directory is present+-}+mkdir :: FilePath -> IO ()+mkdir = Filesystem.createDirectory False++{-| Create a directory tree (equivalent to @mkdir -p@)++    Does not fail if the directory is present+-}+mktree :: FilePath -> IO ()+mktree = Filesystem.createTree++-- | Copy a file+cp :: FilePath -> FilePath -> IO ()+cp = Filesystem.copyFile++-- | Remove a file+rm :: FilePath -> IO ()+rm = Filesystem.removeFile++-- | Remove a directory+rmdir :: FilePath -> IO ()+rmdir = Filesystem.removeDirectory++{-| Remove a directory tree (equivalent to @rm -r@)++    Use at your own risk+-}+rmtree :: FilePath -> IO ()+rmtree = Filesystem.removeTree++-- | Get a file or directory's size+du :: FilePath -> IO Integer+du = Filesystem.getSize++-- | Check if a file exists+testfile :: FilePath -> IO Bool+testfile = Filesystem.isFile++-- | Check if a directory exists+testdir :: FilePath -> IO Bool+testdir = Filesystem.isDirectory++{-| Touch a file, updating the access and modification times to the current time++    Creates an empty file if it does not exist+-}+touch :: FilePath -> IO ()+touch file = do+    exists <- testfile file+    if exists+#ifdef mingw32_HOST_OS+        then do+            handle <- Win32.createFile+                (Filesystem.encodeString file)+                Win32.gENERIC_WRITE+                Win32.fILE_SHARE_NONE+                Nothing+                Win32.oPEN_EXISTING+                Win32.fILE_ATTRIBUTE_NORMAL+                Nothing+            (creationTime, _, _) <- Win32.getFileTime handle+            systemTime <- Win32.getSystemTimeAsFileTime+            Win32.setFileTime handle creationTime systemTime systemTime+#else+        then touchFile (Filesystem.encodeString file)+#endif+        else output file empty++{-| Time how long a command takes in monotonic wall clock time++    Returns the duration alongside the return value+-}+time :: IO a -> IO (a, NominalDiffTime)+time io = do+    TimeSpec seconds1 nanoseconds1 <- getTime Monotonic+    a <- io+    TimeSpec seconds2 nanoseconds2 <- getTime Monotonic+    let t = fromIntegral (    seconds2 -     seconds1)+          + fromIntegral (nanoseconds2 - nanoseconds1) / 10^(9::Int)+    return (a, fromRational t)++{-| Sleep for the given duration++    A numeric literal argument is interpreted as seconds.  In other words,+    @(sleep 2.0)@ will sleep for two seconds.+-}+sleep :: NominalDiffTime -> IO ()+sleep n = threadDelay (truncate (n * 10^(6::Int)))++{-| Exit with the given exit code++    An exit code of @0@ indicates success+-}+exit :: Int -> IO ()+exit 0 = exitWith  ExitSuccess+exit n = exitWith (ExitFailure n)++-- | Throw an exception using the provided `Text` message+die :: Text -> IO ()+die txt = throwIO (userError (unpack txt))++{-| Create a temporary directory underneath the given directory++    Deletes the temporary directory when done+-}+mktempdir+    :: FilePath+    -- ^ Parent directory+    -> Text+    -- ^ Directory name template+    -> Managed FilePath+mktempdir parent prefix = do+    let parent' = Filesystem.encodeString parent+    let prefix' = unpack prefix+    dir' <- managed (withTempDirectory parent' prefix')+    return (Filesystem.decodeString dir')++{-| Create a temporary file underneath the given directory++    Deletes the temporary file when done+-}+mktemp+    :: FilePath+    -- ^ Parent directory+    -> Text+    -- ^ File name template+    -> Managed (FilePath, Handle)+mktemp parent prefix = do+    let parent' = Filesystem.encodeString parent+    let prefix' = unpack prefix+    (file', handle) <- managed (\k ->+        withTempFile parent' prefix' (\file' handle -> k (file', handle)) )+    let file = Filesystem.decodeString file'+    return (file, handle)++-- | Fork a thread, acquiring an `Async` value+fork :: IO a -> Managed (Async a)+fork io = managed (withAsync io)++-- | Read lines of `Text` from standard input+stdin :: Shell Text+stdin = inhandle IO.stdin++-- | Read lines of `Text` from a file+input :: FilePath -> Shell Text+input file = do+    handle <- using (readonly file)+    inhandle handle++-- | Read lines of `Text` from a `Handle`+inhandle :: Handle -> Shell Text+inhandle handle = Shell (\(FoldM step begin done) -> do+    x0 <- begin+    let loop x = do+            eof <- IO.hIsEOF handle+            if eof+                then done x+                else do+                    txt <- Text.hGetLine handle+                    x'  <- step x txt+                    loop $! x'+    loop $! x0 )++-- | Stream lines of `Text` to standard output+stdout :: Shell Text -> IO ()+stdout s = sh (do+    txt <- s+    liftIO (echo txt) )++-- | Stream lines of `Text` to standard error+stderr :: Shell Text -> IO ()+stderr s = sh (do+    txt <- s+    liftIO (err txt) )++-- | Stream lines of `Text` to a file+output :: FilePath -> Shell Text -> IO ()+output file s = sh (do+    handle <- using (writeonly file)+    txt    <- s+    liftIO (Text.hPutStrLn handle txt) )++-- | Stream lines of `Text` to append to a file+append :: FilePath -> Shell Text -> IO ()+append file s = sh (do+    handle <- using (appendonly file)+    txt    <- s+    liftIO (Text.hPutStrLn handle txt) )++-- | Acquire a `Managed` read-only `Handle` from a `FilePath`+readonly :: FilePath -> Managed Handle+readonly file = managed (Filesystem.withFile file IO.ReadMode)++-- | Acquire a `Managed` write-only `Handle` from a `FilePath`+writeonly :: FilePath -> Managed Handle+writeonly file = managed (Filesystem.withFile file IO.WriteMode)++-- | Acquire a `Managed` append-only `Handle` from a `FilePath`+appendonly :: FilePath -> Managed Handle+appendonly file = managed (Filesystem.withFile file IO.AppendMode)++-- | Combine the output of multiple `Shell`s, in order+cat :: [Shell a] -> Shell a+cat = msum++-- | Keep all lines that match the given `Pattern`+grep :: Pattern a -> Shell Text -> Shell Text+grep pattern s = do+    txt <- s+    _:_ <- return (match pattern txt)+    return txt++{-| Replace all occurrences of a `Pattern` with its `Text` result++    Warning: Do not use a `Pattern` that matches the empty string, since it will+    match an infinite number of times+-}+sed :: Pattern Text -> Shell Text -> Shell Text+sed pattern s = do+    let pattern' = fmap Text.concat+            (many (pattern <|> fmap Text.singleton anyChar))+    txt    <- s+    txt':_ <- return (match pattern' txt)+    return txt'++-- | Search a directory recursively for all files matching the given `Pattern`+find :: Pattern a -> FilePath -> Shell FilePath+find pattern dir = do+    path <- lstree dir+    Right txt <- return (Filesystem.toText path)+    _:_       <- return (match pattern txt)+    return path++-- | A Stream of @\"y\"@s+yes :: Shell Text+yes = Shell (\(FoldM step begin _) -> do+    x0 <- begin+    let loop x = do+            x' <- step x "y"+            loop $! x'+    loop $! x0 )++-- | Limit a `Shell` to a fixed number of values+limit :: Int -> Shell a -> Shell a+limit n s = Shell (\(FoldM step begin done) -> do+    ref <- newIORef 0  -- I feel so dirty+    let step' x a = do+            n' <- readIORef ref+            writeIORef ref (n' + 1)+            if n' < n then step x a else return x+    foldIO s (FoldM step' begin done) )++{-| Limit a `Shell` to values that satisfy the predicate++    This terminates the stream on the first value that does not satisfy the+    predicate+-}+limitWhile :: (a -> Bool) -> Shell a -> Shell a+limitWhile predicate s = Shell (\(FoldM step begin done) -> do+    ref <- newIORef True+    let step' x a = do+            b <- readIORef ref+            let b' = b && predicate a+            writeIORef ref b'+            if b' then step x a else return x+    foldIO s (FoldM step' begin done) )++-- | Get the current time+date :: IO UTCTime+date = getCurrentTime++-- | Get the time a file was last modified+datefile :: FilePath -> IO UTCTime+datefile = Filesystem.getModified
+ src/Turtle/Shell.hs view
@@ -0,0 +1,200 @@+{-# LANGUAGE RankNTypes #-}++{-| You can think of `Shell` as @[]@ + `IO` + `Managed`.  In fact, you can embed+    all three of them within a `Shell`:++> select ::        [a] -> Shell a+> liftIO ::      IO a  -> Shell a+> using  :: Managed a  -> Shell a++    Those three embeddings obey these laws:++> do { x <- select m; select (f x) } = select (do { x <- m; f x })+> do { x <- liftIO m; liftIO (f x) } = liftIO (do { x <- m; f x })+> do { x <- with   m; using  (f x) } = using  (do { x <- m; f x })+>+> select (return x) = return x+> liftIO (return x) = return x+> using  (return x) = return x++    ... and `select` obeys these additional laws:++> select xs <|> select ys = select (xs <|> ys)+> select empty = empty++    You typically won't build `Shell`s using the `Shell` constructor.  Instead,+    use these functions to generate primitive `Shell`s:++    * `empty`, to create a `Shell` that outputs nothing++    * `return`, to create a `Shell` that outputs a single value++    * `select`, to range over a list of values within a `Shell`++    * `liftIO`, to embed an `IO` action within a `Shell`++    * `using`, to acquire a `Managed` resource within a `Shell`+    +    Then use these classes to combine those primitive `Shell`s into larger+    `Shell`s:++    * `Alternative`, to concatenate `Shell` outputs using (`<|>`)++    * `Monad`, to build `Shell` comprehensions using @do@ notation++    If you still insist on building your own `Shell` from scratch, then the+    `Shell` you build must satisfy this law:++> -- For every shell `s`:+> foldIO s (FoldM step begin done) = do+>     x  <- step+>     x' <- foldIO s (FoldM step (return x) return)+>     done x'++    ... which is a fancy way of saying that your `Shell` must call @\'begin\'@+    exactly once when it begins and call @\'done\'@ exactly once when it ends.+-}++module Turtle.Shell (+    -- * Shell+      Shell(..)+    , fold+    , sh+    , view++    -- * Embeddings+    , select+    , liftIO+    , using+    ) where++import Control.Applicative (Applicative(..), Alternative(..), liftA2)+import Control.Monad (MonadPlus(..), ap)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Managed (Managed, with)+import Control.Foldl (Fold(..), FoldM(..))+import qualified Control.Foldl as Foldl+import Data.Monoid (Monoid(..))+import Data.String (IsString(..))++-- | A @(Shell a)@ is a protected stream of @a@'s with side effects+newtype Shell a = Shell { foldIO :: forall r . FoldM IO a r -> IO r }++-- | Use a `Fold` to reduce the stream of @a@'s produced by a `Shell`+fold :: Shell a -> Fold a b -> IO b+fold s f = foldIO s (Foldl.generalize f)++-- | Run a `Shell` to completion, discarding any unused values+sh :: Shell a -> IO ()+sh s = fold s (pure ())++-- | Run a `Shell` to completion, `print`ing any unused values+view :: Show a => Shell a -> IO ()+view s = sh (do+    x <- s+    liftIO (print x) )++instance Functor Shell where+    fmap f s = Shell (\(FoldM step begin done) ->+        let step' x a = step x (f a)+        in  foldIO s (FoldM step' begin done) )++instance Applicative Shell where+    pure  = return+    (<*>) = ap++instance Monad Shell where+    return a = Shell (\(FoldM step begin done) -> do+       x  <- begin+       x' <- step x a+       done x' )++    m >>= f = Shell (\(FoldM step0 begin0 done0) -> do+        let step1 x a = foldIO (f a) (FoldM step0 (return x) return)+        foldIO m (FoldM step1 begin0 done0) )++    fail _ = mzero++instance Alternative Shell where+    empty = Shell (\(FoldM _ begin done) -> do+        x <- begin+        done x )++    s1 <|> s2 = Shell (\(FoldM step begin done) -> do+        x <- foldIO s1 (FoldM step begin return)+        foldIO s2 (FoldM step (return x) done) )++instance MonadPlus Shell where+    mzero = empty++    mplus = (<|>)++instance MonadIO Shell where+    liftIO io = Shell (\(FoldM step begin done) -> do+        x  <- begin+        a  <- io+        x' <- step x a+        done x' )++instance Monoid a => Monoid (Shell a) where+    mempty  = pure mempty+    mappend = liftA2 mappend++instance Num a => Num (Shell a) where+    fromInteger n = pure (fromInteger n)++    (+) = liftA2 (+)+    (*) = liftA2 (*)+    (-) = liftA2 (-)++    abs    = fmap abs+    signum = fmap signum+    negate = fmap negate++instance Fractional a => Fractional (Shell a) where+    fromRational n = pure (fromRational n)++    recip = fmap recip++    (/) = liftA2 (/)++instance Floating a => Floating (Shell a) where+    pi = pure pi++    exp   = fmap exp+    sqrt  = fmap sqrt+    log   = fmap log+    sin   = fmap sin+    tan   = fmap tan+    cos   = fmap cos+    asin  = fmap sin+    atan  = fmap atan+    acos  = fmap acos+    sinh  = fmap sinh+    tanh  = fmap tanh+    cosh  = fmap cosh+    asinh = fmap asinh+    atanh = fmap atanh+    acosh = fmap acosh++    (**)    = liftA2 (**)+    logBase = liftA2 logBase++instance IsString a => IsString (Shell a) where+    fromString str = pure (fromString str)++-- | Convert a list to a `Shell` that emits each element of the list+select :: [a] -> Shell a+select as = Shell (\(FoldM step begin done) -> do+    x0 <- begin+    let step' a k x = do+            x' <- step x a+            k $! x'+    foldr step' done as $! x0 )++-- | Acquire a `Managed` resource within a `Shell` in an exception-safe way+using :: Managed a -> Shell a+using resource = Shell (\(FoldM step begin done) -> do+    x  <- begin+    x' <- with resource (step x)+    done x' )
+ src/Turtle/Tutorial.hs view
@@ -0,0 +1,1308 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}++{-| Use @turtle@ if you want to write light-weight and maintainable shell+    scripts.++    @turtle@ embeds shell scripting directly within Haskell for three main+    reasons:++    * Haskell code is easy to refactor and maintain because the language is+      statically typed++    * Haskell is syntactically lightweight, thanks to global type inference++    * Haskell programs can be type-checked and interpreted very rapidly (< 1+      second)++    These features make Haskell ideal for scripting, particularly for replacing+    large and unwieldy Bash scripts.++    This tutorial introduces how to use the @turtle@ library to write Haskell+    scripts.  This assumes no prior knowledge of Haskell, but does assume prior+    knowledge of Bash or a similar shell scripting language.++    If you are already proficient with Haskell, then you can get quickly up to+    speed by reading the Quick Start guide at the top of "Turtle.Prelude".++    To follow along with the examples, install the Haskell Platform:++    <http://www.haskell.org/platform/>++    ... and then install the @turtle@ library by running:++> $ cabal install turtle+-}++module Turtle.Tutorial (+    -- * Introduction+    -- $introduction++    -- * Comparison+    -- $compare++    -- * Subroutines+    -- $do++    -- * Types+    -- $types++    -- * Shell+    -- $shell++    -- * Type signatures+    -- $signatures++    -- * System+    -- $system++    -- * String formatting+    -- $format++    -- * Streams+    -- $streams++    -- * Loops+    -- $loops++    -- * Folds+    -- $folds++    -- * Input and output+    -- $io++    -- * Patterns+    -- $patterns++    -- * Exception Safety+    -- $exceptions++    -- * Conclusion+    -- $conclusion+    ) where++import Turtle++-- $introduction+-- Let's translate some simple Bash scripts to Haskell and work our way up to+-- more complex scripts.  Here is an example \"Hello, world!\" script written+-- in both languages:+--+-- @+-- #!\/usr\/bin\/env runhaskell+--                                     -- #!\/bin\/bash+-- {-\# LANGUAGE OverloadedStrings \#-}  --+--                                     --+-- import "Turtle"                       --+--                                     --+-- main = `echo` \"Hello, world!\"         -- echo Hello, world!+-- @+--+-- In Haskell you can use @--@ to comment out the rest of a line.  The above+-- example uses comments to show the equivalent Bash script side-by-side with+-- the Haskell script.+--+-- You can execute the above code by saving it to the file @example.hs@.  If you+-- are copying and pasting the code, then remove the leading 1-space indent.+-- After you save the file, make the script executable and run the script:+--+-- > $ chmod u+x example.hs +-- > $ ./example.hs+-- > Hello, world!+--+-- If you delete the first line of the program, you can also compile the above+-- code to generate a native executable which will have a much faster startup+-- time and improved performance:+--+-- > $ ghc -O2 example.hs  # -O2 turns on all optimizations+-- > $ ./example+-- > Hello, world!+--+-- You can even run Haskell code interactively using @ghci@, which is an+-- interactive REPL for Haskell.  You can either use @ghci@ by itself:+--+-- > $ ghci+-- > <ghci links in some libraries>+-- > Prelude> :set -XOverloadedStrings+-- > Prelude> import Turtle+-- > Prelude Turtle> echo "Hello, world!"+-- > <ghci links in some libraries>+-- > Hello, world!+-- > Prelude Turtle> :quit+-- > $+--+-- ... or you can load Haskell code into @ghci@, which will bring all top-level+-- values from that program into scope:+--+-- > $ ghci example.hs+-- > <ghci links in some libraries>+-- > [1 of 1] Compiling Main             ( example.hs, interpreted )+-- > Ok, modules loaded: Main.+-- > *Main> main+-- > <ghci links in some libraries>+-- > Hello, world!+-- > *Main> :quit+-- > $+--+-- From now on I'll omit @ghci@'s linker output in tutorial examples.  You can+-- also silence this linker output by passing the @-v0@ flag to @ghci@.++-- $compare+-- You'll already notice a few differences between the Haskell code and Bash+-- code.+--+-- First, the Haskell code requires two additional lines of overhead to import+-- the @turtle@ library and enable overloading of string literals.  This+-- overhead is mostly unavoidable.+--+-- Second, the Haskell `echo` explicitly quotes its string argument whereas the+-- Bash @echo@ does not.  In Bash every token is a string by default and you+-- distinguish variables by prepending a dollar sign to them.  In Haskell the+-- the situation is reversed: every token is a variable by default and you+-- distinguish strings by quoting them.  The following example highlights the+-- difference:+--+-- > #!/usr/bin/env runhaskell+-- >                                     -- #!/bin/bash+-- > {-# LANGUAGE OverloadedStrings #-}  --+-- >                                     --+-- > import Turtle                       --+-- >                                     --+-- > str = "Hello!"                      --STR=Hello!+-- >                                     --+-- > main = echo str                     --echo $STR+--+-- Third, you have to explicitly assign a subroutine to @main@ to specify which+-- subroutine to run when your program begins.  This is because Haskell lets you+-- define things out of order.  For example, we could have written our original+-- program this way instead:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > main = echo str+-- > +-- > str = "Hello, world!"+--+-- Notice how the above program defines @str@ after @main@, which is valid.+-- Haskell does not care in what order you define top-level values or functions+-- (using the @=@ sign).  However, the top level of a Haskell program only+-- permits definitions.  If you were to insert a statement at the top-level:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > echo "Hello, world!"+--+-- ... then you would get this error when you tried to run your program:+--+-- > example.hs:7:1: Parse error: naked expression at top level++-- $do+-- You can use @do@ notation to create a subroutine that runs more than one+-- command:+--+-- > #!/usr/bin/env runhaskell+-- >                                     -- #!/bin/bash+-- > {-# LANGUAGE OverloadedStrings #-}  --+-- >                                     --+-- > import Turtle                       --+-- >                                     --+-- > main = do                           --+-- >     echo "Line 1"                   -- echo Line 1+-- >     echo "Line 2"                   -- echo Line 2+-- +-- > $ ./example.hs+-- > Line 1+-- > Line 2+--+-- @do@ blocks can use either use the indentation level to control their+-- duration or they can use curly braces and semicolons.  To see the full rules+-- for @do@ syntax, read: <http://en.wikibooks.org/wiki/Haskell/Indentation>.+--+-- Some commands can return a value, and you can store the result of a command+-- using the @<-@ symbol.  For example, the following program prints the+-- creation time of the current working directory by storing two intermediate+-- results:+--+-- @+-- #!\/usr\/bin\/env runhaskell+--                            -- #!\/bin\/bash+-- import Turtle              --+--                            --+-- main = do                  --+--     dir  <- `pwd`            -- DIR=$(pwd)+--     time <- `datefile` dir   -- TIME=$(date -r $DIR)+--     `print` time             -- echo $TIME+-- @+--+-- > $ ./example.hs+-- > 2015-01-24 03:40:31 UTC+--+-- The main difference between @=@ and @<-@ is that:+--+-- * The @<-@ symbol is overloaded and its meaning is context-dependent; in this+--   context it just means \"store the current result\"+--+-- * The @=@ symbol is not overloaded and always means that the two sides of the+--   equality are interchangeable+--+-- @do@ notation lets you combine smaller subroutines into larger subroutines.+-- For example, we could refactor the above code to split the first two commands+-- into their own smaller subroutine and then invoke that smaller subroutine+-- within a larger subroutine:+--+-- > #!/usr/bin/env runhaskell+-- >                             -- #!/bin/bash+-- > import Turtle               --+-- >                             --+-- > datePwd = do                -- datePwd() {+-- >     dir    <- pwd           --     DIR=$(pwd)+-- >     result <- datefile dir  --     RESULT=$(date -r $DIR)+-- >     return result           --     echo $RESULT+-- >                             -- }+-- > main = do                   --+-- >     time <- datePwd         -- TIME=$(datePwd)+-- >     print time              -- echo $TIME+--+-- The refactored program still returns the exact same result:+--+-- > $ ./example.hs+-- > 2015-01-24 03:40:31 UTC+--+-- We can also simplify the code a little bit because @do@ notation implicitly+-- returns the value of the last command within a subroutine.  We can use this+-- trick to simplify both the Haskell and Bash code:+--+-- > datePwd = do      -- datePwd() {+-- >     dir <- pwd    --     DIR=$(pwd)+-- >     datefile dir  --     date -r $DIR+-- >                   -- }+--+-- However, keep in mind that the `return` statement is something of a misnomer+-- since it does not break or exit from the surrounding subroutine.  All it+-- does is create a trivial subroutine that has no side effects and returns its+-- argument as its result.  If you `return` an expression, you're just giving+-- it a new name:+--+-- > do x <- return expr  -- X=EXPR+-- >    command x         -- command $X+-- > +-- > -- Same as:+-- > command expr         -- command EXPR+--+-- In fact, the first line is equivalent to @let x = expr@, which more closely+-- mirrors the equivalent Bash syntax:+--+-- > do let x = expr      -- X=EXPR+-- >    command x         -- command $X+-- > +-- > -- Same as:+-- > command expr         -- command EXPR+--+-- Also, for a subroutine with a single command, you can omit the @do@:+--+-- > main = do echo "Hello, world!"+-- > +-- > -- Same as:+-- > main =    echo "Hello, world!"++-- $types+--+-- Notice how the above Haskell example used `print` instead of `echo`.  Run the+-- following script to find out what happens if we choose `echo` instead:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > import Turtle+-- > +-- > main = do+-- >     dir  <- pwd+-- >     time <- datefile dir+-- >     echo time+--+-- If we run that we get a type error:+--+-- > $ ./example.hs+-- > +-- > example.hs:8:10:+-- >     Couldn't match expected type `Text' with actual type `UTCTime'+-- >     In the first argument of `echo', namely `time'+-- >     In a stmt of a 'do' block: echo time+-- >     In the expression:+-- >       do { dir <- pwd;+-- >            time <- datefile dir;+-- >            echo time }+--+-- The error points to the last line of our program: @(example.hs:8:10)@ means+-- line 8, column 10 of our program.  If you study the error message closely+-- you'll see that the `echo` function expects a `Text` value, but we passed it+-- @\'time\'@, which was a `UTCTime` value.  Although the error is at the end of+-- our script, Haskell catches this error before even running the script.  When+-- we \"interpret\" a Haskell script the Haskell compiler actually compiles the+-- script without any optimizations to generate a temporary executable and then+-- runs the executable, much like Perl does for Perl scripts.+--+-- You might wonder: \"where are the types?\"  None of the above programs had+-- any type signatures or type annotations, yet the compiler still detected type+-- errors correctly.  This is because Haskell uses \"global type inference\" to+-- detect errors, meaning that the compiler can infer the types of expressions+-- within the program without any assistance from the programmer.+--+-- You can even ask the compiler what the type of an expression is using @ghci@.+-- Let's open up the REPL and import this library so that we can study the types+-- and deduce why our program failed:+--+-- > $ ghci+-- > Prelude> import Turtle+-- > Prelude Turtle>+--+-- You can interrogate the REPL for an expression's type using the @:type@+-- command:+--+-- @+-- Prelude Turtle> :type pwd+-- pwd :: `IO` Turtle.`Turtle.FilePath`+-- @+--+-- Whenever you see something of the form @(x :: t)@, that means that @\'x\'@+-- is a value of type @\'t\'@.  The REPL says that `pwd` is a subroutine ('IO')+-- that returns a `Turtle.FilePath`.  The "Turtle" prefix before+-- `Turtle.FilePath` is just the module name since the `Turtle.FilePath`+-- exported by the @turtle@ library conflicts with the default `FilePath`+-- exported by Haskell's @Prelude@.  The compiler uses the fully qualified name,+-- @"Turtle".`FilePath`@, to avoid ambiguity.+--+-- We can similarly ask for the type of `datefile`:+--+-- @+-- Prelude Turtle> :type datefile+-- datefile :: Turtle.`Turtle.FilePath` -> `IO` `UTCTime`+-- @+--+-- `datefile` is a function whose argument must be a `Turtle.FilePath` and whose+-- result is a subroutine (`IO`) that returns a `UTCTime`.  Notice how the+-- input argument of `datefile` (which is a `Turtle.FilePath`) is the same type+-- as the return value of `pwd` (also a `Turtle.FilePath`).+--+-- Now let's study type of `echo` to see why we get the type error:+--+-- @+-- Prelude Turtle> :type echo+-- echo :: `Text` -> `IO` ()+-- @+--+-- The above type says that `echo` is a function whose argument is a value of+-- type `Text` and whose result is a subroutine (`IO`) with an empty return+-- value (denoted @\'()\'@).+--+-- Now we can understand the type error: `echo` expects a `Text` argument but+-- `datefile` returns a `UTCTime`, which is not the same thing.  Unlike Bash,+-- not everything is `Text` in Haskell and the compiler will not cast or coerce+-- types for you.+--+-- The reason `print` worked is because `print` has a more general type than+-- `echo`:+--+-- @+-- Prelude Turtle> :type print+-- print :: `Show` a => a -> `IO` ()+-- @+--+-- This type signature says that `print` can display any value of type @\'a\'@+-- so long as @\'a\'@ implements the `Show` interface.  In this case `UTCTime`+-- does implement the `Show` interface, so everything works out when we use+-- `print`.+--+-- This library provides a helper function that lets you convert any type that+-- implements `Show` into a `Text` value:+-- +-- @+-- \-\- This behaves like Python's \`repr\` function+-- `repr` :: `Show` a => a -> `Text`+-- @+--+-- You could therefore implement `print` in terms of `echo` and `repr`:+--+-- >  print x = echo (repr x)++-- $shell+--+-- You can use @ghci@ for more than just inferring types.  @ghci@ is a+-- general-purpose Haskell shell for your system when you extend it with+-- @turtle@:+--+-- @+-- $ ghci+-- Prelude> :set -XOverloadedStrings+-- Prelude> import Turtle+-- Prelude Turtle> `cd` \"/tmp\"+-- Prelude Turtle> `pwd`+-- FilePath \"/tmp\"+-- Prelude Turtle> `mkdir` \"test\"+-- Prelude Turtle> `cd` \"test\"+-- Prelude Turtle> `touch` \"file\"+-- Prelude Turtle> `testfile` \"file\"+-- True+-- Prelude Turtle> `rm` \"file\"+-- Prelude Turtle> `testfile` \"file\"+-- False+-- @+--+-- You can also optionally configure @ghci@ to run the first two commands every+-- time you launch @ghci@.  Just create a @.ghci@ within your current directory+-- with these two lines:+--+-- > :set -XOverloadedStrings+-- > import Turtle+--+-- The following @ghci@ examples will all assume that you run these two commands+-- at the beginning of every session, either manually or automatically.  You can+-- even enable those two commands permanently by adding the above @.ghci@ file+-- to your home directory.+--+-- Within @ghci@ you can run a subroutine and @ghci@ will `print` the+-- subroutine's value if it is not empty:+--+-- @+-- Prelude Turtle> `shell` \"true\" empty+-- ExitSuccess+-- Prelude Turtle> `shell` \"false\" empty+-- ExitFailure 1+-- @+--+-- You can also type in a pure expression and @ghci@ will evaluate that+-- expression:+--+-- @+-- Prelude Turtle> 2 + 2+-- 4+-- Prelude Turtle> \"123\" `<>` \"456\"  -- (\<\>) concatenates strings+-- \"123456\"+-- @+--+-- This works because @ghci@ automatically wraps anything that's not a+-- subroutine with `print`.  It's as if we had written:+--+-- > Prelude Turtle> print (2 + 2)+-- > 4+-- > Prelude Turtle> print ("123" <> "456")+-- > "123456"++-- $signatures+--+-- Haskell performs global type inference, meaning that the compiler never+-- requires any type signatures.  When you add type signatures, they are purely+-- for the benefit of the programmer and behave like machine-checked+-- documentation.+--+-- Let's illustrate this by adding types to our original script:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > import Turtle+-- > +-- > datePwd :: IO UTCTime  -- Type signature+-- > datePwd = do+-- >     dir <- pwd+-- >     datefile dir+-- > +-- > main :: IO ()          -- Type signature+-- > main = do+-- >     time <- datePwd+-- >     print time+--+-- The first type signature says that @datePwd@ is a subroutine that returns a+-- `UTCTime`:+--+-- > --         +----- A subroutine ...+-- > --         |+-- > --         |  +-- ... that returns `UTCTime`+-- > --         |  |+-- > --         v  v+-- > datePwd :: IO UTCTime+--+-- The second type signature says that @main@ is a subroutine that returns an+-- empty value:+--+-- > --      +----- A subroutine ...+-- > --      |+-- > --      |  +-- ... that returns an empty value (i.e. `()`)+-- > --      |  |+-- > --      v  v+-- > main :: IO ()+--+-- Not every top-level value has to be a subroutine, though.  For example, you+-- can define unadorned `Text` values at the top-level, as we saw previously:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > str :: Text+-- > str = "Hello!"+-- > +-- > main :: IO ()+-- > main = echo str+--+-- These type annotations do not assist the compiler.  Instead, the compiler+-- independently infers the type and then checks whether it matches the+-- documented type.  If there is a mismatch the compiler will raise a type+-- error.+--+-- Let's test this out by providing an incorrect type for @\'str\'@:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > str :: Int+-- > str = "Hello!"+-- > +-- > main :: IO ()+-- > main = echo str+--+-- If you run that script, you will get two error messages:+--+-- > $ ./example.hs+-- > +-- > example.hs:8:7:+-- >     No instance for (IsString Int)+-- >       arising from the literal `"Hello, world!"'+-- >     Possible fix: add an instance declaration for (IsString Int)+-- >     In the expression: "Hello, world!"+-- >     In an equation for `str': str = "Hello, world!"+-- > +-- > example.hs:11:13:+-- >     Couldn't match expected type `Text' with actual type `Int'+-- >     In the first argument of `echo', namely `str'+-- >     In the expression: echo str+-- >     In an equation for `main': main = echo str+--+-- The first error message relates to the @OverloadedStrings@ extensions. When+-- we enable @OverloadedStrings@ the compiler overloads string literals,+-- interpreting them as any type that implements the `IsString` interface.  The+-- error message says that `Int` does not implement the `IsString` interface so+-- the compiler cannot interpret a string literal as an `Int`.  On the other+-- hand the `Text` and `Turtle.FilePath` types do implement `IsString`, which+-- is why we can interpret string literals as `Text` or `Turtle.FilePath`+-- values.+--+-- The second error message says that `echo` expects a `Text` value, but we+-- declared @str@ to be an `Int`, so the compiler aborts compilation, requiring+-- us to either fix or delete our type signature.+--+-- Notice that there is nothing wrong with the program other than the type+-- signature we added.  If we were to delete the type signature the program+-- would compile and run correctly.  The sole purpose of this type signature is+-- for us to communicate our expectations to the compiler so that the compiler+-- can alert us if the code does not match our expectations.+--+-- Let's also try reversing the type error, providing a number where we expect+-- a string:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > str :: Text+-- > str = 4+-- > +-- > main :: IO ()+-- > main = echo str+--+-- This gives a different error:+--+-- > $ ./example.hs+-- > +-- > example.hs:8:7:+-- >     No instance for (Num Text)+-- >       arising from the literal `4'+-- >     Possible fix: add an instance declaration for (Num Text)+-- >     In the expression: 4+-- >     In an equation for `str': str = 4+--+-- Haskell also automatically overloads numeric literals, too.  The compiler+-- interprets integer literals as any type that implements the `Num` interface.+-- The `Text` type does not implement the `Num` interface, so we cannot+-- interpret integer literals as `Text` strings.++-- $system+--+-- You can invoke arbitrary shell commands using the `shell` command.  For+-- example, we can write a program that creates an empty directory and then+-- uses a `shell` command to archive the directory:+--+-- @+-- #!\/usr\/bin\/env runhaskell+--                                              -- #!\/bin\/bash+-- {-\# LANGUAGE OverloadedStrings \#-}           --+--                                              --+-- import Turtle                                --+--                                              --+-- main = do                                    --+--     mkdir \"test\"                             -- mkdir test+--     `shell` \"tar czf test.tar.gz test\" empty   -- tar czf test.tar.gz test+-- @+--+-- If you run this program, it will generate the @test.tar.gz@ archive:+--+-- > $ ./example.hs+-- > ExitSuccess+-- > $ echo $?+-- > 0+-- > $ ls test.tar.gz+-- > test.tar.gz+--+-- Like @ghci@, the @runhaskell@ command running our script prints any non-empty+-- result of the @main@ subroutine (`ExitSuccess` in this case).+--+-- The easiest way to learn a new command like `shell` is to view its+-- documentation.  Click on the word `shell`, which will take you to+-- documentation that looks like this:+--+-- @+-- `shell`+--     :: Text         -- Command line+--     -> Shell Text   -- Standard input (as lines of \`Text\`)+--     -> IO `ExitCode`  -- Exit code of the shell command+-- @+--+-- The first argument is a `Text` representation of the command to run.  The+-- second argument lets you feed input to the command, and you can provide+-- `empty` for now to feed no input.+--+-- The final result is an `ExitCode`, which you can use to detect whether the+-- command completed successfully.  For example, we could print a more+-- descriptive error message if an external command fails:+--+-- @+-- #!\/usr\/bin\/env runhaskell+-- +-- {-\# LANGUAGE OverloadedStrings \#-}+-- +-- import Turtle+--+-- main = do+--     let cmd = \"false\"+--     x <- shell cmd empty+--     case x of+--         ExitSuccess   -> return ()+--         ExitFailure n -> `die` (cmd \<\> \" failed with exit code: \" \<\> repr n)+-- @+--+-- This prints an error message since the @false@ command always fails:+--+-- > $ ./example.hs+-- > example.hs: user error (false failed with exit code: 1)+--+-- Most of the commands in this library do not actually invoke an external+-- shell.  Instead, they indirectly wrap other Haskell libraries that bind to C+-- code.++-- $format+--+-- This library provides type-safe string formatting utilities, too.  For+-- example, instead of writing this:+--+-- >  cmd <> " failed with exit code: " <> repr n+--+-- ... you could format the string using @printf@ style instead:+--+-- >  format (s%" failed with exit code: "%d) cmd n+--+-- What's neat is that the compiler will automatically infer the number of+-- arguments and their types from the `Format` string:+--+-- > $ ghci+-- > Prelude Turtle> :type format (s%" failed with exit code: "%d)+-- > format (s%" failed with exit code: "%d) :: Text -> Int -> Text+--+-- The compiler deduces that the above `Format` string requires one argument of+-- type `Text` to satisfy the `s` at the beginning of the format string and+-- another argument of type `Int` to satisfy the `d` at the end of the format+-- string.+--+-- If you are interested in this feature, check out the "Turtle.Format" module+-- for more details.++-- $streams+-- The @turtle@ library provides support for streaming computations, just like+-- Bash.  The primitive @turtle@ streams are little more verbose than their+-- Bash counterparts, but @turtle@ streams can be built and combined in more+-- ways.+--+-- The key type for streams is the `Shell` type, which represents a stream of+-- values.  For example, the `ls` function has a streaming result:+--+-- @+-- Prelude Turtle> :type `ls`+-- `ls` :: Turtle.FilePath -> `Shell` Turtle.FilePath+-- @+--+-- That type says that `ls` takes a single `Turtle.FilePath` as its argument+-- (the directory to list) and the result is a `Shell` stream of+-- `Turtle.FilePath`s (the immediate children of that directory).+--+-- You can't run a `Shell` stream directly within @ghci@.  You will get a type+-- error like this if you try:+--+-- > Prelude Turtle> ls "/tmp"+-- > +-- > <interactive>:2:1:+-- >     No instance for (Show (Shell Turtle.FilePath))+-- >       arising from a use of `print'+-- >     Possible fix:+-- >       add an instance declaration for (Show (Shell Turtle.FilePath))+-- >     In a stmt of an interactive GHCi command: print it+--+-- Instead, you must consume the stream as it is generated and the simplest way+-- to consume a `Shell` stream is `view`:+--+-- @+-- `view` :: Show a => Shell a -> IO ()+-- @+--+-- `view` takes any `Shell` stream of values and `print`s them to standard+-- output:+--+-- > Prelude Turtle> view (ls "/tmp")+-- > FilePath "/tmp/.X11-unix"+-- > FilePath "/tmp/.X0-lock"+-- > FilePath "/tmp/pulse-PKdhtXMmr18n"+-- > FilePath "/tmp/pulse-xHYcZ3zmN3Fv"+-- > FilePath "/tmp/tracker-gabriel"+-- > FilePath "/tmp/pulse-PYi1hSlWgNj2"+-- > FilePath "/tmp/orbit-gabriel"+-- > FilePath "/tmp/ssh-vREYGbWGpiCa"+-- > FilePath "/tmp/.ICE-unix+--+-- You can build your own `Shell` streams using a few primitive operations,+--+-- The first primitive is `empty`, which represents an empty stream of values:+--+-- @+-- Prelude Turtle> view `empty`  -- Outputs nothing+-- Prelude Turtle>+-- @+--+-- Another way to say that is:+--+-- @+-- view `empty` = return ()+-- @+--+-- The type of empty is:+--+-- @+-- `empty` :: Shell a+-- @+-- +-- The lower-case @\'a\'@ is \"polymorphic\", meaning that it will type check as+-- any type.  That means that you can produce an `empty` stream of any type of+-- value.+--+-- The next simplest function is `return`, which lets you take any value and+-- transform it into a singleton `Shell` that emits just that one value:+--+-- @+-- Prelude Turtle> view (`return` 1)+-- 1+-- @+--+-- Another way to say that is:+--+-- @+-- view (`return` x) = print x+-- @+--+-- The type of `return` is:+--+-- @+-- `return` :: a -> Shell a+-- @+--+-- Notice that this is the same `return` function we saw before.  This is+-- because `return` is overloaded and works with both `IO` and `Shell`.+--+-- You can also take any subroutine ('IO') and transform it into a singleton+-- `Shell`:+--+-- @+-- Prelude Turtle> view (`liftIO` readline)+-- ABC\<Enter\>+-- Just \"ABC\"+-- @+--+-- Another way to say that is:+--+-- @+-- view (`liftIO` io) = do x <- io+--                       print x+-- @+--+-- The type of `liftIO` is:+--+-- @+-- `liftIO` :: IO a -> Shell a+-- @+--+-- Once you have those primitive `Shell` streams you can begin to combine them+-- into larger `Shell` streams.  For example, you can concatenate two `Shell`+-- streams using (`<|>`):+--+-- @+-- view (return 1 `<|>` return 2)+-- 1+-- 2+-- @+--+-- Another way to say that is:+--+-- @+-- view (xs `<|>` ys) = do view xs+--                       view ys+-- @+--+-- The type of (`<|>`) is:+--+-- @+-- (`<|>`) :: Shell a -> Shell a -> Shell a+-- @+--+-- In other words, you can concatenate two `Shell` streams of the same element+-- type to get a new `Shell` stream, also of the same element type.+--+-- Let's try using (`<|>`) on two real streams:+--+-- > Prelude Turtle> view (ls "/tmp" <|> ls "/usr")+-- > FilePath "/tmp/.X11-unix"+-- > FilePath "/tmp/.X0-lock"+-- > FilePath "/tmp/pulse-PKdhtXMmr18n"+-- > FilePath "/tmp/pulse-xHYcZ3zmN3Fv"+-- > FilePath "/tmp/tracker-gabriel"+-- > FilePath "/tmp/pulse-PYi1hSlWgNj2"+-- > FilePath "/tmp/orbit-gabriel"+-- > FilePath "/tmp/ssh-vREYGbWGpiCa"+-- > FilePath "/tmp/.ICE-unix"+-- > FilePath "/usr/lib"+-- > FilePath "/usr/src"+-- > FilePath "/usr/sbin"+-- > FilePath "/usr/include"+-- > FilePath "/usr/share"+-- > FilePath "/usr/games"+-- > FilePath "/usr/local"+-- > FilePath "/usr/bin"+--+-- Finally, note that `Shell` implements the `IsString` interface, so a string+-- literal will type-check as a `Shell` that emits a single `Text` value:+--+-- > Prelude Turtle> view "123"+-- > "123"+-- > Prelude Turtle> view (return "123")  -- Same thing+-- > "123"+-- > Prelude Turtle> view ("123" <|> "456")+-- > "123"+-- > "456"+-- > Prelude Turtle> view (return "123" <|> return "456")  -- Same thing+-- > "123"+-- > "456"++-- $loops+--+-- This library also provides the `select` function for conveniently emitting a+-- list of values:+--+-- @+-- Prelude Turtle> view (`select` [1, 2, 3])+-- 1+-- 2+-- 3+-- @+--+-- We can use `select` to implement loops within a `Shell`:+--+-- > #!/usr/bin/env runhaskell+-- >                                     -- #!/bin/bash+-- > {-# LANGUAGE OverloadedStrings #-}  --+-- >                                     --+-- > import Turtle                       --+-- >                                     --+-- > example = do                        --+-- >     x <- select [1, 2]              -- for x in 1 2; do+-- >     y <- select [3, 4]              --     for y in 3 4; do+-- >     liftIO (print (x, y))           --         echo \(${x},${y}\);+-- >                                     --     done;+-- > main = sh example                   -- done+--+-- That will `print` every permutation of @\'x\'@ and @\'y\'@:+--+-- > $ ./example+-- > (1,3)+-- > (1,4)+-- > (2,3)+-- > (2,4)+--+-- This uses the `sh` utility instead of `view`.  The only difference is that+-- `sh` doesn't print any values (since `print` is doing that already):+--+-- @+-- `sh` :: Shell a -> IO ()+-- @+--+-- This trick isn't limited to `select`.  You can loop over the output of any+-- `Shell` by just binding its result.  For example, this is how `view` loops+-- over its argument:+--+-- > view :: Show a => Shell a -> IO ()+-- > view s = sh (do+-- >     x <- s -- `x` ranges over every output of `s`+-- >     liftIO (print x) )+--+-- You can also loop over a stream in a one-liner, still using @do@ notation.+-- Just insert semi-colons between statements:+--+-- > Prelude Turtle> -- for file in /tmp/*; do echo $file; done+-- > Prelude Turtle> sh (do file <- ls "/tmp"; liftIO (print file))+-- > FilePath "/tmp/.X11-unix"+-- > FilePath "/tmp/.X0-lock"+-- > FilePath "/tmp/pulse-PKdhtXMmr18n"+-- > FilePath "/tmp/pulse-xHYcZ3zmN3Fv"+-- > FilePath "/tmp/tracker-gabriel"+-- > FilePath "/tmp/pulse-PYi1hSlWgNj2"+-- > FilePath "/tmp/orbit-gabriel"+-- > FilePath "/tmp/ssh-vREYGbWGpiCa"+-- > FilePath "/tmp/.ICE-unix"++-- $folds+--+-- There are other ways you can consume a `Shell` stream.  For example, you can+-- `fold` the stream using predefined `Fold`s from "Control.Foldl":+--+-- @+-- Prelude Turtle> import qualified "Control.Foldl" as Fold+-- Prelude Turtle Fold> `fold` (ls \"/tmp\") Fold.length+-- 9+-- @+--+-- @+-- Prelude Turtle Fold> `fold` (ls \"/tmp\") Fold.head+-- Just (FilePath \"\/tmp\/.X11-unix\")+-- @+--+-- @+-- Prelude Turtle Fold> `fold` (ls \"\/tmp\") Fold.list+-- [FilePath \"\/tmp\/.X11-unix\",FilePath \"\/tmp\/.X0-lock\",FilePath \"\/tmp\/pulse-PKd+-- htXMmr18n\",FilePath \"\/tmp\/pulse-xHYcZ3zmN3Fv\",FilePath \"\/tmp\/tracker-gabriel+-- \",FilePath \"\/tmp\/pulse-PYi1hSlWgNj2\",FilePath \"\/tmp\/orbit-gabriel\",FilePath +-- \"\/tmp\/ssh-vREYGbWGpiCa\",FilePath \"\/tmp\/.ICE-unix\"]+-- @+--+-- You can also compute multiple things in a single pass over the stream:+--+-- > Prelude Turtle> fold (select [1..10]) ((,) <$> Fold.minimum <*> Fold.maximum)+-- > (Just 1,Just 10)+--+-- If you are interested in this feature, check out the documentation in+-- "Control.Foldl".++-- $io+--+-- @turtle@ comes with built-in support for the standard text streams.+--+-- For example, you can write to standard output using the `stdout` utility:+--+-- @+-- `stdout` :: Shell Text -> IO ()+-- `stdout` s = sh (do+--     txt <- s+--     liftIO (echo txt)+-- @+--+-- `stdout` outputs each `Text` value on its own line:+--+-- > Prelude Turtle> stdout "Line 1"+-- > Line 1+-- > Prelude Turtle> stdout ("Line 1" <|> "Line 2")+-- > Line 1+-- > Line 2+--+-- Another useful stream is `stdin`, which emits one line of `Text` per line of+-- standard input:+--+-- @+-- `stdin` :: Shell Text+-- @+--+-- Let's combine `stdin` and `stdout` to forward all input from standard input+-- to standard output:+--+-- > #!/usr/bin/env runhaskell+-- >                                     -- #!/bin/bash+-- > {-# LANGUAGE OverloadedStrings #-}  --+-- >                                     --+-- > import Turtle                       --+-- >                                     --+-- > main = stdout stdin                 -- cat+--+-- If you run that it will continue to echo lines until you signal end of input+-- using @Ctrl-D@:+--+-- > $ ./example.hs+-- > ABC<Enter>+-- > ABC+-- > Test<Enter>+-- > Test+-- > 42<Enter>+-- > 42+-- > <Ctrl-D>+-- > $+--+-- You can also read and write to files using the `input` and `output`+-- utilities:+--+-- @+-- Prelude Turtle> `output` \"file.txt\" (\"Test\" \<|\> \"ABC\" \<|\> \"42\")+-- Prelude Turtle> stdout (`input` \"file.txt\")+-- Test+-- ABC+-- 42+-- @++-- $patterns+--+-- You can transform streams using Unix-like utilities.  For example, you can+-- filter a stream using `grep`.+--+-- @+-- Prelude Turtle> stdout (input \"file.txt\")+-- Test+-- ABC+-- 42+-- Prelude Turtle> stdout (`grep` \"ABC\" (input \"file.txt\"))+-- ABC+-- @+--+-- Let's look at the type of `grep`:+--+-- @+-- `grep` :: Pattern a -> Shell Text -> Shell Text+-- @+--+-- The first argument of `grep` is actually a `Pattern`, which implements+-- `IsString`.  When we pass a string literal we just create a `Pattern` that+-- matches the given literal.+--+-- `Pattern`s generalize regular expressions and you can use this table to+-- roughly translate several regular expression idioms to `Pattern`s:+--+-- @+-- Regex      Pattern+-- =========  =========+-- \"string\"   \"string\"+-- .          `dot`+-- e1 e2      e1 `<>` e2+-- e1 | e2    e1 `<|>` e2+-- e*         `star` e+-- e+         `plus` e+-- e*?        `selfless` (`star` e)+-- e+?        `selfless` (`plus` e)+-- e{n}       `count` n e+-- e?         `optional` e+-- [xyz]      `oneOf` \"xyz\"+-- [^xyz]     `noneOf` \"xyz\"+-- @+--+-- Here are some examples:+--+-- > Prelude Turtle> -- grep '^[[:digit:]]\+$' file.txt+-- > Prelude Turtle> stdout (grep (plus digit) (input "file.txt"))+-- > 42+-- > Prelude Turtle> -- grep '^[[:digit:]]\+\|Test$' file.txt+-- > Prelude Turtle> stdout (grep (plus digit <|> "Test") (input "file.txt"))+-- > Test+-- > 42+--+-- Note that @turtle@'s `grep` subtly differs from the traditional @grep@+-- command.  The `Pattern` you provide must match the entire line.  If you+-- want to match the interior of a line, you can use the `has` utility:+--+-- @+-- Prelude Turtle> -- grep B file.txt+-- Prelude Turtle> stdout (grep (`has` \"B\") (input \"file.txt\"))+-- ABC+-- @+--+-- You can also use `prefix` or `suffix` to match the beginning or end of a+-- string, respectively:+--+-- @+-- Prelude Turtle> -- grep '^A' file.txt+-- Prelude Turtle> stdout (grep (`prefix` \"A\") (input \"file.txt\"))+-- ABC+-- Prelude Turtle> -- grep 'C$' file.txt+-- Prelude Turtle> stdout (grep (`suffix` \"C\") (input \"file.txt\"))+-- ABC+-- @+--+-- `sed` also uses `Pattern`s, too, and is more flexible than Unix @sed@:+--+-- @+-- Prelude Turtle> -- sed 's/C/D/g' file.txt+-- Prelude Turtle> stdout (`sed` (\"C\" `*>` return \"D\") (input \"file.txt\"))+-- Test+-- ABD+-- 42+-- Prelude Turtle> -- sed 's\/[[:digit:]]\/!\/g' file.txt+-- Prelude Turtle> stdout (`sed` (digit `*>` return \"!\") (input \"file.txt\"))+-- Test+-- ABC+-- !!+-- Prelude Turtle> import qualified Data.Text as Text+-- Prelude Turtle> -- rev file.txt+-- Prelude Turtle> stdout (`sed` (`fmap` Text.reverse (plus dot)) (input \"file.txt\"))+-- tseT+-- CBA+-- 24+-- Prelude Turtle>+-- @+--+-- You can also use `Pattern`s by themselves to parse arbitrary text into more+-- structured values:+--+-- @+-- Prelude Turtle> let pair = do x <- `decimal`; \" \"; y <- `decimal`; return (x, y)+-- Prelude Turtle> :type pair+-- pair :: `Pattern` (Integer, Integer)+-- Prelude Turtle> `match` pair \"123 456\"+-- [(123,456)]+-- Prelude Turtle> data Pet = Cat | Dog deriving (Show)+-- Prelude Turtle> let pet = (\"cat\" *> return Cat) \<|\> (\"dog\" *> return Dog) :: `Pattern` Pet+-- Prelude Turtle> `match` pet \"dog\"+-- [Dog]+-- Prelude Turtle> `match` (pet \``sepBy`\` \",\") \"cat,dog,cat\"+-- [[Cat,Dog,Cat]]+-- @+--+-- See the "Turtle.Pattern" module for more details if you are interested in+-- writing more complex `Pattern`s.++-- $exceptions+--+-- Sometimes you may want to acquire resources and ensure they get released+-- correctly if there are any exceptions.  You can use `Managed` resources to+-- acquire things safely within a `Shell`.+--+-- You can think of a `Managed` resource as some resource that needs to be+-- acquired and then released afterwards.  Example: you want to create a+-- temporary file and then guarantee it's deleted afterwards, even if the+-- program fails with an exception.+--+-- "Turtle.Prelude" provides two `Managed` utilities for creating temporary+-- directories or files:+--+-- @+-- `mktempdir`+--     :: FilePath          -- Parent directory+--     -> Text              -- Directory name template+--     -> `Managed` FilePath  -- Temporary directory+-- @+--+-- @+-- `mktemp`+--     :: FilePath                    -- Parent directory+--     -> Text                        -- File name template+--     -> `Managed` (FilePath, Handle)  -- Temporary file+-- @+--+-- You can acquire a `Managed` resource within a `Shell` with `using`:+--+-- @+-- `using` :: Managed a -> Shell a+-- @+--+-- ... and here is an example of creating a temporary directory and file within+-- a `Shell`:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > main = sh (do+-- >     dir       <- using (mktempdir "/tmp" "turtle")+-- >     (file, _) <- using (mktemp dir "turtle")+-- >     liftIO (print file) )+--+-- When you run the above script it will print out the name of the temporary+-- directory and file:+--+-- > $ ./example.hs+-- > FilePath "/tmp/turtle15976/turtle15976"+--+-- ... and you can verify that they were deleted afterwards:+--+-- > Turtle Prelude> view (find (has "turtle") "/tmp")+-- > Turtle Prelude> -- No results+--+-- As an exercise, try inserting an exception and verifying that the temporary:+-- file and directory are still cleaned up correctly:+--+-- > #!/usr/bin/env runhaskell+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > main = sh (do+-- >     dir       <- using (mktempdir "/tmp" "turtle")+-- >     (file, _) <- using (mktemp dir "turtle")+-- >     liftIO (print file)+-- >     liftIO (die "Urk!") )+--+-- To learn more about `Managed` resources, read the documentation in+-- "Control.Monad.Managed".++-- $conclusion+--+-- By this point you should be able to write basic shell scripts in Haskell.  If+-- you would like to learn more advanced tricks, take the time to read the+-- documentation in these modules:+--+-- * "Turtle.Prelude"+--+-- * "Turtle.Format"+--+-- * "Turtle.Pattern"+--+-- * "Turtle.Shell"+--+-- * "Control.Foldl"+--+-- * "Control.Monad.Managed"+--+-- If you have more questions or need help learning the library, ask a question+-- on Stack Overflow under the @haskell-turtle@ tag.  For bugs or feature+-- requests, create an issue on Github at+-- <https://github.com/Gabriel439/Haskell-Turtle-Library/issues>+--+-- This library provides an extended suite of Unix-like utilities, but would+-- still benefit from adding more utilities for better parity with the Unix+-- ecosystem.  Pull requests to add new utilities are highly welcome!+--+-- The @turtle@ library does not yet provide support for command line argument+-- parsing, but I highly recommend the @optparse-applicative@ library for this+-- purpose.  A future release of this library might include a simplified+-- interface to @optparse-applicative@.
+ test/Main.hs view
@@ -0,0 +1,6 @@+module Main where++import Test.DocTest++main :: IO ()+main = doctest ["src/Turtle/Pattern.hs", "src/Turtle/Format.hs"]
+ turtle.cabal view
@@ -0,0 +1,79 @@+Name: turtle+Version: 1.0.0+Cabal-Version: >=1.10+Build-Type: Simple+License: BSD3+License-File: LICENSE+Copyright: 2015 Gabriel Gonzalez+Author: Gabriel Gonzalez+Maintainer: Gabriel439@gmail.com+Bug-Reports: https://github.com/Gabriel439/Haskell-Turtle-Library/issues+Synopsis: Shell programming, Haskell-style+Description: @turtle@ is a reimplementation of the Unix command line environment+    in Haskell so that you can use Haskell as both a shell and a scripting+    language+    .+    Features include:+    .+    * Batteries included: Command an extended suite of predefined utilities+    .+    * Interoperability: You can still run external shell commands+    .+    * Portability: Works on Windows, OS X, and Linux+    .+    * Exception safety: Safely acquire and release resources +    .+    * Streaming: Transform or fold command output in constant space+    .+    * Patterns: Use typed regular expressions that can parse structured values+    .+    * Formatting: Type-safe @printf@-style text formatting+    .+    * Modern: Supports @text@ and @system-filepath@+    .+    Read "Turtle.Tutorial" for a detailed tutorial or "Turtle.Prelude" for a+    quick-start guide+Category: System+Source-Repository head+    Type: git+    Location: https://github.com/Gabriel439/Haskell-Turtle-Library++Library+    HS-Source-Dirs: src+    Build-Depends:+        base            >= 4       && < 5  ,+        async           >= 2.0.0.0 && < 2.1,+        clock           >= 0.4.1.2 && < 0.5,+        directory                     < 1.3,+        foldl                         < 1.1,+        managed                       < 1.1,+        process         >= 1.0.1.1 && < 1.3,+        system-filepath >= 0.3.1   && < 0.5,+        system-fileio   >= 0.2.1   && < 0.4,+        temporary                     < 1.3,+        text                          < 1.3,+        time                          < 1.6,+        transformers    >= 0.2.0.0 && < 0.5+    if os(windows)+        Build-Depends: Win32 >= 2.2.0.1 && < 2.4+    else+        Build-Depends: unix  >= 2.5.1.0 && < 2.8+    Exposed-Modules:+        Turtle,+        Turtle.Format,+        Turtle.Pattern,+        Turtle.Shell,+        Turtle.Prelude,+        Turtle.Tutorial+    GHC-Options: -O2 -Wall+    Default-Language: Haskell2010++test-suite tests+    Type: exitcode-stdio-1.0+    HS-Source-Dirs: test+    Main-Is: Main.hs+    GHC-Options: -O2 -Wall+    Default-Language: Haskell2010+    Build-Depends:+        base         >= 4      && < 5  ,+        doctest      >= 0.9.12 && < 0.10