boilerplate-0.0.2: library/Boilerplate/RuleFinder.hs
module Boilerplate.RuleFinder (findRules) where
import Control.Monad (join)
import Data.Containers.ListUtils (nubOrdOn)
import qualified Data.List as L
import Data.Text (Text)
import qualified Data.Text as T
import Data.Traversable (for)
import HsInspect.Util (locateDominating)
import qualified HsInspect.Util as H
import System.Directory (makeAbsolute)
import System.FilePath (dropExtension, makeRelative, pathSeparator,
takeBaseName, takeDirectory)
-- Finds all .rule files that live in dominant directories named "boilerplate"
-- starting from the file. The files are sorted lexiographically within each
-- boilerplate directory, starting with the nearest directory and working
-- outwards.
--
-- Both fully qualified and short names are provided to make it easier for
-- callers to find a rule [(fqn, short, path)].
findRules :: FilePath -> IO [(Text, Text, FilePath)]
findRules file = do
parent <- makeAbsolute $ takeDirectory file
dirs <- locateDirs parent
batches <- for dirs $ \dir -> do
files <- H.walkSuffix ".rule" dir
pure $ namer dir <$> L.sort files
-- remove dupes when a direct ancestor is called `boilerplate`
pure . nubOrdOn (\(_, _, c) -> c) $ join batches
locateDirs :: FilePath -> IO [FilePath]
locateDirs dir = do
mdir <- locateDominating ("boilerplate" ==) dir
case mdir of
Nothing -> pure []
Just hit ->
let grandparent = takeDirectory $ takeDirectory hit
in if grandparent == dir
then pure [hit]
else (hit :) <$> locateDirs grandparent
namer :: FilePath -> FilePath -> (Text, Text, FilePath)
namer dir file =
let fqn = replace pathSeparator '.' . dropExtension $ makeRelative dir file
short = takeBaseName file
replace from to = fmap (\c -> if c == from then to else c)
in (T.pack fqn, T.pack short, file)