{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Redundant where" #-}

module Lib (-- | This is a binary package, and @Lib@ is the heart of it, so 
            --   there are no library functions in this module.
            listDir) where

import Parser (Args(..))
import Data.List (zipWith5)
import Data.Map qualified as Map
import System.FilePath ( (</>), takeExtension)
import System.Directory
    ( doesDirectoryExist,
      doesFileExist,
      getDirectoryContents,
      getModificationTime,
      getFileSize,
      getPermissions,
      Permissions(..) )
import Data.Time.Clock (UTCTime)
import Config
import Colors

splitAtChar :: Char -> String -> [String]
splitAtChar :: Char -> String -> [String]
splitAtChar Char
_ [] = []
splitAtChar Char
c String
str =
  let (String
before, String
rest) = forall a. (a -> Bool) -> [a] -> ([a], [a])
break (forall a. Eq a => a -> a -> Bool
== Char
c) String
str
  in String
before forall a. a -> [a] -> [a]
: case String
rest of
       []    -> []
       (Char
_:String
t) -> Char -> String -> [String]
splitAtChar Char
c String
t

rightPad :: Int -> String -> String
rightPad :: Int -> String -> String
rightPad Int
x String
xs = String
xs forall a. [a] -> [a] -> [a]
++ forall a. Int -> a -> [a]
replicate (Int
x forall a. Num a => a -> a -> a
- forall (t :: * -> *) a. Foldable t => t a -> Int
length String
xs) Char
' '

pad :: [String] -> [String]
pad :: [String] -> [String]
pad [String]
xs = forall a b. (a -> b) -> [a] -> [b]
map (Int -> String -> String
rightPad Int
m) [String]
xs where m :: Int
m = forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
xs

filesize :: FilePath -> IO Integer
filesize :: String -> IO Integer
filesize String
path = do
    Bool
isFile <- String -> IO Bool
doesFileExist String
path
    if Bool
isFile
        then String -> IO Integer
getFileSize String
path
        else forall (m :: * -> *) a. Monad m => a -> m a
return Integer
0          -- if a directory

formatFilename :: String -> String -> IO String
formatFilename :: String -> String -> IO String
formatFilename String
path String
f = do
    Bool
dir <- String -> IO Bool
doesDirectoryExist forall a b. (a -> b) -> a -> b
$ String
path String -> String -> String
</> String
f
    if Bool
dir then
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
directoryIcon forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
byellow String
f
    else
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault String
fileIcon (String -> String
takeExtension forall a b. (a -> b) -> a -> b
$ String
path String -> String -> String
</> String
f) Map String String
iconConfig forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
bgreen String
f

parseFilesize :: Integer -> String
parseFilesize :: Integer -> String
parseFilesize Integer
s =
    if | Integer
s forall a. Eq a => a -> a -> Bool
== Integer
0      -> String -> String
bred String
"---"
       | Integer
s forall a. Ord a => a -> a -> Bool
<= Integer
1024   -> forall a. Show a => a -> String
show  Integer
s               forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
red      String
"B"
       | Integer
s forall a. Ord a => a -> a -> Bool
<= Integer
1024forall a b. (Num a, Integral b) => a -> b -> a
^Integer
2 -> forall a. Show a => a -> String
show (Integer
s forall a. Integral a => a -> a -> a
`div` Integer
1024)   forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
byellow  String
"KB"
       | Integer
s forall a. Ord a => a -> a -> Bool
<= Integer
1024forall a b. (Num a, Integral b) => a -> b -> a
^Integer
3 -> forall a. Show a => a -> String
show (Integer
s forall a. Integral a => a -> a -> a
`div` Integer
1024forall a b. (Num a, Integral b) => a -> b -> a
^Integer
2) forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
cyan     String
"MB"
       | Bool
otherwise   -> forall a. Show a => a -> String
show (Integer
s forall a. Integral a => a -> a -> a
`div` Integer
1024forall a b. (Num a, Integral b) => a -> b -> a
^Integer
3) forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
magenta  String
"GB"

parsePermissions :: Permissions -> String
parsePermissions :: Permissions -> String
parsePermissions Permissions
p =
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [String -> String
bgreen String
rr, String -> String
cyan String
ww, String -> String
byellow String
xx, String -> String
magenta String
ss, String
" | "] where
    rr :: String
rr = if Permissions -> Bool
readable Permissions
p   then String
"r" else String
"-"
    ww :: String
ww = if Permissions -> Bool
writable Permissions
p   then String
"w" else String
"-"
    xx :: String
xx = if Permissions -> Bool
executable Permissions
p then String
"x" else String
"-"
    ss :: String
ss = if Permissions -> Bool
searchable Permissions
p then String
"s" else String
"-"

parseTime :: UTCTime -> String
parseTime :: UTCTime -> String
parseTime UTCTime
t = String -> String
bcyan String
x forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String -> String
byellow String
y forall a. [a] -> [a] -> [a]
++ String -> String
bred String
" UTC" where
    (String
x:String
y:[String]
_) = Char -> String -> [String]
splitAtChar Char
' ' (forall a. [a] -> a
head (Char -> String -> [String]
splitAtChar Char
'.' forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> String
show UTCTime
t))

lsList :: Args -> IO ()
lsList :: Args -> IO ()
lsList Args
args = do
    [String]
dc <- String -> IO [String]
getDirectoryContents String
path
    let files :: [String]
files = forall a. [a] -> [a]
reverse [String]
dc
    --line numbers
    let numbers :: [String]
numbers = [String] -> [String]
pad forall a b. (a -> b) -> a -> b
$ if Bool
nums then [forall a. Show a => a -> String
show Int
i forall a. [a] -> [a] -> [a]
++ String
"." | Int
i <- [Int
1..forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
files]]
                                else [String
"" | String
_ <- [String]
files]
    --filenames and icons
    [String]
colored <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (String -> String -> IO String
formatFilename String
path) [String]
files
    let filenames :: [String]
filenames = forall a b. (a -> b) -> [a] -> [b]
map ( forall a. [a] -> [a] -> [a]
++ String
" | ") [String]
x where
        x :: [String]
x = if Bool
dots then [String] -> [String]
pad [String]
colored
                    else [String] -> [String]
pad forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (\String
xs -> String
xsforall a. [a] -> Int -> a
!!Int
5 forall a. Eq a => a -> a -> Bool
/= Char
'.') [String]
colored
    --file permissions
    [String]
perms <- if Bool
perm
        then do
            [Permissions]
p <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (\String
f -> String -> IO Permissions
getPermissions forall a b. (a -> b) -> a -> b
$ String
path String -> String -> String
</> String
f) [String]
files
            [Bool]
d <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (\String
f -> String -> IO Bool
doesDirectoryExist forall a b. (a -> b) -> a -> b
$ String
path String -> String -> String
</> String
f) [String]
files
            let dflags :: [String]
dflags = forall a b. (a -> b) -> [a] -> [b]
map (\Bool
b -> String -> String
red forall a b. (a -> b) -> a -> b
$ if Bool
b then String
"d" else String
".") [Bool]
d
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith forall a. [a] -> [a] -> [a]
(++) [String]
dflags forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Permissions -> String
parsePermissions [Permissions]
p
        else do
            forall (m :: * -> *) a. Monad m => a -> m a
return [String
"" | String
_ <- [String]
files]
    --file sizes
    [String]
filesizes <- if Bool
size
        then do
            [Integer]
s <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (\String
f -> String -> IO Integer
filesize forall a b. (a -> b) -> a -> b
$ String
path String -> String -> String
</> String
f) [String]
files
            let sizes :: [String]
sizes = forall a b. (a -> b) -> [a] -> [b]
map Integer -> String
parseFilesize [Integer]
s
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a. [a] -> [a] -> [a]
++ String
" | ") forall a b. (a -> b) -> a -> b
$ [String] -> [String]
pad [String]
sizes
        else do
            forall (m :: * -> *) a. Monad m => a -> m a
