-- | Directory controller for SourceCodeReader.hs and SourceCodeReaderTypes.hs.
module Drasil.Meta.Analysis.DirectoryController (createFolder, createFile, finder, getDirectories,
  DrasilPack, FileName, FolderName, File(..), Folder(..)) where

import Data.List ((\\), isInfixOf, isPrefixOf, isSuffixOf, partition, sort)
import System.Directory (doesDirectoryExist, listDirectory,
  setCurrentDirectory)
import System.FilePath (joinPath)

type FilterPrefix = String
type DrasilPack = String
type FileName = FilePath
type FolderName = FilePath

-- File and Folder data types for storing drasil- package, name and filepath
data File = File { File -> DrasilPack
fileDrasilPack :: DrasilPack
                 , File -> DrasilPack
fileName :: FileName
                 , File -> DrasilPack
filePath :: FilePath
                 } deriving (Int -> File -> ShowS
[File] -> ShowS
File -> DrasilPack
(Int -> File -> ShowS)
-> (File -> DrasilPack) -> ([File] -> ShowS) -> Show File
forall a.
(Int -> a -> ShowS)
-> (a -> DrasilPack) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> File -> ShowS
showsPrec :: Int -> File -> ShowS
$cshow :: File -> DrasilPack
show :: File -> DrasilPack
$cshowList :: [File] -> ShowS
showList :: [File] -> ShowS
Show)

data Folder = Folder { Folder -> DrasilPack
folderDrasilPack :: DrasilPack
                     , Folder -> DrasilPack
folderName :: FolderName
                     , Folder -> DrasilPack
folderPath :: FilePath
                     } deriving (Int -> Folder -> ShowS
[Folder] -> ShowS
Folder -> DrasilPack
(Int -> Folder -> ShowS)
-> (Folder -> DrasilPack) -> ([Folder] -> ShowS) -> Show Folder
forall a.
(Int -> a -> ShowS)
-> (a -> DrasilPack) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Folder -> ShowS
showsPrec :: Int -> Folder -> ShowS
$cshow :: Folder -> DrasilPack
show :: Folder -> DrasilPack
$cshowList :: [Folder] -> ShowS
showList :: [Folder] -> ShowS
Show)

-- used to create new folders of folder data type
createFolder :: FilePath -> DrasilPack -> FolderName -> Folder
createFolder :: DrasilPack -> DrasilPack -> DrasilPack -> Folder
createFolder DrasilPack
dp DrasilPack
drpk DrasilPack
fn = Folder {folderDrasilPack :: DrasilPack
folderDrasilPack=DrasilPack
drpk,folderName :: DrasilPack
folderName=DrasilPack
fn,folderPath :: DrasilPack
folderPath=DrasilPack
dp}

-- used to create new files of file data type
createFile :: FilePath -> DrasilPack -> FileName -> File
createFile :: DrasilPack -> DrasilPack -> DrasilPack -> File
createFile DrasilPack
fp DrasilPack
drpk DrasilPack
fn = File {fileDrasilPack :: DrasilPack
fileDrasilPack=DrasilPack
drpk,fileName :: DrasilPack
fileName=DrasilPack
fn,filePath :: DrasilPack
filePath=DrasilPack
fp}

-- iterates through drasil- package; outputs subdirectories and haskell files
iterator :: Folder -> IO ([Folder],[File])
iterator :: Folder -> IO ([Folder], [File])
iterator Folder
folder = do
  DrasilPack -> IO ()
setCurrentDirectory (Folder -> DrasilPack
folderPath Folder
folder)
  [DrasilPack]
rawContents <- DrasilPack -> IO [DrasilPack]
listDirectory (Folder -> DrasilPack
folderName Folder
folder)

  let bakedContents :: ([Folder], [File])
bakedContents = ([Folder]
folders,[File]
files)
      folders :: [Folder]
