packages feed

hs-bindgen-1.0.0.0: app/HsBindgen/Cli/ToolSupport/Literate.hs

{-# LANGUAGE ApplicativeDo #-}

-- | @hs-bindgen-cli tool-support literate@ command
--
-- Intended for qualified import.
--
-- > import HsBindgen.Cli.ToolSupport.Literate qualified as Literate
module HsBindgen.Cli.ToolSupport.Literate (
    -- * CLI help
    info
    -- * Options (provided by @cabal-install@ on the command line)
  , Opts(..)
  , parseOpts
    -- * Execution
  , exec
  ) where

import Control.Exception (throwIO)
import Data.List.NonEmpty (NonEmpty (..))
import GHC.Exception (Exception (..))
import Options.Applicative hiding (info)
import Options.Applicative qualified as O
import Text.Read (readMaybe)

import HsBindgen
import HsBindgen.App
import HsBindgen.App.Output (OutputMode (..), OutputOptions,
                             SingleFileCategory (..), buildCategoryChoice,
                             parseOutputOptions)
import HsBindgen.ArtefactM
import HsBindgen.Config
import HsBindgen.Config.Internal (BindgenConfig)
import HsBindgen.Errors
import HsBindgen.IR.C qualified as C
import HsBindgen.Macro

{-------------------------------------------------------------------------------
  CLI help
-------------------------------------------------------------------------------}

info :: InfoMod a
info = progDesc $ mconcat [
      "Generate Haskell module from C header, acting as literate Haskell"
    , " preprocessor"
    ]

{-------------------------------------------------------------------------------
  Options (provided by @cabal-install@ on the command line)
-------------------------------------------------------------------------------}

-- | Command line options when the literate preprocessor is invoked
--
-- NOTE: Most of the /actual/ @hs-bindgen-cli@ arguments come from parsing the
-- /contents/ of the file we are processing.
data Opts = Opts {
      input  :: FilePath
    , output :: FilePath
    }
  deriving (Show, Eq)

parseOpts :: Parser Opts
parseOpts = do
    -- When @cabal-install@ calls GHC and the preprocessor, it passes some
    -- standard flags, which we do not (all) use.  In particular, it passes
    -- @-hide-all-packages@.
    _ <- strOption @String $ mconcat [
             short 'h'
           , metavar "IGNORED"
           , help "Ignore some preprocessor options provided by cabal-install"
           ]

    input  <- strArgument $ metavar "IN"
    output <- strArgument $ metavar "OUT"

    return Opts{
        input  = input
      , output = output
      }

{-------------------------------------------------------------------------------
  Options (provided at the top of literate Haskell files)
-------------------------------------------------------------------------------}

data Lit = Lit {
      globalOpts     :: GlobalOpts
    , config         :: Config
    , uniqueId       :: UniqueId
    , baseModuleName :: BaseModuleName
    , qualifiedStyle :: QualifiedStyle
    , outputOptions  :: OutputOptions
    , inputs         :: [C.UncheckedRootDirective]
    }

parseLit :: Parser Lit
parseLit = Lit
  <$> parseGlobalOpts
  <*> parseConfig
  <*> parseUniqueId
  <*> parseBaseModuleName
  <*> parseQualifiedStyle
  <*> parseOutputOptions (SingleFile (SingleFileSafe "" :| []))
  <*> parseInputs

{-------------------------------------------------------------------------------
  Execution
-------------------------------------------------------------------------------}

exec :: Opts -> IO ()
exec opts = do
    args <- maybe (throwIO' "cannot parse literate file") return . readMaybe
      =<< readFile opts.input
    lit <- handleParseResult $ pureParseLit args

    let bindgenConfig :: BindgenConfig
        bindgenConfig =
          toBindgenConfig
            lit.config
            lit.uniqueId
            lit.baseModuleName
            (buildCategoryChoice lit.outputOptions)

        -- It is understood that literate mode will overwrite existing files
        -- (generated files will anyway live in @dist-newstyle@ or similar)
        filePolicy :: FilePolicy
        filePolicy = AllowFileOverwrite

        mrc :: ModuleRenderConfig
        mrc = ModuleRenderConfig {
            qualifiedStyle = lit.qualifiedStyle
          }

        artefact :: Artefact CExpr ()
        artefact = writeBindings mrc filePolicy DoNotCreateOutputDirs opts.output

    hsBindgen
      lit.globalOpts.unsafe
      lit.globalOpts.safe
      bindgenConfig
      lit.inputs
      artefact
  where
    throwIO' :: String -> IO a
    throwIO' = throwIO . LiterateFileException opts.input

    pureParseLit :: [String] -> ParserResult Lit
    pureParseLit =
        execParserPure (prefs subparserInline) (O.info parseLit mempty)

{-------------------------------------------------------------------------------
  Exception
-------------------------------------------------------------------------------}

data LiterateFileException = LiterateFileException FilePath String
  deriving Show

instance Exception LiterateFileException where
    toException = hsBindgenExceptionToException
    fromException = hsBindgenExceptionFromException
    displayException (LiterateFileException path err) =
      "error loading " ++ path ++ ": " ++ err