stack-run-0.1.1.4: src/Main.hs
{-# LANGUAGE CPP #-}
module Main
where
import Control.Applicative ((*>), (<$>))
import Control.Concurrent.Async
import Control.Monad (unless)
import qualified Data.ByteString.Char8 as ByteString
import Data.Conduit
import qualified Data.Conduit.Binary as Conduit.Binary
import qualified Data.Conduit.List as Conduit.List
import Data.Conduit.Process
import Data.List
import Data.List.Utils
import Data.Maybe
import Data.Time
import Distribution.PackageDescription
import Distribution.PackageDescription.Parse
import System.Console.ANSI
#ifndef OS_Win32
import System.Console.Questioner
#endif
import System.Directory
import System.Environment
import System.Exit
import System.FilePath
import System.IO
import System.IO.Error
usage :: String
usage = unlines [ ""
, " Usage: stack run <name|sub-command> [args]"
, ""
, " Commands:"
, ""
, " stack run [name] [args] Compiles and runs the executable specified or the default"
, " stack run -- <name> [args] Like stack run, but will never match a sub-command"
, " stack run -- -- [args] Pass-in arguments to the default executable"
, ""
, " stack run set-default <name> Sets the default executable to run"
, ""
, " stack run --interactive Runs in interactive mode"
, " stack run -i"
, ""
, " stack run help Print this help message"
, " stack run --help"
, " stack run -h"
, ""
]
setDefault :: String -> IO ()
setDefault name = do
pr <- fromMaybe (error "No project root found") <$>
getCabalProjectRootCurrent
writeFile (pr </> ".stack-work" </> ".stack-run-default") name
findDefault :: IO String
findDefault = do
pr <- fromMaybe (error "No project root found") <$>
getCabalProjectRootCurrent
e <- doesFileExist (pr </> ".stack-work" </> ".stack-run-default")
if e then readFile (pr </> ".stack-work" </> ".stack-run-default")
else findDefault' pr
where
findDefault' pr = do
cfp <- fromMaybe (error "No cabal file found") <$>
(find ((== ".cabal") . takeExtension) <$> getDirectoryContents pr)
getPackageDescription (pr </> cfp) >>= getDefaultExecutable
where
getPackageDescription p = parsePackageDescription <$> readFile p
getDefaultExecutable (ParseFailed _) =
error "Failed to parse cabal file"
getDefaultExecutable (ParseOk _ gpd) = case condExecutables gpd of
[] -> error "No executable found"
((d, _):_) -> return d
getExecutables :: IO [String]
getExecutables = do
pr <- fromMaybe (error "No project root found") <$>
getCabalProjectRootCurrent
cfp <- fromMaybe (error "No cabal file found") <$>
(find ((== ".cabal") . takeExtension) <$> getDirectoryContents pr)
pkgParseResult <- getPackageDescription (pr </> cfp)
return $ getExecutables pkgParseResult
where
getPackageDescription p = parsePackageDescription <$> readFile p
getExecutables (ParseFailed _) =
error "Failed to parse cabal file"
getExecutables (ParseOk _ gpd) = case condExecutables gpd of
[] -> error "No executables found"
ds -> map fst ds
getCabalProjectRootCurrent :: IO (Maybe FilePath)
getCabalProjectRootCurrent = flip catchIOError (const (return Nothing)) $
getCurrentDirectory >>= getCabalProjectRoot
where
getCabalProjectRoot :: FilePath -> IO (Maybe FilePath)
getCabalProjectRoot path = do
hasCabal <- any (isSuffixOf ".cabal") <$> getDirectoryContents path
if hasCabal
then return $ Just path
else let parent = takeDirectory path in
if parent /= path
then getCabalProjectRoot parent
else return Nothing
stackRun :: String -> [String] -> IO b
stackRun name as = do
pr <- fromMaybe (error "No project root found") <$> getCabalProjectRootCurrent
stackYmlExists <- doesFileExist (pr </> "stack.yaml")
unless stackYmlExists $ do
ec <- prettyRunCommand "stack init"
case ec of
ExitSuccess -> return ()
f -> exitWith f
start <- getCurrentTime
ec <- prettyRunCommand "stack build"
hSetSGR stderr [SetColor Foreground Vivid Black]
fdiff <- getDiffTime start
hPutStrLn stderr ("stack build took " ++ show fdiff ++ "ms")
hSetSGR stderr [Reset]
case ec of
ExitSuccess -> do
let cmd = "stack exec " ++ name ++ " -- " ++ join " " as
logCommand cmd
hSetSGR stderr [Reset]
hFlush stderr
ph <- runCommand cmd
ec' <- waitForProcess ph
exitWith ec'
f -> exitWith f
where
getDiffTime :: UTCTime -> IO Integer
getDiffTime start = do
now <- getCurrentTime
let diff :: Double
diff = fromRational (toRational (diffUTCTime now start))
fdiff = floor (diff * 1000)
return fdiff
prettyRunCommand :: String -> IO ExitCode
prettyRunCommand cmd = do
hSetBuffering stderr LineBuffering
logCommand cmd
(Inherited, out, err, cph) <- streamingProcess (shell cmd)
runConcurrently $
Concurrently (out $$ (Conduit.Binary.lines =$ Conduit.List.mapM_ putLineGray)) *>
Concurrently (err $$ (Conduit.Binary.lines =$ Conduit.List.mapM_ putLineRed)) *>
Concurrently (waitForStreamingProcess cph)
where
putLineSGR sgr b = do
hSetSGR stderr sgr
hPutStr stderr " "
ByteString.hPutStrLn stderr b
hSetSGR stderr [Reset]
putLineGray = putLineSGR [SetColor Foreground Dull White]
putLineRed = putLineSGR [SetColor Foreground Vivid Black]
logCommand :: String -> IO ()
logCommand cmd = do
hSetSGR stderr [ SetColor Foreground Vivid White
, SetConsoleIntensity BoldIntensity
]
hPutStr stderr "$ "
hSetSGR stderr [Reset]
hSetSGR stderr [SetColor Foreground Vivid Cyan]
hPutStrLn stderr cmd
hSetSGR stderr [Reset]
#ifndef OS_Win32
runInteractive :: [String] -> IO ()
runInteractive as = do
exs <- getExecutables
ex <- prompt ("What executable should we run? ", exs)
stackRun ex as
#endif
main :: IO ()
main = do
args <- getArgs
case args of
("set-default":name:_) -> setDefault name
("set-default":_) -> do
hPutStrLn stderr "Missing required parameter: <name>"
exitFailure
("get-default":_) -> putStrLn =<< findDefault
("--help":_) -> exitUsage
("-h":_) -> exitUsage
#ifndef OS_Win32
("-i":as) -> runInteractive as
("--interactive":as) -> runInteractive as
#endif
("help":_) -> exitUsage
("--":"--":as) -> flip stackRun as =<< findDefault
("--":name:as) -> stackRun name as
(name:as) -> stackRun name as
[] -> flip stackRun [] =<< findDefault
where
exitUsage = do
putStr usage
exitSuccess