{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
module Headroom.Command.Init
( Env(..)
, Paths(..)
, commandInit
, doesAppConfigExist
, findSupportedFileTypes
)
where
import Headroom.Command.Types ( CommandInitOptions(..) )
import Headroom.Command.Utils ( bootstrap )
import Headroom.Configuration ( makeHeadersConfig
, parseConfiguration
)
import Headroom.Configuration.Types ( Configuration(..)
, LicenseType(..)
)
import Headroom.Data.Has ( Has(..) )
import Headroom.Data.Lens ( suffixLenses )
import Headroom.Embedded ( configFileStub
, defaultConfig
, licenseTemplate
)
import Headroom.FileSystem ( FileSystem(..)
, fileExtension
, findFiles
, mkFileSystem
)
import Headroom.FileType ( fileTypeByExt )
import Headroom.FileType.Types ( FileType(..) )
import Headroom.Meta ( TemplateType )
import Headroom.Serialization ( prettyPrintYAML )
import Headroom.Template ( Template(..) )
import Headroom.Types ( fromHeadroomError
, toHeadroomError
)
import Headroom.UI ( Progress(..)
, zipWithProgress
)
import RIO
import qualified RIO.Char as C
import RIO.FilePath ( (</>) )
import qualified RIO.List as L
import qualified RIO.Map as M
import qualified RIO.NonEmpty as NE
import qualified RIO.Text as T
import qualified RIO.Text.Partial as TP
data Env = Env
{ envLogFunc :: LogFunc
, envFileSystem :: FileSystem (RIO Env)
, envInitOptions :: CommandInitOptions
, envPaths :: Paths
}
data Paths = Paths
{ pConfigFile :: FilePath
, pTemplatesDir :: FilePath
}
suffixLenses ''Env
instance HasLogFunc Env where
logFuncL = envLogFuncL
instance Has CommandInitOptions Env where
hasLens = envInitOptionsL
instance Has (FileSystem (RIO Env)) Env where
hasLens = envFileSystemL
instance Has Paths Env where
hasLens = envPathsL
env' :: CommandInitOptions -> LogFunc -> IO Env
env' opts logFunc = do
let paths = Paths { pConfigFile = ".headroom.yaml"
, pTemplatesDir = "headroom-templates"
}
pure $ Env { envLogFunc = logFunc
, envFileSystem = mkFileSystem
, envInitOptions = opts
, envPaths = paths
}
commandInit :: CommandInitOptions
-> IO ()
commandInit opts = bootstrap (env' opts) False $ doesAppConfigExist >>= \case
False -> do
fileTypes <- findSupportedFileTypes
makeTemplatesDir
createTemplates fileTypes
createConfigFile
True -> do
paths <- viewL
throwM . AppConfigAlreadyExists $ pConfigFile paths
findSupportedFileTypes :: (Has CommandInitOptions env, HasLogFunc env)
=> RIO env [FileType]
findSupportedFileTypes = do
opts <- viewL
pHeadersConfig <- cLicenseHeaders <$> parseConfiguration defaultConfig
headersConfig <- makeHeadersConfig pHeadersConfig
fileTypes <- do
allFiles <- mapM (\path -> findFiles path (const True))
(cioSourcePaths opts)
let allFileTypes = fmap (fileExtension >=> fileTypeByExt headersConfig)
(concat allFiles)
pure . L.nub . catMaybes $ allFileTypes
case fileTypes of
[] -> throwM NoProvidedSourcePaths
_ -> do
logInfo $ "Found supported file types: " <> displayShow fileTypes
pure fileTypes
createTemplates :: (Has CommandInitOptions env, HasLogFunc env, Has Paths env)
=> [FileType]
-> RIO env ()
createTemplates fileTypes = do
opts <- viewL
Paths {..} <- viewL
mapM_ (\(p, lf) -> createTemplate pTemplatesDir lf p)
(zipWithProgress $ fmap (cioLicenseType opts, ) fileTypes)
createTemplate :: (HasLogFunc env)
=> FilePath
-> (LicenseType, FileType)
-> Progress
-> RIO env ()
createTemplate templatesDir (licenseType, fileType) progress = do
let extension = NE.head $ templateExtensions @TemplateType
file = (fmap C.toLower . show $ fileType) <> "." <> T.unpack extension
filePath = templatesDir </> file
template = licenseTemplate licenseType fileType
logInfo $ mconcat
[display progress, " Creating template file in ", fromString filePath]
writeFileUtf8 filePath template
createConfigFile :: (Has CommandInitOptions env, HasLogFunc env, Has Paths env)
=> RIO env ()
createConfigFile = do
opts <- viewL
p@Paths {..} <- viewL
logInfo $ "Creating YAML config file in " <> fromString pConfigFile
writeFileUtf8 pConfigFile (configuration opts p)
where
configuration opts paths =
let withSourcePaths = TP.replace
"source-paths: []"
(toYamlList "source-paths" $ cioSourcePaths opts)
withTemplatePaths = TP.replace
"template-paths: []"
(toYamlList "template-paths" [pTemplatesDir paths])
in withTemplatePaths . withSourcePaths $ configFileStub
toYamlList field list =
T.stripEnd . prettyPrintYAML $ M.fromList [(field :: Text, list)]
doesAppConfigExist :: ( HasLogFunc env
, Has (FileSystem (RIO env)) env
, Has Paths env
)
=> RIO env Bool
doesAppConfigExist = do
FileSystem {..} <- viewL
Paths {..} <- viewL
logInfo "Verifying that there's no existing Headroom configuration..."
fsDoesFileExist pConfigFile
makeTemplatesDir :: ( HasLogFunc env
, Has (FileSystem (RIO env)) env
, Has Paths env
)
=> RIO env ()
makeTemplatesDir = do
FileSystem {..} <- viewL
Paths {..} <- viewL
logInfo $ "Creating directory for templates in " <> fromString pTemplatesDir
fsCreateDirectory pTemplatesDir
data CommandInitError
= AppConfigAlreadyExists !FilePath
| NoProvidedSourcePaths
| NoSupportedFileType
deriving (Eq, Show)
instance Exception CommandInitError where
displayException = displayException'
toException = toHeadroomError
fromException = fromHeadroomError
displayException' :: CommandInitError -> String
displayException' = T.unpack . \case
AppConfigAlreadyExists path -> appConfigAlreadyExists path
NoProvidedSourcePaths -> noProvidedSourcePaths
NoSupportedFileType -> noSupportedFileType
where
appConfigAlreadyExists path =
mconcat ["Configuration file '", T.pack path, "' already exists"]
noProvidedSourcePaths = "No source code paths (files or directories) defined"
noSupportedFileType = "No supported file type found in scanned source paths"