folders = (DrasilPack -> Folder) -> [DrasilPack] -> [Folder]
forall a b. (a -> b) -> [a] -> [b]
map (DrasilPack -> DrasilPack -> DrasilPack -> Folder
createFolder DrasilPack
workingDirectory DrasilPack
currentDrasilPack) [DrasilPack]
folderNames
      files :: [File]
files = (DrasilPack -> File) -> [DrasilPack] -> [File]
forall a b. (a -> b) -> [a] -> [b]
map (DrasilPack -> DrasilPack -> DrasilPack -> File
createFile DrasilPack
workingDirectory DrasilPack
currentDrasilPack) [DrasilPack]
fileNames

      folderNames :: [DrasilPack]
folderNames = [DrasilPack] -> [DrasilPack]
forall a. Ord a => [a] -> [a]
sort ([DrasilPack] -> [DrasilPack]) -> [DrasilPack] -> [DrasilPack]
forall a b. (a -> b) -> a -> b
$ [DrasilPack]
rawContents [DrasilPack] -> [DrasilPack] -> [DrasilPack]
forall a. Eq a => [a] -> [a] -> [a]
\\ (DrasilPack -> Bool) -> [DrasilPack] -> [DrasilPack]
forall a. (a -> Bool) -> [a] -> [a]
filter (DrasilPack -> DrasilPack -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isInfixOf DrasilPack
".") [DrasilPack]
rawContents
      fileNames :: [DrasilPack]
fileNames = [DrasilPack] -> [DrasilPack]
forall a. Ord a => [a] -> [a]
sort ([DrasilPack] -> [DrasilPack]) -> [DrasilPack] -> [DrasilPack]
forall a b. (a -> b) -> a -> b
$ (DrasilPack -> Bool) -> [DrasilPack] -> [DrasilPack]
forall a. (a -> Bool) -> [a] -> [a]
filter (DrasilPack -> DrasilPack -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isSuffixOf DrasilPack
".hs") [DrasilPack]
rawContents

      workingDirectory :: DrasilPack
workingDirectory = Folder -> DrasilPack
getFolderPath Folder
folder
      currentDrasilPack :: DrasilPack
currentDrasilPack = Folder -> DrasilPack
folderDrasilPack Folder
folder
  ([Folder], [File]) -> IO ([Folder], [File])
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([Folder], [File])
bakedContents

-- recursively searches for all folders + files in a directory using iterator
finder :: Folder -> IO [File]
finder :: Folder -> IO [File]
finder Folder
folder = do
  ([Folder], [File])
rawData <- Folder -> IO ([Folder], [File])
iterator Folder
folder

  let rawFolders :: [Folder]
rawFolders = ([Folder], [File]) -> [Folder]
forall a b. (a, b) -> a
fst ([Folder], [File])
rawData
  [Folder]
folders <- [Folder] -> IO [Folder]
verifyDirectories [Folder]
rawFolders
  [[File]]
rawFiles <- (Folder -> IO [File]) -> [Folder] -> IO [[File]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM Folder -> IO [File]
finder [Folder]
folders

  let bakedFiles :: [File]
bakedFiles = [[File]] -> [File]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[File]]
rawFiles

  let files :: [File]
files
        | [Folder] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Folder]
folders = ([Folder], [File]) -> [File]
forall a b. (a, b) -> b
snd ([Folder], [File])
rawData
        | Bool
otherwise = [File]
bakedFiles [File] -> [File] -> [File]
forall a. [a] -> [a] -> [a]
++ ([Folder], [File]) -> [File]
forall a b. (a, b) -> b
snd ([Folder], [File])
rawData
  [File] -> IO [File]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [File]
files

-- gets all drasil- packages + filepaths in a list of folder data types
getDirectories :: FilePath -> FilterPrefix -> IO [Folder]
getDirectories :: DrasilPack -> DrasilPack -> IO [Folder]
getDirectories DrasilPack
directoryPath DrasilPack
filterPrefix = do
  -- all raw directory contents
  [DrasilPack]
allPaths <- DrasilPack -> IO [DrasilPack]
listDirectory DrasilPack
directoryPath
  -- raw drasil- package directories + package names
  let rawPackages :: [DrasilPack]
