{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Dhall.Freeze
(
freeze
, freezeImport
, freezeRemoteImport
, Scope(..)
, Intent(..)
) where
import Control.Exception (SomeException)
import Data.Monoid ((<>))
import Data.Maybe (fromMaybe)
import Data.Text
import Dhall.Binary (StandardVersion(..))
import Dhall.Core (Expr(..), Import(..), ImportHashed(..), ImportType(..))
import Dhall.Import (standardVersion)
import Dhall.Parser (exprAndHeaderFromText, Src)
import Dhall.Pretty (CharacterSet, annToAnsiStyle, layoutOpts, prettyCharacterSet)
import Dhall.TypeCheck (X)
import Lens.Family (set)
import System.Console.ANSI (hSupportsANSI)
import qualified Control.Exception
import qualified Control.Monad.Trans.State.Strict as State
import qualified Data.Text.Prettyprint.Doc as Pretty
import qualified Data.Text.Prettyprint.Doc.Render.Terminal as Pretty
import qualified Data.Text.IO
import qualified Dhall.Core
import qualified Dhall.Import
import qualified Dhall.Optics
import qualified Dhall.TypeCheck
import qualified System.FilePath
import qualified System.IO
freezeImport
:: FilePath
-> StandardVersion
-> Import
-> IO Import
freezeImport directory _standardVersion import_ = do
let unprotectedImport =
import_
{ importHashed =
(importHashed import_)
{ hash = Nothing
}
}
let status =
set standardVersion
_standardVersion
(Dhall.Import.emptyStatus directory)
let download =
State.evalStateT (Dhall.Import.loadWith (Embed import_)) status
let handler :: SomeException -> IO (Expr Src X)
handler _ = do
State.evalStateT (Dhall.Import.loadWith (Embed unprotectedImport)) status
expression <- Control.Exception.handle handler download
case Dhall.TypeCheck.typeOf expression of
Left exception -> Control.Exception.throwIO exception
Right _ -> return ()
let normalizedExpression =
Dhall.Core.alphaNormalize (Dhall.Core.normalize expression)
let expressionHash =
Just (Dhall.Import.hashExpression _standardVersion normalizedExpression)
let newImportHashed = (importHashed import_) { hash = expressionHash }
let newImport = import_ { importHashed = newImportHashed }
State.evalStateT (Dhall.Import.exprToImport newImport normalizedExpression) status
return newImport
freezeRemoteImport
:: FilePath
-> StandardVersion
-> Import
-> IO Import
freezeRemoteImport directory _standardVersion import_ = do
case importType (importHashed import_) of
Remote {} -> freezeImport directory _standardVersion import_
_ -> return import_
writeExpr :: Maybe FilePath -> (Text, Expr s Import) -> CharacterSet -> IO ()
writeExpr inplace (header, expr) characterSet = do
let doc = Pretty.pretty header
<> Dhall.Pretty.prettyCharacterSet characterSet expr
let unAnnotated = Pretty.layoutSmart layoutOpts (Pretty.unAnnotate doc)
case inplace of
Just f ->
System.IO.withFile f System.IO.WriteMode (\handle -> do
Pretty.renderIO handle unAnnotated
Data.Text.IO.hPutStrLn handle "" )
Nothing -> do
supportsANSI <- System.Console.ANSI.hSupportsANSI System.IO.stdout
if supportsANSI
then
Pretty.renderIO System.IO.stdout (annToAnsiStyle <$> Pretty.layoutSmart layoutOpts doc)
else
Pretty.renderIO System.IO.stdout unAnnotated
data Scope
= OnlyRemoteImports
| AllImports
data Intent
= Secure
| Cache
freeze
:: Maybe FilePath
-> Scope
-> Intent
-> CharacterSet
-> StandardVersion
-> IO ()
freeze inplace scope intent characterSet _standardVersion = do
(text, directory) <- case inplace of
Nothing -> do
text <- Data.Text.IO.getContents
return (text, ".")
Just file -> do
text <- Data.Text.IO.readFile file
return (text, System.FilePath.takeDirectory file)
(header, parsedExpression) <- Dhall.Core.throws (exprAndHeaderFromText srcInfo text)
let freezeScope =
case scope of
AllImports -> freezeImport
OnlyRemoteImports -> freezeRemoteImport
let freezeFunction = freezeScope directory _standardVersion
let cache
(ImportAlt
(Embed
(Import { importHashed = ImportHashed { hash = Just _expectedHash } })
)
import_@(ImportAlt
(Embed
(Import { importHashed = ImportHashed { hash = Just _actualHash } })
)
_
)
) = do
return import_
cache
(Embed import_@(Import { importHashed = ImportHashed { hash = Nothing } })) = do
frozenImport <- freezeFunction import_
if frozenImport /= import_
then return (ImportAlt (Embed frozenImport) (Embed import_))
else return (Embed import_)
cache expression = do
return expression
let rewrite expression =
case intent of
Secure ->
traverse freezeFunction expression
Cache ->
Dhall.Optics.transformMOf
Dhall.Core.subExpressions
cache
(Dhall.Core.denote expression)
frozenExpression <- rewrite parsedExpression
writeExpr inplace (header, frozenExpression) characterSet
where
srcInfo = fromMaybe "(stdin)" inplace