packages feed

tricorder-0.2.0.0: src/Tricorder/Daemon/Watch.hs

module Tricorder.Daemon.Watch
    ( WatchedFile (..)
    , files
    , publishChange
    , specs
    , isCabalFile
    ) where

import Atelier.Effects.Debounce (Debounce)
import Atelier.Effects.FileWatcher
    ( FileEvent
    , FileWatcher
    , Watch
    , containing
    , dirExt
    , dirWhere
    , excluding
    )
import Atelier.Effects.Publishing.Pub (Pub)
import System.FilePath (takeExtension, takeFileName)
import Text.Regex.TDFA (ExecOption (..), blankCompOpt, blankExecOpt, match)
import Text.Regex.TDFA.TDFA (patternToRegex)

import Atelier.Effects.FileWatcher qualified as FileWatcher
import Atelier.Effects.Publishing.Pub qualified as Pub

import Tricorder.Build.Changes
    ( CabalChangeDetected (..)
    , ChangeKind (..)
    , SourceChangeDetected (..)
    )
import Tricorder.Runtime (ProjectRoot (..))
import Tricorder.Session (Session (..))
import Tricorder.Session.WatchDirs (WatchDirs (..))
import Tricorder.Session.WatchExclusionPatterns (Pattern, WatchExclusionPatterns (..))


data WatchedFile = WatchedFile
    { path :: FilePath
    , event :: FileEvent
    }


files
    :: ( Debounce FilePath :> es
       , FileWatcher :> es
       , Pub WatchedFile :> es
       )
    => ProjectRoot -> Session -> Eff es Void
files projectRoot session =
    FileWatcher.watchFilePathsDebounced watches \filePath fileEvent ->
        Pub.publish $ WatchedFile filePath fileEvent
  where
    watches = specs projectRoot session.watchExclusionPatterns session.watchDirs


publishChange
    :: ( Pub CabalChangeDetected :> es
       , Pub SourceChangeDetected :> es
       )
    => WatchedFile -> Eff es ()
publishChange f =
    case changeKindFor f.path of
        CabalChange -> Pub.publish (CabalChangeDetected f.path f.event)
        SourceChange -> Pub.publish (SourceChangeDetected f.path f.event)


changeKindFor :: FilePath -> ChangeKind
changeKindFor path
    | isCabalFile path = CabalChange
    | otherwise = SourceChange


specs :: ProjectRoot -> WatchExclusionPatterns -> WatchDirs -> [Watch]
specs projectRoot watchExclusionPatterns watchDirs =
    sourceWatches (coerce watchExclusionPatterns) (coerce watchDirs)
        <> cabalWatches projectRoot


sourceWatches :: [Pattern] -> [FilePath] -> [Watch]
sourceWatches exclusionPatterns =
    map \d ->
        dirExt d ".hs"
            `excluding` containing "dist-newstyle"
            `excluding` exclusionMatches exclusionPatterns


exclusionMatches :: [Pattern] -> FilePath -> Bool
exclusionMatches exclusionPatterns fp = any matchPattern exclusionPatterns
  where
    matchPattern p =
        match
            (patternToRegex p blankCompOpt blankExecOpt {captureGroups = False})
            fp


cabalWatches :: ProjectRoot -> [Watch]
cabalWatches (ProjectRoot projectRoot) =
    [dirWhere projectRoot isCabalFile `excluding` containing "dist-newstyle"]


isCabalFile :: FilePath -> Bool
isCabalFile f =
    takeExtension f == ".cabal"
        || takeFileName f `elem` ["cabal.project", "package.yaml"]