packages feed

fay-0.20.0.0: src/main/Main.hs

-- | Main compiler executable.

module Main where

import           Fay
import           Paths_fay                 (version)

import qualified Control.Exception         as E
import           Control.Monad
import           Data.List.Split           (wordsBy)
import           Data.Maybe
import           Data.Version              (showVersion)
import           Options.Applicative
import           Options.Applicative.Types
import           System.Environment

-- | Options and help.
data FayCompilerOptions = FayCompilerOptions
  { optLibrary       :: Bool
  , optFlattenApps   :: Bool
  , optHTMLWrapper   :: Bool
  , optHTMLJSLibs    :: [String]
  , optInclude       :: [String]
  , optPackages      :: [String]
  , optWall          :: Bool
  , optNoGHC         :: Bool
  , optStdout        :: Bool
  , optVersion       :: Bool
  , optOutput        :: Maybe String
  , optPretty        :: Bool
  , optOptimize      :: Bool
  , optGClosure      :: Bool
  , optPackageConf   :: Maybe String
  , optNoRTS         :: Bool
  , optNoStdlib      :: Bool
  , optPrintRuntime  :: Bool
  , optStdlibOnly    :: Bool
  , optBasePath      :: Maybe FilePath
  , optStrict        :: [String]
  , optTypecheckOnly :: Bool
  , optRuntimePath   :: Maybe FilePath
  , optSourceMap     :: Bool
  , optFiles         :: [String]
  }

-- | Main entry point.
main :: IO ()
main = do
  packageConf <- fmap (lookup "HASKELL_PACKAGE_SANDBOX") getEnvironment
  opts <- execParser parser
  let config = addConfigDirectoryIncludePaths ("." : optInclude opts) $
        addConfigPackages (optPackages opts) $ defaultConfig
          { configOptimize         = optOptimize opts
          , configFlattenApps      = optFlattenApps opts
          , configPrettyPrint      = optPretty opts
          , configLibrary          = optLibrary opts
          , configHtmlWrapper      = optHTMLWrapper opts
          , configHtmlJSLibs       = optHTMLJSLibs opts
          , configTypecheck        = not $ optNoGHC opts
          , configWall             = optWall opts
          , configGClosure         = optGClosure opts
          , configPackageConf      = optPackageConf opts <|> packageConf
          , configExportRuntime    = not (optNoRTS opts)
          , configExportStdlib     = not (optNoStdlib opts)
          , configExportStdlibOnly = optStdlibOnly opts
          , configBasePath         = optBasePath opts
          , configStrict           = optStrict opts
          , configTypecheckOnly    = optTypecheckOnly opts
          , configRuntimePath      = optRuntimePath opts
          , configSourceMap        = optSourceMap opts
          }
  if optVersion opts
    then runCommandVersion
    else if optPrintRuntime opts
      then getConfigRuntime config >>= readFile >>= putStr
      else do
        void $ incompatible htmlAndStdout opts "Html wrapping and stdout are incompatible"
        case optFiles opts of
          []    -> putStrLn $ helpTxt ++ "\n  More information: fay --help"
          files -> forM_ files $ \file ->
            compileFromTo config file (if optStdout opts then Nothing else Just (outPutFile opts file))

  where
    parser = info (helper <*> options) (fullDesc <> header helpTxt)

    outPutFile :: FayCompilerOptions -> String -> FilePath
    outPutFile opts file = fromMaybe (toJsName file) $ optOutput opts

-- | All Fay's command-line options.
options :: Parser FayCompilerOptions
options = FayCompilerOptions
  <$> switch (long "library" <> help "Don't automatically call main in generated JavaScript")
  <*> switch (long "flatten-apps" <> help "flatten function applicaton")
  <*> switch (long "html-wrapper" <> help "Create an html file that loads the javascript")
  <*> strsOption (long "html-js-lib" <> metavar "file1[, ..]"
      <> help "javascript files to add to <head> if using option html-wrapper")
  <*> strsOption (long "include" <> metavar "dir1[, ..]"
      <> help "additional directories for include")
  <*> strsOption (long "package" <> metavar "package[, ..]"
      <> help "packages to use for compilation")
  <*> switch (long "Wall" <> help "Typecheck with -Wall")
  <*> switch (long "no-ghc" <> help "Don't typecheck, specify when not working with files")
  <*> switch (long "stdout" <> short 's' <> help "Output to stdout")
  <*> switch (long "version" <> help "Output version number")
  <*> optional (strOption (long "output" <> short 'o' <> metavar "file" <> help "Output to specified file"))
  <*> switch (long "pretty" <> short 'p' <> help "Pretty print the output")
  <*> switch (long "optimize" <> short 'O' <> help "Apply optimizations to generated code")
  <*> switch (long "closure" <> help "Provide help with Google Closure")
  <*> optional (strOption (long "package-conf" <> help "Specify the Cabal package config file"))
  <*> switch (long "no-rts" <> short 'r' <> help "Don't export the RTS")
  <*> switch (long "no-stdlib" <> help "Don't generate code for the Prelude/FFI")
  <*> switch (long "print-runtime" <> help "Print the runtime JS source to stdout")
  <*> switch (long "stdlib" <> help "Only output the stdlib")
  <*> optional (strOption $ long "base-path" <> help "If fay can't find the sources of fay-base you can use this to provide the path. Use --base-path ~/example instead of --base-path=~/example to make sure ~ is expanded properly")
  <*> strsOption (long "strict" <> metavar "modulename[, ..]"
      <> help "Generate strict and uncurried exports for the supplied modules. Simplifies calling Fay from JS")
  <*> switch (long "typecheck-only" <> help "Only invoke GHC for typechecking, don't produce any output")
  <*> optional (strOption $ long "runtime-path" <> help "Custom path to the runtime so you don't have to reinstall fay when modifying it")
  <*> switch (long "sourcemap" <> help "Produce a source map in <outfile>.map")
  <*> many (argument Just (metavar "<hs-file>..."))
  where strsOption m = nullOption (m <> reader (ReadM . Right . wordsBy (== ',')) <> value [])

-- | Make incompatible options.
incompatible :: Monad m
  => (FayCompilerOptions -> Bool)
  -> FayCompilerOptions -> String -> m Bool
incompatible test opts message = if test opts
  then E.throw $ userError message
  else return True

-- | The basic help text.
helpTxt :: String
helpTxt = concat
  ["fay -- The fay compiler from (a proper subset of) Haskell to Javascript\n\n"
  ,"  fay <hs-file>... processes each .hs file"
  ]

-- | Print the command version.
runCommandVersion :: IO ()
runCommandVersion = putStrLn $ "fay " ++ showVersion version

-- | Incompatible options.
htmlAndStdout :: FayCompilerOptions -> Bool
htmlAndStdout opts = optHTMLWrapper opts && optStdout opts