2018-06-07 12:26:38 +02:00
|
|
|
{-# LANGUAGE DeriveAnyClass #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
|
|
|
|
module Dhall.Main
|
|
|
|
( -- Commands
|
|
|
|
parseOptions
|
|
|
|
, parserInfoOptions
|
|
|
|
, 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)
|
|
|
|
import Data.Monoid (mempty, (<>))
|
2018-06-09 10:00:52 +02:00
|
|
|
import Data.Text (Text)
|
2018-06-07 12:26:38 +02:00
|
|
|
import Data.Text.Prettyprint.Doc (Pretty)
|
|
|
|
import Data.Version (showVersion)
|
|
|
|
import Dhall.Core (Expr, Import)
|
|
|
|
import Dhall.Import (Imported(..), load)
|
|
|
|
import Dhall.Parser (Src)
|
2018-07-13 06:30:47 +02:00
|
|
|
import Dhall.Pretty (annToAnsiStyle, prettyExpr, layoutOpts)
|
2018-06-07 12:26:38 +02:00
|
|
|
import Dhall.TypeCheck (DetailedTypeError(..), TypeError, X)
|
|
|
|
import Options.Applicative (Parser, ParserInfo)
|
|
|
|
import System.Exit (exitFailure)
|
|
|
|
import System.IO (Handle)
|
|
|
|
|
|
|
|
import qualified Paths_dhall as Meta
|
|
|
|
|
|
|
|
import qualified Control.Exception
|
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-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
|
|
|
|
import qualified Dhall.Lint
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Dhall.Parser
|
2018-06-08 06:33:03 +02:00
|
|
|
import qualified Dhall.Repl
|
2018-06-07 12:26:38 +02:00
|
|
|
import qualified Dhall.TypeCheck
|
|
|
|
import qualified Options.Applicative
|
|
|
|
import qualified System.Console.ANSI
|
|
|
|
import qualified System.IO
|
|
|
|
|
|
|
|
data Options = Options
|
|
|
|
{ mode :: Mode
|
|
|
|
, explain :: Bool
|
|
|
|
, plain :: Bool
|
|
|
|
}
|
|
|
|
|
2018-06-25 17:00:11 +02:00
|
|
|
data Mode
|
|
|
|
= Default
|
|
|
|
| Version
|
|
|
|
| Resolve
|
|
|
|
| Type
|
|
|
|
| Normalize
|
|
|
|
| Repl
|
|
|
|
| Format (Maybe FilePath)
|
2018-06-26 19:30:54 +02:00
|
|
|
| Freeze (Maybe FilePath)
|
2018-06-25 17:00:11 +02:00
|
|
|
| Hash
|
|
|
|
| Diff Text Text
|
|
|
|
| Lint (Maybe FilePath)
|
2018-06-10 19:54:22 +02:00
|
|
|
|
|
|
|
parseInplace :: Parser String
|
|
|
|
parseInplace =
|
|
|
|
Options.Applicative.strOption
|
|
|
|
( Options.Applicative.long "inplace"
|
|
|
|
<> Options.Applicative.help "Modify the specified file in-place"
|
|
|
|
<> Options.Applicative.metavar "FILE"
|
|
|
|
)
|
2018-06-07 12:26:38 +02:00
|
|
|
|
|
|
|
parseOptions :: Parser Options
|
|
|
|
parseOptions = Options <$> parseMode <*> parseExplain <*> parsePlain
|
|
|
|
where
|
|
|
|
parseExplain =
|
|
|
|
Options.Applicative.switch
|
|
|
|
( Options.Applicative.long "explain"
|
|
|
|
<> Options.Applicative.help "Explain error messages in more detail"
|
|
|
|
)
|
|
|
|
|
|
|
|
parsePlain =
|
|
|
|
Options.Applicative.switch
|
|
|
|
( Options.Applicative.long "plain"
|
|
|
|
<> Options.Applicative.help "Disable syntax highlighting"
|
|
|
|
)
|
|
|
|
|
2018-06-10 19:54:22 +02:00
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
parseMode :: Parser Mode
|
|
|
|
parseMode =
|
2018-06-09 10:00:52 +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" diffParser
|
2018-06-10 19:54:22 +02:00
|
|
|
<|> subcommand "hash" "Compute semantic hashes for Dhall expressions" (pure Hash)
|
2018-06-25 17:00:11 +02:00
|
|
|
<|> subcommand "lint" "Improve Dhall code" parseLint
|
2018-06-10 19:54:22 +02:00
|
|
|
<|> formatSubcommand
|
2018-06-26 19:30:54 +02:00
|
|
|
<|> freezeSubcommand
|
2018-06-07 12:26:38 +02:00
|
|
|
<|> pure Default
|
|
|
|
where
|
2018-06-09 10:00:52 +02:00
|
|
|
subcommand name description modeParser =
|
2018-06-07 12:26:38 +02:00
|
|
|
Options.Applicative.subparser
|
|
|
|
( Options.Applicative.command name parserInfo
|
|
|
|
<> Options.Applicative.metavar name
|
|
|
|
)
|
|
|
|
where
|
|
|
|
parserInfo =
|
|
|
|
Options.Applicative.info parser
|
|
|
|
( Options.Applicative.fullDesc
|
|
|
|
<> Options.Applicative.progDesc description
|
|
|
|
)
|
|
|
|
|
|
|
|
parser =
|
2018-06-09 10:00:52 +02:00
|
|
|
Options.Applicative.helper <*> modeParser
|
|
|
|
|
|
|
|
diffParser =
|
|
|
|
Diff <$> argument "expr1" <*> argument "expr2"
|
|
|
|
where
|
|
|
|
argument =
|
|
|
|
fmap Data.Text.pack
|
|
|
|
. Options.Applicative.strArgument
|
|
|
|
. Options.Applicative.metavar
|
2018-06-07 12:26:38 +02:00
|
|
|
|
2018-06-25 17:00:11 +02:00
|
|
|
parseLint =
|
|
|
|
Lint <$> optional parseInplace
|
|
|
|
|
2018-06-10 19:54:22 +02:00
|
|
|
formatSubcommand =
|
|
|
|
Options.Applicative.hsubparser
|
|
|
|
( Options.Applicative.command "format" parserInfo
|
|
|
|
<> Options.Applicative.metavar "format"
|
|
|
|
)
|
|
|
|
where parserInfo =
|
|
|
|
Options.Applicative.info parserWithHelper
|
|
|
|
( Options.Applicative.fullDesc
|
|
|
|
<> Options.Applicative.progDesc "Formatter for the Dhall language"
|
|
|
|
)
|
|
|
|
parserWithHelper = Options.Applicative.helper <*> parser
|
|
|
|
parser = Format <$> optional parseInplace
|
|
|
|
|
2018-06-26 19:30:54 +02:00
|
|
|
freezeSubcommand = subcommand "freeze" "Add hashes to all import statements of an expression" parseFreeze
|
|
|
|
where
|
|
|
|
parseFreeze = Freeze <$> optional parseInplace
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
parserInfoOptions :: ParserInfo Options
|
|
|
|
parserInfoOptions =
|
|
|
|
Options.Applicative.info
|
|
|
|
(Options.Applicative.helper <*> parseOptions)
|
|
|
|
( Options.Applicative.progDesc "Interpreter for the Dhall language"
|
|
|
|
<> Options.Applicative.fullDesc
|
|
|
|
)
|
|
|
|
|
|
|
|
command :: Options -> IO ()
|
|
|
|
command (Options {..}) = do
|
|
|
|
System.IO.hSetEncoding System.IO.stdin System.IO.utf8
|
|
|
|
|
|
|
|
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.hSetEncoding System.IO.stderr System.IO.utf8
|
|
|
|
System.IO.hPrint System.IO.stderr e
|
|
|
|
System.Exit.exitFailure
|
|
|
|
|
|
|
|
let render :: Pretty a => Handle -> Expr s a -> IO ()
|
|
|
|
render h e = do
|
|
|
|
let doc = prettyExpr e
|
|
|
|
|
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 ""
|
|
|
|
|
|
|
|
handle $ case mode of
|
|
|
|
Version -> do
|
|
|
|
putStrLn (showVersion Meta.version)
|
|
|
|
|
|
|
|
Default -> do
|
|
|
|
expression <- getExpression
|
|
|
|
|
|
|
|
resolvedExpression <- load expression
|
|
|
|
|
|
|
|
inferredType <- throws (Dhall.TypeCheck.typeOf resolvedExpression)
|
|
|
|
|
|
|
|
render System.IO.stderr (Dhall.Core.normalize inferredType)
|
|
|
|
|
|
|
|
Data.Text.IO.hPutStrLn System.IO.stderr mempty
|
|
|
|
|
|
|
|
render System.IO.stdout (Dhall.Core.normalize resolvedExpression)
|
|
|
|
|
|
|
|
Resolve -> do
|
|
|
|
expression <- getExpression
|
|
|
|
|
|
|
|
resolvedExpression <- load expression
|
|
|
|
|
|
|
|
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
|
|
|
|
Dhall.Repl.repl explain
|
|
|
|
|
2018-06-09 10:00:52 +02:00
|
|
|
Diff expr1 expr2 -> do
|
|
|
|
expression1 <- Dhall.inputExpr expr1
|
|
|
|
|
|
|
|
expression2 <- Dhall.inputExpr expr2
|
|
|
|
|
|
|
|
let diff = Dhall.Diff.diffNormalized expression1 expression2
|
|
|
|
prettyDiff = fmap annToAnsiStyle diff
|
|
|
|
|
|
|
|
Pretty.hPutDoc System.IO.stdout prettyDiff
|
|
|
|
|
2018-06-10 19:54:22 +02:00
|
|
|
Format inplace -> do
|
|
|
|
Dhall.Format.format inplace
|
|
|
|
|
2018-06-26 19:30:54 +02:00
|
|
|
Freeze inplace -> do
|
|
|
|
Dhall.Freeze.freeze inplace
|
|
|
|
|
2018-06-10 19:54:22 +02:00
|
|
|
Hash -> do
|
|
|
|
Dhall.Hash.hash
|
|
|
|
|
2018-06-25 17:00:11 +02:00
|
|
|
Lint inplace -> do
|
|
|
|
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
|
|
|
|
|
|
|
|
let doc = Pretty.pretty header <> Pretty.pretty lintedExpression
|
|
|
|
|
|
|
|
System.IO.withFile file System.IO.WriteMode (\h -> do
|
2018-07-13 06:30:47 +02:00
|
|
|
Pretty.renderIO h (Pretty.layoutSmart layoutOpts doc)
|
2018-06-25 17:00:11 +02:00
|
|
|
Data.Text.IO.hPutStrLn h "" )
|
|
|
|
Nothing -> do
|
|
|
|
System.IO.hSetEncoding System.IO.stdin System.IO.utf8
|
|
|
|
text <- Data.Text.IO.getContents
|
|
|
|
|
|
|
|
(header, expression) <- throws (Dhall.Parser.exprAndHeaderFromText "(stdin)" text)
|
|
|
|
|
|
|
|
let lintedExpression = Dhall.Lint.lint expression
|
|
|
|
|
|
|
|
let doc = Pretty.pretty header <> prettyExpr lintedExpression
|
|
|
|
|
|
|
|
supportsANSI <- System.Console.ANSI.hSupportsANSI System.IO.stdout
|
|
|
|
|
|
|
|
if supportsANSI
|
|
|
|
then
|
|
|
|
Pretty.renderIO
|
|
|
|
System.IO.stdout
|
2018-07-13 06:30:47 +02:00
|
|
|
(fmap annToAnsiStyle (Pretty.layoutSmart layoutOpts doc))
|
2018-06-25 17:00:11 +02:00
|
|
|
else
|
|
|
|
Pretty.renderIO
|
|
|
|
System.IO.stdout
|
2018-07-13 06:30:47 +02:00
|
|
|
(Pretty.layoutSmart layoutOpts (Pretty.unAnnotate doc))
|
2018-06-25 17:00:11 +02:00
|
|
|
|
2018-06-07 12:26:38 +02:00
|
|
|
main :: IO ()
|
|
|
|
main = do
|
|
|
|
options <- Options.Applicative.execParser parserInfoOptions
|
|
|
|
command options
|