return [String
"" | String
_ <- [String]
files]
    -- modidfication times
    [String]
modtimes <- if Bool
time
        then do
            [UTCTime]
t <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (\String
f -> String -> IO UTCTime
getModificationTime forall a b. (a -> b) -> a -> b
$ String
path String -> String -> String
</> String
f) [String]
files
            let times :: [String]
times = forall a b. (a -> b) -> [a] -> [b]
map UTCTime -> String
parseTime [UTCTime]
t
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a. [a] -> [a] -> [a]
++ String
" | ") forall a b. (a -> b) -> a -> b
$ [String] -> [String]
pad [String]
times
        else do
            forall (m :: * -> *) a. Monad m => a -> m a
return [String
"" | String
_ <- [String]
files]

    String -> IO ()
putStrLn forall a b. (a -> b) -> a -> b
$ [String] -> String
unlines forall a b. (a -> b) -> a -> b
$ forall a b c d e f.
(a -> b -> c -> d -> e -> f)
-> [a] -> [b] -> [c] -> [d] -> [e] -> [f]
zipWith5 (\String
n String
p String
s String
f String
t -> String
n forall a. [a] -> [a] -> [a]
++ String
" | " forall a. [a] -> [a] -> [a]
++ String
p forall a. [a] -> [a] -> [a]
++ String
s forall a. [a] -> [a] -> [a]
++ String
f forall a. [a] -> [a] -> [a]
++ String
t)
                            [String]
