{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Dhall.Schemas
(
schemasCommand
, Schemas(..)
, rewriteWithSchemas
, SchemasError(..)
) where
import Control.Applicative (empty)
import Control.Exception (Exception)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Void (Void)
import Dhall.Crypto (SHA256Digest)
import Dhall.Map (Map)
import Dhall.Pretty (CharacterSet (..), detectCharacterSet)
import Dhall.Src (Src)
import Dhall.Syntax (Expr (..), Import, Var (..))
import Dhall.Util
( Censor (..)
, Header (..)
, Input (..)
, MultipleCheckFailed (..)
, OutputMode (..)
)
import qualified Control.Exception as Exception
import qualified Data.Map
import qualified Data.Maybe as Maybe
import qualified Data.Text.IO as Text.IO
import qualified Data.Void as Void
import qualified Dhall.Core as Core
import qualified Dhall.Import as Import
import qualified Dhall.Map as Map
import qualified Dhall.Normalize as Normalize
import qualified Dhall.Optics as Optics
import qualified Dhall.Parser as Parser
import qualified Dhall.Pretty
import qualified Dhall.Substitution as Substitution
import qualified Dhall.Syntax as Syntax
import qualified Dhall.TypeCheck as TypeCheck
import qualified Dhall.Util as Util
import qualified Prettyprinter as Pretty
import qualified Prettyprinter.Render.Terminal as Pretty.Terminal
import qualified Prettyprinter.Render.Text as Pretty.Text
import qualified System.AtomicWrite.Writer.LazyText as AtomicWrite
import qualified System.Console.ANSI as ANSI
import qualified System.IO as IO
data Schemas = Schemas
{ Schemas -> Maybe CharacterSet
chosenCharacterSet :: Maybe CharacterSet
, Schemas -> Censor
censor :: Censor
, Schemas -> Input
input :: Input
, Schemas -> OutputMode
outputMode :: OutputMode
, Schemas -> Text
schemas :: Text
}
schemasCommand :: Schemas -> IO ()
schemasCommand :: Schemas -> IO ()
schemasCommand Schemas{Maybe CharacterSet
Text
OutputMode
Input
Censor
chosenCharacterSet :: Schemas -> Maybe CharacterSet
censor :: Schemas -> Censor
input :: Schemas -> Input
outputMode :: Schemas -> OutputMode
schemas :: Schemas -> Text
chosenCharacterSet :: Maybe CharacterSet
censor :: Censor
input :: Input
outputMode :: OutputMode
schemas :: Text
..} = do
(FilePath
inputName, Text
originalText) <- case Input
input of
InputFile FilePath
file -> (,) FilePath
file (Text -> (FilePath, Text)) -> IO Text -> IO (FilePath, Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> FilePath -> IO Text
Text.IO.readFile FilePath
file
Input
StandardInput -> (,) FilePath
"(input)" (Text -> (FilePath, Text)) -> IO Text -> IO (FilePath, Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Text
Text.IO.getContents
(Header Text
header, Expr Src Import
expression) <- Censor -> FilePath -> Text -> IO (Header, Expr Src Import)
Util.getExpressionAndHeaderFromStdinText Censor
censor FilePath
inputName Text
originalText
let characterSet :: CharacterSet
characterSet = CharacterSet -> Maybe CharacterSet -> CharacterSet
forall a. a -> Maybe a -> a
fromMaybe (Expr Src Import -> CharacterSet
forall a. Expr Src a -> CharacterSet
detectCharacterSet Expr Src Import
expression) Maybe CharacterSet
chosenCharacterSet
Expr Src Import
schemasRecord <- Either ParseError (Expr Src Import) -> IO (Expr Src Import)
forall e (io :: * -> *) a.
(Exception e, MonadIO io) =>
Either e a -> io a
Core.throws (FilePath -> Text -> Either ParseError (Expr Src Import)
Parser.exprFromText FilePath
"(schemas)" Text
schemas)
Expr Src Import
schemasExpression <- Expr Src Import -> Expr Src Import -> IO (Expr Src Import)
rewriteWithSchemas Expr Src Import
schemasRecord Expr Src Import
expression
let docStream :: SimpleDocStream Ann
docStream =
Doc Ann -> SimpleDocStream Ann
forall ann. Doc ann -> SimpleDocStream ann
Dhall.Pretty.layout
( Text -> Doc Ann
forall ann. Text -> Doc ann
forall a ann. Pretty a => a -> Doc ann
Pretty.pretty Text
header
Doc Ann -> Doc Ann -> Doc Ann
forall a. Semigroup a => a -> a -> a
<> CharacterSet -> Expr Src Import -> Doc Ann
forall a. Pretty a => CharacterSet -> Expr Src a -> Doc Ann
Dhall.Pretty.prettyCharacterSet CharacterSet
characterSet Expr Src Import
schemasExpression
Doc Ann -> Doc Ann -> Doc Ann
forall a. Semigroup a => a -> a -> a
<> Doc Ann
"\n"
)
let schemasText :: Text
schemasText = SimpleDocStream Ann -> Text
forall ann. SimpleDocStream ann -> Text
Pretty.Text.renderStrict SimpleDocStream Ann
docStream
case OutputMode
outputMode of
OutputMode
Write ->
case Input
input of
InputFile FilePath
file ->
if Text
originalText Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
schemasText
then () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
else FilePath -> Text -> IO ()
AtomicWrite.atomicWriteFile
FilePath
file
(SimpleDocStream Ann -> Text
forall ann. SimpleDocStream ann -> Text
Pretty.Text.renderLazy SimpleDocStream Ann
docStream)
Input
StandardInput -> do
Bool
supportsANSI <- Handle -> IO Bool
ANSI.hSupportsANSI Handle
IO.stdout
Handle -> SimpleDocStream AnsiStyle -> IO ()
Pretty.Terminal.renderIO
Handle
IO.stdout
(if Bool
supportsANSI
then (Ann -> AnsiStyle)
-> SimpleDocStream Ann -> SimpleDocStream AnsiStyle
forall a b. (a -> b) -> SimpleDocStream a -> SimpleDocStream b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Ann -> AnsiStyle
Dhall.Pretty.annToAnsiStyle SimpleDocStream Ann
docStream
else SimpleDocStream Ann -> SimpleDocStream AnsiStyle
forall ann xxx. SimpleDocStream ann -> SimpleDocStream xxx
Pretty.unAnnotateS SimpleDocStream Ann
docStream)
OutputMode
Check ->
if Text
originalText Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
schemasText
then () -> IO ()
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
else do
let command :: Text
command = Text
"rewrite-with-schemas"
let modified :: Text
modified = Text
"rewritten"
let inputs :: NonEmpty Input
inputs = Input -> NonEmpty Input
forall a. a -> NonEmpty a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Input
input
MultipleCheckFailed -> IO ()
forall e a. Exception e => e -> IO a
Exception.throwIO MultipleCheckFailed{NonEmpty Input
Text
command :: Text
modified :: Text
inputs :: NonEmpty Input
command :: Text
modified :: Text
inputs :: NonEmpty Input
..}
decodeSchema :: Expr s Void -> Maybe (Expr s Void, Map Text (Expr s Void))
decodeSchema :: forall s. Expr s X -> Maybe (Expr s X, Map Text (Expr s X))
decodeSchema (RecordLit Map Text (RecordField s X)
m)
| Just Expr s X
_Type <- RecordField s X -> Expr s X
forall s a. RecordField s a -> Expr s a
Core.recordFieldValue (RecordField s X -> Expr s X)
-> Maybe (RecordField s X) -> Maybe (Expr s X)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Map Text (RecordField s X) -> Maybe (RecordField s X)
forall k v. Ord k => k -> Map k v -> Maybe v
Map.lookup Text
"Type" Map Text (RecordField s X)
m
, Just (RecordLit Map Text (RecordField s X)
_default) <- RecordField s X -> Expr s X
forall s a. RecordField s a -> Expr s a
Core.recordFieldValue (RecordField s X -> Expr s X)
-> Maybe (RecordField s X) -> Maybe (Expr s X)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Map Text (RecordField s X) -> Maybe (RecordField s X)
forall k v. Ord k => k -> Map k v -> Maybe v
Map.lookup Text
"default" Map Text (RecordField s X)
m =
(Expr s X, Map Text (Expr s X))
-> Maybe (Expr s X, Map Text (Expr s X))
forall a. a -> Maybe a
Just (Expr s X
_Type, RecordField s X -> Expr s X
forall s a. RecordField s a -> Expr s a
Core.recordFieldValue (RecordField s X -> Expr s X)
-> Map Text (RecordField s X) -> Map Text (Expr s X)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map Text (RecordField s X)
_default)
decodeSchema Expr s X
_ =
Maybe (Expr s X, Map Text (Expr s X))
forall a. Maybe a
Nothing
decodeSchemas
:: Expr s Void
-> Maybe (Data.Map.Map SHA256Digest (Text, Map Text (Expr s Void)))
decodeSchemas :: forall s.
Expr s X -> Maybe (Map SHA256Digest (Text, Map Text (Expr s X)))
decodeSchemas (RecordLit Map Text (RecordField s X)
keyValues) = do
Map Text (Expr s X, Map Text (Expr s X))
m <- (RecordField s X -> Maybe (Expr s X, Map Text (Expr s X)))
-> Map Text (RecordField s X)
-> Maybe (Map Text (Expr s X, Map Text (Expr s X)))
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> Map Text a -> f (Map Text b)
traverse (Expr s X -> Maybe (Expr s X, Map Text (Expr s X))
forall s. Expr s X -> Maybe (Expr s X, Map Text (Expr s X))
decodeSchema (Expr s X -> Maybe (Expr s X, Map Text (Expr s X)))
-> (RecordField s X -> Expr s X)
-> RecordField s X
-> Maybe (Expr s X, Map Text (Expr s X))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RecordField s X -> Expr s X
forall s a. RecordField s a -> Expr s a
Core.recordFieldValue) Map Text (RecordField s X)
keyValues
let typeMetadata :: Map SHA256Digest (Text, Map Text (Expr s X))
typeMetadata = [(SHA256Digest, (Text, Map Text (Expr s X)))]
-> Map SHA256Digest (Text, Map Text (Expr s X))
forall k a. Ord k => [(k, a)] -> Map k a
Data.Map.fromList ([(SHA256Digest, (Text, Map Text (Expr s X)))]
-> Map SHA256Digest (Text, Map Text (Expr s X)))
-> [(SHA256Digest, (Text, Map Text (Expr s X)))]
-> Map SHA256Digest (Text, Map Text (Expr s X))
forall a b. (a -> b) -> a -> b
$ do
(Text
name, (Expr s X
_Type, Map Text (Expr s X)
_default)) <- Map Text (Expr s X, Map Text (Expr s X))
-> [(Text, (Expr s X, Map Text (Expr s X)))]
forall k v. Ord k => Map k v -> [(k, v)]
Map.toList Map Text (Expr s X, Map Text (Expr s X))
m
(SHA256Digest, (Text, Map Text (Expr s X)))
-> [(SHA256Digest, (Text, Map Text (Expr s X)))]
forall a. a -> [a]
forall (m :: * -> *) a. Monad m => a -> m a
return (Expr X X -> SHA256Digest
Import.hashExpression (Expr s X -> Expr X X
forall s a t. Expr s a -> Expr t a
Syntax.denote Expr s X
_Type), (Text
name, Map Text (Expr s X)
_default))
Map SHA256Digest (Text, Map Text (Expr s X))
-> Maybe (Map SHA256Digest (Text, Map Text (Expr s X)))
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return Map SHA256Digest (Text, Map Text (Expr s X))
typeMetadata
decodeSchemas Expr s X
_ =
Maybe (Map SHA256Digest (Text, Map Text (Expr s X)))
forall a. Maybe a
forall (f :: * -> *) a. Alternative f => f a
empty
rewriteWithSchemas
:: Expr Src Import
-> Expr Src Import
-> IO (Expr Src Import)
rewriteWithSchemas :: Expr Src Import -> Expr Src Import -> IO (Expr Src Import)
rewriteWithSchemas Expr Src Import
_schemas Expr Src Import
expression = do
Expr Src X
resolvedSchemas <- Expr Src Import -> IO (Expr Src X)
Import.load Expr Src Import
_schemas
Expr Src X
resolvedExpression <- Expr Src Import -> IO (Expr Src X)
Import.load Expr Src Import
expression
Expr Src X
_ <- Either (TypeError Src X) (Expr Src X) -> IO (Expr Src X)
forall e (io :: * -> *) a.
(Exception e, MonadIO io) =>
Either e a -> io a
Core.throws (Expr Src X -> Either (TypeError Src X) (Expr Src X)
forall s. Expr s X -> Either (TypeError s X) (Expr s X)
TypeCheck.typeOf Expr Src X
resolvedSchemas)
Expr Src X
_ <- Either (TypeError Src X) (Expr Src X) -> IO (Expr Src X)
forall e (io :: * -> *) a.
(Exception e, MonadIO io) =>
Either e a -> io a
Core.throws (Expr Src X -> Either (TypeError Src X) (Expr Src X)
forall s. Expr s X -> Either (TypeError s X) (Expr s X)
TypeCheck.typeOf Expr Src X
resolvedExpression)
let normalizedSchemas :: Expr t X
normalizedSchemas = Expr Src X -> Expr t X
forall a s t. Eq a => Expr s a -> Expr t a
Normalize.normalize Expr Src X
resolvedSchemas
let normalizedExpression :: Expr t X
normalizedExpression = Expr Src X -> Expr t X
forall a s t. Eq a => Expr s a -> Expr t a
Normalize.normalize Expr Src X
resolvedExpression
Map SHA256Digest (Text, Map Text (Expr Src X))
typeMetadata <- case Expr Src X
-> Maybe (Map SHA256Digest (Text, Map Text (Expr Src X)))
forall s.
Expr s X -> Maybe (Map SHA256Digest (Text, Map Text (Expr s X)))
decodeSchemas Expr Src X
forall {t}. Expr t X
normalizedSchemas of
Just Map SHA256Digest (Text, Map Text (Expr Src X))
typeMetadata -> Map SHA256Digest (Text, Map Text (Expr Src X))
-> IO (Map SHA256Digest (Text, Map Text (Expr Src X)))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Map SHA256Digest (Text, Map Text (Expr Src X))
typeMetadata
Maybe (Map SHA256Digest (Text, Map Text (Expr Src X)))
Nothing -> SchemasError -> IO (Map SHA256Digest (Text, Map Text (Expr Src X)))
forall e a. Exception e => e -> IO a
Exception.throwIO SchemasError
NotASchemaRecord
let schemasRewrite :: Expr Src X -> Expr Src X
schemasRewrite subExpression :: Expr Src X
subExpression@(RecordLit Map Text (RecordField Src X)
keyValues) =
Expr Src X -> Maybe (Expr Src X) -> Expr Src X
forall a. a -> Maybe a -> a
Maybe.fromMaybe Expr Src X
subExpression (Maybe (Expr Src X) -> Expr Src X)
-> Maybe (Expr Src X) -> Expr Src X
forall a b. (a -> b) -> a -> b
$ do
let substitutions :: Map Text (Expr t X)
substitutions = Text -> Expr t X -> Map Text (Expr t X)
forall k v. k -> v -> Map k v
Map.singleton Text
"schemas" Expr t X
forall {t}. Expr t X
normalizedSchemas
let substitutedExpression :: Expr Src X
substitutedExpression =
Expr Src X -> Map Text (Expr Src X) -> Expr Src X
forall s a. Expr s a -> Substitutions s a -> Expr s a
Substitution.substitute Expr Src X
subExpression Map Text (Expr Src X)
forall {t}. Map Text (Expr t X)
substitutions
SHA256Digest
hash <- case Expr Src X -> Either (TypeError Src X) (Expr Src X)
forall s. Expr s X -> Either (TypeError s X) (Expr s X)
TypeCheck.typeOf Expr Src X
substitutedExpression of
Left TypeError Src X
_ ->
Maybe SHA256Digest
forall a. Maybe a
forall (f :: * -> *) a. Alternative f => f a
empty
Right Expr Src X
subExpressionType ->
SHA256Digest -> Maybe SHA256Digest
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Expr X X -> SHA256Digest
Import.hashExpression (Expr Src X -> Expr X X
forall s a t. Expr s a -> Expr t a
Syntax.denote Expr Src X
subExpressionType))
(Text
name, Map Text (Expr Src X)
_default) <- SHA256Digest
-> Map SHA256Digest (Text, Map Text (Expr Src X))
-> Maybe (Text, Map Text (Expr Src X))
forall k a. Ord k => k -> Map k a -> Maybe a
Data.Map.lookup SHA256Digest
hash Map SHA256Digest (Text, Map Text (Expr Src X))
typeMetadata
let diff :: a -> a -> Maybe a
diff a
a a
b | a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
b = Maybe a
forall a. Maybe a
Nothing
| Bool
otherwise = a -> Maybe a
forall a. a -> Maybe a
Just a
a
let defaultedKeyValues :: Map Text (RecordField Src X)
defaultedKeyValues =
Expr Src X -> RecordField Src X
forall s a. Expr s a -> RecordField s a
Core.makeRecordField (Expr Src X -> RecordField Src X)
-> Map Text (Expr Src X) -> Map Text (RecordField Src X)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
Map Text (Expr Src X) -> Map Text (Expr Src X)
forall k v. Map k v -> Map k v
Map.fromMap (
(Expr Src X -> Expr Src X -> Maybe (Expr Src X))
-> Map Text (Expr Src X)
-> Map Text (Expr Src X)
-> Map Text (Expr Src X)
forall k a b.
Ord k =>
(a -> b -> Maybe a) -> Map k a -> Map k b -> Map k a
Data.Map.differenceWith Expr Src X -> Expr Src X -> Maybe (Expr Src X)
forall {a}. Eq a => a -> a -> Maybe a
diff
(Map Text (Expr Src X) -> Map Text (Expr Src X)
forall k v. Map k v -> Map k v
Map.toMap (Map Text (Expr Src X) -> Map Text (Expr Src X))
-> Map Text (Expr Src X) -> Map Text (Expr Src X)
forall a b. (a -> b) -> a -> b
$ RecordField Src X -> Expr Src X
forall s a. RecordField s a -> Expr s a
Core.recordFieldValue (RecordField Src X -> Expr Src X)
-> Map Text (RecordField Src X) -> Map Text (Expr Src X)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map Text (RecordField Src X)
keyValues)
(Map Text (Expr Src X) -> Map Text (Expr Src X)
forall k v. Map k v -> Map k v
Map.toMap Map Text (Expr Src X)
_default))
let defaultedRecord :: Expr Src X
defaultedRecord = Map Text (RecordField Src X) -> Expr Src X
forall s a. Map Text (RecordField s a) -> Expr s a
RecordLit Map Text (RecordField Src X)
defaultedKeyValues
Expr Src X -> Maybe (Expr Src X)
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Expr Src X -> Expr Src X -> Expr Src X
forall s a. Expr s a -> Expr s a -> Expr s a
RecordCompletion (Expr Src X -> FieldSelection Src -> Expr Src X
forall s a. Expr s a -> FieldSelection s -> Expr s a
Field Expr Src X
"schemas" (FieldSelection Src -> Expr Src X)
-> FieldSelection Src -> Expr Src X
forall a b. (a -> b) -> a -> b
$ Text -> FieldSelection Src
forall s. Text -> FieldSelection s
Core.makeFieldSelection Text
name) Expr Src X
defaultedRecord)
schemasRewrite Expr Src X
subExpression =
Expr Src X
subExpression
let rewrittenExpression :: Expr Src Import
rewrittenExpression :: Expr Src Import
rewrittenExpression =
(X -> Import) -> Expr Src X -> Expr Src Import
forall a b. (a -> b) -> Expr Src a -> Expr Src b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap X -> Import
forall a. X -> a
Void.absurd (ASetter (Expr Src X) (Expr Src X) (Expr Src X) (Expr Src X)
-> (Expr Src X -> Expr Src X) -> Expr Src X -> Expr Src X
forall a b. ASetter a b a b -> (b -> b) -> a -> b
Optics.transformOf ASetter (Expr Src X) (Expr Src X) (Expr Src X) (Expr Src X)
forall (f :: * -> *) s a.
Applicative f =>
(Expr s a -> f (Expr s a)) -> Expr s a -> f (Expr s a)
Syntax.subExpressions Expr Src X -> Expr Src X
schemasRewrite Expr Src X
forall {t}. Expr t X
normalizedExpression)
if Var -> Expr Src Import -> Bool
forall a s. Eq a => Var -> Expr s a -> Bool
Normalize.freeIn (Text -> Int -> Var
V Text
"schemas" Int
0) Expr Src Import
rewrittenExpression
then Expr Src Import -> IO (Expr Src Import)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Binding Src Import -> Expr Src Import -> Expr Src Import
forall s a. Binding s a -> Expr s a -> Expr s a
Let (Text -> Expr Src Import -> Binding Src Import
forall s a. Text -> Expr s a -> Binding s a
Syntax.makeBinding Text
"schemas" Expr Src Import
_schemas) Expr Src Import
rewrittenExpression)
else Expr Src Import -> IO (Expr Src Import)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Expr Src Import
expression
data SchemasError = NotASchemaRecord
deriving (Show SchemasError
Typeable SchemasError
(Typeable SchemasError, Show SchemasError) =>
(SchemasError -> SomeException)
-> (SomeException -> Maybe SchemasError)
-> (SchemasError -> FilePath)
-> Exception SchemasError
SomeException -> Maybe SchemasError
SchemasError -> FilePath
SchemasError -> SomeException
forall e.
(Typeable e, Show e) =>
(e -> SomeException)
-> (SomeException -> Maybe e) -> (e -> FilePath) -> Exception e
$ctoException :: SchemasError -> SomeException
toException :: SchemasError -> SomeException
$cfromException :: SomeException -> Maybe SchemasError
fromException :: SomeException -> Maybe SchemasError
$cdisplayException :: SchemasError -> FilePath
displayException :: SchemasError -> FilePath
Exception)
instance Show SchemasError where
show :: SchemasError -> FilePath
show SchemasError
NotASchemaRecord =
FilePath
forall string. IsString string => string
Util._ERROR FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> FilePath
": The --schemas argument is not a record of schemas"