{-# LANGUAGE OverloadedStrings #-} -- | This module contains the implementation of the @dhall freeze@ subcommand module Dhall.Freeze ( -- * Freeze freeze , hashImport ) 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(..)) import Dhall.Import (standardVersion) import Dhall.Parser (exprAndHeaderFromText, Src) import Dhall.Pretty (annToAnsiStyle, layoutOpts) 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.TypeCheck import qualified System.FilePath import qualified System.IO -- | Retrieve an `Import` and update the hash to match the latest contents hashImport :: FilePath -- ^ Current working directory -> StandardVersion -> Import -> IO Import hashImport 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 -- Try again without the semantic integrity check if decoding fails 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 parseExpr :: String -> Text -> IO (Text, Expr Src Import) parseExpr src txt = case exprAndHeaderFromText src txt of Left err -> Control.Exception.throwIO err Right x -> return x writeExpr :: Maybe FilePath -> (Text, Expr s Import) -> IO () writeExpr inplace (header, expr) = do let doc = Pretty.pretty header <> Pretty.pretty expr let stream = Pretty.layoutSmart layoutOpts doc case inplace of Just f -> System.IO.withFile f System.IO.WriteMode (\handle -> do Pretty.renderIO handle (annToAnsiStyle <$> stream) 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 (Pretty.layoutSmart layoutOpts (Pretty.unAnnotate doc)) -- | Implementation of the @dhall freeze@ subcommand freeze :: Maybe FilePath -- ^ Modify file in-place if present, otherwise read from @stdin@ and write -- to @stdout@ -> StandardVersion -> IO () freeze inplace _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) <- parseExpr srcInfo text frozenExpression <- traverse (hashImport directory _standardVersion) parsedExpression writeExpr inplace (header, frozenExpression) where srcInfo = fromMaybe "(stdin)" inplace