rawPackages = [DrasilPack] -> [DrasilPack]
forall a. Ord a => [a] -> [a]
sort ([DrasilPack] -> [DrasilPack]) -> [DrasilPack] -> [DrasilPack]
forall a b. (a -> b) -> a -> b
$ (DrasilPack -> Bool) -> [DrasilPack] -> [DrasilPack]
forall a. (a -> Bool) -> [a] -> [a]
filter (DrasilPack -> DrasilPack -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf DrasilPack
filterPrefix) [DrasilPack]
allPaths
      packageNames :: [DrasilPack]
packageNames = ShowS -> [DrasilPack] -> [DrasilPack]
forall a b. (a -> b) -> [a] -> [b]
map (DrasilPack -> ShowS
forall a. Eq a => [a] -> [a] -> [a]
\\DrasilPack
"drasil-") [DrasilPack]
rawPackages
  -- convert list of directories into folder data types
      directories :: [Folder]
directories = (DrasilPack -> DrasilPack -> Folder)
-> [DrasilPack] -> [DrasilPack] -> [Folder]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (DrasilPack -> DrasilPack -> DrasilPack -> Folder
createFolder DrasilPack
directoryPath) [DrasilPack]
packageNames [DrasilPack]
rawPackages
  [Folder] -> IO [Folder]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [Folder]
directories

-- verifies that each folder/directory exists
verifyDirectories :: [Folder] -> IO [Folder]
verifyDirectories :: [Folder] -> IO [Folder]
verifyDirectories [Folder]
rawFolders = do
  let rawDirectories :: [DrasilPack]
rawDirectories = (Folder -> DrasilPack) -> [Folder] -> [DrasilPack]
forall a b. (a -> b) -> [a] -> [b]
map Folder -> DrasilPack
getFolderPath [Folder]
rawFolders
  [Bool]
boolFolders <- (DrasilPack -> IO Bool) -> [DrasilPack] -> IO [Bool]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM DrasilPack -> IO Bool
doesDirectoryExist [DrasilPack]
rawDirectories
  let verifiedDirectories :: [Folder]
verifiedDirectories = ([Folder], [Folder]) -> [Folder]
forall a b. (a, b) -> b
snd (([Folder], [Folder]) -> [Folder])
-> ([Folder], [Folder]) -> [Folder]
forall a b. (a -> b) -> a -> b
$ (Folder -> Bool) -> [Folder] -> ([Folder], [Folder])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Folder -> Bool
nullFolder ((Bool -> Folder -> Folder) -> [Bool] -> [Folder] -> [Folder]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Bool -> Folder -> Folder
fBool [Bool]
boolFolders [Folder]
rawFolders)
  [Folder] -> IO [Folder]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [Folder]
verifiedDirectories

-- combines lists of Booleans and Folders (if True, Folder exists)
fBool :: Bool -> Folder -> Folder
fBool :: Bool -> Folder -> Folder
fBool Bool
b Folder
f = if Bool
b then Folder
f else DrasilPack -> DrasilPack -> DrasilPack -> Folder
createFolder DrasilPack
"" DrasilPack
"" DrasilPack
""

-- checks if a folder is null (empty)
nullFolder :: Folder -> Bool
nullFolder :: Folder -> Bool
nullFolder Folder
folder = Bool
empty where
  empty :: Bool
empty = (DrasilPack -> Bool) -> [DrasilPack] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all DrasilPack -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Folder -> DrasilPack
folderName Folder
folder,Folder -> DrasilPack
folderPath Folder
folder,Folder -> DrasilPack
folderDrasilPack Folder
folder]

-- creates new folder path with folder name + path (to extract folder contents)
getFolderPath :: Folder -> FilePath
getFolderPath :: Folder -> DrasilPack
getFolderPath Folder
folder = [DrasilPack] -> DrasilPack
joinPath [Folder -> DrasilPack
folderPath Folder
folder, Folder -> DrasilPack
folderName Folder
folder]