packages feed

flowdock-api-0.1.0.0: src/Main.hs

module Main where

import           Control.Applicative
import           Control.Monad
import           Control.Monad.IO.Class
import           Data.Aeson
import qualified Data.ByteString.Lazy as LB
import           Data.List (isPrefixOf, intercalate)
import           Data.List.Split (splitOn)
import           Data.Text (Text, pack)
import           Options.Applicative
import           Options.Applicative.Arrows
import           System.Exit
import           System.FilePath
import           System.Directory
import           System.IO

import qualified Flowdock as Flowdock
import qualified Flowdock.Push as Push

-- -----------------------------------------------------------------------------
-- Option Handling

data Args = Args GlobalOptions Command
          deriving Show

data GlobalOptions = GlobalOptions
    { globalConfigFile  :: !(Maybe FilePath)
    } deriving Show

data Command
    = Chat ChatOptions
    | Inbox InboxOptions
    deriving Show

data ChatOptions = ChatOptions
    { chatUserName :: !String
    , chatTags     :: !(Maybe String)
    , chatMessage  :: !String
    } deriving Show

data InboxOptions = InboxOptions
    { messageSource   :: !String
    , messageFrom     :: !String
    , messageSubject  :: !String
    , messageBody     :: !String
    , messageFromName :: !(Maybe String)
    , messageReplyTo  :: !(Maybe String)
    , messageProject  :: !(Maybe String)
    , messageTags     :: !(Maybe String)
    , messageLink     :: !(Maybe String)
    } deriving Show

version :: Parser (a -> a)
version = infoOption Flowdock.version
  ( long "version"
  <> help "Print the version information." )

parser :: Parser Args
parser = runA $ proc () -> do
    opts <- asA globalOptions -< ()
    cmds <- (asA . hsubparser)
              ( command "chat"
                  (info chatParser
                        (progDesc "Sends a chat message to another user."))
              <> command "inbox"
                  (info inboxParser
                        (progDesc "Sends a message to a team inbox."))) -< ()
    A version >>> A helper -< Args opts cmds

globalOptions :: Parser GlobalOptions
globalOptions = GlobalOptions
            <$> optional (strOption
                ( long "config-file"
                <> short 'c'
                <> metavar "CONFIG"
                <> help "The config file to use."))

chatParser :: Parser Command
chatParser = runA $ proc () -> do
    config <- asA chatOptions -< ()
    returnA -< Chat config

chatOptions :: Parser ChatOptions
chatOptions = ChatOptions
            <$> strOption
                ( long "other-user"
                <> short 'u'
                <> metavar "USER_NAME"
                <> help "The other user to send the chat message to.")
            <*> optional (strOption
                ( long "tags"
                <> short 't'
                <> metavar "TAGS"
                <> help "Tags to attach to the message, separated by commas." ))
            <*> argument str (metavar "MESSAGE")

inboxParser :: Parser Command
inboxParser = runA $ proc () -> do
    config <- asA inboxOptions -< ()
    returnA -< Inbox config

inboxOptions :: Parser InboxOptions
inboxOptions = InboxOptions
            <$> strOption
              ( long "source"
              <> short 'p'
              <> metavar "PROGRAM"
              <> help "The source program using Flowdock." )
            <*> strOption
              ( long "from"
              <> short 'f'
              <> metavar "EMAIL"
              <> help "Email address of the message sender." )
            <*> strOption
              ( long "subject"
              <> short 's'
              <> metavar "SUBJECT"
              <> help "The subject of the message." )
            <*> argument str (metavar "CONTENT")
            <*> optional (strOption
              ( long "from-name"
              <> short 'n'
              <> metavar "NAME"
              <> help "The name of the message sender." ))
            <*> optional (strOption
              ( long "reply-to"
              <> short 'r'
              <> metavar "REPLY_TO"
              <> help "The email address for replies to this message." ))
            <*> optional (strOption
              ( long "project"
              <> short 'P'
              <> metavar "PROJECT"
              <> help "Project for message categorization." ))
            <*> optional (strOption
              ( long "tags"
              <> short 't'
              <> metavar "TAGS"
              <> help "A list of tags, separated by commas." ))
            <*> optional (strOption
              ( long "link"
              <> short 'l'
              <> metavar "LINK"
              <> help "The link associated with this message." ))

