packages feed

harmony-0.1.0.1: harmony-commandline/Main.hs

module Main where

import           Control.Monad              (forM_, unless)
import           Data.Maybe                 (fromJust)
import qualified Generation.OutputGenerator as OG
import           Language.ErrM
import           Language.Par
import           System.Console.GetOpt
import           System.Environment         (getArgs, getProgName)
import           System.Exit                (ExitCode (ExitFailure), exitWith)
import qualified TypeCheck.ApiSpec          as AS
import qualified TypeCheck.StaticCheck      as SC

-- | Flags of the executable
data Options = Options
  { generationFunctions :: [OG.GenerationFunction]
  , outputDir           :: FilePath
  }

defaultOptions :: Options
defaultOptions = Options
  { generationFunctions = []
  , outputDir = "./harmony_output"
  }

-- | Definition of the flags expected by the executable
options :: [OptDescr (Options -> Options)]
options = [ Option "c" ["client"]
                   (OptArg (\c options -> options { generationFunctions = parseClient c:generationFunctions options})
                           "CLIENTS")
                   "Desired output for the client: {-cpython}"
          , Option "s" ["server"]
                   (OptArg (\s options -> options { generationFunctions = parseServer s:generationFunctions options})
                           "SERVERS")
                   "Desired output for the server: {-sjs}"
          , Option "o" ["output_dir"]
                   (OptArg (\dir options -> options { outputDir = fromJust dir })
                           "OUTPUT_DIR")
                   "Output path"
          ]

-- | Parse flag input
parseClient, parseServer :: Maybe String -> OG.GenerationFunction
parseClient (Just "js") = OG.generateJSClient
parseClient (Just "python") = OG.generatePythonClient
parseClient (Just other) = error $ "Could not parse client: " ++ other
parseClient Nothing = error "parseClient: not expected Nothing as flag"
parseServer (Just "js") = OG.generateJSServer
parseServer (Just other) = error $ "Could not parse server" ++ other
parseServer Nothing = error "parseServer: not expected Nothing as flag"

-- | Entry point, parses arguments, then the specification file and generates all the desired
-- | outputs.
main :: IO ()
main = do
  (inputFile, outputPath, toGenerateList) <- parseArgs
  contents <- readFile inputFile
  case pSpecification (myLexer contents) of
    Bad err -> ioError (userError err)
    Ok tree -> case SC.staticCheck tree of
                 Bad err -> ioError (userError err)
                 Ok env -> generateOutput env toGenerateList outputPath
  return ()

-- | Parses the arguments and returns a list of targets to generate.
parseArgs :: IO (FilePath, FilePath, [OG.GenerationFunction])
parseArgs = do
  args <- getArgs
  programName <- getProgName

  case getOpt Permute options args of
    (flags, args, []) -> do
      unless (length args == 1)
             (printUsageAndExitWithError Nothing programName options)
      unless (not $ null flags)
             (printUsageAndExitWithError (Just "Select at least a client/server") programName options)
      return (head args, outputDir info, generationFunctions info)
        where
          info :: Options
          info = foldl (flip ($))  defaultOptions flags
    -- TODO: check if this should be another printUsageAndExitWithError call
    (_, _, errs) -> ioError (userError (concat errs ++ usage programName options))

-- | Generate all the output required.
generateOutput :: AS.ApiSpec -> [OG.GenerationFunction] -> FilePath -> IO ()
generateOutput apiSpec genFunctions outputPath = forM_ genFunctions (\f -> f outputPath apiSpec)

-- | Generates the usage message.
usage :: String -> [OptDescr (Options -> Options)] -> String
usage progName = usageInfo header
  where
    header = "Usage: " ++ progName ++ " [OPTION...] input_file"

-- | Prints usage and exits with error.
printUsageAndExitWithError :: Maybe String -> String -> [OptDescr (Options -> Options)] -> IO ()
printUsageAndExitWithError message progName options = do
  case message of
    Just str -> putStrLn $ "ERROR: " ++ str ++ "\n"
    Nothing -> return ()
  putStrLn $ usage progName options
  exitWith (ExitFailure 2)