Turtle
The Shell type represents a stream of values. You can think of Shell as [] + IO + Managed
.
newtype Shell a = Shell { _foldIO :: forall r . FoldM IO a r -> IO r }
You invoke any external shell commands using proc or shell. If you don’t care about throwing an error instead of returning the error code you would use procs
and shells
; proc(s)
is more secure but it won’t do any shell string interpolation.
shell
:: Text -- Command line
-> Shell Line -- Lines of standard input to feed to program
-> io ExitCode
shells :: Text-> Shell Line -> io ()
select :: [a] -> Shell a
liftIO :: IO a -> Shell a
using :: Managed a -> Shell a
-- usual construction primitive
empty :: Shell a
-- consume the stream by printing it to stdout
view :: Show a => Shell a -> IO ()
stdout :: Shell Text -> IO ()
-- consume the (side-effect) stream, discarding any unused values
sh :: MonadIO io => Shell a -> io ()
You can simulate piping the result of a command with inshell
or inproc
:
inshell :: Text -> Shell Line -> Shell Line
inproc "curl" ["-s"
, "http://"
] empty (1)
& output "filename.ext" (2)
1 | keep the result of a command as a stream |
2 | pipe and copy |
When using inshell you lose the ability to care about the exit code of the command that produces the stream. |
Shell
is also an instance of MonadPlus
(and thus Alternative).
So you can concatenate two Shell streams using <|>
.
Folding
Whenever you peek into the value of a shell stream using ←
you are effectively looping over all values (as the list monad does). For instance this code is bogus :
bogus
|
You will need to consume the stream and one good way to do so is using fold from the foldl
package:
import qualified Control.Foldl as Fold
main = do
not_found <- fold (find (prefix (text "/home/vagrant/zsh")) "/home/vagrant") Fold.null
when (not_found) $ do ...
Similarly here is an utility function that checks if a file is empty:
isFileEmpty :: MonadIO io => FilePath -> io Bool
isFileEmpty path =
fold (input path) Fold.null
FilePath
Turtle
is using the deprecated system-filepath package
to handle filepaths in a more secure way[1]. Watch out as it is at time a bit surprising:
|
When appending filepath
and text
the best strategy is probably to keep the filepath encoding and then convert to text if necessary:
let path = "foo" </> fromText eclipseVersion </> "plugin"
_path = format fp path
Use </> for appending filepaths, use <> for appending text.
|
Command line options
data Command
= Console
| Stack (Maybe StackName, StackCommand)
deriving (Show)
commandParser :: Parser Command
commandParser =
Console <$ subcommand "console" "Help msg" (pure ())
<|> Stack <$> subcommand "stack" "Help msg" stackParser (1)
1 | remaining parser (after 'stack') |
When using a group you will need a single datatype to extract the value of the rest of the command. Don’t do this:
|