proteome-0.9.9.9: lib/Proteome/Project/Resolve.hs
module Proteome.Project.Resolve where
import Control.Monad (foldM)
import Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT))
import Data.List (nub)
import Data.List.Extra (firstJust)
import Data.Map.Strict ((!?))
import qualified Data.Map.Strict as Map (toList, union)
import Exon (exon)
import qualified Log
import Path (Abs, Dir, Path, dirname, isProperPrefixOf, parent, parseRelDir, stripProperPrefix, toFilePath, (</>))
import Path.IO (doesDirExist)
import Ribosome (Settings)
import qualified Ribosome.Settings as Settings
import System.FilePath.Glob (globDir1)
import qualified System.FilePath.Glob as Glob (compile)
import System.FilePattern.Directory (getDirectoryFiles)
import Proteome.Config (defaultTypeMarkers)
import Proteome.Data.Project (Project (Project))
import qualified Proteome.Data.ProjectConfig as ProjectConfig
import Proteome.Data.ProjectConfig (ProjectConfig (ProjectConfig))
import Proteome.Data.ProjectLang (ProjectLang (ProjectLang))
import Proteome.Data.ProjectMetadata (ProjectMetadata (DirProject, VirtualProject))
import Proteome.Data.ProjectName (ProjectName (ProjectName))
import Proteome.Data.ProjectRoot (ProjectRoot (ProjectRoot), unProjectRoot)
import Proteome.Data.ProjectSpec (ProjectSpec (ProjectSpec))
import qualified Proteome.Data.ProjectSpec as PS (ProjectSpec (..))
import Proteome.Data.ProjectType (ProjectType (ProjectType))
import qualified Proteome.Data.ResolveError as ResolveError (ResolveError (..))
import Proteome.Data.ResolveError (ResolveError)
import Proteome.Path (dropSlash, parseAbsDirMaybe, rootPathSegment)
import Proteome.Project (pathData)
import qualified Proteome.Settings as Settings (projectConfig, projects)
projectFromSegments :: ProjectType -> ProjectName -> ProjectRoot -> Project
projectFromSegments tpe name root =
Project (DirProject name root (Just tpe)) [] Nothing []
projectFromSpec :: ProjectSpec -> Project
projectFromSpec (ProjectSpec name root tpe types lang langs) =
Project (DirProject name root tpe) types lang langs
hasProjectRoot :: ProjectRoot -> ProjectSpec -> Bool
hasProjectRoot root spec = root == PS.root spec
hasProjectTypeName :: ProjectType -> ProjectName -> ProjectSpec -> Bool
hasProjectTypeName tpe' name' (ProjectSpec name _ (Just tpe) _ _ _) =
name' == name && tpe' == tpe
hasProjectTypeName _ _ _ = False
byProjectTypeName :: [ProjectSpec] -> ProjectName -> ProjectType -> Maybe ProjectSpec
byProjectTypeName specs name tpe = find (hasProjectTypeName tpe name) specs
matchProjectBases :: [Path Abs Dir] -> ProjectRoot -> Bool
matchProjectBases baseDirs (ProjectRoot root) = (parent . parent) root `elem` baseDirs
byProjectBaseSubpath ::
Members [Stop ResolveError, Embed IO] r =>
ProjectName ->
ProjectType ->
Path Abs Dir ->
Sem r (Maybe Project)
byProjectBaseSubpath n@(ProjectName name) t@(ProjectType tpe) base = do
tpePath <- stopEitherAs (ResolveError.ParsePath tpe) $ parseRelDir (toString tpe)
namePath <- stopEitherAs (ResolveError.ParsePath name) $ parseRelDir (toString name)
let root = base </> tpePath </> namePath
exists <- doesDirExist root
pure $ if exists then Just $ projectFromSegments t n (ProjectRoot root) else Nothing
byProjectBasesSubpath ::
Members [Stop ResolveError, Embed IO] r =>
[Path Abs Dir] ->
ProjectName ->
ProjectType ->
Sem r (Maybe Project)
byProjectBasesSubpath baseDirs name tpe =
foldM subpath Nothing baseDirs
where
subpath (Just p) _ = pure (Just p)
subpath Nothing a = byProjectBaseSubpath name tpe a
virtualProject :: ProjectName -> Project
virtualProject name =
Project (VirtualProject name) [] Nothing []
resolveByTypeAndPath :: [Path Abs Dir] -> ProjectName -> ProjectType -> ProjectRoot -> Maybe Project
resolveByTypeAndPath baseDirs name tpe root =
if matchProjectBases baseDirs root then Just (projectFromSegments tpe name root) else Nothing
resolveByType ::
Members [Stop ResolveError, Embed IO] r =>
[Path Abs Dir] ->
[ProjectSpec] ->
ProjectName ->
ProjectType ->
Sem r (Maybe Project)
resolveByType baseDirs explicit name tpe = do
byBaseSubpath <- byProjectBasesSubpath baseDirs name tpe
pure (byBaseSubpath <|> projectFromSpec <$> byTypeName)
where
byTypeName =
byProjectTypeName explicit name tpe
fromProjectRoot :: Path Abs Dir -> Project
fromProjectRoot dir =
projectFromSegments tpe name root
where
(root, name, tpe) = pathData dir
projectFromNameIn ::
Members [Stop ResolveError, Embed IO] r =>
ProjectName ->
Path Abs Dir ->
Sem r (Maybe Project)
projectFromNameIn (ProjectName name) base =
fmap fromProjectRoot . join . find isJust <$> matches
where
matches =
fmap (parseAbsDirMaybe . toText) <$> glob
glob =
embed $ globDir1 (Glob.compile ("*/" <> toString name)) (toFilePath base)
resolveByName ::
Members [Stop ResolveError, Embed IO] r =>
[Path Abs Dir] ->
ProjectName ->
Sem r (Maybe Project)
resolveByName baseDirs name =
firstJustM (projectFromNameIn name) baseDirs
globDir ::
Members [Stop ResolveError, Embed IO] r =>
Path Abs Dir ->
[Text] ->
Sem r (Maybe FilePath)
globDir root patterns =
(head =<<) <$> tryMaybe (getDirectoryFiles (toFilePath root) (toString <$> patterns))
resolveFromDirContents ::
Members [Stop ResolveError, Embed IO] r =>
Map ProjectType [Text] ->
ProjectName ->
ProjectRoot ->
Sem r (Maybe Project)
resolveFromDirContents typeMarkers name projectRoot@(ProjectRoot root) =
fmap cons <$> firstJustM match (Map.toList typeMarkers)
where
cons projectType =
projectFromSegments projectType name projectRoot
match (tpe, patterns) =
(tpe <$) <$> globDir root patterns
resolveByRoot ::
Members [Stop ResolveError, Embed IO] r =>
ProjectConfig ->
ProjectName ->
[ProjectSpec] ->
ProjectRoot ->
Sem r (Maybe Project)
resolveByRoot (ProjectConfig _ _ _ _ typeMarkers _ _) name explicit root =
maybe (resolveFromDirContents typeMarkers name root) (pure . Just . projectFromSpec) fromExplicit
where
fromExplicit = find (hasProjectRoot root) explicit
augment ::
Eq a =>
Ord k =>
Map k [a] ->
k ->
[a] ->
[a]
augment m tpe as =
case m !? tpe of
Just extra -> nub $ as ++ extra
Nothing -> as
augmentTypes :: ProjectConfig -> ProjectType -> [ProjectType] -> [ProjectType]
augmentTypes (ProjectConfig _ _ _ typeMap _ _ _) =
augment typeMap
resolveLanguage :: ProjectConfig -> ProjectType -> ProjectLang
resolveLanguage (ProjectConfig _ _ _ _ _ langMap _) t@(ProjectType tpe) =
fromMaybe (ProjectLang tpe) (langMap !? t)
augmentLangs :: ProjectConfig -> ProjectLang -> [ProjectLang] -> [ProjectLang]
augmentLangs (ProjectConfig _ _ _ _ _ _ langsMap) =
augment langsMap
augmentFromConfig :: ProjectConfig -> Project -> Project
augmentFromConfig config (Project meta@(DirProject _ _ (Just tpe)) types lang langs) =
Project meta (augmentTypes config tpe types) (Just lang') (augmentLangs config lang' langs)
where
lang' = fromMaybe (resolveLanguage config tpe) lang
augmentFromConfig _ project =
project
fromName ::
Members [Stop ResolveError, Log, Embed IO] r =>
[ProjectSpec] ->
ProjectConfig ->
ProjectName ->
Maybe ProjectType ->
Sem r Project
fromName explicit config name tpe = do
let baseDirs = ProjectConfig.baseDirs config
byType <- join <$> traverse (resolveByType baseDirs explicit name) tpe
byName <- resolveByName baseDirs name
let byNameOrVirtual = fromMaybe (virtualProject name) byName
let project = fromMaybe byNameOrVirtual byType
Log.debug (logMsg byType byName)
pure (augmentFromConfig config project)
where
logMsg byType byName =
[exon|resolved project: byType(#{show byType}) byName(#{show byName})|]
fromNameSettings ::
Members [Settings, Stop ResolveError, Log, Embed IO] r =>
ProjectName ->
Maybe ProjectType ->
Sem r Project
fromNameSettings name tpe = do
explicit <- Settings.get Settings.projects
config <- projectConfig
fromName explicit config name tpe
projectConfig ::
Member Settings r =>
Sem r ProjectConfig
projectConfig =
(#typeMarkers %~ (`Map.union` defaultTypeMarkers)) <$> Settings.get Settings.projectConfig
rootExplicit :: [ProjectSpec] -> ProjectRoot -> Maybe Project
rootExplicit explicit root =
projectFromSpec <$> find (hasProjectRoot root) explicit
rootProjectTypes ::
Map ProjectType [Path Abs Dir] ->
ProjectName ->
ProjectRoot ->
Maybe Project
rootProjectTypes tpes name root@(ProjectRoot rootDir) =
cons . fst <$> find (elem rootDir . snd) (Map.toList tpes)
where
cons tpe =
projectFromSegments tpe name root
rootTypeDirs ::
Map ProjectType [Path Abs Dir] ->
ProjectName ->
ProjectRoot ->
Maybe Project
rootTypeDirs types name root@(ProjectRoot rootDir) =
cons . fst <$> find (matchType . snd) (Map.toList types)
where
cons tpe =
projectFromSegments tpe name root
matchType =
any match
match base = do
isProperPrefixOf base rootDir
rootBaseDirs ::
[Path Abs Dir] ->
ProjectName ->
ProjectRoot ->
Maybe Project
rootBaseDirs bases name root@(ProjectRoot rootDir) =
firstJust match bases
where
match base = do
rel <- stripProperPrefix base rootDir
let tpe = rootPathSegment (parent rel)
if rel /= tpe
then Just (projectFromSegments (ProjectType (dropSlash tpe)) name root)
else Nothing
projectName :: ProjectRoot -> ProjectName
projectName =
ProjectName . dropSlash . dirname . unProjectRoot
firstJustMOr ::
Monad m =>
a ->
[m (Maybe a)] ->
m a
firstJustMOr fallback =
fmap (fromMaybe fallback) . runMaybeT . asum @[] . fmap MaybeT
fromRoot ::
Members [Stop ResolveError, Embed IO] r =>
[ProjectSpec] ->
ProjectConfig ->
ProjectRoot ->
Sem r Project
fromRoot explicit config@ProjectConfig {..} root = do
let
byRoot =
asum @[] [
rootExplicit explicit root,
rootProjectTypes projectTypes name root,
rootTypeDirs typeDirs name root,
rootBaseDirs baseDirs name root
]
project <- firstJustMOr (virtualProject name) [
pure byRoot,
resolveFromDirContents typeMarkers name root
]
pure (augmentFromConfig config project)
where
name =
projectName root
fromRootSettings ::
Members [Settings, Stop ResolveError, Embed IO] r =>
ProjectRoot ->
Sem r Project
fromRootSettings root = do
explicit <- Settings.get Settings.projects
config <- projectConfig
fromRoot explicit config root