{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}

module Nix.Render where

import           Prelude                 hiding ( readFile )

#if !MIN_VERSION_base(4,13,0)
import           Control.Monad.Fail             ( MonadFail )
#endif
import           Control.Monad.Trans
import           Data.ByteString                ( ByteString )
import qualified Data.ByteString               as BS
import qualified Data.Set                      as Set
import qualified Data.Text                     as T
import qualified Data.Text.Encoding            as T
import           Data.Void
import           Nix.Expr.Types.Annotated
import           Prettyprinter
import qualified System.Directory              as S
import qualified System.Posix.Files            as S
import           Text.Megaparsec.Error
import           Text.Megaparsec.Pos

class MonadFail m => MonadFile m where
    readFile :: FilePath -> m ByteString
    default readFile :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m ByteString
    readFile = m' ByteString -> t m' ByteString
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' ByteString -> t m' ByteString)
-> (FilePath -> m' ByteString) -> FilePath -> t m' ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' ByteString
forall (m :: * -> *). MonadFile m => FilePath -> m ByteString
readFile
    listDirectory :: FilePath -> m [FilePath]
    default listDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m [FilePath]
    listDirectory = m' [FilePath] -> t m' [FilePath]
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' [FilePath] -> t m' [FilePath])
-> (FilePath -> m' [FilePath]) -> FilePath -> t m' [FilePath]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' [FilePath]
forall (m :: * -> *). MonadFile m => FilePath -> m [FilePath]
listDirectory
    getCurrentDirectory :: m FilePath
    default getCurrentDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => m FilePath
    getCurrentDirectory = m' FilePath -> t m' FilePath
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift m' FilePath
forall (m :: * -> *). MonadFile m => m FilePath
getCurrentDirectory
    canonicalizePath :: FilePath -> m FilePath
    default canonicalizePath :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m FilePath
    canonicalizePath = m' FilePath -> t m' FilePath
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' FilePath -> t m' FilePath)
-> (FilePath -> m' FilePath) -> FilePath -> t m' FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' FilePath
forall (m :: * -> *). MonadFile m => FilePath -> m FilePath
canonicalizePath
    getHomeDirectory :: m FilePath
    default getHomeDirectory :: (MonadTrans t, MonadFile m', m ~ t m') => m FilePath
    getHomeDirectory = m' FilePath -> t m' FilePath
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift m' FilePath
forall (m :: * -> *). MonadFile m => m FilePath
getHomeDirectory
    doesPathExist :: FilePath -> m Bool
    default doesPathExist :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m Bool
    doesPathExist = m' Bool -> t m' Bool
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' Bool -> t m' Bool)
-> (FilePath -> m' Bool) -> FilePath -> t m' Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' Bool
forall (m :: * -> *). MonadFile m => FilePath -> m Bool
doesPathExist
    doesFileExist :: FilePath -> m Bool
    default doesFileExist :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m Bool
    doesFileExist = m' Bool -> t m' Bool
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' Bool -> t m' Bool)
-> (FilePath -> m' Bool) -> FilePath -> t m' Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' Bool
forall (m :: * -> *). MonadFile m => FilePath -> m Bool
doesFileExist
    doesDirectoryExist :: FilePath -> m Bool
    default doesDirectoryExist :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m Bool
    doesDirectoryExist = m' Bool -> t m' Bool
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' Bool -> t m' Bool)
-> (FilePath -> m' Bool) -> FilePath -> t m' Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' Bool
forall (m :: * -> *). MonadFile m => FilePath -> m Bool
doesDirectoryExist
    getSymbolicLinkStatus :: FilePath -> m S.FileStatus
    default getSymbolicLinkStatus :: (MonadTrans t, MonadFile m', m ~ t m') => FilePath -> m S.FileStatus
    getSymbolicLinkStatus = m' FileStatus -> t m' FileStatus
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m' FileStatus -> t m' FileStatus)
-> (FilePath -> m' FileStatus) -> FilePath -> t m' FileStatus
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> m' FileStatus
forall (m :: * -> *). MonadFile m => FilePath -> m FileStatus
getSymbolicLinkStatus

instance MonadFile IO where
  readFile :: FilePath -> IO ByteString
readFile              = FilePath -> IO ByteString
BS.readFile
  listDirectory :: FilePath -> IO [FilePath]
listDirectory         = FilePath -> IO [FilePath]
S.listDirectory
  getCurrentDirectory :: IO FilePath
getCurrentDirectory   = IO FilePath
S.getCurrentDirectory
  canonicalizePath :: FilePath -> IO FilePath
canonicalizePath      = FilePath -> IO FilePath
S.canonicalizePath
  getHomeDirectory :: IO FilePath
getHomeDirectory      = IO FilePath
S.getHomeDirectory
  doesPathExist :: FilePath -> IO Bool
doesPathExist         = FilePath -> IO Bool
S.doesPathExist
  doesFileExist :: FilePath -> IO Bool
doesFileExist         = FilePath -> IO Bool
S.doesFileExist
  doesDirectoryExist :: FilePath -> IO Bool
doesDirectoryExist    = FilePath -> IO Bool
S.doesDirectoryExist
  getSymbolicLinkStatus :: FilePath -> IO FileStatus
getSymbolicLinkStatus = FilePath -> IO FileStatus
S.getSymbolicLinkStatus

posAndMsg :: SourcePos -> Doc a -> ParseError s Void
posAndMsg :: SourcePos -> Doc a -> ParseError s Void
posAndMsg (SourcePos _ lineNo :: Pos
lineNo _) msg :: Doc a
msg = Int -> Set (ErrorFancy Void) -> ParseError s Void
forall s e. Int -> Set (ErrorFancy e) -> ParseError s e
FancyError
  (Pos -> Int
unPos Pos
lineNo)
  ([ErrorFancy Void] -> Set (ErrorFancy Void)
forall a. Ord a => [a] -> Set a
Set.fromList [FilePath -> ErrorFancy Void
forall e. FilePath -> ErrorFancy e
ErrorFail (Doc a -> FilePath
forall a. Show a => a -> FilePath
show Doc a
msg) :: ErrorFancy Void])

renderLocation :: MonadFile m => SrcSpan -> Doc a -> m (Doc a)
renderLocation :: SrcSpan -> Doc a -> m (Doc a)
renderLocation (SrcSpan (SourcePos file :: FilePath
file begLine :: Pos
begLine begCol :: Pos
begCol) (SourcePos file' :: FilePath
file' endLine :: Pos
endLine endCol :: Pos
endCol)) msg :: Doc a
msg
  | FilePath
file FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
/= "<string>" Bool -> Bool -> Bool
&& FilePath
file FilePath -> FilePath -> Bool
forall a. Eq a => a -> a -> Bool
== FilePath
file'
  = do
    Bool
exist <- FilePath -> m Bool
forall (m :: * -> *). MonadFile m => FilePath -> m Bool
doesFileExist FilePath
file
    if Bool
exist
      then do
        Doc a
txt <- FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a -> m (Doc a)
forall (m :: * -> *) a.
MonadFile m =>
FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a -> m (Doc a)
sourceContext FilePath
file Pos
begLine Pos
begCol Pos
endLine Pos
endCol Doc a
msg
        Doc a -> m (Doc a)
forall (m :: * -> *) a. Monad m => a -> m a
return
          (Doc a -> m (Doc a)) -> Doc a -> m (Doc a)
forall a b. (a -> b) -> a -> b
$ [Doc a] -> Doc a
forall ann. [Doc ann] -> Doc ann
vsep
              [ "In file "
              Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a
forall a. FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a
errorContext FilePath
file Pos
begLine Pos
begCol Pos
endLine Pos
endCol
              Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> ":"
              , Doc a
txt
              ]
      else Doc a -> m (Doc a)
forall (m :: * -> *) a. Monad m => a -> m a
return Doc a
msg
renderLocation (SrcSpan beg :: SourcePos
beg end :: SourcePos
end) msg :: Doc a
msg =
  FilePath -> m (Doc a)
forall (m :: * -> *) a. MonadFail m => FilePath -> m a
fail
    (FilePath -> m (Doc a)) -> FilePath -> m (Doc a)
forall a b. (a -> b) -> a -> b
$  "Don't know how to render range from "
    FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ SourcePos -> FilePath
forall a. Show a => a -> FilePath
show SourcePos
beg
    FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ " to "
    FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ SourcePos -> FilePath
forall a. Show a => a -> FilePath
show SourcePos
end
    FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ " for error: "
    FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ Doc a -> FilePath
forall a. Show a => a -> FilePath
show Doc a
msg

errorContext :: FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a
errorContext :: FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a
errorContext path :: FilePath
path bl :: Pos
bl bc :: Pos
bc _el :: Pos
_el _ec :: Pos
_ec =
  FilePath -> Doc a
forall a ann. Pretty a => a -> Doc ann
pretty FilePath
path Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> ":" Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> Int -> Doc a
forall a ann. Pretty a => a -> Doc ann
pretty (Pos -> Int
unPos Pos
bl) Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> ":" Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> Int -> Doc a
forall a ann. Pretty a => a -> Doc ann
pretty (Pos -> Int
unPos Pos
bc)

sourceContext
  :: MonadFile m => FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a -> m (Doc a)
sourceContext :: FilePath -> Pos -> Pos -> Pos -> Pos -> Doc a -> m (Doc a)
sourceContext path :: FilePath
path (Pos -> Int
unPos -> Int
begLine) (Pos -> Int
unPos -> Int
_begCol) (Pos -> Int
unPos -> Int
endLine) (Pos -> Int
unPos -> Int
_endCol) msg :: Doc a
msg
  = do
    let beg' :: Int
beg' = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max 1 (Int -> Int -> Int
forall a. Ord a => a -> a -> a
min Int
begLine (Int
begLine Int -> Int -> Int
forall a. Num a => a -> a -> a
- 3))
        end' :: Int
end' = Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
endLine (Int
endLine Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 3)
    [Doc a]
ls <-
      (Text -> Doc a) -> [Text] -> [Doc a]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Doc a
forall a ann. Pretty a => a -> Doc ann
pretty
      ([Text] -> [Doc a])
-> (ByteString -> [Text]) -> ByteString -> [Doc a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.   Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
take (Int
end' Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
beg')
      ([Text] -> [Text])
-> (ByteString -> [Text]) -> ByteString -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.   Int -> [Text] -> [Text]
forall a. Int -> [a] -> [a]
drop (Int -> Int
forall a. Enum a => a -> a
pred Int
beg')
      ([Text] -> [Text])
-> (ByteString -> [Text]) -> ByteString -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.   Text -> [Text]
T.lines
      (Text -> [Text]) -> (ByteString -> Text) -> ByteString -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.   ByteString -> Text
T.decodeUtf8
      (ByteString -> [Doc a]) -> m ByteString -> m [Doc a]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> m ByteString
forall (m :: * -> *). MonadFile m => FilePath -> m ByteString
readFile FilePath
path
    let
      nums :: [FilePath]
nums    = ((Int, Doc a) -> FilePath) -> [(Int, Doc a)] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map (Int -> FilePath
forall a. Show a => a -> FilePath
show (Int -> FilePath)
-> ((Int, Doc a) -> Int) -> (Int, Doc a) -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int, Doc a) -> Int
forall a b. (a, b) -> a
fst) ([(Int, Doc a)] -> [FilePath]) -> [(Int, Doc a)] -> [FilePath]
forall a b. (a -> b) -> a -> b
$ [Int] -> [Doc a] -> [(Int, Doc a)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
beg' ..] [Doc a]
ls
      longest :: Int
longest = [Int] -> Int
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ((FilePath -> Int) -> [FilePath] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map FilePath -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [FilePath]
nums)
      nums' :: [FilePath]
nums'   = ((FilePath -> FilePath) -> [FilePath] -> [FilePath])
-> [FilePath] -> (FilePath -> FilePath) -> [FilePath]
forall a b c. (a -> b -> c) -> b -> a -> c
flip (FilePath -> FilePath) -> [FilePath] -> [FilePath]
forall a b. (a -> b) -> [a] -> [b]
map [FilePath]
nums ((FilePath -> FilePath) -> [FilePath])
-> (FilePath -> FilePath) -> [FilePath]
forall a b. (a -> b) -> a -> b
$ \n :: FilePath
n -> Int -> Char -> FilePath
forall a. Int -> a -> [a]
replicate (Int
longest Int -> Int -> Int
forall a. Num a => a -> a -> a
- FilePath -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length FilePath
n) ' ' FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
n
      pad :: FilePath -> FilePath
pad n :: FilePath
n | FilePath -> Int
forall a. Read a => FilePath -> a
read FilePath
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
begLine = "==> " FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
n
            | Bool
otherwise         = "    " FilePath -> FilePath -> FilePath
forall a. [a] -> [a] -> [a]
++ FilePath
n
      ls' :: [Doc a]
ls' = (Doc a -> Doc a -> Doc a) -> [Doc a] -> [Doc a] -> [Doc a]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Doc a -> Doc a -> Doc a
forall ann. Doc ann -> Doc ann -> Doc ann
(<+>)
                    ((FilePath -> Doc a) -> [FilePath] -> [Doc a]
forall a b. (a -> b) -> [a] -> [b]
map (FilePath -> Doc a
forall a ann. Pretty a => a -> Doc ann
pretty (FilePath -> Doc a) -> (FilePath -> FilePath) -> FilePath -> Doc a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
pad) [FilePath]
nums')
                    ((Doc a -> Doc a -> Doc a) -> [Doc a] -> [Doc a] -> [Doc a]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Doc a -> Doc a -> Doc a
forall ann. Doc ann -> Doc ann -> Doc ann
(<+>) (Doc a -> [Doc a]
forall a. a -> [a]
repeat "| ") [Doc a]
ls)
    Doc a -> m (Doc a)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Doc a -> m (Doc a)) -> Doc a -> m (Doc a)
forall a b. (a -> b) -> a -> b
$ [Doc a] -> Doc a
forall ann. [Doc ann] -> Doc ann
vsep ([Doc a] -> Doc a) -> [Doc a] -> Doc a
forall a b. (a -> b) -> a -> b
$ [Doc a]
ls' [Doc a] -> [Doc a] -> [Doc a]
forall a. [a] -> [a] -> [a]
++ [Doc a
msg]