{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}

module Test.Tasty.Sugar.Candidates
  (
    candidateToPath
  , findCandidates
  , makeCandidate
  , candidateMatchPrefix
  , candidateMatchSuffix
  )
where

import           Control.Monad ( filterM, guard )
import           Data.Bifunctor ( first )
import qualified Data.List as DL
import           Data.Maybe ( fromMaybe, isNothing )
import           Numeric.Natural
import           System.Directory ( doesDirectoryExist, getCurrentDirectory
                                  , listDirectory, doesDirectoryExist )
import           System.FilePath ( (</>), isRelative, makeRelative
                                 , splitPath, takeDirectory, takeFileName)

import           Test.Tasty.Sugar.Iterations
import           Test.Tasty.Sugar.Types


findCandidates :: CUBE -> FilePath -> IO ([Either String CandidateFile])
findCandidates :: CUBE -> FilePath -> IO [Either FilePath CandidateFile]
findCandidates CUBE
cube FilePath
inDir =
  let collectDirEntries :: FilePath -> IO [Either FilePath CandidateFile]
collectDirEntries FilePath
d =
        let recurse :: Bool
recurse = FilePath -> FilePath
takeFileName FilePath
d forall a. Eq a => a -> a -> Bool
== FilePath
"*"
            top :: Maybe FilePath
top = if Bool
recurse then forall a. a -> Maybe a
Just (FilePath -> FilePath
takeDirectory FilePath
d) else forall a. Maybe a
Nothing
            start :: FilePath
start = if Bool
recurse then FilePath -> FilePath
takeDirectory FilePath
d else FilePath
d
        in Maybe FilePath -> FilePath -> IO [Either FilePath CandidateFile]
dirListWithPaths Maybe FilePath
top FilePath
start
      dirListWithPaths :: Maybe FilePath -> FilePath -> IO [Either FilePath CandidateFile]
dirListWithPaths Maybe FilePath
topDir FilePath
d =
        -- putStrLn ("Reading " <> show d) >>
        FilePath -> IO Bool
doesDirectoryExist FilePath
d forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
          Bool
True ->
            do [FilePath]
dirContents <- FilePath -> IO [FilePath]
listDirectory FilePath
d
               case Maybe FilePath
topDir of
                 Maybe FilePath
Nothing -> do
                   let mkC :: FilePath -> CandidateFile
mkC = CUBE -> FilePath -> [FilePath] -> FilePath -> CandidateFile
makeCandidate CUBE
cube FilePath
d []
                   forall (m :: * -> *) a. Monad m => a -> m a
return (forall a b. b -> Either a b
Right forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> CandidateFile
mkC forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [FilePath]
dirContents)
                 Just FilePath
topdir -> do
                   let subs :: [FilePath]
subs = forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null)
                              (forall a. [a] -> [a]
init
                               forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. [a] -> [a]
init (FilePath -> [FilePath]
splitPath
                                          forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> FilePath
makeRelative FilePath
topdir (FilePath
d FilePath -> FilePath -> FilePath
</> FilePath
"x")))
                   let mkC :: FilePath -> CandidateFile
mkC = CUBE -> FilePath -> [FilePath] -> FilePath -> CandidateFile
makeCandidate CUBE
cube FilePath
topdir [FilePath]
subs
                   [FilePath]
subdirs <- forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM (FilePath -> IO Bool
doesDirectoryExist forall b c a. (b -> c) -> (a -> b) -> a -> c
. (FilePath
d FilePath -> FilePath -> FilePath
</>)) [FilePath]
dirContents
                   let here :: [Either a CandidateFile]
here = forall a b. b -> Either a b
Right forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> CandidateFile
mkC forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [FilePath]
subdirs)) [FilePath]
dirContents)
                   [[Either FilePath CandidateFile]]
