headroom-0.1.3.0: src/Headroom/Command/Init.hs
{-|
Module : Headroom.Command.Init
Description : Logic for Init command
Copyright : (c) 2019-2020 Vaclav Svejcar
License : BSD-3
Maintainer : vaclav.svejcar@gmail.com
Stability : experimental
Portability : POSIX
Logic for the @init@ command, used to generate initial configuration boilerplate
for Headroom.
-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module Headroom.Command.Init
( commandInit
, doesAppConfigExist
, findSupportedFileTypes
)
where
import Headroom.AppConfig ( AppConfig(..)
, prettyPrintAppConfig
)
import Headroom.Command.Init.Env
import Headroom.Command.Shared ( bootstrap )
import Headroom.Embedded ( licenseTemplate )
import Headroom.FileSystem ( fileExtension
, findFiles
)
import Headroom.FileType ( FileType
, fileTypeByExt
)
import Headroom.Global ( TemplateType )
import Headroom.License ( License(..) )
import Headroom.Template ( templateExtensions )
import Headroom.Types ( HeadroomError(..)
, InitCommandError(..)
)
import Headroom.UI.Progress ( Progress(..)
, zipWithProgress
)
import RIO
import qualified RIO.Char as C
import RIO.Directory ( createDirectory
, doesFileExist
, getCurrentDirectory
)
import RIO.FilePath ( (</>) )
import qualified RIO.HashMap as HM
import qualified RIO.List as L
import qualified RIO.NonEmpty as NE
import qualified RIO.Text as T
env' :: InitOptions -> LogFunc -> IO Env
env' opts logFunc = do
currentDir <- getCurrentDirectory
let paths = Paths { pCurrentDir = currentDir
, pConfigFile = ".headroom.yaml"
, pTemplatesDir = "headroom-templates"
}
pure $ Env { envLogFunc = logFunc, envInitOptions = opts, envPaths = paths }
-- | Handler for /Init/ command.
commandInit :: InitOptions -- ^ /Init/ command options
-> IO () -- ^ execution result
commandInit opts = bootstrap (env' opts) False $ doesAppConfigExist >>= \case
False -> do
fileTypes <- findSupportedFileTypes
makeTemplatesDir
createTemplates fileTypes
createConfigFile
True -> throwM $ InitCommandError AppConfigAlreadyExists
-- | Recursively scans provided source paths for known file types for which
-- templates can be generated.
findSupportedFileTypes :: (HasInitOptions env, HasLogFunc env)
=> RIO env [FileType]
findSupportedFileTypes = do
opts <- view initOptionsL
fileTypes <- do
allFiles <- mapM (\path -> findFiles path (const True)) (ioSourcePaths opts)
let allFileTypes = fmap (fileExtension >=> fileTypeByExt) (concat allFiles)
pure $ L.nub . catMaybes $ allFileTypes
case fileTypes of
[] -> throwM $ InitCommandError NoSourcePaths
_ -> do
logInfo $ "Found supported file types: " <> displayShow fileTypes
pure fileTypes
createTemplates :: (HasInitOptions env, HasLogFunc env, HasPaths env)
=> [FileType]
-> RIO env ()
createTemplates fileTypes = do
opts <- view initOptionsL
paths <- view pathsL
let templatesDir = pCurrentDir paths </> pTemplatesDir paths
mapM_ (\(p, l) -> createTemplate templatesDir l p)
(zipWithProgress $ fmap (License (ioLicenseType opts)) fileTypes)
createTemplate :: (HasLogFunc env)
=> FilePath
-> License
-> Progress
-> RIO env ()
createTemplate templatesDir license@(License _ fileType) progress = do
let extension = NE.head $ templateExtensions (Proxy :: Proxy TemplateType)
file = (fmap C.toLower . show $ fileType) <> "." <> T.unpack extension
filePath = templatesDir </> file
template = licenseTemplate license
logInfo $ mconcat
[display progress, " Creating template file in ", fromString filePath]
writeFileUtf8 filePath template
createConfigFile :: (HasInitOptions env, HasLogFunc env, HasPaths env)
=> RIO env ()
createConfigFile = do
opts <- view initOptionsL
paths <- view pathsL
let filePath = pCurrentDir paths </> pConfigFile paths
content = prettyPrintAppConfig $ appConfig opts paths
logInfo $ "Creating YAML config file in " <> fromString filePath
writeFileUtf8 filePath content
where
variables = HM.fromList
[ ("author" , "John Smith")
, ("email" , "john.smith@example.com")
, ("project", "My project")
, ("year" , "2020")
]
appConfig opts paths = mempty { acSourcePaths = ioSourcePaths opts
, acTemplatePaths = [pTemplatesDir paths]
, acVariables = variables
}
-- | Checks whether application config file already exists.
doesAppConfigExist :: (HasLogFunc env, HasPaths env) => RIO env Bool
doesAppConfigExist = do
paths <- view pathsL
logInfo "Verifying that there's no existing Headroom configuration..."
doesFileExist $ pCurrentDir paths </> pConfigFile paths
-- | Creates directory for template files.
makeTemplatesDir :: (HasLogFunc env, HasPaths env) => RIO env ()
makeTemplatesDir = do
paths <- view pathsL
let templatesDir = pCurrentDir paths </> pTemplatesDir paths
logInfo $ "Creating directory for templates in " <> fromString templatesDir
createDirectory templatesDir