{-# LANGUAGE LambdaCase #-} {-# LANGUAGE DeriveGeneric #-} {- | Module : Neovim.BuildTool Description : Utilities and types to manage build tool dependent things Copyright : (c) Sebastian Witte License : Apache-2.0 Maintainer : woozletoff@gmail.com Stability : experimental Portability : GHC -} module Neovim.BuildTool where import Neovim import Data.List (isSuffixOf) import Control.Monad.IO.Class import GHC.Generics import Data.Yaml import System.Directory import System.FilePath (takeDirectory, ()) data BuildTool = Stack | Cabal CabalType | Shake | Make | Cmake | Ninja | Scons | Custom deriving (Show, Read, Eq, Ord, Generic) instance ToJSON BuildTool instance FromJSON BuildTool data CabalType = Plain | Sandbox | NewBuild deriving (Show, Read, Eq, Ord, Enum, Generic) instance ToJSON CabalType instance FromJSON CabalType newtype Directory = Directory { getDirectory :: FilePath } deriving (Show, Eq, Ord) -- | If the monadic boolean predicate returns true, wrap the given object in a -- 'Just' constructor, otherwise return 'Nothing'. partialM :: Monad m => (a -> m Bool) -> a -> m (Maybe a) partialM fp a = fp a >>= \case True -> return (Just a) False -> return Nothing -- | Create 'Just' a 'Directory' value if the given filepath exists and -- otherwise return 'Nothing'. This method does not create an actual directory -- on your file system. mkDirectory :: MonadIO io => FilePath -> io (Maybe Directory) mkDirectory mdir = fmap Directory <$> partialM (liftIO . doesDirectoryExist) mdir newtype File = File { getFile :: FilePath } deriving (Show, Eq, Ord) -- | Create 'Just' a 'File' value if the given file exists and is not a -- directory. Otherwise return 'Nothing'. This function does not alter your -- filesystem. mkFile :: MonadIO io => Maybe Directory -> FilePath -> io (Maybe File) mkFile mdir mfile = let f = maybe mfile (\d -> getDirectory d mfile) mdir in fmap File <$> partialM (liftIO . doesFileExist) f -- | Calculate the list of all parent directories for the given directory. This -- function also returns the initially specified directory. thisAndParentDirectories :: Directory -> [Directory] thisAndParentDirectories dir | parentDir == dir = [dir] | otherwise = dir : thisAndParentDirectories parentDir where parentDir = Directory . takeDirectory $ getDirectory dir -- | Given a list of build tool identifier functions, apply these to all the -- given directories and return the value of the first function that returns a -- 'BuildTool' value or 'Nothing' if no function ever returns a 'BuildTool'. -- The identifier functions and directories are tried in the order as supplied -- to this function. determineProjectSettings :: MonadIO io => [Directory -> io (Maybe BuildTool)] -> [Directory] -> io (Maybe (BuildTool, Directory)) determineProjectSettings identifiers = go identifiers where go _ [] = return Nothing go [] (_:ps) = go identifiers ps go (i:is) pps@(p:_) = i p >>= \case Just buildTool -> return (Just (buildTool, p)) Nothing -> go is pps -- | This list contains some build tool identifier functions for usual setups. defaultProjectIdentifiers :: MonadIO io => [Directory -> io (Maybe BuildTool)] defaultProjectIdentifiers = [ maybeCabalSandbox, maybeStack, maybeCabal ] -- | Same as 'determineProjectSettings' 'defaultProjetIdentifiers'. guessProjectSettings :: MonadIO io => [Directory] -> io (Maybe (BuildTool, Directory)) guessProjectSettings = determineProjectSettings defaultProjectIdentifiers -- | Check if directory contains a @stack.yaml@ file and return 'Just' 'Stack' -- in this case. maybeStack :: MonadIO io => Directory -> io (Maybe BuildTool) maybeStack d = fmap (const Stack) <$> mkFile (Just d) "stack.yaml" -- | Check if the directory contains a @cabal.sandbox.config@ file and return -- 'Just' ('Cabal' 'Sandbox') in that case. maybeCabalSandbox :: MonadIO io => Directory -> io (Maybe BuildTool) maybeCabalSandbox d = fmap (const (Cabal Sandbox)) <$> mkFile (Just d) "cabal.sandbox.config" -- | Check if the directory contains a cabal file and return 'Just' ('Cabal' -- 'Plain') if present. maybeCabal :: MonadIO io => Directory -> io (Maybe BuildTool) maybeCabal d = do ls <- liftIO . getDirectoryContents $ getDirectory d go $ filter (".cabal" `isSuffixOf`)ls where go [] = return Nothing go (f:fs) = mkFile (Just d) f >>= \case Nothing -> go fs Just _ -> -- TODO if cabal version >= 1.24(?), use NewBuild here? return $ Just (Cabal Plain)