packages feed

giak-0.1.0.0: src/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Concurrent.Async                      (async, cancel, wait)
import Control.Concurrent.STM.TMQueue                (TMQueue, newTMQueueIO,
                                                      tryReadTMQueue,
                                                      writeTMQueue)
import Control.Monad
import Control.Monad.Extra
import Control.Monad.STM                             (atomically)
import Data.List.NonEmpty                            (NonEmpty (..))
import Data.Maybe                                    (isJust)
import Data.Monoid                                   ((<>))
import Distribution.PackageDescription.Configuration (flattenPackageDescription)
import Distribution.PackageDescription.Parse         (readPackageDescription)
import IOCTL                                         (writeOnTTY)
import System.Directory                              (doesFileExist,
                                                      findExecutable)
import System.FilePath.Glob                          (namesMatching)

import qualified Data.Text                       as T
import qualified Data.Text.Encoding              as T
import qualified Distribution.PackageDescription as Cabal
import qualified Distribution.Verbosity          as Verbosity
import qualified Wybor

data GiakResult = GiakResult BuildTool Cabal.Executable

data BuildTool = Cabal | Stack

type GatherChan = TMQueue (NonEmpty (T.Text, T.Text))

buildCommand :: GiakResult -> T.Text
buildCommand = T.pack . unwords . buildCommand'
  where
    buildCommand' (GiakResult Stack exe) = ["stack", "exec", Cabal.exeName exe]
    buildCommand' (GiakResult Cabal exe) = ["cabal", "exec", Cabal.exeName exe]

main :: IO ()
main = do
    results <- newTMQueueIO
    gatherer <- async $ gatherExecutables results
    async (displayChoices results) >>= wait
    cancel gatherer

gatherExecutables :: GatherChan -> IO ()
gatherExecutables chan =
    loadResults >>= mapM_ (\res -> do
        let res' = (toDisplayedText res, buildCommand res)
        atomically $ writeTMQueue chan $  res' :| [])

toDisplayedText :: GiakResult -> T.Text
toDisplayedText res@(GiakResult _ exe) = name <> "   > " <> command
  where command = buildCommand res
        name = T.pack $ Cabal.exeName exe

displayChoices :: GatherChan -> IO ()
displayChoices results = do
    c <- Wybor.select . Wybor.fromIO . atomically $ tryReadTMQueue results
    case c of
        Right (Just c') -> writeOnTTY $ T.encodeUtf8 c'
        _ -> return ()

loadResults :: IO [GiakResult]
loadResults = do
    ss <- checkCabal
    ifM isStackReady
        (return $ toStackExe <$> ss)
        $ return ss
  where
    toStackExe (GiakResult _ exe) = GiakResult Stack exe

isStackReady :: IO Bool
isStackReady = isStackInstalled &&^ hasStackYaml
  where
    isStackInstalled = isJust <$> findExecutable "stack"
    hasStackYaml = doesFileExist "stack.yaml"

checkCabal :: IO [GiakResult]
checkCabal = namesMatching "*.cabal" >>= fromCabalFiles

fromCabalFiles :: [FilePath] -> IO [GiakResult]
fromCabalFiles fs = do
    ress <- forM fs $ \f -> (mkResult <$>) <$> readExecutables f
    return $ concat ress

readExecutables :: FilePath -> IO [Cabal.Executable]
readExecutables f =
  Cabal.executables . flattenPackageDescription <$>
    readPackageDescription Verbosity.silent f

mkResult :: Cabal.Executable -> GiakResult
mkResult = GiakResult Cabal