scrod-0.2026.2.21: source/library/Scrod/Executable/Main.hs
-- | CLI entry point. Reads Haskell source from stdin, parses it via the
-- GHC API, converts to Scrod's core representation, and renders as HTML
-- or JSON to stdout.
module Scrod.Executable.Main where
import qualified Control.Monad as Monad
import qualified Control.Monad.Catch as Exception
import qualified Control.Monad.Trans.Class as Trans
import qualified Control.Monad.Trans.Except as ExceptT
import qualified Data.Bifunctor as Bifunctor
import qualified Data.ByteString.Builder as Builder
import qualified Data.Foldable as Foldable
import qualified Data.Version as Version
import qualified GHC.Stack as Stack
import qualified PackageInfo_scrod as PackageInfo
import qualified Scrod.Convert.FromGhc as FromGhc
import qualified Scrod.Convert.ToHtml as ToHtml
import qualified Scrod.Convert.ToJsonSchema as ToJsonSchema
import qualified Scrod.Executable.Config as Config
import qualified Scrod.Executable.Flag as Flag
import qualified Scrod.Executable.Format as Format
import qualified Scrod.Extra.Builder as Builder
import qualified Scrod.Extra.Either as Either
import qualified Scrod.Extra.Semigroup as Semigroup
import qualified Scrod.Ghc.Parse as Parse
import qualified Scrod.Json.ToJson as ToJson
import qualified Scrod.Json.Value as Json
import qualified Scrod.Unlit as Unlit
import qualified Scrod.Xml.Document as Xml
import qualified System.Console.GetOpt as GetOpt
import qualified System.Environment as Environment
import qualified System.IO as IO
defaultMain :: (Stack.HasCallStack) => IO ()
defaultMain = do
name <- Environment.getProgName
arguments <- Environment.getArgs
result <- mainWith name arguments getContents
either putStr (Builder.hPutBuilder IO.stdout) result
mainWith ::
(Stack.HasCallStack, Exception.MonadThrow m) =>
String ->
[String] ->
m String ->
m (Either String Builder.Builder)
mainWith name arguments myGetContents = ExceptT.runExceptT $ do
flags <- Trans.lift $ Flag.fromArguments arguments
config <- Trans.lift $ Config.fromFlags flags
let version = Version.showVersion PackageInfo.version
Monad.when (Config.help config) $ do
let header =
unlines
[ unwords [name, "version", version],
Semigroup.around "<" ">" PackageInfo.homepage
]
ExceptT.throwE $ GetOpt.usageInfo header Flag.optDescrs
Monad.when (Config.version config) . ExceptT.throwE $ version <> "\n"
Monad.when (Config.schema config) . ExceptT.throwE $
Builder.toString (Json.encode ToJsonSchema.toJsonSchema) <> "\n"
contents <- Trans.lift myGetContents
source <-
if Config.literate config
then Either.throw . Bifunctor.first userError $ Unlit.unlit contents
else pure contents
let isSignature = Config.signature config
let ghcOpts = Foldable.toList (Config.ghcOptions config)
result <- Either.throw . Bifunctor.first userError $ Parse.parse isSignature ghcOpts source
module_ <- Either.throw . Bifunctor.first userError $ FromGhc.fromGhc isSignature result
let convert = case Config.format config of
Format.Json -> Json.encode . ToJson.toJson
Format.Html -> Xml.encodeHtml . ToHtml.toHtml
pure $ convert module_ <> Builder.charUtf8 '\n'