turtle 1.2.2 → 1.2.3
raw patch · 4 files changed
+98/−6 lines, 4 files
Files
- src/Turtle/Options.hs +24/−0
- src/Turtle/Prelude.hs +28/−3
- src/Turtle/Tutorial.hs +45/−2
- turtle.cabal +1/−1
src/Turtle/Options.hs view
@@ -35,6 +35,7 @@ ( -- * Types Parser , ArgName + , CommandName , ShortName , Description , HelpMessage @@ -59,6 +60,7 @@ , arg -- * Consume parsers + , subcommand , options ) where @@ -97,6 +99,15 @@ -- | 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 @@ -206,3 +217,16 @@ 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)))
src/Turtle/Prelude.hs view
@@ -132,6 +132,7 @@ , rmtree , testfile , testdir + , testpath , date , datefile , touch @@ -236,6 +237,7 @@ 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 ( @@ -254,6 +256,7 @@ 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 @@ -352,6 +355,7 @@ let open = do (Just hIn, Nothing, Nothing, ph) <- Process.createProcess p' + IO.hSetBuffering hIn IO.LineBuffering return (hIn, ph) -- Prevent double close @@ -386,6 +390,7 @@ 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 @@ -453,6 +458,7 @@ 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 @@ -623,9 +629,19 @@ else return child else return child --- | Move a file or directory +{-| 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 (Filesystem.rename oldPath newPath) +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 @@ -668,6 +684,14 @@ 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 @@ -840,7 +864,8 @@ die :: MonadIO io => Text -> io a die txt = liftIO (throwIO (userError (unpack txt))) -infixr 2 .&&., .||. +infixr 2 .||. +infixr 3 .&&. {-| Analogous to `&&` in Bash
src/Turtle/Tutorial.hs view
@@ -24,15 +24,21 @@ 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". - The easiest way to follow along with the examples is to download the - `stack` package management tool by following the instructions here: + 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` @@ -41,6 +47,13 @@ <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 ( @@ -97,6 +110,9 @@ -- * Conclusion -- $conclusion + + -- * FAQ + -- $faq ) where import Turtle @@ -1614,3 +1630,30 @@ -- 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.
turtle.cabal view
@@ -1,5 +1,5 @@ Name: turtle -Version: 1.2.2 +Version: 1.2.3 Cabal-Version: >=1.10 Build-Type: Simple License: BSD3