content: {-| This module contains the top-level entrypoint and options parsing for the
      @dhall@ executable
  -}
  
  {-# LANGUAGE ApplicativeDo     #-}
  {-# LANGUAGE LambdaCase        #-}
  {-# LANGUAGE NamedFieldPuns    #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards   #-}
  
  module Dhall.Main
      ( -- * Options
        Options(..)
      , Mode(..)
      , ResolveMode(..)
      , parseOptions
      , parserInfoOptions
  
        -- * Execution
      , Dhall.Main.command
      , main
      ) where
  
  import Control.Applicative (optional, (<|>))
  import Control.Exception   (Handler (..), SomeException)
  import Control.Monad       (when)
  import Data.Foldable       (for_)
  import Data.List.NonEmpty  (NonEmpty (..), nonEmpty)
  import Data.Maybe          (fromMaybe)
  import Data.Monoid         (Endo (..))
  import Data.Text           (Text)
  import Data.Void           (Void)
  import Dhall.Freeze        (Intent (..), Scope (..))
  import Dhall.Import
      ( Depends (..)
      , Imported (..)
      , SemanticCacheMode (..)
      , _semanticCacheMode
      )
  import Dhall.Package       (PackagingMode (..), writePackage)
  import Dhall.Parser        (Src)
  import Dhall.Pretty
      ( Ann
      , CharacterSet (..)
      , annToAnsiStyle
      , detectCharacterSet
      )
  import Dhall.Schemas       (Schemas (..))
  import Dhall.TypeCheck     (Censored (..), DetailedTypeError (..), TypeError)
  import Dhall.Version       (dhallVersionString)
  import Lens.Micro          (set)
  import Options.Applicative (Parser, ParserInfo)
  import Prettyprinter       (Doc, Pretty)
  import System.Exit         (ExitCode, exitFailure)
  import System.IO           (Handle)
  import Text.Dot            ((.->.))
  
  import Dhall.Core
      ( Expr (Annot)
      , Import (..)
      , ImportHashed (..)
      , ImportType (..)
      , URL (..)
      , pretty
      )
  import Dhall.Util
      ( Censor (..)
      , CheckFailed (..)
      , Header (..)
      , Input (..)
      , Output (..)
      , OutputMode (..)
      , Transitivity (..)
      , handleMultipleChecksFailed
      )
  
  import qualified Codec.CBOR.JSON
  import qualified Codec.CBOR.Read
  import qualified Codec.CBOR.Write
  import qualified Control.Exception
  import qualified Control.Monad.Trans.State.Strict   as State
  import qualified Data.Aeson
  import qualified Data.Aeson.Encode.Pretty
  import qualified Data.ByteString.Lazy
  import qualified Data.ByteString.Lazy.Char8
  import qualified Data.Map
  import qualified Data.Text
  import qualified Data.Text.IO
  import qualified Dhall
  import qualified Dhall.Binary
  import qualified Dhall.Core
  import qualified Dhall.Diff
  import qualified Dhall.DirectoryTree                as DirectoryTree
  import qualified Dhall.Format
  import qualified Dhall.Freeze
  import qualified Dhall.Import
  import qualified Dhall.Import.Types
  import qualified Dhall.Lint
  import qualified Dhall.Map
  import qualified Dhall.Package
  import qualified Dhall.Pretty
  import qualified Dhall.Repl
  import qualified Dhall.Schemas
  import qualified Dhall.Tags
  import qualified Dhall.TypeCheck
  import qualified Dhall.Util
  import qualified GHC.IO.Encoding
  import qualified Options.Applicative
  import qualified Prettyprinter                      as Pretty
  import qualified Prettyprinter.Render.Terminal      as Pretty
  import qualified Prettyprinter.Render.Text          as Pretty.Text
  import qualified System.AtomicWrite.Writer.LazyText as AtomicWrite.LazyText
  import qualified System.Console.ANSI
  import qualified System.Exit                        as Exit
  import qualified System.FilePath
  import qualified System.IO
  import qualified Text.Dot
  import qualified Text.Pretty.Simple
  
  -- | Top-level program options
  data Options = Options
      { mode               :: Mode
      , explain            :: Bool
      , plain              :: Bool
      , chosenCharacterSet :: Maybe CharacterSet
      , censor             :: Censor
      }
  
  -- | The subcommands for the @dhall@ executable
  data Mode
      = Default
            { file :: Input
            , output :: Output
            , annotate :: Bool
            , alpha :: Bool
            , semanticCacheMode :: SemanticCacheMode
            , version :: Bool
            }
      | Version
      | Resolve
            { file :: Input
            , resolveMode :: Maybe ResolveMode
            , semanticCacheMode :: SemanticCacheMode
            }
      | Type
            { file :: Input
            , quiet :: Bool
            , semanticCacheMode :: SemanticCacheMode
            }
      | Normalize { file :: Input , alpha :: Bool }
      | Repl
      | Format { deprecatedInPlace :: Bool, transitivity :: Transitivity, outputMode :: OutputMode, inputs :: NonEmpty Input }
      | Freeze { deprecatedInPlace :: Bool, transitivity :: Transitivity, all_ :: Bool, cache :: Bool, outputMode :: OutputMode, inputs :: NonEmpty Input }
      | Hash { file :: Input, cache :: Bool }
      | Diff { expr1 :: Text, expr2 :: Text }
      | Lint { deprecatedInPlace :: Bool, transitivity :: Transitivity, outputMode :: OutputMode, inputs :: NonEmpty Input }
      | Tags
            { input :: Input
            , output :: Output
            , suffixes :: Maybe [Text]
            , followSymlinks :: Bool
            }
      | Encode { file :: Input, json :: Bool }
      | Decode { file :: Input, json :: Bool, quiet :: Bool }
      | Text { file :: Input, output :: Output }
      | DirectoryTree { allowSeparators :: Bool, file :: Input, path :: FilePath }
      | Schemas { file :: Input, outputMode :: OutputMode, schemas :: Text }
      | SyntaxTree { file :: Input, noted :: Bool }
      | Package
          { packageOptions :: Endo Dhall.Package.Options
          , packageFiles :: NonEmpty FilePath
          }
  
  -- | This specifies how to resolve transitive dependencies
  data ResolveMode
      = Dot
      -- ^ Generate a DOT file for @graphviz@
      | ListTransitiveDependencies
      -- ^ List all transitive dependencies as text, one per line
      | ListImmediateDependencies
      -- ^ List immediate dependencies as text, one per line
  
  -- | Groups of subcommands
  data Group
      = Manipulate
      | Generate
      | Interpret
      | Convert
      | Miscellaneous
      | Debugging
  
  groupDescription :: Group -> String
  groupDescription group = case group of
      Manipulate -> "Manipulate Dhall code"
      Generate -> "Generate other formats from Dhall"
      Interpret -> "Interpret Dhall"
      Convert -> "Convert Dhall to and from its binary representation"
      Miscellaneous -> "Miscellaneous"
      Debugging -> "Debugging this interpreter"
  
  -- | `Parser` for the `Options` type
  parseOptions :: Parser Options
  parseOptions =
          Options
      <$> parseMode
      <*> switch "explain" "Explain error messages in more detail"
      <*> switch "plain" "Disable syntax highlighting"
      <*> parseCharacterSet
      <*> parseCensor
    where
      switch name description =
          Options.Applicative.switch
              (   Options.Applicative.long name
              <>  Options.Applicative.help description
              )
  
      parseCensor = fmap f (switch "censor" "Hide source code in error messages")
        where
          f True  = Censor
          f False = NoCensor
  
      parseCharacterSet =
              Options.Applicative.flag'
                  (Just Unicode)
                  (   Options.Applicative.long "unicode"
                  <>  Options.Applicative.help "Format code using only Unicode syntax"
                  )
          <|> Options.Applicative.flag'
                  (Just ASCII)
                  (   Options.Applicative.long "ascii"
                  <>  Options.Applicative.help "Format code using only ASCII syntax"
                  )
          <|> pure Nothing
  
  subcommand :: Group -> String -> String -> Parser a -> Parser a
  subcommand group name description parser =
      Options.Applicative.hsubparser
          (   Options.Applicative.command name parserInfo
          <>  Options.Applicative.metavar name
          <>  Options.Applicative.commandGroup (groupDescription group)
          )
    where
      parserInfo =
          Options.Applicative.info parser
              (   Options.Applicative.fullDesc
              <>  Options.Applicative.progDesc description
              )
  
  parseMode :: Parser Mode
  parseMode =
          subcommand
              Manipulate
              "format"
              "Standard code formatter for the Dhall language"
              (Format <$> deprecatedInPlace <*> parseTransitiveSwitch <*> parseCheck "formatted" <*> parseFiles)
      <|> subcommand
              Manipulate
              "freeze"
              "Add integrity checks to remote import statements of an expression"
              (Freeze <$> deprecatedInPlace <*> parseTransitiveSwitch <*> parseAllFlag <*> parseCacheFlag <*> parseCheck "frozen" <*> parseFiles)
      <|> subcommand
              Manipulate
              "lint"
              "Improve Dhall code by using newer language features and removing dead code"
              (Lint <$> deprecatedInPlace <*> parseTransitiveSwitch <*> parseCheck "linted" <*> parseFiles)
      <|> subcommand
              Manipulate
              "rewrite-with-schemas"
              "Simplify Dhall code using a schemas record"
              (Dhall.Main.Schemas <$> parseInplaceNonTransitive <*> parseCheck "rewritten" <*> parseSchemasRecord)
      <|> subcommand
              Generate
              "text"
              "Render a Dhall expression that evaluates to a Text literal"
              (Text <$> parseFile <*> parseOutput)
      <|> subcommand
              Generate
              "to-directory-tree"
              "Convert nested records of Text literals into a directory tree"
              (DirectoryTree <$> parseDirectoryTreeAllowSeparators <*> parseFile <*> parseDirectoryTreeOutput)
      <|> subcommand
              Interpret
              "resolve"
              "Resolve an expression's imports"
              (Resolve <$> parseFile <*> parseResolveMode <*> parseSemanticCacheMode)
      <|> subcommand
              Interpret
              "type"
              "Infer an expression's type"
              (Type <$> parseFile <*> parseQuiet <*> parseSemanticCacheMode)
      <|> subcommand
              Interpret
              "normalize"
              "Normalize an expression"
              (Normalize <$> parseFile <*> parseAlpha)
      <|> subcommand
              Convert
              "encode"
              "Encode a Dhall expression to binary"
              (Encode <$> parseFile <*> parseJSONFlag)
      <|> subcommand
              Convert
              "decode"
              "Decode a Dhall expression from binary"
              (Decode <$> parseFile <*> parseJSONFlag <*> parseQuiet)
      <|> subcommand
              Miscellaneous
              "repl"
              "Interpret expressions in a REPL"
              (pure Repl)
      <|> subcommand
              Miscellaneous
              "diff"
              "Render the difference between the normal form of two expressions"
              (Diff <$> argument "expr1" <*> argument "expr2")
      <|> subcommand
              Miscellaneous
              "hash"
              "Compute semantic hashes for Dhall expressions"
              (Hash <$> parseFile <*> parseCache)
      <|> subcommand
              Miscellaneous
              "package"
              "Create a package.dhall referencing the provided paths"
              (Package <$> parsePackageOptions <*> parsePackageFiles)
      <|> subcommand
              Miscellaneous
              "tags"
              "Generate etags file"
              (Tags <$> parseInput <*> parseTagsOutput <*> parseSuffixes <*> parseFollowSymlinks)
      <|> subcommand
              Miscellaneous
              "version"
              "Display version"
              (pure Version)
      <|> subcommand
              Debugging
              "haskell-syntax-tree"
              "Output the parsed syntax tree (for debugging)"
              (SyntaxTree <$> parseFile <*> parseNoted)
      <|> (   Default
          <$> parseFile
          <*> parseOutput
          <*> parseAnnotate
          <*> parseAlpha
          <*> parseSemanticCacheMode
          <*> parseVersion
          )
    where
      deprecatedInPlace =
          Options.Applicative.switch
              (   Options.Applicative.long "inplace"
              <>  Options.Applicative.internal -- completely hidden from help
              )
  
      argument =
              fmap Data.Text.pack
          .   Options.Applicative.strArgument
          .   Options.Applicative.metavar
  
      parseFile = fmap f (optional p)
        where
          f  Nothing    = StandardInput
          f (Just file) = InputFile file
  
          p = Options.Applicative.strOption
                  (   Options.Applicative.long "file"
                  <>  Options.Applicative.help "Read expression from a file instead of standard input"
                  <>  Options.Applicative.metavar "FILE"
                  <>  Options.Applicative.action "file"
                  )
  
      parseFiles = fmap f (Options.Applicative.many p)
        where
          -- Parse explicit stdin in the input filepaths
          parseStdin inputs
              | InputFile "-" `elem` inputs = StandardInput : filter (/= InputFile "-") inputs
              | otherwise = inputs
  
          f = fromMaybe (pure StandardInput) . nonEmpty . parseStdin . fmap InputFile
  
          p = Options.Applicative.strArgument
                  (   Options.Applicative.help "Read expression from files instead of standard input"
                  <>  Options.Applicative.metavar "FILES"
                  <>  Options.Applicative.action "file"
                  )
  
      parseOutput = fmap f (optional p)
        where
          f Nothing = StandardOutput
          f (Just file) = OutputFile file
  
          p = Options.Applicative.strOption
                  (   Options.Applicative.long "output"
                  <>  Options.Applicative.help "Write result to a file instead of standard output"
                  <>  Options.Applicative.metavar "FILE"
                  <>  Options.Applicative.action "file"
                  )
  
      parseAlpha =
          Options.Applicative.switch
              (   Options.Applicative.long "alpha"
              <>  Options.Applicative.help "α-normalize expression"
              )
  
      parseAnnotate =
          Options.Applicative.switch
              (   Options.Applicative.long "annotate"
              <>  Options.Applicative.help "Add a type annotation to the output"
              )
  
      parseSemanticCacheMode =
          Options.Applicative.flag
              UseSemanticCache
              IgnoreSemanticCache
              (   Options.Applicative.long "no-cache"
              <>  Options.Applicative.help
                    "Handle protected imports as if the cache was empty"
              )
  
      parseVersion =
          Options.Applicative.switch
              (   Options.Applicative.long "version"
              <>  Options.Applicative.help "Display version"
              )
  
      parseResolveMode =
            Options.Applicative.flag' (Just Dot)
                (   Options.Applicative.long "dot"
                <>  Options.Applicative.help
                      "Output import dependency graph in dot format"
                )
          <|>
            Options.Applicative.flag' (Just ListImmediateDependencies)
                (   Options.Applicative.long "immediate-dependencies"
                <>  Options.Applicative.help
                      "List immediate import dependencies"
                )
          <|>
            Options.Applicative.flag' (Just ListTransitiveDependencies)
                (   Options.Applicative.long "transitive-dependencies"
                <>  Options.Applicative.help
                      "List transitive import dependencies in post-order"
                )
          <|> pure Nothing
  
      parseQuiet =
          Options.Applicative.switch
              (   Options.Applicative.long "quiet"
              <>  Options.Applicative.help "Don't print the result"
              )
  
      parseInplace =
          Options.Applicative.strOption
              (   Options.Applicative.long "inplace"
              <>  Options.Applicative.help "Modify the specified file in-place"
              <>  Options.Applicative.metavar "FILE"
              <>  Options.Applicative.action "file"
              )
  
      parseTransitiveSwitch = Options.Applicative.flag NonTransitive Transitive
          (   Options.Applicative.long "transitive"
          <>  Options.Applicative.help "Modify the input and its transitive relative imports in-place"
          )
  
      parseInplaceNonTransitive =
              fmap InputFile parseInplace
          <|> pure StandardInput
  
      parseInput = fmap f (optional p)
        where
          f  Nothing    = StandardInput
          f (Just path) = InputFile path
  
          p = Options.Applicative.strOption
              (   Options.Applicative.long "path"
              <>  Options.Applicative.help "Index all files in path recursively. Will get list of files from STDIN if omitted."
              <>  Options.Applicative.metavar "PATH"
              <>  Options.Applicative.action "file"
              <>  Options.Applicative.action "directory"
              )
  
      parseTagsOutput = fmap f (optional p)
        where
          f  Nothing    = OutputFile "tags"
          f (Just file) = OutputFile file
  
          p = Options.Applicative.strOption
              (   Options.Applicative.long "output"
              <>  Options.Applicative.help "The name of the file that the tags are written to. Defaults to \"tags\""
              <>  Options.Applicative.metavar "FILENAME"
              <>  Options.Applicative.action "file"
              )
  
      parseSuffixes = fmap f (optional p)
        where
          f  Nothing    = Just [".dhall"]
          f (Just "")   = Nothing
          f (Just line) = Just (Data.Text.splitOn " " line)
  
          p = Options.Applicative.strOption
              (   Options.Applicative.long "suffixes"
              <>  Options.Applicative.help "Index only files with suffixes. \"\" to index all files."
              <>  Options.Applicative.metavar "SUFFIXES"
              )
  
      parseFollowSymlinks =
          Options.Applicative.switch
          (   Options.Applicative.long "follow-symlinks"
          <>  Options.Applicative.help "Follow symlinks when recursing directories"
          )
  
      parseJSONFlag =
          Options.Applicative.switch
          (   Options.Applicative.long "json"
          <>  Options.Applicative.help "Use JSON representation of CBOR"
          )
  
      parseAllFlag =
          Options.Applicative.switch
          (   Options.Applicative.long "all"
          <>  Options.Applicative.help "Add integrity checks to all imports (not just remote imports) except for missing imports"
          )
  
      parseCacheFlag =
          Options.Applicative.switch
          (   Options.Applicative.long "cache"
          <>  Options.Applicative.help "Add fallback unprotected imports when using integrity checks purely for caching purposes"
          )
  
      parseCheck processed = fmap adapt switch
        where
          adapt True  = Check
          adapt False = Write
  
          switch =
              Options.Applicative.switch
              (   Options.Applicative.long "check"
              <>  Options.Applicative.help ("Only check if the input is " <> processed)
              )
  
      parseSchemasRecord =
          Options.Applicative.strOption
              (   Options.Applicative.long "schemas"
              <>  Options.Applicative.help "A record of schemas"
              <>  Options.Applicative.metavar "EXPR"
              )
  
      parseDirectoryTreeAllowSeparators =
          Options.Applicative.switch
              (   Options.Applicative.long "allow-path-separators"
              <>  Options.Applicative.help "Whether to allow path separators in file names"
              )
  
      parseDirectoryTreeOutput =
          Options.Applicative.strOption
              (   Options.Applicative.long "output"
              <>  Options.Applicative.help "The destination path to create"
              <>  Options.Applicative.metavar "PATH"
              <>  Options.Applicative.action "directory"
              )
  
      parseNoted =
          Options.Applicative.switch
              (   Options.Applicative.long "noted"
              <>  Options.Applicative.help "Print `Note` constructors"
              )
  
      parseCache =
          Options.Applicative.switch
              (   Options.Applicative.long "cache"
              <>  Options.Applicative.help "Cache the hashed expression"
              )
  
      parsePackageOptions :: Parser (Endo Dhall.Package.Options)
      parsePackageOptions = do
          packageMode <- (optional . Options.Applicative.flag' RecursiveSubpackages)
              ( Options.Applicative.short 'r'
              <> Options.Applicative.long "recursive"
              <> Options.Applicative.help "Create packages for all subdirectories first."
              )
  
          packageFileName <- (optional . Options.Applicative.strOption)
                  (   Options.Applicative.long "name"
                  <>  Options.Applicative.help "The filename of the package"
                  <>  Options.Applicative.metavar "NAME"
                  <>  Options.Applicative.action "file"
                  )
  
          pure $
              maybe mempty (Endo . set Dhall.Package.packagingMode) packageMode <>
              maybe mempty (Endo . set Dhall.Package.packageFileName) packageFileName
  
      parsePackageFiles = (:|) <$> p <*> Options.Applicative.many p
        where
          p = Options.Applicative.strArgument
                  (   Options.Applicative.help "Paths that may either point to files or directories. If the latter is the case all *.dhall files in the directory will be included."
                  <>  Options.Applicative.metavar "PATH"
                  <>  Options.Applicative.action "file"
                  )
  
  -- | `ParserInfo` for the `Options` type
  parserInfoOptions :: ParserInfo Options
  parserInfoOptions =
      Options.Applicative.info
          (Options.Applicative.helper <*> parseOptions)
          (   Options.Applicative.progDesc "Interpreter for the Dhall language"
          <>  Options.Applicative.fullDesc
          )
  
  noHeaders :: Import -> Import
  noHeaders
      (Import { importHashed = ImportHashed { importType = Remote URL{ .. }, ..}, .. }) =
      Import { importHashed = ImportHashed { importType = Remote URL{ headers = Nothing, .. }, .. }, .. }
  noHeaders i =
      i
  
  -- | Run the command specified by the `Options` type
  command :: Options -> IO ()
  command (Options {..}) = do
      GHC.IO.Encoding.setLocaleEncoding System.IO.utf8
  
      let rootDirectory = \case
              InputFile f   -> System.FilePath.takeDirectory f
              StandardInput -> "."
  
      let toStatus = Dhall.Import.emptyStatus . rootDirectory
  
      let getExpression = Dhall.Util.getExpression censor
  
      -- The characterSet detection used here only works on the source
      -- expression, before any transformation is applied. This helper is there
      -- make sure the detection is done on the correct expr.
      let getExpressionAndCharacterSet file = do
              expr <- getExpression file
  
              let characterSet = fromMaybe (detectCharacterSet expr) chosenCharacterSet
  
              return (expr, characterSet)
  
      let handle io =
              Control.Exception.catches io
                  [ Handler handleTypeError
                  , Handler handleImported
                  , Handler handleExitCode
                  ]
            where
              handleAll e = do
                  let string = show (e :: SomeException)
  
                  if not (null string)
                      then System.IO.hPutStrLn System.IO.stderr string
                      else return ()
  
                  System.Exit.exitFailure
  
              handleTypeError e = Control.Exception.handle handleAll $ do
                  let _ = e :: TypeError Src Void
                  System.IO.hPutStrLn System.IO.stderr ""
                  if explain
                      then
                          case censor of
                              Censor   -> Control.Exception.throwIO (CensoredDetailed (DetailedTypeError e))
                              NoCensor -> Control.Exception.throwIO (DetailedTypeError e)
  
                      else do
                          Data.Text.IO.hPutStrLn System.IO.stderr "\ESC[2mUse \"dhall --explain\" for detailed errors\ESC[0m"
                          case censor of
                              Censor   -> Control.Exception.throwIO (Censored e)
                              NoCensor -> Control.Exception.throwIO e
  
              handleImported (Imported ps e) = Control.Exception.handle handleAll $ do
                  let _ = e :: TypeError Src Void
                  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)
  
              handleExitCode e =
                  Control.Exception.throwIO (e :: ExitCode)
  
      let renderDoc :: Handle -> Doc Ann -> IO ()
          renderDoc h doc = do
              let stream = Dhall.Pretty.layout doc
  
              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 ""
  
      let render :: Pretty a => Handle -> CharacterSet -> Expr Src a -> IO ()
          render h characterSet expression = do
              let doc = Dhall.Pretty.prettyCharacterSet characterSet expression
  
              renderDoc h doc
  
      let writeDocToFile :: FilePath -> Doc ann -> IO ()
          writeDocToFile file doc = do
              let stream = Dhall.Pretty.layout (doc <> "\n")
  
              AtomicWrite.LazyText.atomicWriteFile file (Pretty.Text.renderLazy stream)
  
      handle $ case mode of
          Version ->
              putStrLn dhallVersionString
  
          Default {..} -> do
              if version
                  then do
                      putStrLn dhallVersionString
                      Exit.exitSuccess
                  else return ()
  
              (expression, characterSet) <- getExpressionAndCharacterSet file
  
              resolvedExpression <-
                  Dhall.Import.loadRelativeTo (rootDirectory file) semanticCacheMode expression
  
              inferredType <- Dhall.Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)
  
              let normalizedExpression = Dhall.Core.normalize resolvedExpression
  
              let alphaNormalizedExpression =
                      if alpha
                      then Dhall.Core.alphaNormalize normalizedExpression
                      else normalizedExpression
  
              let annotatedExpression =
                      if annotate
                          then Annot alphaNormalizedExpression inferredType
                          else alphaNormalizedExpression
  
              case output of
                  StandardOutput -> render System.IO.stdout characterSet annotatedExpression
  
                  OutputFile file_ ->
                      writeDocToFile
                          file_
                          (Dhall.Pretty.prettyCharacterSet characterSet annotatedExpression)
  
          Resolve { resolveMode = Just Dot, ..} -> do
              expression <- getExpression file
  
              (Dhall.Import.Types.Status { _graph, _stack }) <-
                  State.execStateT (Dhall.Import.loadWith expression) (toStatus file) { _semanticCacheMode = semanticCacheMode }
  
              let (rootImport :| _) = _stack
                  imports = rootImport : map parent _graph ++ map child _graph
                  importIds = Data.Map.fromList (zip imports [Text.Dot.userNodeId i | i <- [0..]])
  
              let dotNode (i, nodeId) =
                      Text.Dot.userNode
                          nodeId
                          [ ("label", Data.Text.unpack $ pretty (convert i))
                          , ("shape", "box")
                          , ("style", "rounded")
                          ]
                    where
                      convert = noHeaders . Dhall.Import.chainedImport
  
              let dotEdge (Depends parent child) =
                      case (Data.Map.lookup parent importIds, Data.Map.lookup child importIds) of
                          (Just from, Just to) -> from .->. to
                          _                    -> pure ()
  
              let dot = do Text.Dot.attribute ("rankdir", "LR")
                           mapM_ dotNode (Data.Map.assocs importIds)
                           mapM_ dotEdge _graph
  
              putStr . ("strict " <>) . Text.Dot.showDot $ dot
  
          Resolve { resolveMode = Just ListImmediateDependencies, ..} -> do
              expression <- getExpression file
  
              mapM_ (print . Pretty.pretty . noHeaders) expression
  
          Resolve { resolveMode = Just ListTransitiveDependencies, ..} -> do
              expression <- getExpression file
  
              (Dhall.Import.Types.Status { _cache }) <-
                  State.execStateT (Dhall.Import.loadWith expression) (toStatus file) { _semanticCacheMode = semanticCacheMode }
  
              mapM_ print
                   .   fmap ( Pretty.pretty
                            . noHeaders
                            . Dhall.Import.chainedImport
                            )
                   .   reverse
                   .   Dhall.Map.keys
                   $   _cache
  
          Resolve { resolveMode = Nothing, ..} -> do
              (expression, characterSet) <- getExpressionAndCharacterSet file
  
              resolvedExpression <-
                  Dhall.Import.loadRelativeTo (rootDirectory file) semanticCacheMode expression
  
              render System.IO.stdout characterSet resolvedExpression
  
          Normalize {..} -> do
              (expression, characterSet) <- getExpressionAndCharacterSet file
  
              resolvedExpression <- Dhall.Import.assertNoImports expression
  
              _ <- Dhall.Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)
  
              let normalizedExpression = Dhall.Core.normalize resolvedExpression
  
              let alphaNormalizedExpression =
                      if alpha
                      then Dhall.Core.alphaNormalize normalizedExpression
                      else normalizedExpression
  
              render System.IO.stdout characterSet alphaNormalizedExpression
  
          Type {..} -> do
              (expression, characterSet) <- getExpressionAndCharacterSet file
  
              resolvedExpression <-
                  Dhall.Import.loadRelativeTo (rootDirectory file) semanticCacheMode expression
  
              inferredType <- Dhall.Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)
  
              if quiet
                  then return ()
                  else render System.IO.stdout characterSet inferredType
  
          Repl ->
              Dhall.Repl.repl
                  (fromMaybe Unicode chosenCharacterSet) -- Default to Unicode if no characterSet specified
                  explain
  
          Diff {..} -> do
              expression1 <- Dhall.inputExpr expr1
  
              expression2 <- Dhall.inputExpr expr2
  
              let diff = Dhall.Diff.diffNormalized expression1 expression2
  
              renderDoc System.IO.stdout (Dhall.Diff.doc diff)
  
              if Dhall.Diff.same diff
                  then return ()
                  else Exit.exitFailure
  
          Format {..} -> do
              when deprecatedInPlace $
                  System.IO.hPutStrLn System.IO.stderr "Warning: the flag \"--inplace\" is deprecated"
  
              Dhall.Format.format Dhall.Format.Format{..}
  
          Freeze {..} -> do
              when deprecatedInPlace $
                  System.IO.hPutStrLn System.IO.stderr "Warning: the flag \"--inplace\" is deprecated"
  
              let scope = if all_ then AllImports else OnlyRemoteImports
  
              let intent = if cache then Cache else Secure
  
              Dhall.Freeze.freeze outputMode transitivity inputs scope intent chosenCharacterSet censor
  
          Hash {..} -> do
              expression <- getExpression file
  
              resolvedExpression <-
                  Dhall.Import.loadRelativeTo (rootDirectory file) UseSemanticCache expression
  
              _ <- Dhall.Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)
  
              let normalizedExpression =
                      Dhall.Core.alphaNormalize (Dhall.Core.normalize resolvedExpression)
  
              if cache
                  then Dhall.Import.writeExpressionToSemanticCache normalizedExpression
                  else return ()
  
              Data.Text.IO.putStrLn (Dhall.Import.hashExpressionToCode normalizedExpression)
  
          Lint { transitivity = transitivity0, ..} -> do
              when deprecatedInPlace $
                  System.IO.hPutStrLn System.IO.stderr "Warning: the flag \"--inplace\" is deprecated"
  
              handleMultipleChecksFailed "lint" "linted" go inputs
            where
              go input = do
                  let directory = case input of
                          StandardInput  -> "."
                          InputFile file -> System.FilePath.takeDirectory file
  
                  let status = Dhall.Import.emptyStatus directory
  
                  (inputName, originalText, transitivity) <- case input of
                      InputFile file -> do
                          text <- Data.Text.IO.readFile file
  
                          return (file, text, transitivity0)
                      StandardInput -> do
                          text <- Data.Text.IO.getContents
  
                          return ("(input)", text, NonTransitive)
  
                  (Header header, parsedExpression) <-
                      Dhall.Util.getExpressionAndHeaderFromStdinText censor inputName originalText
  
                  let characterSet = fromMaybe (detectCharacterSet parsedExpression) chosenCharacterSet
  
                  case transitivity of
                      Transitive ->
                          for_ parsedExpression $ \import_ -> do
                              maybeFilepath <- Dhall.Import.dependencyToFile status import_
  
                              for_ maybeFilepath $ \filepath ->
                                  go (InputFile filepath)
  
                      NonTransitive ->
                          return ()
  
                  let lintedExpression = Dhall.Lint.lint parsedExpression
  
                  let doc =   Pretty.pretty header
                          <>  Dhall.Pretty.prettyCharacterSet characterSet lintedExpression
  
                  let stream = Dhall.Pretty.layout doc
  
                  let modifiedText = Pretty.Text.renderStrict stream <> "\n"
  
                  case outputMode of
                      Write -> do
                          case input of
                              InputFile file ->
                                  if originalText == modifiedText
                                      then return ()
                                      else writeDocToFile file doc
  
                              StandardInput ->
                                  renderDoc System.IO.stdout doc
  
                          return (Right ())
  
                      Check ->
                          return $
                              if originalText == modifiedText
                                  then Right ()
                                  else Left CheckFailed{..}
  
          Encode {..} -> do
              expression <- getExpression file
  
              let bytes = Dhall.Binary.encodeExpression (Dhall.Core.denote expression)
  
              if json
                  then do
                      let decoder = Codec.CBOR.JSON.decodeValue False
  
                      (_, value) <- Dhall.Core.throws (Codec.CBOR.Read.deserialiseFromBytes decoder bytes)
  
                      let jsonBytes = Data.Aeson.Encode.Pretty.encodePretty value
  
                      Data.ByteString.Lazy.Char8.putStrLn jsonBytes
  
                  else
                      Data.ByteString.Lazy.putStr bytes
  
          Decode {..} -> do
              bytes <-
                  case file of
                      InputFile f   -> Data.ByteString.Lazy.readFile f
                      StandardInput -> Data.ByteString.Lazy.getContents
  
              expression <-
                  if json
                      then do
                          value <- case Data.Aeson.eitherDecode' bytes of
                              Left  string -> fail string
                              Right value  -> return value
  
                          let encoding = Codec.CBOR.JSON.encodeValue value
  
                          let cborgBytes = Codec.CBOR.Write.toLazyByteString encoding
  
                          Dhall.Core.throws (Dhall.Binary.decodeExpression cborgBytes)
                      else
                          Dhall.Core.throws (Dhall.Binary.decodeExpression bytes)
  
  
              if quiet
                  then return ()
                  else do
                      let doc =
                              Dhall.Pretty.prettyCharacterSet
                                  (fromMaybe Unicode chosenCharacterSet) -- default to Unicode
                                  (Dhall.Core.renote expression :: Expr Src Import)
  
                      renderDoc System.IO.stdout doc
  
          Text {..} -> do
              expression <- getExpression file
  
              resolvedExpression <-
                  Dhall.Import.loadRelativeTo (rootDirectory file) UseSemanticCache expression
  
              _ <- Dhall.Core.throws (Dhall.TypeCheck.typeOf (Annot resolvedExpression Dhall.Core.Text))
  
              let normalizedExpression = Dhall.Core.normalize resolvedExpression
  
              case normalizedExpression of
                  Dhall.Core.TextLit (Dhall.Core.Chunks [] text) ->
                      let write = case output of
                            StandardOutput -> Data.Text.IO.putStr
                            OutputFile file_ -> Data.Text.IO.writeFile file_
                      in write text
                  _ -> do
                      let invalidDecoderExpected :: Expr Void Void
                          invalidDecoderExpected = Dhall.Core.Text
  
                      let invalidDecoderExpression :: Expr Void Void
                          invalidDecoderExpression = normalizedExpression
  
                      Control.Exception.throwIO (Dhall.InvalidDecoder {..})
  
          Tags {..} -> do
              tags <- Dhall.Tags.generate input suffixes followSymlinks
  
              case output of
                  OutputFile file ->
                      System.IO.withFile file System.IO.WriteMode (`Data.Text.IO.hPutStr` tags)
  
                  StandardOutput -> Data.Text.IO.putStrLn tags
  
          DirectoryTree {..} -> do
              expression <- getExpression file
  
              resolvedExpression <-
                  Dhall.Import.loadRelativeTo (rootDirectory file) UseSemanticCache expression
  
              _ <- Dhall.Core.throws (Dhall.TypeCheck.typeOf resolvedExpression)
  
              let normalizedExpression = Dhall.Core.normalize resolvedExpression
  
              DirectoryTree.toDirectoryTree allowSeparators path normalizedExpression
  
          Dhall.Main.Schemas{..} ->
              Dhall.Schemas.schemasCommand Dhall.Schemas.Schemas{ input = file, ..}
  
          SyntaxTree {..} -> do
              expression <- getExpression file
  
              if noted then
                  Text.Pretty.Simple.pPrintNoColor expression
              else
                  let denoted :: Expr Void Import
                      denoted = Dhall.Core.denote expression
                  in Text.Pretty.Simple.pPrintNoColor denoted
  
          Package {..} -> do
              let options = appEndo
                      (maybe mempty (Endo . set Dhall.Package.characterSet) chosenCharacterSet
                      <> packageOptions
                      ) Dhall.Package.defaultOptions
              writePackage options packageFiles
  
  -- | Entry point for the @dhall@ executable
  main :: IO ()
  main = do
      options <- Options.Applicative.execParser parserInfoOptions
  
      Dhall.Main.command options
  