{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Client.Init.FileCreators -- Copyright : (c) Brent Yorgey 2009 -- License : BSD-like -- -- Maintainer : cabal-devel@haskell.org -- Stability : provisional -- Portability : portable -- -- Functions to create files during 'cabal init'. -- ----------------------------------------------------------------------------- module Distribution.Client.Init.FileCreators ( -- * Commands writeProject , writeLicense , writeChangeLog , prepareLibTarget , prepareExeTarget , prepareTestTarget ) where import Prelude hiding (writeFile, readFile) import Distribution.Client.Compat.Prelude hiding (head, empty, writeFile, readFile) import qualified Data.Set as Set (member) import Distribution.Client.Init.Defaults import Distribution.Client.Init.Licenses ( bsd2, bsd3, gplv2, gplv3, lgpl21, lgpl3, agplv3, apache20, mit, mpl20, isc ) import Distribution.Client.Init.Types hiding (putStrLn, putStr, message) import qualified Distribution.Client.Init.Types as T import Distribution.Fields.Pretty (PrettyField(..), showFields') import qualified Distribution.SPDX as SPDX import Distribution.Types.PackageName import Distribution.Client.Init.Format import Distribution.CabalSpecVersion (showCabalSpecVersion) import System.FilePath ((), (<.>)) import Distribution.FieldGrammar.Newtypes import Distribution.License (licenseToSPDX) -- -------------------------------------------------------------------- -- -- File generation writeProject :: Interactive m => ProjectSettings -> m () writeProject (ProjectSettings opts pkgDesc libTarget exeTarget testTarget) | null pkgName = do message opts T.Error "no package name given, so no .cabal file can be generated\n" | otherwise = do -- clear prompt history a bit" message opts T.Log $ "Using cabal specification: " ++ showCabalSpecVersion (_optCabalSpec opts) writeLicense opts pkgDesc writeChangeLog opts pkgDesc let pkgFields = mkPkgDescription opts pkgDesc commonStanza = mkCommonStanza opts libStanza <- prepareLibTarget opts libTarget exeStanza <- prepareExeTarget opts exeTarget testStanza <- prepareTestTarget opts testTarget (reusedCabal, cabalContents) <- writeCabalFile opts $ pkgFields ++ [commonStanza, libStanza, exeStanza, testStanza] when (null $ _pkgSynopsis pkgDesc) $ message opts T.Warning "No synopsis given. You should edit the .cabal file and add one." message opts T.Info "You may want to edit the .cabal file and add a Description field." when reusedCabal $ do existingCabal <- readFile $ unPackageName (_optPkgName opts) ++ ".cabal" when (existingCabal /= cabalContents) $ message opts T.Warning "A .cabal file was found and not updated, if updating is desired please use the '--overwrite' option." -- clear out last line for presentation. T.putStrLn "" where pkgName = unPackageName $ _optPkgName opts prepareLibTarget :: Interactive m => WriteOpts -> Maybe LibTarget -> m (PrettyField FieldAnnotation) prepareLibTarget _ Nothing = return PrettyEmpty prepareLibTarget opts (Just libTarget) = do void $ writeDirectoriesSafe opts $ filter (/= ".") srcDirs -- avoid writing when conflicting exposed paths may -- exist. when (expMods == (myLibModule :| [])) . void $ writeFileSafe opts libPath myLibHs return $ mkLibStanza opts libTarget where expMods = _libExposedModules libTarget srcDirs = _libSourceDirs libTarget libPath = case srcDirs of path:_ -> path _hsFilePath myLibFile _ -> _hsFilePath myLibFile prepareExeTarget :: Interactive m => WriteOpts -> Maybe ExeTarget -> m (PrettyField FieldAnnotation) prepareExeTarget _ Nothing = return PrettyEmpty prepareExeTarget opts (Just exeTarget) = do void $ writeDirectoriesSafe opts appDirs void $ writeFileSafe opts mainPath mainHs return $ mkExeStanza opts exeTarget where exeMainIs = _exeMainIs exeTarget pkgType = _optPkgType opts appDirs = _exeApplicationDirs exeTarget mainFile = _hsFilePath exeMainIs mainPath = case appDirs of appPath:_ -> appPath mainFile _ -> mainFile mainHs = unlines . mkLiterate exeMainIs $ if pkgType == LibraryAndExecutable then myLibExeHs else myExeHs prepareTestTarget :: Interactive m => WriteOpts -> Maybe TestTarget -> m (PrettyField FieldAnnotation) prepareTestTarget _ Nothing = return PrettyEmpty prepareTestTarget opts (Just testTarget) = do void $ writeDirectoriesSafe opts testDirs' void $ writeFileSafe opts testPath myTestHs return $ mkTestStanza opts testTarget where testDirs' = _testDirs testTarget testMainIs = _hsFilePath $ _testMainIs testTarget testPath = case testDirs' of p:_ -> p testMainIs _ -> testMainIs writeCabalFile :: Interactive m => WriteOpts -> [PrettyField FieldAnnotation] -- ^ .cabal fields -> m (Bool, String) writeCabalFile opts fields = do let cabalContents = showFields' annCommentLines postProcessFieldLines 4 fields reusedCabal <- writeFileSafe opts cabalFileName cabalContents return (reusedCabal, cabalContents) where cabalFileName = pkgName ++ ".cabal" pkgName = unPackageName $ _optPkgName opts -- | Write the LICENSE file. -- -- For licenses that contain the author's name(s), the values are taken -- from the 'authors' field of 'InitFlags', and if not specified will -- be the string "???". -- -- If the license type is unknown no license file will be prepared and -- a warning will be raised. -- writeLicense :: Interactive m => WriteOpts -> PkgDescription -> m () writeLicense writeOpts pkgDesc = do year <- show <$> getCurrentYear case licenseFile year (_pkgAuthor pkgDesc) of Just licenseText -> void $ writeFileSafe writeOpts "LICENSE" licenseText Nothing -> message writeOpts T.Warning "unknown license type, you must put a copy in LICENSE yourself." where getLid (Left (SPDX.License (SPDX.ELicense (SPDX.ELicenseId lid) Nothing))) = Just lid getLid (Right l) = getLid . Left $ licenseToSPDX l getLid _ = Nothing licenseFile year auth = case getLid . getSpecLicense $ _pkgLicense pkgDesc of Just SPDX.BSD_2_Clause -> Just $ bsd2 auth year Just SPDX.BSD_3_Clause -> Just $ bsd3 auth year Just SPDX.Apache_2_0 -> Just apache20 Just SPDX.MIT -> Just $ mit auth year Just SPDX.MPL_2_0 -> Just mpl20 Just SPDX.ISC -> Just $ isc auth year Just SPDX.GPL_2_0_only -> Just gplv2 Just SPDX.GPL_3_0_only -> Just gplv3 Just SPDX.LGPL_2_1_only -> Just lgpl21 Just SPDX.LGPL_3_0_only -> Just lgpl3 Just SPDX.AGPL_3_0_only -> Just agplv3 Just SPDX.GPL_2_0_or_later -> Just gplv2 Just SPDX.GPL_3_0_or_later -> Just gplv3 Just SPDX.LGPL_2_1_or_later -> Just lgpl21 Just SPDX.LGPL_3_0_or_later -> Just lgpl3 Just SPDX.AGPL_3_0_or_later -> Just agplv3 _ -> Nothing -- | Writes the changelog to the current directory. -- writeChangeLog :: Interactive m => WriteOpts -> PkgDescription -> m () writeChangeLog opts pkgDesc | Just docs <- _pkgExtraDocFiles pkgDesc , defaultChangelog `Set.member` docs = go | defaultChangelog `elem` _pkgExtraSrcFiles pkgDesc = go | otherwise = return () where changeLog = unlines [ "# Revision history for " ++ prettyShow (_pkgName pkgDesc) , "" , "## " ++ prettyShow (_pkgVersion pkgDesc) ++ " -- YYYY-mm-dd" , "" , "* First version. Released on an unsuspecting world." ] go = void $ writeFileSafe opts defaultChangelog changeLog -- -------------------------------------------------------------------- -- -- Utilities data WriteAction = Overwrite | Fresh | Existing deriving Eq instance Show WriteAction where show Overwrite = "Overwriting" show Fresh = "Creating fresh" show Existing = "Using existing" -- | Possibly generate a message to stdout, taking into account the -- --quiet flag. message :: Interactive m => WriteOpts -> T.Severity -> String -> m () message opts = T.message (_optVerbosity opts) -- | Write a file \"safely\" if it doesn't exist, backing up any existing version when -- the overwrite flag is set. writeFileSafe :: Interactive m => WriteOpts -> FilePath -> String -> m Bool writeFileSafe opts fileName content = do exists <- doesFileExist fileName let action | exists && doOverwrite = Overwrite | not exists = Fresh | otherwise = Existing go exists message opts T.Log $ show action ++ " file " ++ fileName ++ "..." return $ action == Existing where doOverwrite = _optOverwrite opts go exists | not exists = do writeFile fileName content | exists && doOverwrite = do newName <- findNewPath fileName message opts T.Log $ concat [ fileName , " already exists. Backing up old version in " , newName ] copyFile fileName newName -- backups the old file removeExistingFile fileName -- removes the original old file writeFile fileName content -- writes the new file | otherwise = return () writeDirectoriesSafe :: Interactive m => WriteOpts -> [String] -> m Bool writeDirectoriesSafe opts dirs = fmap or $ for dirs $ \dir -> do exists <- doesDirectoryExist dir let action | exists && doOverwrite = Overwrite | not exists = Fresh | otherwise = Existing go dir exists message opts T.Log $ show action ++ " directory ./" ++ dir ++ "..." return $ action == Existing where doOverwrite = _optOverwrite opts go dir exists | not exists = do createDirectory dir | exists && doOverwrite = do newDir <- findNewPath dir message opts T.Log $ concat [ dir , " already exists. Backing up old version in " , newDir ] renameDirectory dir newDir -- backups the old directory createDirectory dir -- creates the new directory | otherwise = return () findNewPath :: Interactive m => FilePath -> m FilePath findNewPath dir = go (0 :: Int) where go n = do let newDir = dir <.> ("save" ++ show n) e <- doesDirectoryExist newDir if e then go (succ n) else return newDir