packages feed

turtle 1.2.3 → 1.2.4

raw patch · 12 files changed

+4844/−4630 lines, 12 filesdep ~clocksetup-changed

Dependency ranges changed: clock

Files

LICENSE view
@@ -1,24 +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.
+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
@@ -1,2 +1,2 @@-import Distribution.Simple
-main = defaultMain
+import Distribution.Simple+main = defaultMain
bench/Main.hs view
@@ -1,40 +1,40 @@-{-# LANGUAGE OverloadedStrings #-}
-module Main where
-
-import qualified Data.Text as Text
-import Criterion.Main
-import Turtle
-
-boundedNaive :: Int -> Int -> Pattern a -> Pattern [a]
-boundedNaive m n p = do
-    x <- choice (map pure [m..n])
-    count x p
-
-main :: IO ()
-main = defaultMain
-  [ bgroup "Pattern"
-      [ let cats = Text.replicate 1000 "cat"
-            furniture = Text.replicate 5 "   "
-        in bgroup "Cat Lady's House"
-             [ bench "Basic"
-                $ nf (match (many "cat")) cats
-             , bench "Letters"
-                $ nf (match (many (mconcat ["c", "a", "t"]))) cats
-             , bench "Spaces"
-                $ nf (match (many "cat" <* spaces)) (cats <> furniture)
-             , bench "Prefix"
-                $ nf (match (prefix (many "cat"))) (cats <> furniture)
-             ]
-      , let hearts n = Text.replicate n "heart"
-        in bgroup "Love Knows No Bounds"
-            [ bench "500-700:650 Naive"
-               $ nf (match (boundedNaive 500 700 "heart")) (hearts 650)
-            , bench "500-700:650"
-               $ nf (match (bounded 500 700 "heart")) (hearts 650)
-            , bench "5000-7000:6500 Naive"
-               $ nf (match (boundedNaive 5000 7000 "heart")) (hearts 6500)
-            , bench "5000-7000:6500"
-               $ nf (match (bounded 5000 7000 "heart")) (hearts 6500)
-            ]
-      ]
-  ]
+{-# LANGUAGE OverloadedStrings #-}+module Main where++import qualified Data.Text as Text+import Criterion.Main+import Turtle++boundedNaive :: Int -> Int -> Pattern a -> Pattern [a]+boundedNaive m n p = do+    x <- choice (map pure [m..n])+    count x p++main :: IO ()+main = defaultMain+  [ bgroup "Pattern"+      [ let cats = Text.replicate 1000 "cat"+            furniture = Text.replicate 5 "   "+        in bgroup "Cat Lady's House"+             [ bench "Basic"+                $ nf (match (many "cat")) cats+             , bench "Letters"+                $ nf (match (many (mconcat ["c", "a", "t"]))) cats+             , bench "Spaces"+                $ nf (match (many "cat" <* spaces)) (cats <> furniture)+             , bench "Prefix"+                $ nf (match (prefix (many "cat"))) (cats <> furniture)+             ]+      , let hearts n = Text.replicate n "heart"+        in bgroup "Love Knows No Bounds"+            [ bench "500-700:650 Naive"+               $ nf (match (boundedNaive 500 700 "heart")) (hearts 650)+            , bench "500-700:650"+               $ nf (match (bounded 500 700 "heart")) (hearts 650)+            , bench "5000-7000:6500 Naive"+               $ nf (match (boundedNaive 5000 7000 "heart")) (hearts 6500)+            , bench "5000-7000:6500"+               $ nf (match (bounded 5000 7000 "heart")) (hearts 6500)+            ]+      ]+  ]
src/Turtle.hs view
@@ -1,161 +1,161 @@-{-# LANGUAGE CPP #-}
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-
--- | 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.Options
-    , module Turtle.Shell
-    , module Turtle.Prelude
-    , module Control.Applicative
-    , module Control.Monad
-    , module Control.Monad.IO.Class
-    , module Data.Monoid
-    , module Control.Monad.Managed
-    , module Filesystem.Path.CurrentOS
-    , Fold(..)
-    , FoldM(..)
-    , Text
-    , UTCTime
-    , NominalDiffTime
-    , Handle
-    , ExitCode(..)
-    , IsString(..)
-    , (&)
-    ) where
-
-import Turtle.Format
-import Turtle.Pattern
-import Turtle.Options
-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 (Managed, managed, runManaged, with)
-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)
-
-#if MIN_VERSION_base(4,8,0)
-import Data.Function ((&))
-#else
-infixl 1 &
-
--- | '&' is a reverse application operator.  This provides notational
--- convenience.  Its precedence is one higher than that of the forward
--- application operator '$', which allows '&' to be nested in '$'.
-(&) :: a -> (a -> b) -> b
-x & f = f x
-#endif
+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}++-- | 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.Options+    , module Turtle.Shell+    , module Turtle.Prelude+    , module Control.Applicative+    , module Control.Monad+    , module Control.Monad.IO.Class+    , module Data.Monoid+    , module Control.Monad.Managed+    , module Filesystem.Path.CurrentOS+    , Fold(..)+    , FoldM(..)+    , Text+    , UTCTime+    , NominalDiffTime+    , Handle+    , ExitCode(..)+    , IsString(..)+    , (&)+    ) where++import Turtle.Format+import Turtle.Pattern+import Turtle.Options+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 (Managed, managed, runManaged, with)+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)++#if MIN_VERSION_base(4,8,0)+import Data.Function ((&))+#else+infixl 1 &++-- | '&' is a reverse application operator.  This provides notational+-- convenience.  Its precedence is one higher than that of the forward+-- application operator '$', which allows '&' to be nested in '$'.+(&) :: a -> (a -> b) -> b+x & f = f x+#endif
src/Turtle/Format.hs view
@@ -1,195 +1,195 @@-{-# 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
-    , fp
-
-    -- * Utilities
-    , repr
-    ) where
-
-import Control.Category (Category(..))
-import Data.Monoid ((<>))
-import Data.String (IsString(..))
-import Data.Text (Text, pack)
-import Data.Word
-import Filesystem.Path.CurrentOS (FilePath, toText)
-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
-
--- | `Format` a `Filesystem.Path.CurrentOS.FilePath` into `Text`
-fp :: Format r (FilePath -> r)
-fp = makeFormat (\fpath -> either id id (toText fpath))
-
-{-| 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
+{-# 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+    , fp++    -- * Utilities+    , repr+    ) where++import Control.Category (Category(..))+import Data.Monoid ((<>))+import Data.String (IsString(..))+import Data.Text (Text, pack)+import Data.Word+import Filesystem.Path.CurrentOS (FilePath, toText)+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 `Integral` value as a signed decimal++>>> format d 25+"25"+>>> format d (-25)+"-25"+-}+d :: Integral n => Format r (n -> r)+d = makeFormat (pack . show . toInteger)++{-| `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++-- | `Format` a `Filesystem.Path.CurrentOS.FilePath` into `Text`+fp :: Format r (FilePath -> r)+fp = makeFormat (\fpath -> either id id (toText fpath))++{-| 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/Options.hs view
@@ -1,232 +1,237 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
--- | Example usage of this module:
---
--- > -- options.hs
--- >
--- > {-# LANGUAGE OverloadedStrings #-}
--- >
--- > import Turtle
--- >
--- > parser :: Parser (Text, Int)
--- > parser = (,) <$> optText "name" 'n' "Your first name"
--- >              <*> optInt  "age"  'a' "Your current age"
--- >
--- > main = do
--- >     (name, age) <- options "Greeting script" parser
--- >     echo (format ("Hello there, "%s) name)
--- >     echo (format ("You are "%d%" years old") age)
---
--- > $ ./options --name John --age 42
--- > Hello there, John
--- > You are 42 years old
---
--- > $ ./options --help
--- > Greeting script
--- >
--- > Usage: options (-n|--name NAME) (-a|--age AGE)
--- >
--- > Available options:
--- >  -h,--help                Show this help text
--- >  --name NAME              Your first name
--- >  --age AGE                Your current age
-
-module Turtle.Options
-    ( -- * Types
-      Parser
-    , ArgName
-    , CommandName
-    , ShortName
-    , Description
-    , HelpMessage
-
-      -- * Flag-based option parsers
-    , switch
-    , optText
-    , optInt
-    , optInteger
-    , optDouble
-    , optPath
-    , optRead
-    , opt
-
-    -- * Positional argument parsers
-    , argText
-    , argInt
-    , argInteger
-    , argDouble
-    , argPath
-    , argRead
-    , arg
-
-      -- * Consume parsers
-    , subcommand
-    , options
-
-    ) where
-
-import Data.Monoid
-import Data.Foldable
-import Data.String (IsString)
-import Text.Read (readMaybe)
-import Data.Text (Text)
-import qualified Data.Text as Text
-import Data.Optional
-import Control.Applicative
-import Control.Monad.IO.Class
-import Filesystem.Path.CurrentOS (FilePath, fromText)
-import Options.Applicative (Parser)
-import qualified Options.Applicative as Opts
-import qualified Options.Applicative.Types as Opts
-import Prelude hiding (FilePath)
-
--- | Parse the given options from the command line
-options :: MonadIO io => Description -> Parser a -> io a
-options desc parser = liftIO
-    $ Opts.execParser
-    $ Opts.info (Opts.helper <*> parser)
-                (Opts.header (Text.unpack (getDescription desc)))
-
-{-| The name of a command-line argument
-
-    This is used to infer the long name and metavariable for the command line
-    flag.  For example, an `ArgName` of @\"name\"@ will create a @--name@ flag
-    with a @NAME@ metavariable
--}
-newtype ArgName = ArgName { getArgName :: Text }
-    deriving (IsString)
-
--- | The short one-character abbreviation for a flag (i.e. @-n@)
-type ShortName = Char
-
-{-| The name of a sub-command
-
-    This is lower-cased to create a sub-command.  For example, a `CommandName` of
-    @\"Name\"@ will parse `name` on the command line before parsing the
-    remaining arguments using the command's subparser.
--}
-newtype CommandName = CommandName { getCommandName :: Text }
-    deriving (IsString)
-
-{-| A brief description of what your program does
-
-    This description will appear in the header of the @--help@ output
--}
-newtype Description = Description { getDescription :: Text }
-    deriving (IsString)
-
-{-| A helpful message explaining what a flag does
-
-    This will appear in the @--help@ output
--}
-newtype HelpMessage = HelpMessage { getHelpMessage :: Text }
-    deriving (IsString)
-
-{-| This parser returns `True` if the given flag is set and `False` if the
-    flag is absent
--}
-switch
-    :: ArgName
-    -> ShortName
-    -> Optional HelpMessage
-    -> Parser Bool
-switch argName c helpMessage
-   = Opts.switch
-   $ (Opts.long . Text.unpack . getArgName) argName
-  <> Opts.short c
-  <> foldMap (Opts.help . Text.unpack . getHelpMessage) helpMessage
-
-{- | Build a flag-based option parser for any type by providing a `Text`-parsing
-     function
--}
-opt :: (Text -> Maybe a)
-    -> ArgName
-    -> ShortName
-    -> Optional HelpMessage
-    -> Parser a
-opt argParse argName c helpMessage
-   = Opts.option (argParseToReadM argParse)
-   $ Opts.metavar (Text.unpack (Text.toUpper (getArgName argName)))
-  <> Opts.long (Text.unpack (getArgName argName))
-  <> Opts.short c
-  <> foldMap (Opts.help . Text.unpack . getHelpMessage) helpMessage
-
--- | Parse any type that implements `Read`
-optRead :: Read a => ArgName -> ShortName -> Optional HelpMessage -> Parser a
-optRead = opt (readMaybe . Text.unpack)
-
--- | Parse an `Int` as a flag-based option
-optInt :: ArgName -> ShortName -> Optional HelpMessage -> Parser Int
-optInt = optRead
-
--- | Parse an `Integer` as a flag-based option
-optInteger :: ArgName -> ShortName -> Optional HelpMessage -> Parser Integer
-optInteger = optRead
-
--- | Parse a `Double` as a flag-based option
-optDouble :: ArgName -> ShortName -> Optional HelpMessage -> Parser Double
-optDouble = optRead
-
--- | Parse a `Text` value as a flag-based option
-optText :: ArgName -> ShortName -> Optional HelpMessage -> Parser Text
-optText = opt Just
-
--- | Parse a `FilePath` value as a flag-based option
-optPath :: ArgName -> ShortName -> Optional HelpMessage -> Parser FilePath
-optPath argName short msg = fmap fromText (optText argName short msg)
-
-{- | Build a positional argument parser for any type by providing a
-    `Text`-parsing function
--}
-arg :: (Text -> Maybe a)
-    -> ArgName
-    -> Optional HelpMessage
-    -> Parser a
-arg argParse argName helpMessage
-   = Opts.argument (argParseToReadM argParse)
-   $ Opts.metavar (Text.unpack (Text.toUpper (getArgName argName)))
-  <> foldMap (Opts.help . Text.unpack . getHelpMessage) helpMessage
-
--- | Parse any type that implements `Read` as a positional argument
-argRead :: Read a => ArgName -> Optional HelpMessage -> Parser a
-argRead = arg (readMaybe . Text.unpack)
-
--- | Parse an `Int` as a positional argument
-argInt :: ArgName -> Optional HelpMessage -> Parser Int
-argInt = argRead
-
--- | Parse an `Integer` as a positional argument
-argInteger :: ArgName -> Optional HelpMessage -> Parser Integer
-argInteger = argRead
-
--- | Parse a `Double` as a positional argument
-argDouble :: ArgName -> Optional HelpMessage -> Parser Double
-argDouble = argRead
-
--- | Parse a `Text` as a positional argument
-argText :: ArgName -> Optional HelpMessage -> Parser Text
-argText = arg Just
-
--- | Parse a `FilePath` as a positional argument
-argPath :: ArgName -> Optional HelpMessage -> Parser FilePath
-argPath argName msg = fmap fromText (argText argName msg)
-
-argParseToReadM :: (Text -> Maybe a) -> Opts.ReadM a
-argParseToReadM f = do
-    s <- Opts.readerAsk
-    case f (Text.pack s) of
-        Just a -> return a
-        Nothing -> Opts.readerAbort Opts.ShowHelpText
-
-{-| Create a sub-command that parses `CommandName` and then parses the rest
-    of the command-line arguments
-
-    The sub-command will have its own `Description` and help text
--}
-subcommand :: CommandName -> Description -> Parser a -> Parser a
-subcommand cmdName desc p =
-    Opts.subparser (Opts.command name info <> Opts.metavar name)
-  where
-    name = Text.unpack (getCommandName cmdName)
-
-    info = Opts.info p (Opts.header (Text.unpack (getDescription desc)))
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | Example usage of this module:+--+-- > -- options.hs+-- >+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > import Turtle+-- >+-- > parser :: Parser (Text, Int)+-- > parser = (,) <$> optText "name" 'n' "Your first name"+-- >              <*> optInt  "age"  'a' "Your current age"+-- >+-- > main = do+-- >     (name, age) <- options "Greeting script" parser+-- >     echo (format ("Hello there, "%s) name)+-- >     echo (format ("You are "%d%" years old") age)+--+-- > $ ./options --name John --age 42+-- > Hello there, John+-- > You are 42 years old+--+-- > $ ./options --help+-- > Greeting script+-- >+-- > Usage: options (-n|--name NAME) (-a|--age AGE)+-- >+-- > Available options:+-- >  -h,--help                Show this help text+-- >  --name NAME              Your first name+-- >  --age AGE                Your current age+--+-- See the "Turtle.Tutorial" module which contains more examples on how to use+-- command-line parsing.++module Turtle.Options+    ( -- * Types+      Parser+    , ArgName+    , CommandName+    , ShortName+    , Description+    , HelpMessage++      -- * Flag-based option parsers+    , switch+    , optText+    , optInt+    , optInteger+    , optDouble+    , optPath+    , optRead+    , opt++    -- * Positional argument parsers+    , argText+    , argInt+    , argInteger+    , argDouble+    , argPath+    , argRead+    , arg++      -- * Consume parsers+    , subcommand+    , options++    ) where++import Data.Monoid+import Data.Foldable+import Data.String (IsString)+import Text.Read (readMaybe)+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Optional+import Control.Applicative+import Control.Monad.IO.Class+import Filesystem.Path.CurrentOS (FilePath, fromText)+import Options.Applicative (Parser)+import qualified Options.Applicative as Opts+import qualified Options.Applicative.Types as Opts+import Prelude hiding (FilePath)++-- | Parse the given options from the command line+options :: MonadIO io => Description -> Parser a -> io a+options desc parser = liftIO+    $ Opts.execParser+    $ Opts.info (Opts.helper <*> parser)+                (Opts.header (Text.unpack (getDescription desc)))++{-| The name of a command-line argument++    This is used to infer the long name and metavariable for the command line+    flag.  For example, an `ArgName` of @\"name\"@ will create a @--name@ flag+    with a @NAME@ metavariable+-}+newtype ArgName = ArgName { getArgName :: Text }+    deriving (IsString)++-- | The short one-character abbreviation for a flag (i.e. @-n@)+type ShortName = Char++{-| The name of a sub-command++    This is lower-cased to create a sub-command.  For example, a `CommandName` of+    @\"Name\"@ will parse `name` on the command line before parsing the+    remaining arguments using the command's subparser.+-}+newtype CommandName = CommandName { getCommandName :: Text }+    deriving (IsString)++{-| A brief description of what your program does++    This description will appear in the header of the @--help@ output+-}+newtype Description = Description { getDescription :: Text }+    deriving (IsString)++{-| A helpful message explaining what a flag does++    This will appear in the @--help@ output+-}+newtype HelpMessage = HelpMessage { getHelpMessage :: Text }+    deriving (IsString)++{-| This parser returns `True` if the given flag is set and `False` if the+    flag is absent+-}+switch+    :: ArgName+    -> ShortName+    -> Optional HelpMessage+    -> Parser Bool+switch argName c helpMessage+   = Opts.switch+   $ (Opts.long . Text.unpack . getArgName) argName+  <> Opts.short c+  <> foldMap (Opts.help . Text.unpack . getHelpMessage) helpMessage++{- | Build a flag-based option parser for any type by providing a `Text`-parsing+     function+-}+opt :: (Text -> Maybe a)+    -> ArgName+    -> ShortName+    -> Optional HelpMessage+    -> Parser a+opt argParse argName c helpMessage+   = Opts.option (argParseToReadM argParse)+   $ Opts.metavar (Text.unpack (Text.toUpper (getArgName argName)))+  <> Opts.long (Text.unpack (getArgName argName))+  <> Opts.short c+  <> foldMap (Opts.help . Text.unpack . getHelpMessage) helpMessage++-- | Parse any type that implements `Read`+optRead :: Read a => ArgName -> ShortName -> Optional HelpMessage -> Parser a+optRead = opt (readMaybe . Text.unpack)++-- | Parse an `Int` as a flag-based option+optInt :: ArgName -> ShortName -> Optional HelpMessage -> Parser Int+optInt = optRead++-- | Parse an `Integer` as a flag-based option+optInteger :: ArgName -> ShortName -> Optional HelpMessage -> Parser Integer+optInteger = optRead++-- | Parse a `Double` as a flag-based option+optDouble :: ArgName -> ShortName -> Optional HelpMessage -> Parser Double+optDouble = optRead++-- | Parse a `Text` value as a flag-based option+optText :: ArgName -> ShortName -> Optional HelpMessage -> Parser Text+optText = opt Just++-- | Parse a `FilePath` value as a flag-based option+optPath :: ArgName -> ShortName -> Optional HelpMessage -> Parser FilePath+optPath argName short msg = fmap fromText (optText argName short msg)++{- | Build a positional argument parser for any type by providing a+    `Text`-parsing function+-}+arg :: (Text -> Maybe a)+    -> ArgName+    -> Optional HelpMessage+    -> Parser a+arg argParse argName helpMessage+   = Opts.argument (argParseToReadM argParse)+   $ Opts.metavar (Text.unpack (Text.toUpper (getArgName argName)))+  <> foldMap (Opts.help . Text.unpack . getHelpMessage) helpMessage++-- | Parse any type that implements `Read` as a positional argument+argRead :: Read a => ArgName -> Optional HelpMessage -> Parser a+argRead = arg (readMaybe . Text.unpack)++-- | Parse an `Int` as a positional argument+argInt :: ArgName -> Optional HelpMessage -> Parser Int+argInt = argRead++-- | Parse an `Integer` as a positional argument+argInteger :: ArgName -> Optional HelpMessage -> Parser Integer+argInteger = argRead++-- | Parse a `Double` as a positional argument+argDouble :: ArgName -> Optional HelpMessage -> Parser Double+argDouble = argRead++-- | Parse a `Text` as a positional argument+argText :: ArgName -> Optional HelpMessage -> Parser Text+argText = arg Just++-- | Parse a `FilePath` as a positional argument+argPath :: ArgName -> Optional HelpMessage -> Parser FilePath+argPath argName msg = fmap fromText (argText argName msg)++argParseToReadM :: (Text -> Maybe a) -> Opts.ReadM a+argParseToReadM f = do+    s <- Opts.readerAsk+    case f (Text.pack s) of+        Just a -> return a+        Nothing -> Opts.readerAbort Opts.ShowHelpText++{-| Create a sub-command that parses `CommandName` and then parses the rest+    of the command-line arguments++    The sub-command will have its own `Description` and help text+-}+subcommand :: CommandName -> Description -> Parser a -> Parser a+subcommand cmdName desc p =+    Opts.subparser (Opts.command name info <> Opts.metavar name)+  where+    name = Text.unpack (getCommandName cmdName)++    info = Opts.info+        (Opts.helper <*> p)+        (Opts.header (Text.unpack (getDescription desc)))
src/Turtle/Pattern.hs view
@@ -1,728 +1,728 @@-{-# LANGUAGE OverloadedStrings          #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE TypeFamilies               #-}
-{-# OPTIONS_GHC -fno-warn-missing-methods #-}
-
-{-| 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) :: Pattern Bool;
-    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
-    , asciiCI
-    , oneOf
-    , noneOf
-    , space
-    , spaces
-    , spaces1
-    , tab
-    , newline
-    , crlf
-    , upper
-    , lower
-    , alphaNum
-    , letter
-    , digit
-    , hexDigit
-    , octDigit
-
-    -- * Numbers
-    , decimal
-    , signed
-
-    -- * Combinators
-    , prefix
-    , suffix
-    , has
-    , begins
-    , ends
-    , contains
-    , invert
-    , once
-    , star
-    , plus
-    , selfless
-    , choice
-    , count
-    , lowerBounded
-    , upperBounded
-    , bounded
-    , option
-    , between
-    , skip
-    , within
-    , fixed
-    , sepBy
-    , sepBy1
-
-    -- * High-efficiency primitives
-    , chars
-    , chars1
-    ) where
-
-import Control.Applicative
-import Control.Monad
-import Control.Monad.Trans.Class (lift)
-import Control.Monad.Trans.State
-import Data.Char
-import Data.List (foldl')
-import Data.Monoid
-import Data.String (IsString(..))
-import Data.Text (Text)
-import qualified Data.Text as Text
-import Prelude -- Fix redundant import warnings
-
--- | 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
-
--- | Pattern forms a semiring, this is the closest approximation
-instance Monoid a => Num (Pattern a) where
-    fromInteger n = Pattern (lift (replicate (fromInteger n) mempty))
-    (+) = (<|>)
-    (*) = (<>)
-
-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 a specific string in a case-insensitive way
-
-    This only handles ASCII strings
-
->>> match (asciiCI "abc") "ABC"
-["ABC"]
--}
-asciiCI :: Text -> Pattern Text
-asciiCI before' = Pattern (do
-    txt <- get
-    let (before, after) = Text.splitAt (Text.length before') txt
-    guard (lowerChars before == lowerChars before')
-    put after
-    return before )
-  where
-    lowerChars = Text.map lowerChar
-    lowerChar c | 'A' <= c && c <= 'Z' = chr (ord c + ord 'a' - ord 'A')
-                | otherwise            = c
-
-{-| 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
-
-{-| @(`invert` p)@ succeeds if @p@ fails and fails if @p@ succeeds
-
->>> match (invert "A") "A"
-[]
->>> match (invert "A") "B"
-[()]
--}
-invert :: Pattern a -> Pattern ()
-invert p = Pattern (StateT (\str -> case runStateT (runPattern p) str of
-    [] -> [((), "")]
-    _  -> [] ))
-
-{-| 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 <* chars
-
-{-| Use this to match the suffix of a string
-
->>> match         "C"  "ABC"
-[]
->>> match (suffix "C") "ABC"
-["C"]
--}
-suffix :: Pattern a -> Pattern a
-suffix p = chars *> 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 = chars *> p <* chars
-
-{-| Match the entire string if it begins with the given pattern
-
-    This returns the entire string, not just the matched prefix
-
->>> match (begins  "A"             ) "ABC"
-["ABC"]
->>> match (begins ("A" *> pure "1")) "ABC"
-["1BC"]
--}
-begins :: Pattern Text -> Pattern Text
-begins pattern = pattern <> chars
-
-{-| Match the entire string if it ends with the given pattern
-
-    This returns the entire string, not just the matched prefix
-
->>> match (ends  "C"             ) "ABC"
-["ABC"]
->>> match (ends ("C" *> pure "1")) "ABC"
-["AB1"]
--}
-ends :: Pattern Text -> Pattern Text
-ends pattern = chars <> pattern
-
-{-| Match the entire string if it contains the given pattern
-
-    This returns the entire string, not just the interior pattern
-
->>> match (contains  "B"             ) "ABC"
-["ABC"]
->>> match (contains ("B" *> pure "1")) "ABC"
-["A1C"]
--}
-contains :: Pattern Text -> Pattern Text
-contains pattern = chars <> pattern <> chars
-
-{-| Parse 0 or more occurrences of the given character
-
->>> match (star anyChar) "123"
-["123"]
->>> match (star anyChar) ""
-[""]
-
-    See also: `chars`
--}
-star :: Pattern Char -> Pattern Text
-star p = fmap Text.pack (many p)
-
-{-| Parse 1 or more occurrences of the given character
-
->>> match (plus digit) "123"
-["123"]
->>> match (plus digit) ""
-[]
-
-    See also: `chars1`
--}
-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
-
-{-| Apply the given pattern at least the given number of times, collecting the
-    results
-
->>> match (lowerBounded 5 dot) "123"
-[]
->>> match (lowerBounded 2 dot) "123"
-["123"]
--}
-lowerBounded :: Int -> Pattern a -> Pattern [a]
-lowerBounded n p = do
-    ps1 <- count n p
-    ps2 <- many p
-    return (ps1 ++ ps2)
-
-{-| Apply the given pattern 0 or more times, up to a given bound,
-    collecting the results
-
->>> match (upperBounded 5 dot) "123"
-["123"]
->>> match (upperBounded 2 dot) "123"
-[]
->>> match ((,) <$> upperBounded 2 dot <*> chars) "123"
-[("12","3"),("1","23")]
--}
-upperBounded :: Int -> Pattern a -> Pattern [a]
-upperBounded n p
-    | n <= 0 = mempty
-    | n == 1 = fmap pure p
-    | otherwise = (:) <$> p <*> option (upperBounded (n - 1) p)
-
-{-| Apply the given pattern a number of times restricted by given
-    lower and upper bounds, collecting the results
-
->>> match (bounded 2 5 "cat") "catcatcat"
-[["cat","cat","cat"]]
->>> match (bounded 2 5 "cat") "cat"
-[]
->>> match (bounded 2 5 "cat") "catcatcatcatcatcat"
-[]
-
-`bounded` could be implemented naively as follows:
-
-> bounded m n p = do
->   x <- choice (map pure [m..n])
->   count x p
-
--}
-bounded :: Int -> Int -> Pattern a -> Pattern [a]
-bounded m n p
-    | m == n = count m p
-    | m  < n = (++) <$> count m p <*> option (upperBounded (n - m) p)
-    | otherwise = mzero
-
-{-| 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) 
-
--- | Like @star dot@ or @star anyChar@, except more efficient
-chars :: Pattern Text
-chars = Pattern (StateT (\txt ->
-    reverse (zip (Text.inits txt) (Text.tails txt)) ))
-
--- | Like @plus dot@ or @plus anyChar@, except more efficient
-chars1 :: Pattern Text
-chars1 = Text.cons <$> dot <*> chars
+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies               #-}+{-# OPTIONS_GHC -fno-warn-missing-methods #-}++{-| 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) :: Pattern Bool;+    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+    , asciiCI+    , oneOf+    , noneOf+    , space+    , spaces+    , spaces1+    , tab+    , newline+    , crlf+    , upper+    , lower+    , alphaNum+    , letter+    , digit+    , hexDigit+    , octDigit++    -- * Numbers+    , decimal+    , signed++    -- * Combinators+    , prefix+    , suffix+    , has+    , begins+    , ends+    , contains+    , invert+    , once+    , star+    , plus+    , selfless+    , choice+    , count+    , lowerBounded+    , upperBounded+    , bounded+    , option+    , between+    , skip+    , within+    , fixed+    , sepBy+    , sepBy1++    -- * High-efficiency primitives+    , chars+    , chars1+    ) where++import Control.Applicative+import Control.Monad+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.State+import Data.Char+import Data.List (foldl')+import Data.Monoid+import Data.String (IsString(..))+import Data.Text (Text)+import qualified Data.Text as Text+import Prelude -- Fix redundant import warnings++-- | 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++-- | Pattern forms a semiring, this is the closest approximation+instance Monoid a => Num (Pattern a) where+    fromInteger n = Pattern (lift (replicate (fromInteger n) mempty))+    (+) = (<|>)+    (*) = (<>)++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 a specific string in a case-insensitive way++    This only handles ASCII strings++>>> match (asciiCI "abc") "ABC"+["ABC"]+-}+asciiCI :: Text -> Pattern Text+asciiCI before' = Pattern (do+    txt <- get+    let (before, after) = Text.splitAt (Text.length before') txt+    guard (lowerChars before == lowerChars before')+    put after+    return before )+  where+    lowerChars = Text.map lowerChar+    lowerChar c | 'A' <= c && c <= 'Z' = chr (ord c + ord 'a' - ord 'A')+                | otherwise            = c++{-| 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++{-| @(`invert` p)@ succeeds if @p@ fails and fails if @p@ succeeds++>>> match (invert "A") "A"+[]+>>> match (invert "A") "B"+[()]+-}+invert :: Pattern a -> Pattern ()+invert p = Pattern (StateT (\str -> case runStateT (runPattern p) str of+    [] -> [((), "")]+    _  -> [] ))++{-| 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 <* chars++{-| Use this to match the suffix of a string++>>> match         "C"  "ABC"+[]+>>> match (suffix "C") "ABC"+["C"]+-}+suffix :: Pattern a -> Pattern a+suffix p = chars *> 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 = chars *> p <* chars++{-| Match the entire string if it begins with the given pattern++    This returns the entire string, not just the matched prefix++>>> match (begins  "A"             ) "ABC"+["ABC"]+>>> match (begins ("A" *> pure "1")) "ABC"+["1BC"]+-}+begins :: Pattern Text -> Pattern Text+begins pattern = pattern <> chars++{-| Match the entire string if it ends with the given pattern++    This returns the entire string, not just the matched prefix++>>> match (ends  "C"             ) "ABC"+["ABC"]+>>> match (ends ("C" *> pure "1")) "ABC"+["AB1"]+-}+ends :: Pattern Text -> Pattern Text+ends pattern = chars <> pattern++{-| Match the entire string if it contains the given pattern++    This returns the entire string, not just the interior pattern++>>> match (contains  "B"             ) "ABC"+["ABC"]+>>> match (contains ("B" *> pure "1")) "ABC"+["A1C"]+-}+contains :: Pattern Text -> Pattern Text+contains pattern = chars <> pattern <> chars++{-| Parse 0 or more occurrences of the given character++>>> match (star anyChar) "123"+["123"]+>>> match (star anyChar) ""+[""]++    See also: `chars`+-}+star :: Pattern Char -> Pattern Text+star p = fmap Text.pack (many p)++{-| Parse 1 or more occurrences of the given character++>>> match (plus digit) "123"+["123"]+>>> match (plus digit) ""+[]++    See also: `chars1`+-}+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++{-| Apply the given pattern at least the given number of times, collecting the+    results++>>> match (lowerBounded 5 dot) "123"+[]+>>> match (lowerBounded 2 dot) "123"+["123"]+-}+lowerBounded :: Int -> Pattern a -> Pattern [a]+lowerBounded n p = do+    ps1 <- count n p+    ps2 <- many p+    return (ps1 ++ ps2)++{-| Apply the given pattern 0 or more times, up to a given bound,+    collecting the results++>>> match (upperBounded 5 dot) "123"+["123"]+>>> match (upperBounded 2 dot) "123"+[]+>>> match ((,) <$> upperBounded 2 dot <*> chars) "123"+[("12","3"),("1","23")]+-}+upperBounded :: Int -> Pattern a -> Pattern [a]+upperBounded n p+    | n <= 0 = mempty+    | n == 1 = fmap pure p+    | otherwise = (:) <$> p <*> option (upperBounded (n - 1) p)++{-| Apply the given pattern a number of times restricted by given+    lower and upper bounds, collecting the results++>>> match (bounded 2 5 "cat") "catcatcat"+[["cat","cat","cat"]]+>>> match (bounded 2 5 "cat") "cat"+[]+>>> match (bounded 2 5 "cat") "catcatcatcatcatcat"+[]++`bounded` could be implemented naively as follows:++> bounded m n p = do+>   x <- choice (map pure [m..n])+>   count x p++-}+bounded :: Int -> Int -> Pattern a -> Pattern [a]+bounded m n p+    | m == n = count m p+    | m  < n = (++) <$> count m p <*> option (upperBounded (n - m) p)+    | otherwise = mzero++{-| 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) ++-- | Like @star dot@ or @star anyChar@, except more efficient+chars :: Pattern Text+chars = Pattern (StateT (\txt ->+    reverse (zip (Text.inits txt) (Text.tails txt)) ))++-- | Like @plus dot@ or @plus anyChar@, except more efficient+chars1 :: Pattern Text+chars1 = Text.cons <$> dot <*> chars
src/Turtle/Prelude.hs view
@@ -1,1308 +1,1461 @@-{-# LANGUAGE CPP                        #-}
-{-# LANGUAGE OverloadedStrings          #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
--- | 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 (suffix "Browser.py") "/usr/lib")
--- FilePath "/usr/lib/python3.4/idlelib/ClassBrowser.py"
--- FilePath "/usr/lib/python3.4/idlelib/RemoteObjectBrowser.py"
--- FilePath "/usr/lib/python3.4/idlelib/PathBrowser.py"
--- FilePath "/usr/lib/python3.4/idlelib/ObjectBrowser.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 (suffix "Browser.py") "/usr/lib") Fold.head
--- Just (FilePath "/usr/lib/python3.4/idlelib/ClassBrowser.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
---
--- Format strings in a type safe way using `format`:
---
--- >>> dir <- pwd
--- >>> format ("I am in the "%fp%" directory") dir
--- "I am in the /tmp directory"
---
--- 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
-    , procStrict
-    , shellStrict
-    , echo
-    , err
-    , readline
-    , arguments
-#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
-    , testfile
-    , testdir
-    , testpath
-    , date
-    , datefile
-    , touch
-    , time
-    , hostname
-    , sleep
-    , exit
-    , die
-    , (.&&.)
-    , (.||.)
-
-    -- * Managed
-    , readonly
-    , writeonly
-    , appendonly
-    , mktemp
-    , mktempfile
-    , mktempdir
-    , fork
-    , wait
-
-    -- * Shell
-    , inproc
-    , inshell
-    , stdin
-    , input
-    , inhandle
-    , stdout
-    , output
-    , outhandle
-    , append
-    , stderr
-    , strict
-    , ls
-    , lsif
-    , lstree
-    , cat
-    , grep
-    , sed
-    , find
-    , yes
-    , nl
-    , paste
-    , endless
-    , limit
-    , limitWhile
-    , cache
-
-    -- * Folds
-    , countChars
-    , countWords
-    , countLines
-
-    -- * Text
-    , cut
-
-    -- * Permissions
-    , Permissions
-    , chmod
-    , getmod
-    , setmod
-    , readable, nonreadable
-    , writable, nonwritable
-    , executable, nonexecutable
-    , searchable, nonsearchable
-    , ooo,roo,owo,oox,oos,rwo,rox,ros,owx,rwx,rws
-
-    -- * File size
-    , du
-    , Size
-    , bytes
-    , kilobytes
-    , megabytes
-    , gigabytes
-    , terabytes
-    , kibibytes
-    , mebibytes
-    , gibibytes
-    , tebibytes
-    ) where
-
-import Control.Applicative
-import Control.Concurrent (threadDelay)
-import Control.Concurrent.Async (Async, withAsync, wait, concurrently)
-import Control.Concurrent.MVar (newMVar, modifyMVar_)
-import qualified Control.Concurrent.STM as STM
-import Control.Exception (bracket, throwIO)
-import Control.Foldl (Fold, FoldM(..), genericLength, handles, list, premap)
-import qualified Control.Foldl.Text
-import Control.Monad (liftM, msum, when, unless)
-import Control.Monad.IO.Class (MonadIO(..))
-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 Data.Traversable
-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 GHC.IO.Exception (IOErrorType(UnsupportedOperation))
-import Network.HostName (getHostName)
-import System.Clock (Clock(..), TimeSpec(..), getTime)
-import System.Environment (
-    getArgs,
-#if MIN_VERSION_base(4,7,0)
-    setEnv,
-    unsetEnv,
-#endif
-#if MIN_VERSION_base(4,6,0)
-    lookupEnv,
-#endif
-    getEnvironment )
-import System.Directory (Permissions)
-import qualified System.Directory as Directory
-import System.Exit (ExitCode(..), exitWith)
-import System.IO (Handle, hClose)
-import qualified System.IO as IO
-import System.IO.Temp (withTempDirectory, withTempFile)
-import System.IO.Error (catchIOError, ioeGetErrorType)
-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, chars, match, selfless, sepBy)
-import Turtle.Shell
-import Turtle.Format (format, w, (%))
-
-{-| Run a command using @execvp@, retrieving the exit code
-
-    The command inherits @stdout@ and @stderr@ for the current process
--}
-proc
-    :: MonadIO io
-    => 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
-    :: MonadIO io
-    => Text
-    -- ^ Command line
-    -> Shell Text
-    -- ^ Lines of standard input
-    -> io ExitCode
-    -- ^ Exit code
-shell cmdLine = system (Process.shell (unpack cmdLine))
-
-{-| Run a command using @execvp@, retrieving the exit code and stdout as a
-    non-lazy blob of Text
-
-    The command inherits @stderr@ for the current process
--}
-procStrict
-    :: MonadIO io
-    => Text
-    -- ^ Command
-    -> [Text]
-    -- ^ Arguments
-    -> Shell Text
-    -- ^ Lines of standard input
-    -> io (ExitCode, Text)
-    -- ^ Exit code and stdout
-procStrict cmd args =
-    systemStrict (Process.proc (Text.unpack cmd) (map Text.unpack args))
-
-{-| Run a command line using the shell, retrieving the exit code and stdout as a
-    non-lazy blob of Text
-
-    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 @stderr@ for the current process
--}
-shellStrict
-    :: MonadIO io
-    => Text
-    -- ^ Command line
-    -> Shell Text
-    -- ^ Lines of standard input
-    -> io (ExitCode, Text)
-    -- ^ Exit code and stdout
-shellStrict cmdLine = systemStrict (Process.shell (Text.unpack cmdLine))
-
-system
-    :: MonadIO io
-    => Process.CreateProcess
-    -- ^ Command
-    -> Shell Text
-    -- ^ Lines of standard input
-    -> io ExitCode
-    -- ^ Exit code
-system p s = liftIO (do
-    let p' = p
-            { Process.std_in  = Process.CreatePipe
-            , Process.std_out = Process.Inherit
-            , Process.std_err = Process.Inherit
-            }
-
-    let open = do
-            (Just hIn, Nothing, Nothing, ph) <- Process.createProcess p'
-            IO.hSetBuffering hIn IO.LineBuffering
-            return (hIn, ph)
-
-    -- Prevent double close
-    mvar <- newMVar False
-    let close handle = do
-            modifyMVar_ mvar (\finalized -> do
-                unless finalized (hClose handle)
-                return True )
-
-    bracket open (\(hIn, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, ph) -> do
-        let feedIn = do
-                sh (do
-                    txt <- s
-                    liftIO (Text.hPutStrLn hIn txt) )
-                close hIn
-        withAsync feedIn (\a -> liftIO (Process.waitForProcess ph) <* wait a) ) )
-
-systemStrict
-    :: MonadIO io
-    => Process.CreateProcess
-    -- ^ Command
-    -> Shell Text
-    -- ^ Lines of standard input
-    -> io (ExitCode, Text)
-    -- ^ Exit code and stdout
-systemStrict p s = liftIO (do
-    let p' = p
-            { Process.std_in  = Process.CreatePipe
-            , Process.std_out = Process.CreatePipe
-            , Process.std_err = Process.Inherit
-            }
-
-    let open = do
-            (Just hIn, Just hOut, Nothing, ph) <- liftIO (Process.createProcess p')
-            IO.hSetBuffering hIn IO.LineBuffering
-            return (hIn, hOut, ph)
-
-    -- Prevent double close
-    mvar <- newMVar False
-    let close handle = do
-            modifyMVar_ mvar (\finalized -> do
-                unless finalized (hClose handle)
-                return True )
-
-    bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, hOut, ph) -> do
-        let feedIn = do
-                sh (do
-                    txt <- s
-                    liftIO (Text.hPutStrLn hIn txt) )
-                close hIn
-
-        concurrently
-            (withAsync feedIn (\a -> liftIO (Process.waitForProcess ph) <* wait a))
-            (Text.hGetContents hOut) ) )
-
-{-| 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
-            }
-
-    let open = do
-            (Just hIn, Just hOut, Nothing, ph) <- liftIO (Process.createProcess p')
-            IO.hSetBuffering hIn IO.LineBuffering
-            return (hIn, hOut, ph)
-
-    -- Prevent double close
-    mvar <- liftIO (newMVar False)
-    let close handle = do
-            modifyMVar_ mvar (\finalized -> do
-                unless finalized (hClose handle)
-                return True )
-
-    (hIn, hOut, ph) <- using (managed (bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph)))
-    let feedIn = do
-            sh (do
-                txt <- s
-                liftIO (Text.hPutStrLn hIn txt) )
-            close hIn
-
-    a <- using (fork feedIn)
-    inhandle hOut <|> (liftIO (Process.waitForProcess ph *> wait a) *> empty)
-
--- | Print to @stdout@
-echo :: MonadIO io => Text -> io ()
-echo txt = liftIO (Text.putStrLn txt)
-
--- | Print to @stderr@
-err :: MonadIO io => Text -> io ()
-err txt = liftIO (Text.hPutStrLn IO.stderr txt)
-
-{-| Read in a line from @stdin@
-
-    Returns `Nothing` if at end of input
--}
-readline :: MonadIO io => io (Maybe Text)
-readline = liftIO (do
-    eof <- IO.isEOF
-    if eof
-        then return Nothing
-        else fmap (Just . pack) getLine )
-
--- | Get command line arguments in a list
-arguments :: MonadIO io => io [Text]
-arguments = liftIO (fmap (map pack) getArgs)
-
-#if MIN_VERSION_base(4,7,0)
--- | Set or modify an environment variable
-export :: MonadIO io => Text -> Text -> io ()
-export key val = liftIO (setEnv (unpack key) (unpack val))
-
--- | Delete an environment variable
-unset :: MonadIO io => Text -> io ()
-unset key = liftIO (unsetEnv (unpack key))
-#endif
-
-#if MIN_VERSION_base(4,6,0)
--- | Look up an environment variable
-need :: MonadIO io => Text -> io (Maybe Text)
-need key = liftIO (fmap (fmap pack) (lookupEnv (unpack key)))
-#endif
-
--- | Retrieve all environment variables
-env :: MonadIO io => io [(Text, Text)]
-env = liftIO (fmap (fmap toTexts) getEnvironment)
-  where
-    toTexts (key, val) = (pack key, pack val)
-
--- | Change the current directory
-cd :: MonadIO io => FilePath -> io ()
-cd path = liftIO (Filesystem.setWorkingDirectory path)
-
--- | Get the current directory
-pwd :: MonadIO io => io FilePath
-pwd = liftIO Filesystem.getWorkingDirectory
-
--- | Get the home directory
-home :: MonadIO io => io FilePath
-home = liftIO Filesystem.getHomeDirectory
-
--- | Canonicalize a path
-realpath :: MonadIO io => FilePath -> io FilePath
-realpath path = liftIO (Filesystem.canonicalizePath path)
-
-#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
-         Directory.readable
-        (Directory.getPermissions (deslash 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
-
-{-| This is used to remove the trailing slash from a path, because
-    `getPermissions` will fail if a path ends with a trailing slash
--}
-deslash :: String -> String
-deslash []     = []
-deslash (c0:cs0) = c0:go cs0
-  where
-    go []     = []
-    go ['\\'] = []
-    go (c:cs) = c:go cs
-
--- | 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
-
-{-| Stream all recursive descendents of the given directory
-
-    This skips any directories that fail the supplied predicate
-
-> lstree = lsif (\_ -> return True)
--}
-lsif :: (FilePath -> IO Bool) -> FilePath -> Shell FilePath
-lsif predicate path = do
-    child <- ls path
-    isDir <- liftIO (testdir child)
-    if isDir
-        then do
-            continue <- liftIO (predicate child)
-            if continue
-                then return child <|> lsif predicate child
-                else return child
-        else return child
-
-{-| Move a file or directory
-
-    Works if the two paths are on the same filesystem.
-    If not, @mv@ will still work when dealing with a regular file,
-    but the operation will not be atomic
--}
-mv :: MonadIO io => FilePath -> FilePath -> io ()
-mv oldPath newPath = liftIO $ catchIOError (Filesystem.rename oldPath newPath)
-   (\ioe -> if ioeGetErrorType ioe == UnsupportedOperation -- certainly EXDEV
-                then do
-                    Filesystem.copyFile oldPath newPath
-                    Filesystem.removeFile oldPath
-                else ioError ioe)
-
-{-| Create a directory
-
-    Fails if the directory is present
--}
-mkdir :: MonadIO io => FilePath -> io ()
-mkdir path = liftIO (Filesystem.createDirectory False path)
-
-{-| Create a directory tree (equivalent to @mkdir -p@)
-
-    Does not fail if the directory is present
--}
-mktree :: MonadIO io => FilePath -> io ()
-mktree path = liftIO (Filesystem.createTree path)
-
--- | Copy a file
-cp :: MonadIO io => FilePath -> FilePath -> io ()
-cp oldPath newPath = liftIO (Filesystem.copyFile oldPath newPath)
-
--- | Remove a file
-rm :: MonadIO io => FilePath -> io ()
-rm path = liftIO (Filesystem.removeFile path)
-
--- | Remove a directory
-rmdir :: MonadIO io => FilePath -> io ()
-rmdir path = liftIO (Filesystem.removeDirectory path)
-
-{-| Remove a directory tree (equivalent to @rm -r@)
-
-    Use at your own risk
--}
-rmtree :: MonadIO io => FilePath -> io ()
-rmtree path = liftIO (Filesystem.removeTree path)
-
--- | Check if a file exists
-testfile :: MonadIO io => FilePath -> io Bool
-testfile path = liftIO (Filesystem.isFile path)
-
--- | Check if a directory exists
-testdir :: MonadIO io => FilePath -> io Bool
-testdir path = liftIO (Filesystem.isDirectory path)
-
--- | Check if a path exists
-testpath :: MonadIO io => FilePath -> io Bool
-testpath path = do
-  exists <- testfile path
-  if exists
-    then return exists
-    else testdir path
-
-{-| Touch a file, updating the access and modification times to the current time
-
-    Creates an empty file if it does not exist
--}
-touch :: MonadIO io => FilePath -> io ()
-touch file = do
-    exists <- testfile file
-    liftIO (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 )
-
-{-| Update a file or directory's user permissions
-
-> chmod rwo        "foo.txt"  -- chmod u=rw foo.txt
-> chmod executable "foo.txt"  -- chmod u+x foo.txt
-> chmod nonwritable "foo.txt" -- chmod u-x foo.txt
--}
-chmod
-    :: MonadIO io
-    => (Permissions -> Permissions)
-    -- ^ Permissions update function
-    -> FilePath
-    -- ^ Path
-    -> io Permissions
-    -- ^ Updated permissions
-chmod modifyPermissions path = liftIO (do
-    let path' = deslash (Filesystem.encodeString path)
-    permissions <- Directory.getPermissions path'
-    let permissions' = modifyPermissions permissions
-        changed = permissions /= permissions'
-    when changed (Directory.setPermissions path' permissions')
-    return permissions' )
-
--- | Get a file or directory's user permissions
-getmod :: MonadIO io => FilePath -> io Permissions
-getmod path = liftIO (do
-    let path' = deslash (Filesystem.encodeString path)
-    Directory.getPermissions path' )
-
--- | Set a file or directory's user permissions
-setmod :: MonadIO io => Permissions -> FilePath -> io ()
-setmod permissions path = liftIO (do
-    let path' = deslash (Filesystem.encodeString path)
-    Directory.setPermissions path' permissions )
-
--- | @+r@
-readable :: Permissions -> Permissions
-readable = Directory.setOwnerReadable True
-
--- | @-r@
-nonreadable :: Permissions -> Permissions
-nonreadable = Directory.setOwnerReadable False
-
--- | @+w@
-writable :: Permissions -> Permissions
-writable = Directory.setOwnerWritable True
-
--- | @-w@
-nonwritable :: Permissions -> Permissions
-nonwritable = Directory.setOwnerWritable False
-
--- | @+x@
-executable :: Permissions -> Permissions
-executable = Directory.setOwnerExecutable True
-
--- | @-x@
-nonexecutable :: Permissions -> Permissions
-nonexecutable = Directory.setOwnerExecutable False
-
--- | @+s@
-searchable :: Permissions -> Permissions
-searchable = Directory.setOwnerSearchable True
-
--- | @-s@
-nonsearchable :: Permissions -> Permissions
-nonsearchable = Directory.setOwnerSearchable False
-
--- | @-r -w -x@
-ooo :: Permissions -> Permissions
-ooo = const Directory.emptyPermissions
-
--- | @+r -w -x@
-roo :: Permissions -> Permissions
-roo = readable . ooo
-
--- | @-r +w -x@
-owo :: Permissions -> Permissions
-owo = writable . ooo
-
--- | @-r -w +x@
-oox :: Permissions -> Permissions
-oox = executable . ooo
-
--- | @-r -w +s@
-oos :: Permissions -> Permissions
-oos = searchable . ooo
-
--- | @+r +w -x@
-rwo :: Permissions -> Permissions
-rwo = readable . writable . ooo
-
--- | @+r -w +x@
-rox :: Permissions -> Permissions
-rox = readable . executable . ooo
-
--- | @+r -w +s@
-ros :: Permissions -> Permissions
-ros = readable . searchable . ooo
-
--- | @-r +w +x@
-owx :: Permissions -> Permissions
-owx = writable . executable . ooo
-
--- | @+r +w +x@
-rwx :: Permissions -> Permissions
-rwx = readable . writable . executable . ooo
-
--- | @+r +w +s@
-rws :: Permissions -> Permissions
-rws = readable . writable . searchable . ooo
-
-{-| Time how long a command takes in monotonic wall clock time
-
-    Returns the duration alongside the return value
--}
-time :: MonadIO io => io a -> io (a, NominalDiffTime)
-time io = do
-    TimeSpec seconds1 nanoseconds1 <- liftIO (getTime Monotonic)
-    a <- io
-    TimeSpec seconds2 nanoseconds2 <- liftIO (getTime Monotonic)
-    let t = fromIntegral (    seconds2 -     seconds1)
-          + fromIntegral (nanoseconds2 - nanoseconds1) / 10^(9::Int)
-    return (a, fromRational t)
-
--- | Get the system's host name
-hostname :: MonadIO io => io Text
-hostname = liftIO (fmap Text.pack getHostName)
-
-{-| 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 :: MonadIO io => NominalDiffTime -> io ()
-sleep n = liftIO (threadDelay (truncate (n * 10^(6::Int))))
-
-{-| Exit with the given exit code
-
-    An exit code of @0@ indicates success
--}
-exit :: MonadIO io => ExitCode -> io a
-exit code = liftIO (exitWith code)
-
--- | Throw an exception using the provided `Text` message
-die :: MonadIO io => Text -> io a
-die txt = liftIO (throwIO (userError (unpack txt)))
-
-infixr 2 .||.
-infixr 3 .&&.
-
-{-| Analogous to `&&` in Bash
-
-    Runs the second command only if the first one returns `ExitSuccess`
--}
-(.&&.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode
-cmd1 .&&. cmd2 = do
-    r <- cmd1
-    case r of
-        ExitSuccess -> cmd2
-        _           -> return r
-
-{-| Analogous to `||` in Bash
-
-    Run the second command only if the first one returns `ExitFailure`
--}
-(.||.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode
-cmd1 .||. cmd2 = do
-    r <- cmd1
-    case r of
-        ExitFailure _ -> cmd2
-        _             -> return r
-
-{-| 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
-
-    Note that this provides the `Handle` of the file in order to avoid a
-    potential race condition from the file being moved or deleted before you
-    have a chance to open the file.  The `mktempfile` function provides a
-    simpler API if you don't need to worry about that possibility.
--}
-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)) )
-    return (Filesystem.decodeString file', handle)
-
-{-| Create a temporary file underneath the given directory
-
-    Deletes the temporary file when done
--}
-mktempfile
-    :: FilePath
-    -- ^ Parent directory
-    -> Text
-    -- ^ File name template
-    -> Managed FilePath
-mktempfile parent prefix = do
-    let parent' = Filesystem.encodeString parent
-    let prefix' = unpack prefix
-    (file', handle) <- managed (\k ->
-        withTempFile parent' prefix' (\file' handle -> k (file', handle)) )
-    liftIO (hClose handle)
-    return (Filesystem.decodeString file')
-
--- | 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 :: MonadIO io => Shell Text -> io ()
-stdout s = sh (do
-    txt <- s
-    liftIO (echo txt) )
-
--- | Stream lines of `Text` to a file
-output :: MonadIO io => 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 a `Handle`
-outhandle :: MonadIO io => Handle -> Shell Text -> io ()
-outhandle handle s = sh (do
-    txt <- s
-    liftIO (Text.hPutStrLn handle txt) )
-
--- | Stream lines of `Text` to append to a file
-append :: MonadIO io => FilePath -> Shell Text -> io ()
-append file s = sh (do
-    handle <- using (appendonly file)
-    txt    <- s
-    liftIO (Text.hPutStrLn handle txt) )
-
--- | Stream lines of `Text` to standard error
-stderr :: MonadIO io => Shell Text -> io ()
-stderr s = sh (do
-    txt <- s
-    liftIO (err txt) )
-
--- | Read in a stream's contents strictly
-strict :: MonadIO io => Shell Text -> io Text
-strict s = liftM Text.unlines (fold s list)
-
--- | Acquire a `Managed` read-only `Handle` from a `FilePath`
-readonly :: FilePath -> Managed Handle
-readonly file = managed (Filesystem.withTextFile file IO.ReadMode)
-
--- | Acquire a `Managed` write-only `Handle` from a `FilePath`
-writeonly :: FilePath -> Managed Handle
-writeonly file = managed (Filesystem.withTextFile file IO.WriteMode)
-
--- | Acquire a `Managed` append-only `Handle` from a `FilePath`
-appendonly :: FilePath -> Managed Handle
-appendonly file = managed (Filesystem.withTextFile 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
-
-    `sed` performs substitution on a line-by-line basis, meaning that
-    substitutions may not span multiple lines.  Additionally, substitutions may
-    occur multiple times within the same line, like the behavior of
-    @s/.../.../g@.
-
-    Warning: Do not use a `Pattern` that matches the empty string, since it will
-    match an infinite number of times.  `sed` tries to detect such `Pattern`s
-    and `die` with an error message if they occur, but this detection is
-    necessarily incomplete.
--}
-sed :: Pattern Text -> Shell Text -> Shell Text
-sed pattern s = do
-    when (matchesEmpty pattern) (die message)
-    let pattern' = fmap Text.concat
-            (many (pattern <|> fmap Text.singleton anyChar))
-    txt    <- s
-    txt':_ <- return (match pattern' txt)
-    return txt'
-  where
-    message = "sed: the given pattern matches the empty string"
-    matchesEmpty = not . null . flip match ""
-
--- | 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 = fmap (\_ -> "y") endless
-
--- | Number each element of a `Shell` (starting at 0)
-nl :: Num n => Shell a -> Shell (n, a)
-nl s = Shell _foldIO'
-  where
-    _foldIO' (FoldM step begin done) = _foldIO s (FoldM step' begin' done')
-      where
-        step' (x, n) a = do
-            x' <- step x (n, a)
-            let n' = n + 1
-            n' `seq` return (x', n')
-        begin' = do
-            x0 <- begin
-            return (x0, 0)
-        done' (x, _) = done x
-
-data ZipState a b = Empty | HasA a | HasAB a b | Done
-
-{-| Merge two `Shell`s together, element-wise
-
-    If one `Shell` is longer than the other, the excess elements are
-    truncated
--}
-paste :: Shell a -> Shell b -> Shell (a, b)
-paste sA sB = Shell _foldIOAB
-  where
-    _foldIOAB (FoldM stepAB beginAB doneAB) = do
-        x0 <- beginAB
-
-        tvar <- STM.atomically (STM.newTVar Empty)
-
-        let begin = return ()
-
-        let stepA () a = STM.atomically (do
-                x <- STM.readTVar tvar
-                case x of
-                    Empty -> STM.writeTVar tvar (HasA a)
-                    Done  -> return ()
-                    _     -> STM.retry )
-        let doneA () = STM.atomically (do
-                x <- STM.readTVar tvar
-                case x of
-                    Empty -> STM.writeTVar tvar Done
-                    Done  -> return ()
-                    _     -> STM.retry )
-        let foldA = FoldM stepA begin doneA
-
-        let stepB () b = STM.atomically (do
-                x <- STM.readTVar tvar
-                case x of
-                    HasA a -> STM.writeTVar tvar (HasAB a b)
-                    Done   -> return ()
-                    _      -> STM.retry )
-        let doneB () = STM.atomically (do
-                x <- STM.readTVar tvar
-                case x of
-                    HasA _ -> STM.writeTVar tvar Done
-                    Done   -> return ()
-                    _      -> STM.retry )
-        let foldB = FoldM stepB begin doneB
-
-        withAsync (foldIO sA foldA) (\asyncA -> do
-            withAsync (foldIO sB foldB) (\asyncB -> do
-                let loop x = do
-                        y <- STM.atomically (do
-                            z <- STM.readTVar tvar
-                            case z of
-                                HasAB a b -> do
-                                    STM.writeTVar tvar Empty
-                                    return (Just (a, b))
-                                Done      -> return  Nothing
-                                _         -> STM.retry )
-                        case y of
-                            Nothing -> return x
-                            Just ab -> do
-                                x' <- stepAB x ab
-                                loop $! x'
-                x' <- loop $! x0
-                wait asyncA
-                wait asyncB
-                doneAB x' ) )
-
--- | A `Shell` that endlessly emits @()@
-endless :: Shell ()
-endless = Shell (\(FoldM step begin _) -> do
-    x0 <- begin
-    let loop x = do
-            x' <- step x ()
-            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) )
-
-{-| Cache a `Shell`'s output so that repeated runs of the script will reuse the
-    result of previous runs.  You must supply a `FilePath` where the cached
-    result will be stored.
-
-    The stored result is only reused if the `Shell` successfully ran to
-    completion without any exceptions.  Note: on some platforms Ctrl-C will
-    flush standard input and signal end of file before killing the program,
-    which may trick the program into \"successfully\" completing.
--}
-cache :: (Read a, Show a) => FilePath -> Shell a -> Shell a
-cache file s = do
-    let cached = do
-            txt <- input file
-            case reads (Text.unpack txt) of
-                [(ma, "")] -> return ma
-                _          ->
-                    die (format ("cache: Invalid data stored in "%w) file)
-    exists <- testfile file
-    mas    <- fold (if exists then cached else empty) list
-    case [ () | Nothing <- mas ] of
-        _:_ -> select [ a | Just a <- mas ]
-        _   -> do
-            handle <- using (writeonly file)
-            let justs = do
-                    a      <- s
-                    liftIO (Text.hPutStrLn handle (Text.pack (show (Just a))))
-                    return a
-            let nothing = do
-                    let n = Nothing :: Maybe ()
-                    liftIO (Text.hPutStrLn handle (Text.pack (show n)))
-                    empty
-            justs <|> nothing
-
--- | Split a line into chunks delimited by the given `Pattern`
-cut :: Pattern a -> Text -> [Text]
-cut pattern txt = head (match (selfless chars `sepBy` pattern) txt)
--- This `head` should be safe ... in theory
-
--- | Get the current time
-date :: MonadIO io => io UTCTime
-date = liftIO getCurrentTime
-
--- | Get the time a file was last modified
-datefile :: MonadIO io => FilePath -> io UTCTime
-datefile path = liftIO (Filesystem.getModified path)
-
--- | Get the size of a file or a directory
-du :: MonadIO io => FilePath -> io Size
-du path = liftIO (fmap Size (Filesystem.getSize path))
-
-{-| An abstract file size
-
-    Specify the units you want by using an accessor like `kilobytes`
-
-    The `Num` instance for `Size` interprets numeric literals as bytes
--}
-newtype Size = Size { _bytes :: Integer } deriving (Num)
-
-instance Show Size where
-    show = show . _bytes
-
--- | Extract a size in bytes
-bytes :: Integral n => Size -> n
-bytes = fromInteger . _bytes
-
--- | @1 kilobyte = 1000 bytes@
-kilobytes :: Integral n => Size -> n
-kilobytes = (`div` 1000) . bytes
-
--- | @1 megabyte = 1000 kilobytes@
-megabytes :: Integral n => Size -> n
-megabytes = (`div` 1000) . kilobytes
-
--- | @1 gigabyte = 1000 megabytes@
-gigabytes :: Integral n => Size -> n
-gigabytes = (`div` 1000) . megabytes
-
--- | @1 terabyte = 1000 gigabytes@
-terabytes :: Integral n => Size -> n
-terabytes = (`div` 1000) . gigabytes
-
--- | @1 kibibyte = 1024 bytes@
-kibibytes :: Integral n => Size -> n
-kibibytes = (`div` 1024) . bytes
-
--- | @1 mebibyte = 1024 kibibytes@
-mebibytes :: Integral n => Size -> n
-mebibytes = (`div` 1024) . kibibytes
-
--- | @1 gibibyte = 1024 mebibytes@
-gibibytes :: Integral n => Size -> n
-gibibytes = (`div` 1024) . mebibytes
-
--- | @1 tebibyte = 1024 gibibytes@
-tebibytes :: Integral n => Size -> n
-tebibytes = (`div` 1024) . gibibytes
-
-{-| Count the number of characters in the stream (like @wc -c@)
-
-    This uses the convention that the elements of the stream are implicitly
-    ended by newlines that are one character wide
--}
-countChars :: Integral n => Fold Text n
-countChars = Control.Foldl.Text.length + charsPerNewline * countLines
-
-charsPerNewline :: Num a => a
-#ifdef mingw32_HOST_OS
-charsPerNewline = 2
-#else
-charsPerNewline = 1
-#endif
-
--- | Count the number of words in the stream (like @wc -w@)
-countWords :: Integral n => Fold Text n
-countWords = premap Text.words (handles traverse genericLength)
-
-{-| Count the number of lines in the stream (like @wc -l@)
-
-    This uses the convention that each element of the stream represents one
-    line
--}
-countLines :: Integral n => Fold Text n
-countLines = genericLength
+{-# LANGUAGE CPP                        #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes                 #-}++-- | 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 (suffix "Browser.py") "/usr/lib")+-- FilePath "/usr/lib/python3.4/idlelib/ClassBrowser.py"+-- FilePath "/usr/lib/python3.4/idlelib/RemoteObjectBrowser.py"+-- FilePath "/usr/lib/python3.4/idlelib/PathBrowser.py"+-- FilePath "/usr/lib/python3.4/idlelib/ObjectBrowser.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 (suffix "Browser.py") "/usr/lib") Fold.head+-- Just (FilePath "/usr/lib/python3.4/idlelib/ClassBrowser.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+--+-- Format strings in a type safe way using `format`:+--+-- >>> dir <- pwd+-- >>> format ("I am in the "%fp%" directory") dir+-- "I am in the /tmp directory"+--+-- 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+    , procStrict+    , shellStrict+    , echo+    , err+    , readline+    , arguments+#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+    , testfile+    , testdir+    , testpath+    , date+    , datefile+    , touch+    , time+    , hostname+    , sleep+    , exit+    , die+    , (.&&.)+    , (.||.)++    -- * Managed+    , readonly+    , writeonly+    , appendonly+    , mktemp+    , mktempfile+    , mktempdir+    , fork+    , wait++    -- * Shell+    , inproc+    , inshell+    , inprocWithErr+    , inshellWithErr+    , stdin+    , input+    , inhandle+    , stdout+    , output+    , outhandle+    , append+    , stderr+    , strict+    , ls+    , lsif+    , lstree+    , cat+    , grep+    , sed+    , inplace+    , find+    , yes+    , nl+    , paste+    , endless+    , limit+    , limitWhile+    , cache++    -- * Folds+    , countChars+    , countWords+    , countLines++    -- * Text+    , cut++    -- * Permissions+    , Permissions+    , chmod+    , getmod+    , setmod+    , readable, nonreadable+    , writable, nonwritable+    , executable, nonexecutable+    , searchable, nonsearchable+    , ooo,roo,owo,oox,oos,rwo,rox,ros,owx,rwx,rws++    -- * File size+    , du+    , Size+    , sz+    , bytes+    , kilobytes+    , megabytes+    , gigabytes+    , terabytes+    , kibibytes+    , mebibytes+    , gibibytes+    , tebibytes+    ) where++import Control.Applicative+import Control.Concurrent (threadDelay)+import Control.Concurrent.Async+    (Async, withAsync, withAsyncWithUnmask, wait, waitSTM, concurrently)+import Control.Concurrent.MVar (newMVar, modifyMVar_)+import qualified Control.Concurrent.STM as STM+import qualified Control.Concurrent.STM.TQueue as TQueue+import Control.Exception (bracket, finally, mask_, throwIO)+import Control.Foldl (Fold, FoldM(..), genericLength, handles, list, premap)+import qualified Control.Foldl.Text+import Control.Monad (liftM, msum, when, unless)+import Control.Monad.IO.Class (MonadIO(..))+import Control.Monad.Managed (Managed, managed, runManaged)+#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 Data.Traversable+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 GHC.IO.Exception (IOErrorType(UnsupportedOperation))+import Network.HostName (getHostName)+import System.Clock (Clock(..), TimeSpec(..), getTime)+import System.Environment (+    getArgs,+#if MIN_VERSION_base(4,7,0)+    setEnv,+    unsetEnv,+#endif+#if MIN_VERSION_base(4,6,0)+    lookupEnv,+#endif+    getEnvironment )+import System.Directory (Permissions)+import qualified System.Directory as Directory+import System.Exit (ExitCode(..), exitWith)+import System.IO (Handle, hClose)+import qualified System.IO as IO+import System.IO.Temp (withTempDirectory, withTempFile)+import System.IO.Error (catchIOError, ioeGetErrorType)+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, chars, match, selfless, sepBy)+import Turtle.Shell+import Turtle.Format (Format, format, makeFormat, d, w, (%))++{-| Run a command using @execvp@, retrieving the exit code++    The command inherits @stdout@ and @stderr@ for the current process+-}+proc+    :: MonadIO io+    => 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+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell Text+    -- ^ Lines of standard input+    -> io ExitCode+    -- ^ Exit code+shell cmdLine = system (Process.shell (unpack cmdLine))++{-| Run a command using @execvp@, retrieving the exit code and stdout as a+    non-lazy blob of Text++    The command inherits @stderr@ for the current process+-}+procStrict+    :: MonadIO io+    => Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell Text+    -- ^ Lines of standard input+    -> io (ExitCode, Text)+    -- ^ Exit code and stdout+procStrict cmd args =+    systemStrict (Process.proc (Text.unpack cmd) (map Text.unpack args))++{-| Run a command line using the shell, retrieving the exit code and stdout as a+    non-lazy blob of Text++    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 @stderr@ for the current process+-}+shellStrict+    :: MonadIO io+    => Text+    -- ^ Command line+    -> Shell Text+    -- ^ Lines of standard input+    -> io (ExitCode, Text)+    -- ^ Exit code and stdout+shellStrict cmdLine = systemStrict (Process.shell (Text.unpack cmdLine))++system+    :: MonadIO io+    => Process.CreateProcess+    -- ^ Command+    -> Shell Text+    -- ^ Lines of standard input+    -> io ExitCode+    -- ^ Exit code+system p s = liftIO (do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.Inherit+            , Process.std_err = Process.Inherit+            }++    let open = do+            (Just hIn, Nothing, Nothing, ph) <- Process.createProcess p'+            IO.hSetBuffering hIn IO.LineBuffering+            return (hIn, ph)++    -- Prevent double close+    mvar <- newMVar False+    let close handle = do+            modifyMVar_ mvar (\finalized -> do+                unless finalized (hClose handle)+                return True )++    bracket open (\(hIn, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, ph) -> do+        let feedIn :: (forall a. IO a -> IO a) -> IO ()+            feedIn restore =+                restore (sh (do+                    txt <- s+                    liftIO (Text.hPutStrLn hIn txt) ) )+                `finally` close hIn+        mask_ (withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* wait a) ) ) )++systemStrict+    :: MonadIO io+    => Process.CreateProcess+    -- ^ Command+    -> Shell Text+    -- ^ Lines of standard input+    -> io (ExitCode, Text)+    -- ^ Exit code and stdout+systemStrict p s = liftIO (do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.Inherit+            }++    let open = do+            (Just hIn, Just hOut, Nothing, ph) <- liftIO (Process.createProcess p')+            IO.hSetBuffering hIn IO.LineBuffering+            return (hIn, hOut, ph)++    -- Prevent double close+    mvar <- newMVar False+    let close handle = do+            modifyMVar_ mvar (\finalized -> do+                unless finalized (hClose handle)+                return True )++    bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph) (\(hIn, hOut, ph) -> do+        let feedIn :: (forall a. IO a -> IO a) -> IO ()+            feedIn restore =+                restore (sh (do+                    txt <- s+                    liftIO (Text.hPutStrLn hIn txt) ) )+                `finally` close hIn++        concurrently+            (mask_ (withAsyncWithUnmask feedIn (\a -> liftIO (Process.waitForProcess ph) <* wait a)))+            (Text.hGetContents hOut) ) )++{-| 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+            }++    let open = do+            (Just hIn, Just hOut, Nothing, ph) <- liftIO (Process.createProcess p')+            IO.hSetBuffering hIn IO.LineBuffering+            return (hIn, hOut, ph)++    -- Prevent double close+    mvar <- liftIO (newMVar False)+    let close handle = do+            modifyMVar_ mvar (\finalized -> do+                unless finalized (hClose handle)+                return True )++    (hIn, hOut, ph) <- using (managed (bracket open (\(hIn, _, ph) -> close hIn >> Process.terminateProcess ph)))+    let feedIn :: (forall a. IO a -> IO a) -> IO ()+        feedIn restore =+            restore (sh (do+                txt <- s+                liftIO (Text.hPutStrLn hIn txt) ) )+            `finally` close hIn++    a <- using (managed (mask_ . withAsyncWithUnmask feedIn))+    inhandle hOut <|> (liftIO (Process.waitForProcess ph *> wait a) *> empty)++streamWithErr+    :: Process.CreateProcess+    -- ^ Command+    -> Shell Text+    -- ^ Lines of standard input+    -> Shell (Either Text Text)+    -- ^ Lines of standard output+streamWithErr p s = do+    let p' = p+            { Process.std_in  = Process.CreatePipe+            , Process.std_out = Process.CreatePipe+            , Process.std_err = Process.CreatePipe+            }++    let open = do+            (Just hIn, Just hOut, Just hErr, ph) <- liftIO (Process.createProcess p')+            IO.hSetBuffering hIn IO.LineBuffering+            return (hIn, hOut, hErr, ph)++    -- Prevent double close+    mvar <- liftIO (newMVar False)+    let close handle = do+            modifyMVar_ mvar (\finalized -> do+                unless finalized (hClose handle)+                return True )++    (hIn, hOut, hErr, ph) <- using (managed (bracket open (\(hIn, _, _, ph) -> close hIn >> Process.terminateProcess ph)))+    let feedIn :: (forall a. IO a -> IO a) -> IO ()+        feedIn restore =+            restore (sh (do+                txt <- s+                liftIO (Text.hPutStrLn hIn txt) ) )+            `finally` close hIn++    queue <- liftIO TQueue.newTQueueIO+    let forwardOut :: (forall a. IO a -> IO a) -> IO ()+        forwardOut restore =+            restore (sh (do+                txt <- inhandle hOut+                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Right txt)))) ))+            `finally` STM.atomically (TQueue.writeTQueue queue Nothing)+    let forwardErr :: (forall a. IO a -> IO a) -> IO ()+        forwardErr restore =+            restore (sh (do+                txt <- inhandle hErr+                liftIO (STM.atomically (TQueue.writeTQueue queue (Just (Left  txt)))) ))+            `finally` STM.atomically (TQueue.writeTQueue queue Nothing)+    let drain = Shell (\(FoldM step begin done) -> do+            x0 <- begin+            let loop x numNothing+                    | numNothing < 2 = do+                        m <- STM.atomically (TQueue.readTQueue queue)+                        case m of+                            Nothing -> loop x $! numNothing + 1+                            Just e  -> do+                                x' <- step x e+                                loop x' numNothing+                    | otherwise      = return x+            x1 <- loop x0 (0 :: Int)+            done x1 )++    a <- using (managed (mask_ . withAsyncWithUnmask feedIn    ))+    b <- using (managed (mask_ . withAsyncWithUnmask forwardOut))+    c <- using (managed (mask_ . withAsyncWithUnmask forwardErr))+    let l `also` r = do+            _ <- l <|> (r *> STM.retry)+            _ <- r+            return ()+    let waitAll = STM.atomically (waitSTM a `also` (waitSTM b `also` waitSTM c))+    drain <|> (liftIO (Process.waitForProcess ph *> waitAll) *> empty)++{-| Run a command using the shell, streaming @stdout@ and @stderr@ as lines of+    `Text`.  Lines from @stdout@ are wrapped in `Right` and lines from @stderr@+    are wrapped in `Left`.+-}+inprocWithErr+    :: Text+    -- ^ Command+    -> [Text]+    -- ^ Arguments+    -> Shell Text+    -- ^ Lines of standard input+    -> Shell (Either Text Text)+    -- ^ Lines of standard output+inprocWithErr cmd args =+    streamWithErr (Process.proc (unpack cmd) (map unpack args))+++{-| Run a command line using the shell, streaming @stdout@ and @stderr@ as lines+    of `Text`.  Lines from @stdout@ are wrapped in `Right` and lines from+    @stderr@ are wrapped in `Left`.++    This command is more powerful than `inprocWithErr`, but highly vulnerable to+    code injection if you template the command line with untrusted input+-}+inshellWithErr+    :: Text+    -- ^ Command line+    -> Shell Text+    -- ^ Lines of standard input+    -> Shell (Either Text Text)+    -- ^ Lines of standard output+inshellWithErr cmd = streamWithErr (Process.shell (unpack cmd))++-- | Print to @stdout@+echo :: MonadIO io => Text -> io ()+echo txt = liftIO (Text.putStrLn txt)++-- | Print to @stderr@+err :: MonadIO io => Text -> io ()+err txt = liftIO (Text.hPutStrLn IO.stderr txt)++{-| Read in a line from @stdin@++    Returns `Nothing` if at end of input+-}+readline :: MonadIO io => io (Maybe Text)+readline = liftIO (do+    eof <- IO.isEOF+    if eof+        then return Nothing+        else fmap (Just . pack) getLine )++-- | Get command line arguments in a list+arguments :: MonadIO io => io [Text]+arguments = liftIO (fmap (map pack) getArgs)++#if MIN_VERSION_base(4,7,0)+-- | Set or modify an environment variable+export :: MonadIO io => Text -> Text -> io ()+export key val = liftIO (setEnv (unpack key) (unpack val))++-- | Delete an environment variable+unset :: MonadIO io => Text -> io ()+unset key = liftIO (unsetEnv (unpack key))+#endif++#if MIN_VERSION_base(4,6,0)+-- | Look up an environment variable+need :: MonadIO io => Text -> io (Maybe Text)+need key = liftIO (fmap (fmap pack) (lookupEnv (unpack key)))+#endif++-- | Retrieve all environment variables+env :: MonadIO io => io [(Text, Text)]+env = liftIO (fmap (fmap toTexts) getEnvironment)+  where+    toTexts (key, val) = (pack key, pack val)++-- | Change the current directory+cd :: MonadIO io => FilePath -> io ()+cd path = liftIO (Filesystem.setWorkingDirectory path)++-- | Get the current directory+pwd :: MonadIO io => io FilePath+pwd = liftIO Filesystem.getWorkingDirectory++-- | Get the home directory+home :: MonadIO io => io FilePath+home = liftIO Filesystem.getHomeDirectory++-- | Canonicalize a path+realpath :: MonadIO io => FilePath -> io FilePath+realpath path = liftIO (Filesystem.canonicalizePath path)++#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+         Directory.readable+        (Directory.getPermissions (deslash 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++{-| This is used to remove the trailing slash from a path, because+    `getPermissions` will fail if a path ends with a trailing slash+-}+deslash :: String -> String+deslash []     = []+deslash (c0:cs0) = c0:go cs0+  where+    go []     = []+    go ['\\'] = []+    go (c:cs) = c:go cs++-- | 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++{-| Stream all recursive descendents of the given directory++    This skips any directories that fail the supplied predicate++> lstree = lsif (\_ -> return True)+-}+lsif :: (FilePath -> IO Bool) -> FilePath -> Shell FilePath+lsif predicate path = do+    child <- ls path+    isDir <- liftIO (testdir child)+    if isDir+        then do+            continue <- liftIO (predicate child)+            if continue+                then return child <|> lsif predicate child+                else return child+        else return child++{-| Move a file or directory++    Works if the two paths are on the same filesystem.+    If not, @mv@ will still work when dealing with a regular file,+    but the operation will not be atomic+-}+mv :: MonadIO io => FilePath -> FilePath -> io ()+mv oldPath newPath = liftIO $ catchIOError (Filesystem.rename oldPath newPath)+   (\ioe -> if ioeGetErrorType ioe == UnsupportedOperation -- certainly EXDEV+                then do+                    Filesystem.copyFile oldPath newPath+                    Filesystem.removeFile oldPath+                else ioError ioe)++{-| Create a directory++    Fails if the directory is present+-}+mkdir :: MonadIO io => FilePath -> io ()+mkdir path = liftIO (Filesystem.createDirectory False path)++{-| Create a directory tree (equivalent to @mkdir -p@)++    Does not fail if the directory is present+-}+mktree :: MonadIO io => FilePath -> io ()+mktree path = liftIO (Filesystem.createTree path)++-- | Copy a file+cp :: MonadIO io => FilePath -> FilePath -> io ()+cp oldPath newPath = liftIO (Filesystem.copyFile oldPath newPath)++-- | Remove a file+rm :: MonadIO io => FilePath -> io ()+rm path = liftIO (Filesystem.removeFile path)++-- | Remove a directory+rmdir :: MonadIO io => FilePath -> io ()+rmdir path = liftIO (Filesystem.removeDirectory path)++{-| Remove a directory tree (equivalent to @rm -r@)++    Use at your own risk+-}+rmtree :: MonadIO io => FilePath -> io ()+rmtree path = liftIO (Filesystem.removeTree path)++-- | Check if a file exists+testfile :: MonadIO io => FilePath -> io Bool+testfile path = liftIO (Filesystem.isFile path)++-- | Check if a directory exists+testdir :: MonadIO io => FilePath -> io Bool+testdir path = liftIO (Filesystem.isDirectory path)++-- | Check if a path exists+testpath :: MonadIO io => FilePath -> io Bool+testpath path = do+  exists <- testfile path+  if exists+    then return exists+    else testdir path++{-| Touch a file, updating the access and modification times to the current time++    Creates an empty file if it does not exist+-}+touch :: MonadIO io => FilePath -> io ()+touch file = do+    exists <- testfile file+    liftIO (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 )++{-| Update a file or directory's user permissions++> chmod rwo        "foo.txt"  -- chmod u=rw foo.txt+> chmod executable "foo.txt"  -- chmod u+x foo.txt+> chmod nonwritable "foo.txt" -- chmod u-x foo.txt+-}+chmod+    :: MonadIO io+    => (Permissions -> Permissions)+    -- ^ Permissions update function+    -> FilePath+    -- ^ Path+    -> io Permissions+    -- ^ Updated permissions+chmod modifyPermissions path = liftIO (do+    let path' = deslash (Filesystem.encodeString path)+    permissions <- Directory.getPermissions path'+    let permissions' = modifyPermissions permissions+        changed = permissions /= permissions'+    when changed (Directory.setPermissions path' permissions')+    return permissions' )++-- | Get a file or directory's user permissions+getmod :: MonadIO io => FilePath -> io Permissions+getmod path = liftIO (do+    let path' = deslash (Filesystem.encodeString path)+    Directory.getPermissions path' )++-- | Set a file or directory's user permissions+setmod :: MonadIO io => Permissions -> FilePath -> io ()+setmod permissions path = liftIO (do+    let path' = deslash (Filesystem.encodeString path)+    Directory.setPermissions path' permissions )++-- | @+r@+readable :: Permissions -> Permissions+readable = Directory.setOwnerReadable True++-- | @-r@+nonreadable :: Permissions -> Permissions+nonreadable = Directory.setOwnerReadable False++-- | @+w@+writable :: Permissions -> Permissions+writable = Directory.setOwnerWritable True++-- | @-w@+nonwritable :: Permissions -> Permissions+nonwritable = Directory.setOwnerWritable False++-- | @+x@+executable :: Permissions -> Permissions+executable = Directory.setOwnerExecutable True++-- | @-x@+nonexecutable :: Permissions -> Permissions+nonexecutable = Directory.setOwnerExecutable False++-- | @+s@+searchable :: Permissions -> Permissions+searchable = Directory.setOwnerSearchable True++-- | @-s@+nonsearchable :: Permissions -> Permissions+nonsearchable = Directory.setOwnerSearchable False++-- | @-r -w -x@+ooo :: Permissions -> Permissions+ooo = const Directory.emptyPermissions++-- | @+r -w -x@+roo :: Permissions -> Permissions+roo = readable . ooo++-- | @-r +w -x@+owo :: Permissions -> Permissions+owo = writable . ooo++-- | @-r -w +x@+oox :: Permissions -> Permissions+oox = executable . ooo++-- | @-r -w +s@+oos :: Permissions -> Permissions+oos = searchable . ooo++-- | @+r +w -x@+rwo :: Permissions -> Permissions+rwo = readable . writable . ooo++-- | @+r -w +x@+rox :: Permissions -> Permissions+rox = readable . executable . ooo++-- | @+r -w +s@+ros :: Permissions -> Permissions+ros = readable . searchable . ooo++-- | @-r +w +x@+owx :: Permissions -> Permissions+owx = writable . executable . ooo++-- | @+r +w +x@+rwx :: Permissions -> Permissions+rwx = readable . writable . executable . ooo++-- | @+r +w +s@+rws :: Permissions -> Permissions+rws = readable . writable . searchable . ooo++{-| Time how long a command takes in monotonic wall clock time++    Returns the duration alongside the return value+-}+time :: MonadIO io => io a -> io (a, NominalDiffTime)+time io = do+    TimeSpec seconds1 nanoseconds1 <- liftIO (getTime Monotonic)+    a <- io+    TimeSpec seconds2 nanoseconds2 <- liftIO (getTime Monotonic)+    let t = fromIntegral (    seconds2 -     seconds1)+          + fromIntegral (nanoseconds2 - nanoseconds1) / 10^(9::Int)+    return (a, fromRational t)++-- | Get the system's host name+hostname :: MonadIO io => io Text+hostname = liftIO (fmap Text.pack getHostName)++{-| 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 :: MonadIO io => NominalDiffTime -> io ()+sleep n = liftIO (threadDelay (truncate (n * 10^(6::Int))))++{-| Exit with the given exit code++    An exit code of @0@ indicates success+-}+exit :: MonadIO io => ExitCode -> io a+exit code = liftIO (exitWith code)++-- | Throw an exception using the provided `Text` message+die :: MonadIO io => Text -> io a+die txt = liftIO (throwIO (userError (unpack txt)))++infixr 2 .||.+infixr 3 .&&.++{-| Analogous to `&&` in Bash++    Runs the second command only if the first one returns `ExitSuccess`+-}+(.&&.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode+cmd1 .&&. cmd2 = do+    r <- cmd1+    case r of+        ExitSuccess -> cmd2+        _           -> return r++{-| Analogous to `||` in Bash++    Run the second command only if the first one returns `ExitFailure`+-}+(.||.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode+cmd1 .||. cmd2 = do+    r <- cmd1+    case r of+        ExitFailure _ -> cmd2+        _             -> return r++{-| 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++    Note that this provides the `Handle` of the file in order to avoid a+    potential race condition from the file being moved or deleted before you+    have a chance to open the file.  The `mktempfile` function provides a+    simpler API if you don't need to worry about that possibility.+-}+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)) )+    return (Filesystem.decodeString file', handle)++{-| Create a temporary file underneath the given directory++    Deletes the temporary file when done+-}+mktempfile+    :: FilePath+    -- ^ Parent directory+    -> Text+    -- ^ File name template+    -> Managed FilePath+mktempfile parent prefix = do+    let parent' = Filesystem.encodeString parent+    let prefix' = unpack prefix+    (file', handle) <- managed (\k ->+        withTempFile parent' prefix' (\file' handle -> k (file', handle)) )+    liftIO (hClose handle)+    return (Filesystem.decodeString file')++-- | 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 :: MonadIO io => Shell Text -> io ()+stdout s = sh (do+    txt <- s+    liftIO (echo txt) )++-- | Stream lines of `Text` to a file+output :: MonadIO io => 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 a `Handle`+outhandle :: MonadIO io => Handle -> Shell Text -> io ()+outhandle handle s = sh (do+    txt <- s+    liftIO (Text.hPutStrLn handle txt) )++-- | Stream lines of `Text` to append to a file+append :: MonadIO io => FilePath -> Shell Text -> io ()+append file s = sh (do+    handle <- using (appendonly file)+    txt    <- s+    liftIO (Text.hPutStrLn handle txt) )++-- | Stream lines of `Text` to standard error+stderr :: MonadIO io => Shell Text -> io ()+stderr s = sh (do+    txt <- s+    liftIO (err txt) )++-- | Read in a stream's contents strictly+strict :: MonadIO io => Shell Text -> io Text+strict s = liftM Text.unlines (fold s list)++-- | Acquire a `Managed` read-only `Handle` from a `FilePath`+readonly :: FilePath -> Managed Handle+readonly file = managed (Filesystem.withTextFile file IO.ReadMode)++-- | Acquire a `Managed` write-only `Handle` from a `FilePath`+writeonly :: FilePath -> Managed Handle+writeonly file = managed (Filesystem.withTextFile file IO.WriteMode)++-- | Acquire a `Managed` append-only `Handle` from a `FilePath`+appendonly :: FilePath -> Managed Handle+appendonly file = managed (Filesystem.withTextFile 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++    `sed` performs substitution on a line-by-line basis, meaning that+    substitutions may not span multiple lines.  Additionally, substitutions may+    occur multiple times within the same line, like the behavior of+    @s/.../.../g@.++    Warning: Do not use a `Pattern` that matches the empty string, since it will+    match an infinite number of times.  `sed` tries to detect such `Pattern`s+    and `die` with an error message if they occur, but this detection is+    necessarily incomplete.+-}+sed :: Pattern Text -> Shell Text -> Shell Text+sed pattern s = do+    when (matchesEmpty pattern) (die message)+    let pattern' = fmap Text.concat+            (many (pattern <|> fmap Text.singleton anyChar))+    txt    <- s+    txt':_ <- return (match pattern' txt)+    return txt'+  where+    message = "sed: the given pattern matches the empty string"+    matchesEmpty = not . null . flip match ""++-- | Like `sed`, but operates in place on a `FilePath` (analogous to @sed -i@)+inplace :: MonadIO io => Pattern Text -> FilePath -> io ()+inplace pattern file = liftIO (runManaged (do+    here              <- pwd+    (tmpfile, handle) <- mktemp here "turtle"+    outhandle handle (sed pattern (input file))+    liftIO (hClose handle)+    mv tmpfile file ))+++-- | 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 = fmap (\_ -> "y") endless++-- | Number each element of a `Shell` (starting at 0)+nl :: Num n => Shell a -> Shell (n, a)+nl s = Shell _foldIO'+  where+    _foldIO' (FoldM step begin done) = _foldIO s (FoldM step' begin' done')+      where+        step' (x, n) a = do+            x' <- step x (n, a)+            let n' = n + 1+            n' `seq` return (x', n')+        begin' = do+            x0 <- begin+            return (x0, 0)+        done' (x, _) = done x++data ZipState a b = Empty | HasA a | HasAB a b | Done++{-| Merge two `Shell`s together, element-wise++    If one `Shell` is longer than the other, the excess elements are+    truncated+-}+paste :: Shell a -> Shell b -> Shell (a, b)+paste sA sB = Shell _foldIOAB+  where+    _foldIOAB (FoldM stepAB beginAB doneAB) = do+        x0 <- beginAB++        tvar <- STM.atomically (STM.newTVar Empty)++        let begin = return ()++        let stepA () a = STM.atomically (do+                x <- STM.readTVar tvar+                case x of+                    Empty -> STM.writeTVar tvar (HasA a)+                    Done  -> return ()+                    _     -> STM.retry )+        let doneA () = STM.atomically (do+                x <- STM.readTVar tvar+                case x of+                    Empty -> STM.writeTVar tvar Done+                    Done  -> return ()+                    _     -> STM.retry )+        let foldA = FoldM stepA begin doneA++        let stepB () b = STM.atomically (do+                x <- STM.readTVar tvar+                case x of+                    HasA a -> STM.writeTVar tvar (HasAB a b)+                    Done   -> return ()+                    _      -> STM.retry )+        let doneB () = STM.atomically (do+                x <- STM.readTVar tvar+                case x of+                    HasA _ -> STM.writeTVar tvar Done+                    Done   -> return ()+                    _      -> STM.retry )+        let foldB = FoldM stepB begin doneB++        withAsync (foldIO sA foldA) (\asyncA -> do+            withAsync (foldIO sB foldB) (\asyncB -> do+                let loop x = do+                        y <- STM.atomically (do+                            z <- STM.readTVar tvar+                            case z of+                                HasAB a b -> do+                                    STM.writeTVar tvar Empty+                                    return (Just (a, b))+                                Done      -> return  Nothing+                                _         -> STM.retry )+                        case y of+                            Nothing -> return x+                            Just ab -> do+                                x' <- stepAB x ab+                                loop $! x'+                x' <- loop $! x0+                wait asyncA+                wait asyncB+                doneAB x' ) )++-- | A `Shell` that endlessly emits @()@+endless :: Shell ()+endless = Shell (\(FoldM step begin _) -> do+    x0 <- begin+    let loop x = do+            x' <- step x ()+            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) )++{-| Cache a `Shell`'s output so that repeated runs of the script will reuse the+    result of previous runs.  You must supply a `FilePath` where the cached+    result will be stored.++    The stored result is only reused if the `Shell` successfully ran to+    completion without any exceptions.  Note: on some platforms Ctrl-C will+    flush standard input and signal end of file before killing the program,+    which may trick the program into \"successfully\" completing.+-}+cache :: (Read a, Show a) => FilePath -> Shell a -> Shell a+cache file s = do+    let cached = do+            txt <- input file+            case reads (Text.unpack txt) of+                [(ma, "")] -> return ma+                _          ->+                    die (format ("cache: Invalid data stored in "%w) file)+    exists <- testfile file+    mas    <- fold (if exists then cached else empty) list+    case [ () | Nothing <- mas ] of+        _:_ -> select [ a | Just a <- mas ]+        _   -> do+            handle <- using (writeonly file)+            let justs = do+                    a      <- s+                    liftIO (Text.hPutStrLn handle (Text.pack (show (Just a))))+                    return a+            let nothing = do+                    let n = Nothing :: Maybe ()+                    liftIO (Text.hPutStrLn handle (Text.pack (show n)))+                    empty+            justs <|> nothing++-- | Split a line into chunks delimited by the given `Pattern`+cut :: Pattern a -> Text -> [Text]+cut pattern txt = head (match (selfless chars `sepBy` pattern) txt)+-- This `head` should be safe ... in theory++-- | Get the current time+date :: MonadIO io => io UTCTime+date = liftIO getCurrentTime++-- | Get the time a file was last modified+datefile :: MonadIO io => FilePath -> io UTCTime+datefile path = liftIO (Filesystem.getModified path)++-- | Get the size of a file or a directory+du :: MonadIO io => FilePath -> io Size+du path = liftIO (fmap Size (Filesystem.getSize path))++{-| An abstract file size++    Specify the units you want by using an accessor like `kilobytes`++    The `Num` instance for `Size` interprets numeric literals as bytes+-}+newtype Size = Size { _bytes :: Integer } deriving (Num)++instance Show Size where+    show = show . _bytes++{-| `Format` a `Size` using a human readable representation++>>> format sz 42+"42 B"+>>> format sz 2309+"2.309 KB"+>>> format sz 949203+"949.203 MB"+>>> format sz 1600000000+"1.600 GB"+>>> format sz 999999999999999999+"999999.999 TB"+-}+sz :: Format r (Size -> r)+sz = makeFormat (\(Size numBytes) ->+    let (numKilobytes, remainingBytes    ) = numBytes     `quotRem` 1000+        (numMegabytes, remainingKilobytes) = numKilobytes `quotRem` 1000+        (numGigabytes, remainingMegabytes) = numMegabytes `quotRem` 1000+        (numTerabytes, remainingGigabytes) = numGigabytes `quotRem` 1000+    in  if numKilobytes <= 0+        then format (d%" B" ) remainingBytes+        else if numMegabytes == 0+        then format (d%"."%d%" KB") remainingKilobytes remainingBytes+        else if numGigabytes == 0+        then format (d%"."%d%" MB") remainingMegabytes remainingKilobytes+        else if numTerabytes == 0+        then format (d%"."%d%" GB") remainingGigabytes remainingMegabytes+        else format (d%"."%d%" TB") numTerabytes       remainingGigabytes )++-- | Extract a size in bytes+bytes :: Integral n => Size -> n+bytes = fromInteger . _bytes++-- | @1 kilobyte = 1000 bytes@+kilobytes :: Integral n => Size -> n+kilobytes = (`div` 1000) . bytes++-- | @1 megabyte = 1000 kilobytes@+megabytes :: Integral n => Size -> n+megabytes = (`div` 1000) . kilobytes++-- | @1 gigabyte = 1000 megabytes@+gigabytes :: Integral n => Size -> n+gigabytes = (`div` 1000) . megabytes++-- | @1 terabyte = 1000 gigabytes@+terabytes :: Integral n => Size -> n+terabytes = (`div` 1000) . gigabytes++-- | @1 kibibyte = 1024 bytes@+kibibytes :: Integral n => Size -> n+kibibytes = (`div` 1024) . bytes++-- | @1 mebibyte = 1024 kibibytes@+mebibytes :: Integral n => Size -> n+mebibytes = (`div` 1024) . kibibytes++-- | @1 gibibyte = 1024 mebibytes@+gibibytes :: Integral n => Size -> n+gibibytes = (`div` 1024) . mebibytes++-- | @1 tebibyte = 1024 gibibytes@+tebibytes :: Integral n => Size -> n+tebibytes = (`div` 1024) . gibibytes++{-| Count the number of characters in the stream (like @wc -c@)++    This uses the convention that the elements of the stream are implicitly+    ended by newlines that are one character wide+-}+countChars :: Integral n => Fold Text n+countChars = Control.Foldl.Text.length + charsPerNewline * countLines++charsPerNewline :: Num a => a+#ifdef mingw32_HOST_OS+charsPerNewline = 2+#else+charsPerNewline = 1+#endif++-- | Count the number of words in the stream (like @wc -w@)+countWords :: Integral n => Fold Text n+countWords = premap Text.words (handles traverse genericLength)++{-| Count the number of lines in the stream (like @wc -l@)++    This uses the convention that each element of the stream represents one+    line+-}+countLines :: Integral n => Fold Text n+countLines = genericLength
src/Turtle/Shell.hs view
@@ -1,174 +1,174 @@-{-# LANGUAGE RankNTypes #-}
-{-# OPTIONS_GHC -fno-warn-missing-methods #-}
-
-{-| 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  <- begin
->     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(..)
-    , foldIO
-    , fold
-    , sh
-    , view
-
-    -- * Embeddings
-    , select
-    , liftIO
-    , using
-    ) where
-
-import Control.Applicative
-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
-import Data.String (IsString(..))
-import Prelude -- Fix redundant import warnings
-
--- | 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 @`FoldM` `IO`@ to reduce the stream of @a@'s produced by a `Shell`
-foldIO :: MonadIO io => Shell a -> FoldM IO a r -> io r
-foldIO s f = liftIO (_foldIO s f)
-
--- | Use a `Fold` to reduce the stream of @a@'s produced by a `Shell`
-fold :: MonadIO io => 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 :: MonadIO io => Shell a -> io ()
-sh s = fold s (pure ())
-
--- | Run a `Shell` to completion, `print`ing any unused values
-view :: (MonadIO io, 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
-
--- | Shell forms a semiring, this is the closest approximation
-instance Monoid a => Num (Shell a) where
-    fromInteger n = select (replicate (fromInteger n) mempty)
-
-    (+) = (<|>)
-    (*) = (<>)
-
-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' )
+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -fno-warn-missing-methods #-}++{-| 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  <- begin+>     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(..)+    , foldIO+    , fold+    , sh+    , view++    -- * Embeddings+    , select+    , liftIO+    , using+    ) where++import Control.Applicative+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+import Data.String (IsString(..))+import Prelude -- Fix redundant import warnings++-- | 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 @`FoldM` `IO`@ to reduce the stream of @a@'s produced by a `Shell`+foldIO :: MonadIO io => Shell a -> FoldM IO a r -> io r+foldIO s f = liftIO (_foldIO s f)++-- | Use a `Fold` to reduce the stream of @a@'s produced by a `Shell`+fold :: MonadIO io => 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 :: MonadIO io => Shell a -> io ()+sh s = fold s (pure ())++-- | Run a `Shell` to completion, `print`ing any unused values+view :: (MonadIO io, 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++-- | Shell forms a semiring, this is the closest approximation+instance Monoid a => Num (Shell a) where+    fromInteger n = select (replicate (fromInteger n) mempty)++    (+) = (<|>)+    (*) = (<>)++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
@@ -1,1659 +1,1715 @@-{-# 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".
-
-    If you are on Windows, the easiest way to follow along is to install
-    [Git for Windows](https://git-scm.com/download/win) and use the Git Bash
-    program that it installs to get a fully featured Unix-like environment.
-
-    For all operating systems, the recommended way to compile and run the
-    following examples is to download the `stack` package management tool by
-    following the instructions here:
-
-    <https://github.com/commercialhaskell/stack>
-
-    ... and then run:
-
-> $ stack install turtle
-
-    This tutorial will mostly focus on using Haskell as a scripting language.
-    The first two lines of each script below contain boilerplate instructions
-    so that `stack` will load and run the script.  This helps ensure that a
-    script will run on any computer that has a `stack` executable, as `stack`
-    can install a Haskell compiler if one is not already present.
-    If you are curious about how these two lines work, they are described here:
-
-    <https://github.com/commercialhaskell/stack/blob/master/doc/GUIDE.md#ghcrunghc>
-
-    If you want to make a Windows script independently executable outside of a
-    Git Bash environment, you can either (A) compile the script into an
-    executable or (B) run these two commands from a @cmd@ shell with
-    administrator privileges to make all @*.hs@ scripts executable:
-
-> assoc .hs=Haskell
-> ftype Haskell="C:\path\to\stack.exe" "%1" %*
--}
-
-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
-
-    -- * External commands
-    -- $external
-
-    -- * Patterns
-    -- $patterns
-
-    -- * Exception Safety
-    -- $exceptions
-
-    -- * MonadIO
-    -- $monadio
-
-    -- * Command line options
-    -- $cmdline
-
-    -- * Conclusion
-    -- $conclusion
-
-    -- * FAQ
-    -- $faq
-    ) 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 stack
--- \-\- stack \-\-install-ghc runghc \-\-package turtle
--- \ 
---                                     -- #!\/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 two lines 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:
---
--- > $ # `-O2` turns on all optimizations
--- > $ # `-threaded` helps with piping shell output in and out of Haskell
--- > $ stack ghc -- -O2 -threaded example.hs
--- > $ ./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:
---
--- > $ stack 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
--- > $
---
--- From now on I'll omit @ghci@'s linker output in tutorial examples.  You can
--- also silence this linker output by passing @--ghc-options -v0@ to
--- @stack 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 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- >
--- >                                     -- #!/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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- >
--- >                                     -- #!/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 stack
--- \-\- stack \-\-install-ghc runghc \-\-package turtle
--- \ 
---                            -- #!\/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 stack
--- > -- stack --install-ghc runghc --package turtle
--- >
--- >                             -- #!/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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > 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:
---
--- > $ stack 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 :: `MonadIO` io => io Turtle.`Turtle.FilePath`
--- @
---
--- For right now, ignore all occurrences of `MonadIO` and just read the type
--- as:
---
--- @
--- Prelude Turtle> :type pwd
--- pwd :: `IO` Turtle.`Turtle.FilePath`
--- @
---
--- We will cover `MonadIO` later on.
---
--- 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@:
---
--- @
--- $ stack 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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 stack
--- \-\- stack \-\-install-ghc runghc \-\-package turtle
--- \ 
---                                              -- #!\/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 stack
--- \-\- stack \-\-install-ghc runghc \-\-package turtle
--- 
--- {-\# 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)
---
--- You should also check out the `proc` command, which is less powerful but
--- safer since it decreases the likelihood of code injection or malformed
--- commands:
---
--- @
--- `proc`
---     :: Text         -- Program
---     :: [Text]       -- Arguments
---     -> Shell Text   -- Standard input (as lines of \`Text\`)
---     -> IO ExitCode  -- Exit code of the shell command
--- @
---
--- Most of the commands in this library do not actually invoke an external
--- shell or program.  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:
---
--- > $ stack 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.
---
--- Note that this is also the idiomatic way to convert a `FilePath` to `Text`:
---
--- > format fp :: FilePath -> Text
---
--- 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- >                                     -- #!/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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- >                                     -- #!/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
--- @
-
--- $external
---
--- You can embed external shell commands as streams within your Haskell program.
---
--- For example, suppose that we want to use the system's built in @ls@ command.
--- We can just run:
---
--- > Prelude Turtle> stdout (inshell "ls" empty)
--- > .X11-unix
--- > .X0-lock
--- > pulse-PKdhtXMmr18n
--- > pulse-xHYcZ3zmN3Fv
--- > tracker-gabriel
--- > pulse-PYi1hSlWgNj2
--- > orbit-gabriel
--- > ssh-vREYGbWGpiCa
--- > .ICE-unix
---
--- This works because type of `inshell` is:
---
--- @
--- `inshell`
---     :: Text    -- Command line
---     -> Shell Text  -- Standard input to feed to program
---     -> Shell Text  -- Standard output produced by program
--- @
---
--- This means you can use `inshell` to embed arbitrary external utilities as
--- first class streams within your Haskell program:
---
--- > Turtle Prelude> stdout (inshell "awk '{ print $1 }'" "123 456")
--- > 123
---
--- You should also check out the `inproc` command, which is less powerful but
--- safer since it decreases the likelihood of code injection or malformed
--- commands:
---
--- @
--- `inproc`
---     :: Text        -- Program
---     -> [Text]      -- Arguments
---     -> Shell Text  -- Standard input to feed to program
---     -> Shell Text  -- Standard output produced by program
--- @
---
--- Using `inproc`, you would write:
---
--- > Turtle Prelude> stdout (inproc "awk" ["{ print $1 }"] "123 456")
--- > 123
-
--- $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{m,n}     `bounded` m n e
--- e{0,n}     `upperBounded` 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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 stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# 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".
-
--- $monadio
---
--- If you are sick of having type `liftIO` everywhere, you can omit it.  This
--- is because all subroutines in @turtle@ are overloaded using the `MonadIO`
--- type class, like our original `pwd` command where we first encountered the
--- the `MonadIO` type:
---
--- @
--- Prelude Turtle> :type pwd
--- pwd :: `MonadIO` io => io Turtle.`Turtle.FilePath`
--- @
---
--- This means you can this command is overloaded to run in any context that
--- implements the `MonadIO` interface, including:
---
--- * `IO` (obviously)
---
--- * `Shell`
---
--- * `Managed`
---
--- You can tell if a type constructor like `Shell` implements `MonadIO` by
--- clicking the link to the type constructor and looking for the instance list.
--- There you will see a list of instances like:
---
--- > Monad Shell
--- > Functor Shell
--- > MonadPlus Shell
--- > Applicative Shell
--- > Alternative Shell
--- > MonadIO Shell
--- > ...
--- 
--- These instances represent the overloaded functions associated with `Shell`
--- and we can see from the list that `Shell` implements `MonadIO` so we can
--- use `pwd` (or any other subroutine in this library) within a `Shell`.
---
--- However, not all subroutines in the Haskell ecosystem are overloaded in this
--- way (such as `print`), so you will still occasionally need to wrap
--- subroutines in `liftIO`.
-
--- $cmdline
---
--- The "Turtle.Options" module lets you easily parse command line arguments,
--- using either flags or positional arguments.
---
--- For example, if you want to write a @cp@-like script that takes two
--- positional arguments for the source and destination file, you can write:
---
--- > #!/usr/bin/env stack
--- > -- stack --install-ghc runghc --package turtle
--- >
--- > -- cp.hs
--- >
--- > {-# LANGUAGE OverloadedStrings #-}
--- >
--- > import Turtle
--- > import Prelude hiding (FilePath)
--- >
--- > parser :: Parser (FilePath, FilePath)
--- > parser = (,) <$> argPath "src"  "The source file"
--- >              <*> argPath "dest" "The destination file"
--- >
--- > main = do
--- >     (src, dest) <- options "A simple `cp` script" parser
--- >     cp src dest
---
--- If you run the script without any arguments, you will get an auto-generated
--- usage output:
---
--- > $ ./cp.hs
--- > Usage: cp.hs SRC DEST
---
--- ... and you can get a more descriptive output if you supply the @--help@
--- flag:
---
--- > $ ./cp.hs --help
--- > A simple `cp` utility
--- > 
--- > Usage: cp.hs SRC DEST
--- > 
--- > Available options:
--- >   -h,--help                Show this help text
--- >   SRC                      The source file
--- >   DEST                     The destination file
---
--- ... and the script works as expected if you provide both arguments:
---
--- > echo "Test" > file1.txt
--- > $ ./cp.hs file1.txt file2.txt
--- > cat file2.txt
--- > Test
---
--- This works because `argPath` produces a `Parser`:
---
--- > argPath :: ArgName -> Optional HelpMessage -> Parser FilePath
---
--- ... and multiple `Parser`s can be combined into a single `Parser` using
--- operations from the `Applicative` type class since the `Parser` type
--- implements the `Applicative` interface:
---
--- > instance Applicative Parser
---
--- You can also make any argument optional using the `optional` utility
--- provided by `Control.Applicative`:
---
--- @
--- `optional` :: `Alternative` f => f a -> f (Maybe a)
--- @
---
--- For example, we can change our program to make the destination argument
--- optional, defaulting to `stdout` if the user does not provide a destination:
---
---
--- > #!/usr/bin/env stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# LANGUAGE OverloadedStrings #-}
--- > 
--- > import Turtle
--- > import Prelude hiding (FilePath)
--- > 
--- > parser :: Parser (FilePath, Maybe FilePath)
--- > parser = (,) <$>           argPath "src"  "The source file"
--- >              <*> optional (argPath "dest" "The destination file")
--- > 
--- > main = do
--- >     (src, mDest) <- options "A simple `cp` utility" parser
--- >     case mDest of
--- >         Nothing   -> input src & stdout
--- >         Just dest -> cp src dest
---
--- Now the auto-generated usage information correctly indicates that the second
--- argument is optional:
---
--- > $ ./cp.hs
--- > Usage: cp.hs SRC [DEST]
--- > $ ./cp.hs --help
--- > A simple `cp` utility
--- > 
--- > Usage: cp.hs SRC [DEST]
--- > 
--- > Available options:
--- >   -h,--help                Show this help text
--- >   SRC                      The source file
--- >   DEST                     The destination file
---
--- ... and if we omit the argument the result goes to standard output:
---
--- > $ ./cp.hs file1.txt
--- > Test
---
--- We can use the `optional` utility because the `Parser` type also implements
--- the `Alternative` interface:
---
--- > instance Alternative Parser
---
--- We can also specify arguments on the command lines using flags instead of
--- specifying them positionally.  Let's change our example to specify the
--- input and output using the @--src@ and @--dest@ flags, using @-s@ and @-d@
--- as short-hands for the flags:
---
--- > #!/usr/bin/env stack
--- > -- stack --install-ghc runghc --package turtle
--- > 
--- > {-# LANGUAGE OverloadedStrings #-}
--- > 
--- > import Turtle
--- > import Prelude hiding (FilePath)
--- > 
--- > parser :: Parser (FilePath, FilePath)
--- > parser = (,) <$> optPath "src"  's' "The source file"
--- >              <*> optPath "dest" 'd' "The destination file"
--- > 
--- > main = do
--- >     (src, dest) <- options "A simple `cp` utility" parser
--- >     cp src dest
---
--- This now lets us specify the arguments in terms of flags:
---
--- > $ ./cp
--- > Usage: cp.hs (-s|--src SRC) (-d|--dest DEST)
--- > $ ./cp --help
--- > A simple `cp` utility
--- > 
--- > Usage: cp.hs (-s|--src SRC) (-d|--dest DEST)
--- > 
--- > Available options:
--- >   -h,--help                Show this help text
--- >   -s,--src SRC             The source file
--- >   -d,--dest DEST           The destination file
--- > $ ./cp --src file1.txt --dest file3.txt
--- > $ cat file3.txt
--- > Test
---
--- See the "Turtle.Options" module for more details and utilities related to
--- parsing command line options.  This module is built on top of the
--- @optparse-applicative@ library, which provides even more extensive
--- functionality.
-
--- $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!
-
--- $faq
---
--- These are the most frequently asked questions from new users:
---
--- /Question:/ How do I convert `FilePath` to `Text`?
---
--- /Answer:/ Use @(`format` `fp`)@
---
--- /Question:/ My program prints some extra output every time it starts.  How do
--- I remove it?
---
--- /Answer:/ Compile your program and run the executable instead of interpreting-- the program.
---
--- /Question:/ What's the easiest way to fail with a descriptive error message
--- if a subprocess command like `proc`/`shell` returns a non-zero exit code?
--- code?
---
--- /Answer:/ Use @(`proc` cmd args input `.||.` `die` "Descriptive error message")@
--- or @(`shell` cmdline input `.||.` `die` "Descriptive error message")@, very
--- similar to Bash and Perl.
---
--- /Question:/ How do I close a resource that I acquired?
---
--- /Answer:/ Use `runManaged`, `sh`, or (`<|>`) (all resources acquired in the
--- left stream will close before beginning the right stream).  Alternatively,
--- use `with` to acquire a resource for a limited scope.
+{-# 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".++    If you are on Windows, the easiest way to follow along is to install+    <https://git-scm.com/download/win Git for Windows> and use the Git Bash+    program that it installs to get a fully featured Unix-like environment.++    For all operating systems, the recommended way to compile and run the+    following examples is to download the `stack` package management tool by+    following the instructions here:++    <https://github.com/commercialhaskell/stack>++    ... and then run the following instruction anywhere outside of a Haskell+    project:++> $ stack install turtle++    This tutorial will mostly focus on using Haskell as a scripting language.+    The first two lines of each script below contain boilerplate instructions+    so that `stack` will load and run the script.  This helps ensure that a+    script will run on any computer that has a `stack` executable, as `stack`+    can install a Haskell compiler if one is not already present.+    If you are curious about how these two lines work, they are described here:++    <https://github.com/commercialhaskell/stack/blob/master/doc/GUIDE.md#ghcrunghc>++    If you want to make a Windows script independently executable outside of a+    Git Bash environment, you can either (A) compile the script into an+    executable or (B) run these two commands from a @cmd@ shell with+    administrator privileges to make all @*.hs@ scripts executable:++> assoc .hs=Haskell+> ftype Haskell="C:\path\to\stack.exe" "%1" %*+-}++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++    -- * External commands+    -- $external++    -- * Patterns+    -- $patterns++    -- * Exception Safety+    -- $exceptions++    -- * MonadIO+    -- $monadio++    -- * Command line options+    -- $cmdline++    -- * Conclusion+    -- $conclusion++    -- * FAQ+    -- $faq+    ) 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 stack+-- \-\- stack \-\-install-ghc runghc \-\-package turtle+-- \ +--                                     -- #!\/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 two lines 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:+--+-- > $ # `-O2` turns on all optimizations+-- > $ # `-threaded` helps with piping shell output in and out of Haskell+-- > $ stack ghc -- -O2 -threaded example.hs+-- > $ ./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:+--+-- > $ stack 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+-- > $+--+-- From now on I'll omit @ghci@'s linker output in tutorial examples.  You can+-- also silence this linker output by passing @--ghc-options -v0@ to+-- @stack 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 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- >+-- >                                     -- #!/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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- >+-- >                                     -- #!/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 stack+-- \-\- stack \-\-install-ghc runghc \-\-package turtle+-- \ +--                            -- #!\/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 stack+-- > -- stack --install-ghc runghc --package turtle+-- >+-- >                             -- #!/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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > 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:+--+-- > $ stack 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 :: `MonadIO` io => io Turtle.`Turtle.FilePath`+-- @+--+-- For right now, ignore all occurrences of `MonadIO` and just read the type+-- as:+--+-- @+-- Prelude Turtle> :type pwd+-- pwd :: `IO` Turtle.`Turtle.FilePath`+-- @+--+-- We will cover `MonadIO` later on.+--+-- 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@:+--+-- @+-- $ stack 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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 stack+-- \-\- stack \-\-install-ghc runghc \-\-package turtle+-- \ +--                                              -- #!\/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 stack+-- \-\- stack \-\-install-ghc runghc \-\-package turtle+-- +-- {-\# 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)+--+-- You should also check out the `proc` command, which is less powerful but+-- safer since it decreases the likelihood of code injection or malformed+-- commands:+--+-- @+-- `proc`+--     :: Text         -- Program+--     :: [Text]       -- Arguments+--     -> Shell Text   -- Standard input (as lines of \`Text\`)+--     -> IO ExitCode  -- Exit code of the shell command+-- @+--+-- Most of the commands in this library do not actually invoke an external+-- shell or program.  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:+--+-- > $ stack 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.+--+-- Note that this is also the idiomatic way to convert a `FilePath` to `Text`:+--+-- > format fp :: FilePath -> Text+--+-- 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- >                                     -- #!/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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- >                                     -- #!/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+-- @++-- $external+--+-- You can embed external shell commands as streams within your Haskell program.+--+-- For example, suppose that we want to use the system's built in @ls@ command.+-- We can just run:+--+-- > Prelude Turtle> stdout (inshell "ls" empty)+-- > .X11-unix+-- > .X0-lock+-- > pulse-PKdhtXMmr18n+-- > pulse-xHYcZ3zmN3Fv+-- > tracker-gabriel+-- > pulse-PYi1hSlWgNj2+-- > orbit-gabriel+-- > ssh-vREYGbWGpiCa+-- > .ICE-unix+--+-- This works because type of `inshell` is:+--+-- @+-- `inshell`+--     :: Text    -- Command line+--     -> Shell Text  -- Standard input to feed to program+--     -> Shell Text  -- Standard output produced by program+-- @+--+-- This means you can use `inshell` to embed arbitrary external utilities as+-- first class streams within your Haskell program:+--+-- > Turtle Prelude> stdout (inshell "awk '{ print $1 }'" "123 456")+-- > 123+--+-- You should also check out the `inproc` command, which is less powerful but+-- safer since it decreases the likelihood of code injection or malformed+-- commands:+--+-- @+-- `inproc`+--     :: Text        -- Program+--     -> [Text]      -- Arguments+--     -> Shell Text  -- Standard input to feed to program+--     -> Shell Text  -- Standard output produced by program+-- @+--+-- Using `inproc`, you would write:+--+-- > Turtle Prelude> stdout (inproc "awk" ["{ print $1 }"] "123 456")+-- > 123++-- $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{m,n}     `bounded` m n e+-- e{0,n}     `upperBounded` 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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 stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# 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".++-- $monadio+--+-- If you are sick of having type `liftIO` everywhere, you can omit it.  This+-- is because all subroutines in @turtle@ are overloaded using the `MonadIO`+-- type class, like our original `pwd` command where we first encountered the+-- the `MonadIO` type:+--+-- @+-- Prelude Turtle> :type pwd+-- pwd :: `MonadIO` io => io Turtle.`Turtle.FilePath`+-- @+--+-- This means this command is overloaded to run in any context that+-- implements the `MonadIO` interface, including:+--+-- * `IO` (obviously)+--+-- * `Shell`+--+-- * `Managed`+--+-- You can tell if a type constructor like `Shell` implements `MonadIO` by+-- clicking the link to the type constructor and looking for the instance list.+-- There you will see a list of instances like:+--+-- > Monad Shell+-- > Functor Shell+-- > MonadPlus Shell+-- > Applicative Shell+-- > Alternative Shell+-- > MonadIO Shell+-- > ...+-- +-- These instances represent the overloaded functions associated with `Shell`+-- and we can see from the list that `Shell` implements `MonadIO` so we can+-- use `pwd` (or any other subroutine in this library) within a `Shell`.+--+-- However, not all subroutines in the Haskell ecosystem are overloaded in this+-- way (such as `print`), so you will still occasionally need to wrap+-- subroutines in `liftIO`.++-- $cmdline+--+-- The "Turtle.Options" module lets you easily parse command line arguments,+-- using either flags or positional arguments.+--+-- For example, if you want to write a @cp@-like script that takes two+-- positional arguments for the source and destination file, you can write:+--+-- > #!/usr/bin/env stack+-- > -- stack --install-ghc runghc --package turtle+-- >+-- > -- cp.hs+-- >+-- > {-# LANGUAGE OverloadedStrings #-}+-- >+-- > import Turtle+-- > import Prelude hiding (FilePath)+-- >+-- > parser :: Parser (FilePath, FilePath)+-- > parser = (,) <$> argPath "src"  "The source file"+-- >              <*> argPath "dest" "The destination file"+-- >+-- > main = do+-- >     (src, dest) <- options "A simple `cp` script" parser+-- >     cp src dest+--+-- If you run the script without any arguments, you will get an auto-generated+-- usage output:+--+-- > $ ./cp.hs+-- > Usage: cp.hs SRC DEST+--+-- ... and you can get a more descriptive output if you supply the @--help@+-- flag:+--+-- > $ ./cp.hs --help+-- > A simple `cp` utility+-- > +-- > Usage: cp.hs SRC DEST+-- > +-- > Available options:+-- >   -h,--help                Show this help text+-- >   SRC                      The source file+-- >   DEST                     The destination file+--+-- ... and the script works as expected if you provide both arguments:+--+-- > echo "Test" > file1.txt+-- > $ ./cp.hs file1.txt file2.txt+-- > cat file2.txt+-- > Test+--+-- This works because `argPath` produces a `Parser`:+--+-- > argPath :: ArgName -> Optional HelpMessage -> Parser FilePath+--+-- ... and multiple `Parser`s can be combined into a single `Parser` using+-- operations from the `Applicative` type class since the `Parser` type+-- implements the `Applicative` interface:+--+-- > instance Applicative Parser+--+-- You can also make any argument optional using the `optional` utility+-- provided by `Control.Applicative`:+--+-- @+-- `optional` :: `Alternative` f => f a -> f (Maybe a)+-- @+--+-- For example, we can change our program to make the destination argument+-- optional, defaulting to `stdout` if the user does not provide a destination:+--+--+-- > #!/usr/bin/env stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > import Prelude hiding (FilePath)+-- > +-- > parser :: Parser (FilePath, Maybe FilePath)+-- > parser = (,) <$>           argPath "src"  "The source file"+-- >              <*> optional (argPath "dest" "The destination file")+-- > +-- > main = do+-- >     (src, mDest) <- options "A simple `cp` utility" parser+-- >     case mDest of+-- >         Nothing   -> input src & stdout+-- >         Just dest -> cp src dest+--+-- Now the auto-generated usage information correctly indicates that the second+-- argument is optional:+--+-- > $ ./cp.hs+-- > Usage: cp.hs SRC [DEST]+-- > +-- > $ ./cp.hs --help+-- > A simple `cp` utility+-- > +-- > Usage: cp.hs SRC [DEST]+-- > +-- > Available options:+-- >   -h,--help                Show this help text+-- >   SRC                      The source file+-- >   DEST                     The destination file+--+-- ... and if we omit the argument the result goes to standard output:+--+-- > $ ./cp.hs file1.txt+-- > Test+--+-- We can use the `optional` utility because the `Parser` type also implements+-- the `Alternative` interface:+--+-- > instance Alternative Parser+--+-- We can also specify arguments on the command lines using flags instead of+-- specifying them positionally.  Let's change our example to specify the+-- input and output using the @--src@ and @--dest@ flags, using @-s@ and @-d@+-- as short-hands for the flags:+--+-- > #!/usr/bin/env stack+-- > -- stack --install-ghc runghc --package turtle+-- > +-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > import Prelude hiding (FilePath)+-- > +-- > parser :: Parser (FilePath, FilePath)+-- > parser = (,) <$> optPath "src"  's' "The source file"+-- >              <*> optPath "dest" 'd' "The destination file"+-- > +-- > main = do+-- >     (src, dest) <- options "A simple `cp` utility" parser+-- >     cp src dest+--+-- This now lets us specify the arguments in terms of flags:+--+-- > $ ./cp+-- > Usage: cp.hs (-s|--src SRC) (-d|--dest DEST)+-- > +-- > $ ./cp --help+-- > A simple `cp` utility+-- > +-- > Usage: cp.hs (-s|--src SRC) (-d|--dest DEST)+-- > +-- > Available options:+-- >   -h,--help                Show this help text+-- >   -s,--src SRC             The source file+-- >   -d,--dest DEST           The destination file+-- > +-- > $ ./cp --src file1.txt --dest file3.txt+-- > $ cat file3.txt+-- > Test+--+-- You can also provide `subcommand` functionality such as the following+-- example which pretends to increase or decrease the system volume:+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > +-- > import Turtle+-- > +-- > data Command = IncreaseVolume Int | DecreaseVolume Int deriving (Show)+-- > +-- > parser :: Parser Command+-- > parser+-- >     =   fmap IncreaseVolume +-- >             (subcommand "up" "Turn the volume up"+-- >                 (argInt "amount" "How much to increase the volume") )+-- >     <|> fmap DecreaseVolume+-- >             (subcommand "down" "Turn the volume down"+-- >                 (argInt "amount" "How much to decrease the volume") )+-- > +-- > main = do+-- >     x <- options "Volume adjuster" parser+-- >     case x of+-- >         IncreaseVolume n -> echo (format ("Increasing the volume by "%d) n)+-- >         DecreaseVolume n -> echo (format ("Decreasing the volume by "%d) n)+--+-- This will provide `--help` output at both the top level and for each+-- subcommand:+--+-- > $ ./options --help+-- > Volume adjuster+-- > +-- > Usage: options (up | down)+-- > +-- > Available options:+-- >   -h,--help                Show this help text+-- > +-- >   Available commands:+-- >     up+-- >     down+-- > +-- > $ ./options up --help+-- > Turn the volume up+-- > +-- > Usage: options up AMOUNT+-- > +-- > Available options:+-- >   -h,--help                Show this help text+-- >   AMOUNT                   How much to increase the volume+-- >+-- > $ ./options up 10+-- > Increasing the volume by 10+-- > +--+-- See the "Turtle.Options" module for more details and utilities related to+-- parsing command line options.  This module is built on top of the+-- @optparse-applicative@ library, which provides even more extensive+-- functionality.++-- $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!++-- $faq+--+-- These are the most frequently asked questions from new users:+--+-- /Question:/ How do I convert `FilePath` to `Text`?+--+-- /Answer:/ Use @(`format` `fp`)@+--+-- /Question:/ My program prints some extra output every time it starts.  How do+-- I remove it?+--+-- /Answer:/ Compile your program and run the executable instead of interpreting-- the program.+--+-- /Question:/ What's the easiest way to fail with a descriptive error message+-- if a subprocess command like `proc`/`shell` returns a non-zero exit code?+-- code?+--+-- /Answer:/ Use @(`proc` cmd args input `.||.` `die` "Descriptive error message")@+-- or @(`shell` cmdline input `.||.` `die` "Descriptive error message")@, very+-- similar to Bash and Perl.+--+-- /Question:/ How do I close a resource that I acquired?+--+-- /Answer:/ Use `runManaged`, `sh`, or (`<|>`) (all resources acquired in the+-- left stream will close before beginning the right stream).  Alternatively,+-- use `with` to acquire a resource for a limited scope.
test/Main.hs view
@@ -1,6 +1,6 @@-module Main where
-
-import Test.DocTest
-
-main :: IO ()
-main = doctest ["src/Turtle/Pattern.hs", "src/Turtle/Format.hs"]
+module Main where++import Test.DocTest++main :: IO ()+main = doctest ["src/Turtle/Pattern.hs", "src/Turtle/Format.hs"]
turtle.cabal view
@@ -1,101 +1,101 @@-Name: turtle
-Version: 1.2.3
-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
-    .
-    @turtle@ is designed to be beginner-friendly, but as a result lacks certain
-    features, like tracing commands.  If you feel comfortable using @turtle@
-    then you should also check out the @Shelly@ library which provides similar
-    functionality.
-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     && < 5  ,
-        async                >= 2.0.0.0 && < 2.1,
-        clock                >= 0.4.1.2 && < 0.6,
-        directory            >= 1.0.7   && < 1.3,
-        foldl                >= 1.1     && < 1.2,
-        hostname                           < 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,
-        stm                                < 2.5,
-        temporary                          < 1.3,
-        text                               < 1.3,
-        time                               < 1.6,
-        transformers         >= 0.2.0.0 && < 0.5,
-        optparse-applicative >= 0.11    && < 0.13,
-        optional-args        >= 1.0     && < 2.0
-    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.Options,
-        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.11
-
-benchmark bench
-    Type: exitcode-stdio-1.0
-    HS-Source-Dirs: bench
-    Main-Is: Main.hs
-    GHC-Options: -O2 -Wall -threaded
-    Default-Language: Haskell2010
-    Build-Depends:
-        base         >= 4       && < 5  ,
-        criterion    >= 1.1.0.0 && < 2  ,
-        text                       < 1.3,
-        turtle
+Name: turtle+Version: 1.2.4+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+    .+    @turtle@ is designed to be beginner-friendly, but as a result lacks certain+    features, like tracing commands.  If you feel comfortable using @turtle@+    then you should also check out the @Shelly@ library which provides similar+    functionality.+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     && < 5  ,+        async                >= 2.0.0.0 && < 2.1,+        clock                >= 0.4.1.2 && < 0.7,+        directory            >= 1.0.7   && < 1.3,+        foldl                >= 1.1     && < 1.2,+        hostname                           < 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,+        stm                                < 2.5,+        temporary                          < 1.3,+        text                               < 1.3,+        time                               < 1.6,+        transformers         >= 0.2.0.0 && < 0.5,+        optparse-applicative >= 0.11    && < 0.13,+        optional-args        >= 1.0     && < 2.0+    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.Options,+        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.11++benchmark bench+    Type: exitcode-stdio-1.0+    HS-Source-Dirs: bench+    Main-Is: Main.hs+    GHC-Options: -O2 -Wall -threaded+    Default-Language: Haskell2010+    Build-Depends:+        base         >= 4       && < 5  ,+        criterion    >= 1.1.0.0 && < 2  ,+        text                       < 1.3,+        turtle