2018-07-27 18:07:33 +02:00
|
|
|
{-| This module contains the top-level entrypoint and options parsing for the
|
|
|
|
@dhall@ executable
|
|
|
|
-}
|
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
{-# LANGUAGE DeriveAnyClass #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
|
|
|
|
module Dhall.Main
|
2018-07-27 18:07:33 +02:00
|
|
|
( -- * Options
|
|
|
|
Options(..)
|
|
|
|
, Mode(..)
|
|
|
|
, parseOptions
|
2018-06-07 12:26:38 +02:00
|
|
|
, parserInfoOptions
|
2018-07-27 18:07:33 +02:00
|
|
|
|
|
|
|
-- * Execution
|
2018-06-07 12:26:38 +02:00
|
|
|
, command
|
|
|
|
, main
|
|
|
|
) where
|
|
|
|
|
2018-06-10 19:54:22 +02:00
|
|
|
import Control.Applicative (optional, (<|>))
|
2018-06-07 12:26:38 +02:00
|
|
|
import Control.Exception (Exception, SomeException)
|
2018-08-12 16:29:54 +02:00
|
|
|
import Data.Monoid ((<>))
|
2018-06-09 10:00:52 +02:00
|
|
|
import Data.Text (Text)
|
2018-09-16 17:32:01 +02:00
|
|
|
import Data.Text.Prettyprint.Doc (Doc, Pretty)
|
2018-06-07 12:26:38 +02:00
|
|
|
import Data.Version (showVersion)
|
2018-08-03 15:50:03 +02:00
|
|
|
import Dhall.Binary (ProtocolVersion)
|
2018-08-12 16:29:54 +02:00
|
|
|
import Dhall.Core (Expr(..), Import)
|
2018-08-03 15:50:03 +02:00
|
|
|
import Dhall.Import (Imported(..))
|
2018-06-07 12:26:38 +02:00
|
|
|
import Dhall.Parser (Src)
|
2018-09-16 17:32:01 +02:00
|
|
|
import Dhall.Pretty (Ann, CharacterSet(..), annToAnsiStyle, layoutOpts)
|
2018-06-07 12:26:38 +02:00
|
|
|
import Dhall.TypeCheck (DetailedTypeError(..), TypeError, X)
|
2018-08-03 15:50:03 +02:00
|
|
|
import Lens.Family (set)
|
2018-06-07 12:26:38 +02:00
|
|
|
import Options.Applicative (Parser, ParserInfo)
|
|
|
|
import System.Exit (exitFailure)
|
|
|
|
import System.IO (Handle)
|
|
|
|
|
|
|
|
import qualified Control.Exception
|
2018-08-03 15:50:03 +02:00
|
|
|
import qualified Control.Monad.Trans.State.Strict as State
|
2018-06-09 10:00:52 +02:00
|
|
|
import qualified Data.Text
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Data.Text.IO
|
|
|
|
import qualified Data.Text.Prettyprint.Doc as Pretty
|
|
|
|
import qualified Data.Text.Prettyprint.Doc.Render.Terminal as Pretty
|
2018-06-09 10:00:52 +02:00
|
|
|
import qualified Dhall
|
2018-08-03 15:50:03 +02:00
|
|
|
import qualified Dhall.Binary
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Dhall.Core
|
2018-06-09 10:00:52 +02:00
|
|
|
import qualified Dhall.Diff
|
2018-06-25 17:00:11 +02:00
|
|
|
import qualified Dhall.Format
|
2018-06-26 19:30:54 +02:00
|
|
|
import qualified Dhall.Freeze
|
2018-06-25 17:00:11 +02:00
|
|
|
import qualified Dhall.Hash
|
2018-08-03 15:50:03 +02:00
|
|
|
import qualified Dhall.Import
|
2018-06-25 17:00:11 +02:00
|
|
|
import qualified Dhall.Lint
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Dhall.Parser
|
2018-09-11 15:38:13 +02:00
|
|
|
import qualified Dhall.Pretty
|
2018-06-08 06:33:03 +02:00
|
|
|
import qualified Dhall.Repl
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Dhall.TypeCheck
|
2018-07-14 18:20:03 +02:00
|
|
|
import qualified GHC.IO.Encoding
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Options.Applicative
|
2018-09-11 15:38:13 +02:00
|
|
|
import qualified Paths_dhall as Meta
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified System.Console.ANSI
|
|
|
|
import qualified System.IO
|
|
|
|
|
2018-07-27 18:07:33 +02:00
|
|
|
-- | Top-level program options
|
2018-06-07 12:26:38 +02:00
|
|
|
data Options = Options
|
2018-08-03 15:50:03 +02:00
|
|
|
{ mode :: Mode
|
|
|
|
, explain :: Bool
|
|
|
|
, plain :: Bool
|
2018-09-11 15:38:13 +02:00
|
|
|
, ascii :: Bool
|
2018-08-03 15:50:03 +02:00
|
|
|
, protocolVersion :: ProtocolVersion
|
2018-06-07 12:26:38 +02:00
|
|
|
}
|
|
|
|
|
2018-07-27 18:07:33 +02:00
|
|
|
-- | The subcommands for the @dhall@ executable
|
2018-06-25 17:00:11 +02:00
|
|
|
data Mode
|
2018-08-12 16:29:54 +02:00
|
|
|
= Default { annotate :: Bool }
|
2018-06-25 17:00:11 +02:00
|
|
|
| Version
|
|
|
|
| Resolve
|
|
|
|
| Type
|
|
|
|
| Normalize
|
|
|
|
| Repl
|
2018-09-16 17:32:01 +02:00
|
|
|
| Format { inplace :: Maybe FilePath }
|
|
|
|
| Freeze { inplace :: Maybe FilePath }
|
2018-06-25 17:00:11 +02:00
|
|
|
| Hash
|
2018-09-16 17:32:01 +02:00
|
|
|
| Diff { expr1 :: Text, expr2 :: Text }
|
|
|
|
| Lint { inplace :: Maybe FilePath }
|
2018-06-07 12:26:38 +02:00
|
|
|
|
2018-07-27 18:07:33 +02:00
|
|
|
-- | `Parser` for the `Options` type
|
2018-06-07 12:26:38 +02:00
|
|
|
parseOptions :: Parser Options
|
2018-08-03 15:50:03 +02:00
|
|
|
parseOptions =
|
|
|
|
Options
|
|
|
|
<$> parseMode
|
2018-09-11 15:38:13 +02:00
|
|
|
<*> switch "explain" "Explain error messages in more detail"
|
|
|
|
<*> switch "plain" "Disable syntax highlighting"
|
2018-09-16 17:32:01 +02:00
|
|
|
<*> switch "ascii" "Format code using only ASCII syntax"
|
2018-08-03 15:50:03 +02:00
|
|
|
<*> Dhall.Binary.parseProtocolVersion
|
2018-06-07 12:26:38 +02:00
|
|
|
where
|
2018-09-11 15:38:13 +02:00
|
|
|
switch name description =
|
2018-06-07 12:26:38 +02:00
|
|
|
Options.Applicative.switch
|
2018-09-11 15:38:13 +02:00
|
|
|
( Options.Applicative.long name
|
|
|
|
<> Options.Applicative.help description
|
2018-06-07 12:26:38 +02:00
|
|
|
)
|
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
subcommand :: String -> String -> Parser a -> Parser a
|
|
|
|
subcommand name description parser =
|
|
|
|
Options.Applicative.hsubparser
|
|
|
|
( Options.Applicative.command name parserInfo
|
|
|
|
<> Options.Applicative.metavar name
|
|
|
|
)
|
|
|
|
where
|
|
|
|
parserInfo =
|
|
|
|
Options.Applicative.info parser
|
|
|
|
( Options.Applicative.fullDesc
|
|
|
|
<> Options.Applicative.progDesc description
|
|
|
|
)
|
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
parseMode :: Parser Mode
|
|
|
|
parseMode =
|
2018-09-16 17:32:01 +02:00
|
|
|
subcommand
|
|
|
|
"version"
|
|
|
|
"Display version"
|
|
|
|
(pure Version)
|
|
|
|
<|> subcommand
|
|
|
|
"resolve"
|
|
|
|
"Resolve an expression's imports"
|
|
|
|
(pure Resolve)
|
|
|
|
<|> subcommand
|
|
|
|
"type"
|
|
|
|
"Infer an expression's type"
|
|
|
|
(pure Type)
|
|
|
|
<|> subcommand
|
|
|
|
"normalize"
|
|
|
|
"Normalize an expression"
|
|
|
|
(pure Normalize)
|
|
|
|
<|> subcommand
|
|
|
|
"repl"
|
|
|
|
"Interpret expressions in a REPL"
|
|
|
|
(pure Repl)
|
|
|
|
<|> subcommand
|
|
|
|
"diff"
|
|
|
|
"Render the difference between the normal form of two expressions"
|
|
|
|
(Diff <$> argument "expr1" <*> argument "expr2")
|
|
|
|
<|> subcommand
|
|
|
|
"hash"
|
|
|
|
"Compute semantic hashes for Dhall expressions"
|
|
|
|
(pure Hash)
|
|
|
|
<|> subcommand
|
|
|
|
"lint"
|
|
|
|
"Improve Dhall code"
|
|
|
|
(Lint <$> optional parseInplace)
|
|
|
|
<|> subcommand
|
|
|
|
"format"
|
|
|
|
"Formatter for the Dhall language"
|
|
|
|
(Format <$> optional parseInplace)
|
|
|
|
<|> subcommand
|
|
|
|
"freeze"
|
|
|
|
"Add hashes to all import statements of an expression"
|
|
|
|
(Freeze <$> optional parseInplace)
|
|
|
|
<|> (Default <$> parseAnnotate)
|
2018-06-07 12:26:38 +02:00
|
|
|
where
|
2018-09-16 17:32:01 +02:00
|
|
|
argument =
|
|
|
|
fmap Data.Text.pack
|
|
|
|
. Options.Applicative.strArgument
|
|
|
|
. Options.Applicative.metavar
|
|
|
|
|
|
|
|
parseAnnotate =
|
|
|
|
Options.Applicative.switch
|
|
|
|
(Options.Applicative.long "annotate")
|
|
|
|
|
|
|
|
parseInplace =
|
|
|
|
Options.Applicative.strOption
|
|
|
|
( Options.Applicative.long "inplace"
|
|
|
|
<> Options.Applicative.help "Modify the specified file in-place"
|
|
|
|
<> Options.Applicative.metavar "FILE"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-08-12 16:29:54 +02:00
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
data ImportResolutionDisabled = ImportResolutionDisabled deriving (Exception)
|
|
|
|
|
|
|
|
instance Show ImportResolutionDisabled where
|
|
|
|
show _ = "\nImport resolution is disabled"
|
|
|
|
|
|
|
|
throws :: Exception e => Either e a -> IO a
|
|
|
|
throws (Left e) = Control.Exception.throwIO e
|
|
|
|
throws (Right a) = return a
|
|
|
|
|
|
|
|
getExpression :: IO (Expr Src Import)
|
|
|
|
getExpression = do
|
|
|
|
inText <- Data.Text.IO.getContents
|
|
|
|
|
|
|
|
throws (Dhall.Parser.exprFromText "(stdin)" inText)
|
|
|
|
|
|
|
|
assertNoImports :: Expr Src Import -> IO (Expr Src X)
|
|
|
|
assertNoImports expression =
|
|
|
|
throws (traverse (\_ -> Left ImportResolutionDisabled) expression)
|
|
|
|
|
2018-07-27 18:07:33 +02:00
|
|
|
-- | `ParserInfo` for the `Options` type
|
2018-06-07 12:26:38 +02:00
|
|
|
parserInfoOptions :: ParserInfo Options
|
|
|
|
parserInfoOptions =
|
|
|
|
Options.Applicative.info
|
|
|
|
(Options.Applicative.helper <*> parseOptions)
|
|
|
|
( Options.Applicative.progDesc "Interpreter for the Dhall language"
|
|
|
|
<> Options.Applicative.fullDesc
|
|
|
|
)
|
|
|
|
|
2018-07-27 18:07:33 +02:00
|
|
|
-- | Run the command specified by the `Options` type
|
2018-06-07 12:26:38 +02:00
|
|
|
command :: Options -> IO ()
|
|
|
|
command (Options {..}) = do
|
2018-09-11 15:38:13 +02:00
|
|
|
let characterSet = case ascii of
|
|
|
|
True -> ASCII
|
|
|
|
False -> Unicode
|
|
|
|
|
2018-07-14 18:20:03 +02:00
|
|
|
GHC.IO.Encoding.setLocaleEncoding System.IO.utf8
|
2018-06-07 12:26:38 +02:00
|
|
|
|
2018-08-03 15:50:03 +02:00
|
|
|
let status =
|
|
|
|
set Dhall.Import.protocolVersion protocolVersion (Dhall.Import.emptyStatus ".")
|
|
|
|
|
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
let handle =
|
|
|
|
Control.Exception.handle handler2
|
|
|
|
. Control.Exception.handle handler1
|
|
|
|
. Control.Exception.handle handler0
|
|
|
|
where
|
|
|
|
handler0 e = do
|
|
|
|
let _ = e :: TypeError Src X
|
|
|
|
System.IO.hPutStrLn System.IO.stderr ""
|
|
|
|
if explain
|
|
|
|
then Control.Exception.throwIO (DetailedTypeError e)
|
|
|
|
else do
|
|
|
|
Data.Text.IO.hPutStrLn System.IO.stderr "\ESC[2mUse \"dhall --explain\" for detailed errors\ESC[0m"
|
|
|
|
Control.Exception.throwIO e
|
|
|
|
|
|
|
|
handler1 (Imported ps e) = do
|
|
|
|
let _ = e :: TypeError Src X
|
|
|
|
System.IO.hPutStrLn System.IO.stderr ""
|
|
|
|
if explain
|
|
|
|
then Control.Exception.throwIO (Imported ps (DetailedTypeError e))
|
|
|
|
else do
|
|
|
|
Data.Text.IO.hPutStrLn System.IO.stderr "\ESC[2mUse \"dhall --explain\" for detailed errors\ESC[0m"
|
|
|
|
Control.Exception.throwIO (Imported ps e)
|
|
|
|
|
|
|
|
handler2 e = do
|
|
|
|
let _ = e :: SomeException
|
|
|
|
System.IO.hPrint System.IO.stderr e
|
|
|
|
System.Exit.exitFailure
|
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
let renderDoc :: Handle -> Doc Ann -> IO ()
|
|
|
|
renderDoc h doc = do
|
2018-07-13 06:30:47 +02:00
|
|
|
let stream = Pretty.layoutSmart layoutOpts doc
|
2018-06-07 12:26:38 +02:00
|
|
|
|
|
|
|
supportsANSI <- System.Console.ANSI.hSupportsANSI h
|
|
|
|
let ansiStream =
|
|
|
|
if supportsANSI && not plain
|
|
|
|
then fmap annToAnsiStyle stream
|
|
|
|
else Pretty.unAnnotateS stream
|
|
|
|
|
|
|
|
Pretty.renderIO h ansiStream
|
|
|
|
Data.Text.IO.hPutStrLn h ""
|
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
let render :: Pretty a => Handle -> Expr s a -> IO ()
|
|
|
|
render h expression = do
|
|
|
|
let doc = Dhall.Pretty.prettyCharacterSet characterSet expression
|
|
|
|
|
|
|
|
renderDoc h doc
|
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
handle $ case mode of
|
|
|
|
Version -> do
|
|
|
|
putStrLn (showVersion Meta.version)
|
|
|
|
|
2018-08-12 16:29:54 +02:00
|
|
|
Default {..} -> do
|
2018-06-07 12:26:38 +02:00
|
|
|
expression <- getExpression
|
|
|
|
|
2018-08-03 15:50:03 +02:00
|
|
|
resolvedExpression <- State.evalStateT (Dhall.Import.loadWith expression) status
|
2018-06-07 12:26:38 +02:00
|
|
|
|
|
|
|
inferredType <- throws (Dhall.TypeCheck.typeOf resolvedExpression)
|
|
|
|
|
2018-08-12 16:29:54 +02:00
|
|
|
let normalizedExpression = Dhall.Core.normalize resolvedExpression
|
2018-06-07 12:26:38 +02:00
|
|
|
|
2018-08-12 16:29:54 +02:00
|
|
|
let annotatedExpression =
|
|
|
|
if annotate
|
|
|
|
then Annot normalizedExpression inferredType
|
|
|
|
else normalizedExpression
|
2018-06-07 12:26:38 +02:00
|
|
|
|
2018-08-12 16:29:54 +02:00
|
|
|
render System.IO.stdout annotatedExpression
|
2018-06-07 12:26:38 +02:00
|
|
|
|
|
|
|
Resolve -> do
|
|
|
|
expression <- getExpression
|
|
|
|
|
2018-08-03 15:50:03 +02:00
|
|
|
resolvedExpression <- State.evalStateT (Dhall.Import.loadWith expression) status
|
2018-06-07 12:26:38 +02:00
|
|
|
|
|
|
|
render System.IO.stdout resolvedExpression
|
|
|
|
|
|
|
|
Normalize -> do
|
|
|
|
expression <- getExpression
|
|
|
|
|
|
|
|
resolvedExpression <- assertNoImports expression
|
|
|
|
|
|
|
|
_ <- throws (Dhall.TypeCheck.typeOf resolvedExpression)
|
|
|
|
|
|
|
|
render System.IO.stdout (Dhall.Core.normalize resolvedExpression)
|
|
|
|
|
|
|
|
Type -> do
|
|
|
|
expression <- getExpression
|
|
|
|
|
|
|
|
resolvedExpression <- assertNoImports expression
|
|
|
|
|
|
|
|
inferredType <- throws (Dhall.TypeCheck.typeOf resolvedExpression)
|
|
|
|
|
|
|
|
render System.IO.stdout (Dhall.Core.normalize inferredType)
|
|
|
|
|
2018-06-08 06:33:03 +02:00
|
|
|
Repl -> do
|
2018-09-11 15:38:13 +02:00
|
|
|
Dhall.Repl.repl characterSet explain protocolVersion
|
2018-06-08 06:33:03 +02:00
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
Diff {..} -> do
|
2018-06-09 10:00:52 +02:00
|
|
|
expression1 <- Dhall.inputExpr expr1
|
|
|
|
|
|
|
|
expression2 <- Dhall.inputExpr expr2
|
|
|
|
|
|
|
|
let diff = Dhall.Diff.diffNormalized expression1 expression2
|
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
renderDoc System.IO.stdout diff
|
2018-06-09 10:00:52 +02:00
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
Format {..} -> do
|
2018-09-11 15:38:13 +02:00
|
|
|
Dhall.Format.format characterSet inplace
|
2018-06-10 19:54:22 +02:00
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
Freeze {..} -> do
|
2018-08-03 15:50:03 +02:00
|
|
|
Dhall.Freeze.freeze inplace protocolVersion
|
2018-06-26 19:30:54 +02:00
|
|
|
|
2018-06-10 19:54:22 +02:00
|
|
|
Hash -> do
|
2018-08-03 15:50:03 +02:00
|
|
|
Dhall.Hash.hash protocolVersion
|
2018-06-10 19:54:22 +02:00
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
Lint {..} -> do
|
2018-06-25 17:00:11 +02:00
|
|
|
case inplace of
|
|
|
|
Just file -> do
|
|
|
|
text <- Data.Text.IO.readFile file
|
|
|
|
|
|
|
|
(header, expression) <- throws (Dhall.Parser.exprAndHeaderFromText file text)
|
|
|
|
|
|
|
|
let lintedExpression = Dhall.Lint.lint expression
|
|
|
|
|
2018-09-11 15:38:13 +02:00
|
|
|
let doc = Pretty.pretty header
|
2018-09-16 17:32:01 +02:00
|
|
|
<> Dhall.Pretty.prettyCharacterSet characterSet lintedExpression
|
2018-06-25 17:00:11 +02:00
|
|
|
|
|
|
|
System.IO.withFile file System.IO.WriteMode (\h -> do
|
2018-09-16 17:32:01 +02:00
|
|
|
renderDoc h doc )
|
|
|
|
|
2018-06-25 17:00:11 +02:00
|
|
|
Nothing -> do
|
|
|
|
text <- Data.Text.IO.getContents
|
|
|
|
|
|
|
|
(header, expression) <- throws (Dhall.Parser.exprAndHeaderFromText "(stdin)" text)
|
|
|
|
|
|
|
|
let lintedExpression = Dhall.Lint.lint expression
|
|
|
|
|
2018-09-11 15:38:13 +02:00
|
|
|
let doc = Pretty.pretty header
|
|
|
|
<> Dhall.Pretty.prettyCharacterSet characterSet lintedExpression
|
2018-06-25 17:00:11 +02:00
|
|
|
|
2018-09-16 17:32:01 +02:00
|
|
|
renderDoc System.IO.stdout doc
|
2018-06-25 17:00:11 +02:00
|
|
|
|
2018-07-27 18:07:33 +02:00
|
|
|
-- | Entry point for the @dhall@ executable
|
2018-06-07 12:26:38 +02:00
|
|
|
main :: IO ()
|
|
|
|
main = do
|
|
|
|
options <- Options.Applicative.execParser parserInfoOptions
|
|
|
|
command options
|