packages feed

typed-wire-0.3.0.0: app/Main.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where

import TW.Ast
import TW.Check
import TW.Loader
import TW.Parser
import TW.Types
import qualified TW.CodeGen.Elm as Elm
import qualified TW.CodeGen.Haskell as HS
import qualified TW.CodeGen.PureScript as PS

import qualified Paths_typed_wire as Meta

import Development.GitRev
import Options.Applicative
import System.Directory
import System.FilePath
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Traversable as T
import qualified Data.Version as Vers

data Options
   = Options
   { o_showVersion :: Bool
   , o_sourceDirs :: [FilePath]
   , o_entryPoints :: [ModuleName]
   , o_hsOutDir :: Maybe FilePath
   , o_elmOutDir :: Maybe FilePath
   , o_psOutDir :: Maybe FilePath
   }

optParser :: Parser Options
optParser =
    Options
    <$> switch (long "version" <> help "Show version and exit")
    <*> sourceDirsP
    <*> entryPointsP
    <*> hsOutP
    <*> elmOutP
    <*> psOutP

sourceDirsP :: Parser [FilePath]
sourceDirsP =
    many $ strOption $
    long "include-dir" <> short 'i' <> metavar "DIR" <> help "Directory to search for modules"

entryPointsP :: Parser [ModuleName]
entryPointsP =
    many $ option (str >>= mkMod) $
    long "entrypoint" <> short 'e' <> metavar "MODULE-NAME" <> help "Entrypoint for compiler"
    where
      mkMod t =
          case makeModuleName (T.pack t) of
            Left _ -> fail $ "Can not parse " ++ t ++ " as module"
            Right x -> return x

hsOutP :: Parser (Maybe FilePath)
hsOutP =
    optional $ strOption $
    long "hs-out" <> metavar "DIR" <> help "Generate Haskell bindings to specified dir"

elmOutP :: Parser (Maybe FilePath)
elmOutP =
    optional $ strOption $
    long "elm-out" <> metavar "DIR" <> help "Generate Elm bindings to specified dir"

psOutP :: Parser (Maybe FilePath)
psOutP =
    optional $ strOption $
    long "purescript-out" <> metavar "DIR" <> help "Generate PureScript bindings to specified dir"

main :: IO ()
main =
    execParser opts >>= run
    where
      opts =
          info (helper <*> optParser)
          ( fullDesc
          <> progDesc "Language idependent type-safe communication"
          <> header "Generate bindings using typed-wire for different languages"
          )

showVersion :: IO ()
showVersion =
    T.putStrLn versionMessage
    where
      versionMessage =
          T.unlines
          [ "Version " <> T.pack (Vers.showVersion Meta.version)
          , "Git: " <> $(gitBranch) <> "@" <> $(gitHash)
          , " (" <> $(gitCommitCount) <> " commits in HEAD)"
          , if $(gitDirty) then " (includes uncommited changes)" else ""
          ]

run :: Options -> IO ()
run opts =
    if o_showVersion opts
    then showVersion
    else run' opts

run' :: Options -> IO ()
run' opts =
    do allModules <- loadModules (o_sourceDirs opts) (o_entryPoints opts)
       putStrLn "All modules loaded"
       case allModules of
         Left err -> fail err
         Right ok ->
             case checkModules ok of
               Left err -> fail err
               Right readyModules ->
                   do _ <- T.forM (o_hsOutDir opts) $ \dir ->
                          do T.putStrLn $
                                  "Required Haskell library is "
                                  <> li_name HS.libraryInfo <> "@" <> li_version HS.libraryInfo
                             mapM_ (runner dir HS.makeModule HS.makeFileName) readyModules
                      _ <- T.forM (o_elmOutDir opts) $ \dir ->
                          do T.putStrLn $
                                  "Required Elm library is "
                                  <> li_name Elm.libraryInfo <> " version " <> li_version Elm.libraryInfo
                             mapM_ (runner dir Elm.makeModule Elm.makeFileName) readyModules
                      _ <- T.forM (o_psOutDir opts) $ \dir ->
                          do T.putStrLn $
                                  "Required PureScript library is "
                                  <> li_name PS.libraryInfo <> " version " <> li_version PS.libraryInfo
                             mapM_ (runner dir PS.makeModule PS.makeFileName) readyModules
                      return ()

runner :: FilePath -> (Module -> T.Text) -> (ModuleName -> FilePath) -> Module -> IO ()
runner baseDir mkModule mkFilename m =
    let moduleSrc = mkModule m
        moduleFp = baseDir </> mkFilename (m_name m)
    in do createDirectoryIfMissing True (takeDirectory moduleFp)
          putStrLn $ "Writing " ++ moduleFp ++ " ..."
          T.writeFile moduleFp moduleSrc