subCandidates <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (Maybe FilePath -> FilePath -> IO [Either FilePath CandidateFile]
dirListWithPaths Maybe FilePath
topDir)
                                    ((FilePath
d FilePath -> FilePath -> FilePath
</>) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [FilePath]
subdirs)
                   forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {a}. [Either a CandidateFile]
here forall a. Semigroup a => a -> a -> a
<> (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Either FilePath CandidateFile]]
subCandidates)
          Bool
False -> do
            FilePath
showD <- case FilePath -> Bool
isRelative FilePath
d of
                       Bool
True -> do FilePath
cwd <- IO FilePath
getCurrentDirectory
                                  forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ FilePath
"[" forall a. Semigroup a => a -> a -> a
<> FilePath
cwd forall a. Semigroup a => a -> a -> a
<> FilePath
"/]" forall a. Semigroup a => a -> a -> a
<> FilePath
d
                       Bool
False -> forall (m :: * -> *) a. Monad m => a -> m a
return FilePath
d
            forall (m :: * -> *) a. Monad m => a -> m a
return [forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$ FilePath
showD forall a. Semigroup a => a -> a -> a
<> FilePath
" does not exist"]
  in FilePath -> IO [Either FilePath CandidateFile]
collectDirEntries FilePath
inDir


-- | Create a CandidateFile entry for this top directory, sub-paths, and
-- filename.  In addition, any Explicit parameters with known values that appear
-- in the filename are captured.  Note that:
--
-- * There may be multiple possible matches for a single parameter (e.g. the
--   value is repeated in the name or path, or an undefind value (Nothing)
--   parameter could have multiple possible values extracted from the filename.
--
-- * File name matches are preferred over sub-path matches and will occlude the
--   latter.
--
-- * All possible filename portions and sub-paths will be suggested for non-value
-- * parameters (validParams with Nothing).
--
makeCandidate :: CUBE -> FilePath -> [String] -> FilePath -> CandidateFile
makeCandidate :: CUBE -> FilePath -> [FilePath] -> FilePath -> CandidateFile
makeCandidate CUBE
cube FilePath
topDir [FilePath]
subPath FilePath
fName =
  let fl :: Int
fl = forall (t :: * -> *) a. Foldable t => t a -> Int
DL.length FilePath
fName
      isSep :: Char -> Bool
isSep = (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` CUBE -> FilePath
separators CUBE
cube)
      firstSep :: Int
firstSep = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
fl (forall a. Num a => a -> a -> a
+Int
1) forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> Maybe Int
DL.findIndex Char -> Bool
isSep FilePath
fName
      fle :: Int
fle = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Int
fl (Int
flforall a. Num a => a -> a -> a
-) forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> Maybe Int
DL.findIndex Char -> Bool
isSep forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
DL.reverse FilePath
fName
      -- pmatches is all known parameter values found in the name or directory of
      -- the file.  Note that a single parameter with multiple values may result
      -- in multiple pmatches if more than one of the values is present in a
      -- single filename.
      pmatches :: [(NamedParamMatch, (Natural, Int))]
pmatches = forall a b. (a, b) -> a
fst forall a b. (a -> b) -> a -> b
$ forall a. LogicI a -> ([a], IterStat)
observeIAll
                 forall a b. (a -> b) -> a -> b
$ do ParameterPattern
p <- forall a. Text -> [a] -> LogicI a
eachFrom Text
"param for candidate" forall a b. (a -> b) -> a -> b
$ CUBE -> [ParameterPattern]
validParams CUBE
cube
                      FilePath
v <- forall a. Text -> [a] -> LogicI a
eachFrom Text
"value for param" (forall a. a -> Maybe a -> a
fromMaybe [] (forall a b. (a, b) -> b
snd ParameterPattern
p))
                      -- Note: there maybe multiple v values for a single p that
                      -- are matched in the name.  This is accepted here (and
                      -- this file presumably satisfies either with an Explicit
                      -- match).
                      let vl :: Int
vl = forall (t :: * -> *) a. Foldable t => t a -> Int
DL.length FilePath
v
                      Int
i <- forall a. Text -> [a] -> LogicI a
eachFrom Text
"param starts"
                           forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [Int]
DL.findIndices (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (CUBE -> FilePath
separators CUBE
cube)) FilePath
fName
                      let vs :: Int
vs = Int
i forall a. Num a => a -> a -> a
+ Int
1
                      let ve :: Int
ve = Int
vs forall a. Num a => a -> a -> a
+ Int
vl
                      if forall (t :: * -> *). Foldable t => t Bool -> Bool
and [ Int
ve forall a. Num a => a -> a -> a
+ Int
1 forall a. Ord a => a -> a -> Bool
< Int
fl  -- v fits in fName[i..]
                             , FilePath
v forall a. Eq a => a -> a -> Bool
== forall a. Int -> [a] -> [a]
DL.take Int
vl (forall a. Int -> [a] -> [a]
DL.drop Int
vs FilePath
fName)
                             , forall a. [a] -> a
head (forall a. Int -> [a] -> [a]
DL.drop Int
ve FilePath
fName) forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (CUBE -> FilePath
separators CUBE
cube)
                             ]
                         then forall (m :: * -> *) a. Monad m => a -> m a
return ((forall a b. (a, b) -> a
fst ParameterPattern
p, FilePath -> ParamMatch
Explicit FilePath
v), (forall a. Enum a => Int -> a
toEnum Int
vs, Int
ve))
                        else do forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ FilePath
v forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [FilePath]
subPath
                                forall (m :: * -> *) a. Monad m => a -> m a
return ((forall a b. (a, b) -> a
fst ParameterPattern
p, FilePath -> ParamMatch
Explicit FilePath
v), (Natural
0, Int
0))
      -- pmatchArbitrary will find a parameter with an unspecified value and
      -- assigned otherwise unmatched portions of the filename to that parameter.
      pmatchArbitrary :: [(NamedParamMatch, (Natural, Int))]
pmatchArbitrary =
        case forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
DL.find (forall a. Maybe a -> Bool
isNothing forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd) forall a b. (a -> b) -> a -> b
$ CUBE -> [ParameterPattern]
validParams CUBE
cube of
          Maybe ParameterPattern
Nothing -> []
          Just (FilePath
p,Maybe [FilePath]
_) ->
            let chkRange :: [(Int, Int)]
chkRange = [(Int
firstSep, Int
fle)]
                -- arbs is the (start,len) spans where arbitrary values could
                -- occur
                arbs :: [(Natural, Int)]
arbs = [(Int, Int)] -> [(Natural, Int)] -> [(Natural, Int)]
holes [(Int, Int)]
chkRange (forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [(NamedParamMatch, (Natural, Int))]
pmatches)
                -- getRange extracts a substring range from the fName
                getRange :: (a, Int) -> FilePath
getRange (a
s,Int
e) = let s' :: Int
s' = forall a. Enum a => a -> Int
fromEnum a
s
                                 in forall a. Int -> [a] -> [a]
DL.take (Int
e forall a. Num a => a -> a -> a
- Int
s' forall a. Num a => a -> a -> a
- Int
1) forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
DL.drop Int
s' FilePath
fName
                -- holeVals are the separator-divided values extracted from the
                -- arbs ranges of fName.
                holeVals :: [(FilePath, (Natural, Int))]
holeVals = let neither :: (t -> Bool) -> t -> t -> Bool
neither t -> Bool
f t
a t
b = Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *). Foldable t => t Bool -> Bool
or [t -> Bool
f t
a, t -> Bool
f t
b]
                               splitBySep :: FilePath -> [FilePath]
splitBySep = forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isSep)
                                            forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> a -> Bool) -> [a] -> [[a]]
DL.groupBy (forall {t}. (t -> Bool) -> t -> t -> Bool
neither Char -> Bool
isSep)
                               rangeVals :: (a, Int) -> [(FilePath, (a, Int))]
rangeVals (a, Int)
r = (,(a, Int)
r) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (FilePath -> [FilePath]
splitBySep forall a b. (a -> b) -> a -> b
$ forall {a}. Enum a => (a, Int) -> FilePath
getRange (a, Int)
r)
                           in
                             forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall {a}. Enum a => (a, Int) -> [(FilePath, (a, Int))]
rangeVals [(Natural, Int)]
arbs
                -- dirVals are the subdirectory elements that could be used for
                -- arbitrary value matching (i.e. they don't explicitly match).
                dirVals :: [(FilePath, (Natural, Int))]
dirVals =
                  let pvals :: [Maybe FilePath]
pvals = ParamMatch -> Maybe FilePath
getParamVal forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [(NamedParamMatch, (Natural, Int))]
pmatches
                  in (, (Natural
0,Int
0)) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Maybe FilePath]
pvals) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just) [FilePath]
subPath
            in (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ((FilePath
p,) forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> ParamMatch
Explicit)) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ([(FilePath, (Natural, Int))]
holeVals forall a. Semigroup a => a -> a -> a
<> [(FilePath, (Natural, Int))]
dirVals)
      pAll :: [(NamedParamMatch, (Natural, Int))]
pAll = [(NamedParamMatch, (Natural, Int))]
pmatches forall a. Semigroup a => a -> a -> a
<> [(NamedParamMatch, (Natural, Int))]
pmatchArbitrary
      dropSeps :: a -> a
dropSeps a
i =
        let lst :: FilePath
lst = forall a. [a] -> a
last forall a b. (a -> b) -> a -> b
$ forall a. Eq a => [a] -> [[a]]
DL.group forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
DL.take (forall a. Enum a => a -> Int
fromEnum a
i) FilePath
fName
        in if Char -> Bool
isSep forall a b. (a -> b) -> a -> b
$ forall a. [a] -> a
head FilePath
lst
           then a
i forall a. Num a => a -> a -> a
- (forall a. Enum a => Int -> a
toEnum (forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
lst) forall a. Num a => a -> a -> a
- a
1)
           else a
i
      mtchIdx :: Natural
mtchIdx = forall {a}. (Num a, Enum a) => a -> a
dropSeps
                forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum
                forall a b. (a -> b) -> a -> b
$ forall a. Enum a => Int -> a
toEnum Int
fle
                forall a. a -> [a] -> [a]
: forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
/= Natural
0) (forall a b. (a, b) -> a
fst forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [(NamedParamMatch, (Natural, Int))]
pAll)
  in CandidateFile { candidateDir :: FilePath
candidateDir = FilePath
topDir
                   , candidateSubdirs :: [FilePath]
candidateSubdirs = [FilePath]
subPath
                   , candidateFile :: FilePath
candidateFile = FilePath
fName
                   -- nub the results in case a v value appears twice in a single
                   -- file.  Sort the results for stability in testing.
                   , candidatePMatch :: [NamedParamMatch]
candidatePMatch = forall a. Eq a => [a] -> [a]
DL.nub forall a b. (a -> b) -> a -> b
$ forall a. Ord a => [a] -> [a]
DL.sort forall a b. (a -> b) -> a -> b
$ (forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [(NamedParamMatch, (Natural, Int))]
pAll)
                   , candidateMatchIdx :: Natural
candidateMatchIdx = Natural
mtchIdx
                   }


-- Remove present from chkRange leaving holes.
holes :: [(Int,Int)] -> [(Natural,Int)] -> [(Natural,Int)]
holes :: [(Int, Int)] -> [(Natural, Int)] -> [(Natural, Int)]
holes [(Int, Int)]
chkRange [(Natural, Int)]
present =
  let rmvKnown :: (a, Int) -> [(a, Int)] -> [(a, Int)]
rmvKnown (a, Int)
_ [] = []
      rmvKnown p :: (a, Int)
p@(a
ps,Int
pe) ((a
s,Int
e):[(a, Int)]
rs) =
        if forall a. Num a => a -> a
abs(forall a. Enum a => a -> Int
fromEnum a
ps forall a. Num a => a -> a -> a
- forall a. Enum a => a -> Int
fromEnum a
s) forall a. Ord a => a -> a -> Bool
<= Int
1
        then if Int
pe forall a. Ord a => a -> a -> Bool
> Int
e
             then (a, Int) -> [(a, Int)] -> [(a, Int)]
rmvKnown (forall a. Enum a => Int -> a
toEnum Int
e,Int
pe) [(a, Int)]
rs
             else if forall a. Num a => a -> a
abs(Int
peforall a. Num a => a -> a -> a
-Int
e) forall a. Ord a => a -> a -> Bool
<= Int
1
                  then [(a, Int)]
rs
                  else (forall a. Enum a => Int -> a
toEnum Int
pe forall a. Num a => a -> a -> a
+ a
1, Int
e) forall a. a -> [a] -> [a]
: [(a, Int)]
rs
        else if a
ps forall a. Ord a => a -> a -> Bool
>= a
s Bool -> Bool -> Bool
&& forall a. Enum a => a -> Int
fromEnum a
ps forall a. Ord a => a -> a -> Bool
< Int
e
             then if forall a. Num a => a -> a
abs(Int
pe forall a. Num a => a -> a -> a
- Int
e) forall a. Ord a => a -> a -> Bool
<= Int
1
                  then (a
s, forall a. Enum a => a -> Int
fromEnum a
ps) forall a. a -> [a] -> [a]
: [(a, Int)]
rs
                  else if Int
pe forall a. Ord a => a -> a -> Bool
< Int
e
                       then (a
s, forall a. Enum a => a -> Int
fromEnum a
ps) forall a. a -> [a] -> [a]
: (forall a. Enum a => Int -> a
toEnum Int
pe, Int
e) forall a. a -> [a] -> [a]
: [(a, Int)]
rs
                       else (a
s, forall a. Enum a => a -> Int
fromEnum a
ps) forall a. a -> [a] -> [a]
: (a, Int) -> [(a, Int)] -> [(a, Int)]
rmvKnown (forall a. Enum a => Int -> a
toEnum Int
e, Int
pe) [(a, Int)]
rs
             else (a, Int) -> [(a, Int)] -> [(a, Int)]
rmvKnown (a, Int)
p [(a, Int)]
rs
      r' :: [(Int, Int)]
r' = forall a. (a -> Bool) -> [a] -> [a]
filter (\(Int, Int)
x -> forall a b. (a, b) -> a
fst (Int, Int)
x forall a. Eq a => a -> a -> Bool
/= forall a b. (a, b) -> b
snd (Int, Int)
x) [(Int, Int)]
chkRange
      p' :: [(Natural, Int)]
p' = forall a. (a -> Bool) -> [a] -> [a]
filter (\(Natural
x,Int
y) -> Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *). Foldable t => t Bool -> Bool
and [ Natural
x forall a. Eq a => a -> a -> Bool
== Natural
0, Int
y forall a. Eq a => a -> a -> Bool
== Int
0 ]) [(Natural, Int)]
present
  in forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall {a}.
(Enum a, Num a, Ord a) =>
(a, Int) -> [(a, Int)] -> [(a, Int)]
rmvKnown (forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first forall a. Enum a => Int -> a
toEnum forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [(Int, Int)]
r') (forall a. Ord a => [a] -> [a]
DL.sort [(Natural, Int)]
p')


-- | This converts a CandidatFile into a regular FilePath for access
candidateToPath :: CandidateFile -> FilePath
candidateToPath :: CandidateFile -> FilePath
candidateToPath CandidateFile
c =
  CandidateFile -> FilePath
candidateDir CandidateFile
c FilePath -> FilePath -> FilePath
</> forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr FilePath -> FilePath -> FilePath
(</>) (CandidateFile -> FilePath
candidateFile CandidateFile
c) (CandidateFile -> [FilePath]
candidateSubdirs CandidateFile
c)


-- | Determines if the second CandidateFile argument matches the prefix of the
-- first CandidateFile, up to any separator (if applicable).  This can be used to
-- match possible expected files against the current root file, or possible
-- associated files against the current expected file.
candidateMatchPrefix :: Separators -> CandidateFile -> CandidateFile -> Bool
candidateMatchPrefix :: FilePath -> CandidateFile -> CandidateFile -> Bool
candidateMatchPrefix FilePath
seps CandidateFile
mf CandidateFile
cf =
  let mStart :: FilePath
mStart = CandidateFile -> FilePath
candidateFile CandidateFile
mf
      mStartLen :: Int
mStartLen = forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
mStart
      f :: FilePath
f = CandidateFile -> FilePath
candidateFile CandidateFile
cf
      pfxlen :: Natural
pfxlen = let cl :: Natural
cl = CandidateFile -> Natural
candidateMatchIdx CandidateFile
cf
               in if forall a. Enum a => a -> Int
fromEnum Natural
cl forall a. Eq a => a -> a -> Bool
== forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
f
                  then if forall (t :: * -> *) a. Foldable t => t a -> Bool
null FilePath
seps then forall a. Enum a => Int -> a
toEnum Int
mStartLen else Natural
cl
                  else Natural
cl forall a. Num a => a -> a -> a
- Natural
1
  in FilePath
mStart forall a. Eq a => a -> a -> Bool
== forall a. Int -> [a] -> [a]
DL.take (forall a. Enum a => a -> Int
fromEnum Natural
pfxlen) FilePath
f


candidateMatchSuffix :: Separators -> FileSuffix -> CandidateFile
                     -> CandidateFile -> Bool
candidateMatchSuffix :: FilePath -> FilePath -> CandidateFile -> CandidateFile -> Bool
candidateMatchSuffix FilePath
seps FilePath
sfx CandidateFile
rootf CandidateFile
cf =
  let f :: FilePath
f = CandidateFile -> FilePath
candidateFile CandidateFile
cf
      sfxsep :: Bool
sfxsep = Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null FilePath
sfx) Bool -> Bool -> Bool
&& forall a. [a] -> a
head FilePath
sfx forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` FilePath
seps
  in if forall (t :: * -> *) a. Foldable t => t a -> Bool
null FilePath
sfx
     then FilePath
f forall a. Eq a => a -> a -> Bool
== forall a. (a -> Bool) -> [a] -> [a]
DL.takeWhile (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` FilePath
seps)) FilePath
f
     else forall (t :: * -> *). Foldable t => t Bool -> Bool
and [ forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
f forall a. Ord a => a -> a -> Bool
>= (forall (t :: * -> *) a. Foldable t => t a -> Int
length (CandidateFile -> FilePath
candidateFile CandidateFile
rootf) forall a. Num a => a -> a -> a
+ forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
sfx)
              , FilePath
sfx forall a. Eq a => [a] -> [a] -> Bool
`DL.isSuffixOf` FilePath
f
                -- is char before sfx a separator (and fEnd didn't start
                -- with a separator)?
              , if forall (t :: * -> *) a. Foldable t => t a -> Bool
null FilePath
seps
                then forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
f forall a. Eq a => a -> a -> Bool
== forall (t :: * -> *) a. Foldable t => t a -> Int
length (CandidateFile -> FilePath
candidateFile CandidateFile
rootf) forall a. Num a => a -> a -> a
+ forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
sfx
                else if Bool
sfxsep
                     then Bool
True
                     else forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False ((forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` FilePath
seps) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst)
                          forall a b. (a -> b) -> a -> b
$ forall a. [a] -> Maybe (a, [a])
DL.uncons
                          forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
DL.drop (forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
sfx)
                          forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
reverse FilePath
f
              ]