packages feed

runhs-1.0.0.8: src/Main.hs

{-
resolver: nightly
packages:
  - bytestring
  - file-embed
  - process
  - terminal-size
  - text
  - word-wrap
  - yaml
-}
{-# OPTIONS_GHC -Wall -fno-warn-name-shadowing #-}
{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}

module Main (main) where

import Data.Foldable

import Data.FileEmbed (embedStringFile)
import Data.Yaml (decodeThrow, parseMaybe, withObject, (.:), (.:?))

import qualified Data.ByteString.Char8 as BS

import qualified System.Console.Terminal.Size as Sys
import qualified System.Environment as Sys
import qualified System.Exit as Sys
import qualified System.IO as Sys
import qualified System.Process as Sys

import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import qualified Text.Wrap as Text

data RunSpec = RunSpec
    { file :: FilePath
    , resolver :: String
    , packages :: [String]
    }

spec :: FilePath -> IO RunSpec
spec path = do
    let readHeader =
            decodeThrow . BS.pack
            . unlines
            . snd . break (/= "{-")
            . fst . break (== "-}")
            . fmap (filter (/= '\r'))
            . lines
    header <- readHeader =<< readFile path
    (resolver, packages) <-
        maybe
            (help . Just . unwords $
                ["Unable to parse the front matter in", path, "."])
            pure
        . flip parseMaybe header
        . withObject "header"
        $ \hdr -> do
            resolver <- hdr .: "resolver"
            packages <- fmap fold $ hdr .:? "packages"
            return (resolver, packages)
    return (RunSpec path resolver packages)

stackArgs :: RunSpec -> [String]
stackArgs spec =
    ["--resolver", resolver spec]
    <> (packages spec >>= \package -> ["--package", package])
    <> [file spec]

main :: IO ()
main = do
    Sys.hSetBuffering Sys.stdout Sys.NoBuffering
    args <- Sys.getArgs
    case args of
        "watch":file:args' -> watch args' =<< spec file
        "repl":file:_ -> repl =<< spec file
        "compile":file:_ -> compile =<< spec file
        "script":file:args' -> script args' =<< spec file
        arg1:_
            | arg1 `elem` ["--version", "-version", "-v"] -> version
            | arg1 `elem` ["--help", "-help", "-h"] -> help Nothing
        _ -> help (Just "Unable to parse the command-line arguments.")

runProcess :: Sys.CreateProcess -> IO ()
runProcess process = do
    (_, _, _, h) <- Sys.createProcess process
    code <- Sys.waitForProcess h
    Sys.exitWith code

watch :: [String] -> RunSpec -> IO ()
watch args spec = runProcess $
    Sys.proc "stack" $
        [ "exec"
        , "--resolver"
        , resolver spec
        , "ghcid"
        , "--"
        , "--command"
        , "'" <> (unwords $ "stack" : "repl" : stackArgs spec) <> "'"
        ]
        <> args

repl :: RunSpec -> IO ()
repl spec = runProcess $
    Sys.proc "stack" ("repl" : stackArgs spec)

compile :: RunSpec -> IO ()
compile spec = runProcess $
    Sys.proc "stack" ("ghc" : stackArgs spec)

script :: [String] -> RunSpec -> IO ()
script args spec = runProcess $
    Sys.proc "stack" ("runhaskell" : stackArgs spec <> args)

help :: Maybe String -> IO a
help errMaybe = do
    version
    n <- min 72 . maybe 72 Sys.width <$> Sys.size

    putStrLn (replicate n '~')
    Text.putStrLn
        . Text.wrapText (Text.WrapSettings True False) n
        $ Text.pack $(embedStringFile "README.md")
    putStrLn (replicate n '~')

    case errMaybe of
        Nothing -> Sys.exitWith Sys.ExitSuccess
        Just err -> do
            putStrLn $ unwords ["runhs:", err, "Please see \"Usage\" above."]
            Sys.exitWith (Sys.ExitFailure 1)

version :: IO ()
version =
    traverse_ putStrLn
    . ("runhs" :)
    . filter (\ln -> take 8 ln == "version:" || take 10 ln == "copyright:")
    . lines
    $ $(embedStringFile "runhs.cabal")