cabal-install-3.14.1.0: src/Distribution/Client/Init/NonInteractive/Heuristics.hs
{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Client.Init.NonInteractive.Heuristics
-- Copyright : (c) Benedikt Huber 2009
-- License : BSD-like
--
-- Maintainer : cabal-devel@haskell.org
-- Stability : provisional
-- Portability : portable
--
-- Heuristics for creating initial cabal files.
module Distribution.Client.Init.NonInteractive.Heuristics
( guessPackageName
, guessMainFile
, guessLicense
, guessExtraDocFiles
, guessAuthorName
, guessAuthorEmail
, guessCabalSpecVersion
, guessLanguage
, guessPackageType
, guessSourceDirectories
, guessApplicationDirectories
) where
import Distribution.Client.Compat.Prelude hiding (many, readFile, (<|>))
import Distribution.Simple.Setup (fromFlagOrDefault)
import qualified Data.List as L
import qualified Data.Set as Set
import Distribution.CabalSpecVersion
import Distribution.Client.Init.Defaults
import Distribution.Client.Init.FlagExtractors (getCabalVersionNoPrompt)
import Distribution.Client.Init.Types
import Distribution.Client.Init.Utils
import Distribution.FieldGrammar.Newtypes
import Distribution.Simple.Compiler
import Distribution.Types.PackageName (PackageName)
import Distribution.Version
import Language.Haskell.Extension
import System.FilePath
-- | Guess the main file, returns a default value if none is found.
guessMainFile :: Interactive m => FilePath -> m HsFilePath
guessMainFile pkgDir = do
exists <- doesDirectoryExist pkgDir
if exists
then do
files <- filter isMain <$> listFilesRecursive pkgDir
return $
case files of
[] -> defaultMainIs
(f : _) -> toHsFilePath f
else return defaultMainIs
-- | Juggling characters around to guess the desired cabal version based on
-- the system's cabal version.
guessCabalSpecVersion :: Interactive m => m CabalSpecVersion
guessCabalSpecVersion = do
(_, verString, _) <- readProcessWithExitCode "cabal" ["--version"] ""
case simpleParsec $ takeWhile (not . isSpace) $ dropWhile (not . isDigit) verString of
Just v -> pure $ fromMaybe defaultCabalVersion $ case versionNumbers v of
[x, y, _, _] -> cabalSpecFromVersionDigits [x, y]
[x, y, _] -> cabalSpecFromVersionDigits [x, y]
_ -> Just defaultCabalVersion
Nothing -> pure defaultCabalVersion
-- | Guess the language specification based on the GHC version
guessLanguage :: Interactive m => Compiler -> m Language
guessLanguage Compiler{compilerId = CompilerId GHC ver} =
return $
if ver < mkVersion [7, 0, 1]
then Haskell98
else Haskell2010
guessLanguage _ = return defaultLanguage
-- | Guess the package name based on the given root directory.
guessPackageName :: Interactive m => FilePath -> m PackageName
guessPackageName = filePathToPkgName
-- | Try to guess the license from an already existing @LICENSE@ file in
-- the package directory, comparing the file contents with the ones
-- listed in @Licenses.hs@, for now it only returns a default value.
guessLicense :: Interactive m => InitFlags -> m SpecLicense
guessLicense flags = return . defaultLicense $ getCabalVersionNoPrompt flags
guessExtraDocFiles :: Interactive m => InitFlags -> m (Maybe (Set FilePath))
guessExtraDocFiles flags = do
pkgDir <- fromFlagOrDefault getCurrentDirectory $ return <$> packageDir flags
files <- getDirectoryContents pkgDir
let extraDocCandidates = ["CHANGES", "CHANGELOG", "README"]
extraDocs = [y | x <- extraDocCandidates, y <- files, x == map toUpper (takeBaseName y)]
return $
Just $
if null extraDocs
then Set.singleton defaultChangelog
else Set.fromList extraDocs
-- | Try to guess the package type from the files in the package directory,
-- looking for unique characteristics from each type, defaults to Executable.
guessPackageType :: Interactive m => InitFlags -> m PackageType
guessPackageType flags = do
if fromFlagOrDefault False (initializeTestSuite flags)
then return TestSuite
else do
let lastDir dirs = L.last . splitDirectories $ dirs
srcCandidates = [defaultSourceDir, "src", "source"]
testCandidates = [defaultTestDir, "test", "tests"]
pkgDir <- fromFlagOrDefault getCurrentDirectory $ return <$> packageDir flags
files <- listFilesInside (\x -> return $ lastDir x `notElem` testCandidates) pkgDir
files' <-
filter (not . null . map (`elem` testCandidates) . splitDirectories)
<$> listFilesRecursive pkgDir
let hasExe = not $ null [f | f <- files, isMain $ takeFileName f]
hasLib = not $ null [f | f <- files, lastDir f `elem` srcCandidates]
hasTest = not $ null [f | f <- files', isMain $ takeFileName f]
return $ case (hasLib, hasExe, hasTest) of
(True, True, _) -> LibraryAndExecutable
(True, False, _) -> Library
(False, False, True) -> TestSuite
_ -> Executable
-- | Try to guess the application directories from the package directory,
-- using a default value as fallback.
guessApplicationDirectories :: Interactive m => InitFlags -> m [FilePath]
guessApplicationDirectories flags = do
pkgDirs <-
fromFlagOrDefault
getCurrentDirectory
(return <$> packageDir flags)
pkgDirsContents <- listDirectory pkgDirs
let candidates = [defaultApplicationDir, "app", "src-exe"]
in return $ case [y | x <- candidates, y <- pkgDirsContents, x == y] of
[] -> [defaultApplicationDir]
x -> map (</> pkgDirs) . nub $ x
-- | Try to guess the source directories, using a default value as fallback.
guessSourceDirectories :: Interactive m => InitFlags -> m [FilePath]
guessSourceDirectories flags = do
pkgDir <- fromFlagOrDefault getCurrentDirectory $ return <$> packageDir flags
doesDirectoryExist (pkgDir </> "src")
>>= return . \case
False -> [defaultSourceDir]
True -> ["src"]
-- | Guess author and email using git configuration options.
guessAuthorName :: Interactive m => m (Maybe String)
guessAuthorName = guessGitInfo "user.name"
guessAuthorEmail :: Interactive m => m (Maybe String)
guessAuthorEmail = guessGitInfo "user.email"
guessGitInfo :: Interactive m => String -> m (Maybe String)
guessGitInfo target = do
localInfo <- maybeReadProcessWithExitCode "git" ["config", "--local", target] ""
case localInfo of
Nothing -> return Nothing
Just (_, localStdout, _) ->
if null localStdout
then do
globalInfo <- maybeReadProcessWithExitCode "git" ["config", "--global", target] ""
case globalInfo of
Just (ExitSuccess, globalStdout, _) -> return $ Just (trim globalStdout)
_ -> return Nothing
else return $ Just (trim localStdout)