-- -----------------------------------------------------------------------------
-- JSON Configuration

data FlowdockConfig = FlowdockConfig
    { apiToken :: Text
    } deriving Show

instance FromJSON FlowdockConfig where
    parseJSON (Object o) = do
      token <- o .: "api-token"
      return $ FlowdockConfig token
    parseJSON _ = mzero

defaultPaths :: [FilePath]
defaultPaths = ["~/.flowdock.json", "/etc/flowdock.json"]

expandPath :: FilePath -> IO (FilePath)
expandPath path =
    if "~/" `isPrefixOf` path
      then do
        homeDir <- getHomeDirectory
        return $ joinPath [homeDir, tail $ tail path]
      else return path

lookupConfig :: FilePath -> IO (Either String FlowdockConfig)
lookupConfig path = do
    fullPath <- expandPath path
    exists   <- doesFileExist fullPath
    if exists
      then do
        contents <- LB.readFile fullPath
        return $ eitherDecode contents
      else do
        return $ Left $ "Unable to locate file: " ++ path

findConfig :: Maybe FilePath -> IO (Either String FlowdockConfig)
findConfig (Just cf) = lookupConfig cf
findConfig Nothing   = findConfig' defaultPaths
  where
    findConfig' :: [FilePath] -> IO (Either String FlowdockConfig)
    findConfig' (f:fs) = do
      config <- lookupConfig f
      case config of
        Left _   -> findConfig' fs
        Right fc -> return $ Right fc
    findConfig' [] = do
      return $ Left $ "Unable to find any configuration files at: " ++ (intercalate " " defaultPaths)

-- -----------------------------------------------------------------------------
-- Main

chat :: FlowdockConfig -> ChatOptions -> IO ()
chat (FlowdockConfig apiToken) (ChatOptions user tags message) = do
    let tags' = splitOn "," <$> tags
    let msg = Push.Chat (pack message)
                        (pack user)
                        ((map pack) <$> tags')
    Push.pushAPI (Push.PushApiToken apiToken) $ do
        response <- Push.push msg
        case response of
            Nothing  ->
              liftIO $ exitSuccess
            Just err -> do
              liftIO $ hPutStrLn stderr $ show err
              liftIO $ exitFailure

inbox :: FlowdockConfig -> InboxOptions -> IO ()
inbox (FlowdockConfig apiToken) (InboxOptions source from subject body name replyTo project tags link) = do
    let tags' = splitOn "," <$> tags
    let msg = Push.TeamInbox (pack source)
                             (pack from)
                             (pack subject)
                             (pack body)
                             (pack <$> name)
                             (pack <$> replyTo)
                             (pack <$> project)
                             ((map pack) <$> tags')
                             (pack <$> link)
    Push.pushAPI (Push.PushApiToken apiToken) $ do
        response <- Push.push msg
        case response of
          Nothing -> do
            liftIO $ exitSuccess
          Just err -> do
            liftIO $ hPutStrLn stderr $ show err
            liftIO $ exitFailure

run :: Args -> IO ()
run (Args (GlobalOptions configFile) cmd) = do
    config <- findConfig configFile
    case config of
      Left err -> do
        putStrLn err
        exitFailure
      Right flowdockConfig ->
        case cmd of
          Chat chatOpts   -> chat flowdockConfig chatOpts
          Inbox inboxOpts -> inbox flowdockConfig inboxOpts

main :: IO ()
main = execParser opts >>= run
  where
    opts = info parser
            ( fullDesc
            <> progDesc "Sends messages via the Flowdock API."
            <> header "flowdock - a way to send messages to Flowdock.")