-- | 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 -> [Char]
fileDrasilPack :: DrasilPack
                 , File -> [Char]
fileName :: FileName
                 , File -> [Char]
filePath :: FilePath
                 } deriving (Int -> File -> ShowS
[File] -> ShowS
File -> [Char]
(Int -> File -> ShowS)
-> (File -> [Char]) -> ([File] -> ShowS) -> Show File
forall a.
(Int -> a -> ShowS) -> (a -> [Char]) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> File -> ShowS
showsPrec :: Int -> File -> ShowS
$cshow :: File -> [Char]
show :: File -> [Char]
$cshowList :: [File] -> ShowS
showList :: [File] -> ShowS
Show)

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

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

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

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

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

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

      workingDirectory = Folder -> [Char]
getFolderPath Folder
folder
      currentDrasilPack = Folder -> [Char]
folderDrasilPack Folder
folder
  return bakedContents

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

  let rawFolders = ([Folder], [File]) -> [Folder]
forall a b. (a, b) -> a
fst ([Folder], [File])
rawData
  folders <- verifyDirectories rawFolders
  rawFiles <- mapM finder folders

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

  let 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
  return files

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

-- verifies that each folder/directory exists
verifyDirectories :: [Folder] -> IO [Folder]
verifyDirectories :: [Folder] -> IO [Folder]
verifyDirectories [Folder]
rawFolders = do
  let rawDirectories :: [[Char]]
rawDirectories = (Folder -> [Char]) -> [Folder] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map Folder -> [Char]
getFolderPath [Folder]
rawFolders
  boolFolders <- ([Char] -> IO Bool) -> [[Char]] -> 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 [Char] -> IO Bool
doesDirectoryExist [[Char]]
rawDirectories
  let 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)
  return 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 [Char] -> [Char] -> [Char] -> Folder
createFolder [Char]
"" [Char]
"" [Char]
""

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

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