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:
|