packages feed

webapp-0.1.0: Web/App.hs

{-|
Module      : Web.App
Copyright   : (c) Nathaniel Symer, 2015
License     : MIT
Maintainer  : nate@symer.io
Stability   : experimental
Portability : POSIX

Root modules of webapp.
-}

module Web.App
(
  -- * Functions
  webappMain,
  -- * Exported Modules
  module Web.App.Assets,
  module Web.App.Cookie,
  module Web.App.Daemon,
  module Web.App.FileCache,
  module Web.App.HTTP,
  module Web.App.Password,
  module Web.App.IO,
  module Web.App.Monad
)
where

import Web.App.Assets
import Web.App.Cookie
import Web.App.Daemon
import Web.App.FileCache
import Web.App.HTTP
import Web.App.Password
import Web.App.IO
import Web.App.Monad
import Web.App.TerminalSize

import Control.Applicative
import Options.Applicative
import System.Environment (getArgs)
import Web.Scotty.Trans (ScottyT,ScottyError)
import System.IO

data Cmd
  = StartCommand {
     startCmdDaemonize :: Bool,
    _startCmdInsecure :: Bool,
    _startCmdPort :: Int,
    _startCmdHTTPSSLCert :: FilePath,
    _startCmdHTTPSSLKey :: FilePath,
    _startCmdOutputPath :: Maybe FilePath,
    _startCmdErrorPath :: Maybe FilePath,
    _startCmdPidPath :: FilePath
  }
  | StopCommand {
    _stopCmdPidPath :: FilePath
  }
  | StatusCommand {
    _statusCmdPidPath :: FilePath
  }
  | PasswordCommand {
    _passwordCmdPassword :: String
  }

-- | Read commandline arguments and start app accordingly. When passing an
-- additional CLI parser, it is made available under the @util@ subcommand.
webappMain :: (ScottyError e, WebAppState s) => ScottyT e (WebAppM s) () -- ^ app to start
                                             -> String -- ^ CLI title/description
                                             -> Maybe (Parser a) -- ^ extra CLI parser (available under @util@ subcommand)
                                             -> (a -> IO ()) -- ^ action to apply to parse result of 'utilParser'
                                             -> IO ()
webappMain app title utilParser utilf = getArgs >>= getCommandArgs utilParser title >>= processArgs
  where
    processArgs (Right cmd) = f cmd
    processArgs (Left utils) = utilf utils
    f c@(StartCommand True _ _ _ _ _ _ pidPath) = do
      daemonize pidPath $ f $ c { startCmdDaemonize = False }
    f (StartCommand False False port crt key out err _) = do
      redirectStdout out
      redirectStderr err
      startHTTPS app port crt key
    f (StartCommand False True port _ _ out err _) = do
      redirectStdout out
      redirectStderr err
      startHTTP app port
    f (StopCommand pidPath) = daemonKill 4 pidPath
    f (StatusCommand pidPath) = daemonRunning pidPath >>= putStrLn . showStatus
    f (PasswordCommand pwd) = hashPassword pwd >>= g
      where
        g (Just v) = putStrLn v
        g Nothing = hPutStrLn stderr "failed to hash password"
    showStatus True = "running"
    showStatus False = "stopped"

getCommandArgs :: Maybe (Parser a) -> String -> [String] -> IO (Either a Cmd)
getCommandArgs utilParser title args = do
  w <- maybe 80 snd <$> getTermSize
  handleParseResult $ execParserPure (pprefs w) parser args
  where
    pprefs = ParserPrefs "" False False True
    parser = info (helper <*> ((sp utilParser) <|> parseStart)) (fullDesc <> header title)
    sp Nothing = subparser subCommands
    sp (Just util) = subparser $ subCommands <> (mkcmd "util" "Utilities associated with the application" (Left <$> util))
    subCommands = (mkcmd "start" "Start the application server" parseStart) <>
                  (mkcmd "stop" "Stop the application server" parseStop) <>
                  (mkcmd "status" "Determine if the application server is running" parseStatus)
    parseStart = fmap Right $ StartCommand
      <$> (flag False True $ short 'd' <> long "daemonize" <> help "run the application server daemonized")
      <*> (flag False True $ short 'i' <> long "insecure" <> help "run the application server over insecure HTTP")
      <*> (option auto $ opt "port" 'p' "PORT" (Just 3000) "port to run the application server on")
      <*> (strOption $ opt "https-crt" 'c' "FILEPATH" (Just "server.crt") "SSL certificate file")
      <*> (strOption $ opt "https-key" 'k' "FILEPATH" (Just "server.key") "SSL private key file")
      <*> (optional $ strOption $ opt "stdout" 'o' "FILEPATH" Nothing "redirect standard output to FILEPATH")
      <*> (optional $ strOption $ opt "stderr" 'e' "FILEPATH" Nothing "redirect standard error to FILEPATH")
      <*> (strOption $ opt "pid-file" 'z' "FILEPATH" (Just "/tmp/webapp.pid") "when daemonizing, write the PID to FILEPATH")
    parseStop     = Right <$> StopCommand <$> (strOption $ opt "pid-file" 'z' "FILEPATH" (Just "/tmp/webapp.pid") "pid file")
    parseStatus   = Right <$> StatusCommand <$> (strOption $ opt "pid-file" 'z' "FILEPATH" (Just "/tmp/webapp.pid") "pid file")
    opt lng shrt mvar (Just defVal) hlp = (long lng <> short shrt <> metavar mvar <> value defVal <> help hlp)
    opt lng shrt mvar Nothing       hlp = (long lng <> short shrt <> metavar mvar <> help hlp)
    mkcmd cmd desc p = command cmd $ info (helper <*> p) $ progDesc desc