numbers [String]
perms [String]
filesizes [String]
filenames [String]
modtimes
    where Args {list :: Args -> Bool
list=Bool
_, Bool
size :: Args -> Bool
size :: Bool
size, Bool
dots :: Args -> Bool
dots :: Bool
dots, Bool
perm :: Args -> Bool
perm :: Bool
perm, Bool
nums :: Args -> Bool
nums :: Bool
nums, Bool
time :: Args -> Bool
time :: Bool
time, String
path :: Args -> String
path :: String
path, afl :: Args -> Bool
afl=Bool
_} = Args
args

-- | Entry point, @lslist@ is called when @-l@ is enabled or implied
listDir :: Args -> IO ()
listDir :: Args -> IO ()
listDir Args
args = do
    if Bool
afl then                              -- -a flag
        Args -> IO ()
lsList Args {list :: Bool
list=Bool
True, size :: Bool
size=Bool
True, dots :: Bool
dots=Bool
True, perm :: Bool
perm=Bool
True,
                     nums :: Bool
nums=Bool
True, time :: Bool
time=Bool
True, String
path :: String
path :: String
path, afl :: Bool
afl=Bool
True}
    else if forall (t :: * -> *). Foldable t => t Bool -> Bool
or [Bool
list, Bool
size, Bool
perm, Bool
nums, Bool
time] then --stuff that implies -l
        Args -> IO ()
lsList Args
args
    else do
        [String]
files <- String -> IO [String]
getDirectoryContents String
path
        [String]
colored <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM (String -> String -> IO String
formatFilename String
path) [String]
files
        if Bool
dots then
            String -> IO ()
putStrLn forall a b. (a -> b) -> a -> b
$ [String] -> String
unwords forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
reverse [String]
colored
        else
            String -> IO ()
putStrLn forall a b. (a -> b) -> a -> b
$ [String] -> String
unwords forall a b. (a -> b) -> a -> b
$ forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (\String
x -> String
xforall a. [a] -> Int -> a
!!Int
5 forall a. Eq a => a -> a -> Bool
/= Char
'.') [String]
colored
    where Args {Bool
list :: Bool
list :: Args -> Bool
list, Bool
size :: Bool
size :: Args -> Bool
size, Bool
dots :: Bool
dots :: Args -> Bool
dots, Bool
perm :: Bool
perm :: Args -> Bool
perm, Bool
nums :: Bool
nums :: Args -> Bool
nums, Bool
time :: Bool
time :: Args -> Bool
time, String
path :: String
path :: Args -> String
path, Bool
afl :: Bool
afl :: Args -> Bool
afl} = Args
args