packages feed

hpasteit-0.1.0.0: HPasteIt.hs

{-# LANGUAGE OverloadedStrings #-}
module Main (main) where

import Data.Char
import Data.Monoid
import System.Exit
import System.IO

import qualified Data.ByteString.Char8 as BS

import Control.Exception.Lifted
import Network.HTTP.Conduit
import Network.HTTP.Types
import Options.Applicative

data Options = Options
    { optTitle    :: String
    , optAuthor   :: String
    , optLanguage :: String
    , optChannel  :: String
    , optFile     :: FilePath
    }

optParser :: Parser Options
optParser = Options
        <$> nullOption (mconcat [ reader  Right
                                , value   "via HPasteIt"
                                , short   't'
                                , long    "title"
                                , metavar "TITLE"
                                , help    "The title of the paste; \
                                          \defaults to \"via HPasteIt\"."
                                ])

        <*> nullOption (mconcat [ reader  Right
                                , value   "Anonymous"
                                , short   'a'
                                , long    "author"
                                , metavar "AUTHOR"
                                , help    "The author of the paste; \
                                          \defaults to \"Anonymous\"."
                                ])

        <*> nullOption (mconcat [ reader  (readLanguage . map toLower)
                                , long    "language"
                                , short   'l'
                                , metavar "LANGUAGE"
                                , value   "haskell"
                                , help    "The language of the paste; \
                                          \defaults to Haskell."
                                ])

        <*> nullOption (mconcat [ reader  (readChannel . map toLower)
                                , long    "channel"
                                , short   'c'
                                , metavar "CHANNEL"
                                , value   ""
                                , help    "The IRC channel to notify."
                                ])

        <*> argument Just (mconcat [ metavar "FILE"
                                   , value   "-"
                                   , help    "The path of the file to paste; \
                                             \defaults to - for STDIN."
                                   ])
  where
    -- The string arguments of these functions are lower-case.

    readLanguage :: String -> Either ParseError String
    readLanguage lang
      | Just real_lang <- lookup lang suffixes = Right real_lang
      | lang `elem` langs = Right lang
      | otherwise         = Left (ErrorMsg err_msg)
      where
        suffixes = [ ("hs" , "haskell"        )
                   , ("lhs", "literatehaskell")
                   ]

        -- Not an exhaustive list.
        langs = ["haskell","agda","ocaml","lisp","erlang","literatehaskell"
                ]

        err_msg = "Invalid language. See hpaste.org for a full list of \
                  \supported languages."

    readChannel :: String -> Either ParseError String
    readChannel chan
        = if dropWhile (=='#') chan `elem` chans
            then Right chan
            else Left (ErrorMsg err_msg)
      where
        -- Not an exhaustive list.
        chans = ["haskell"]

        err_msg = "Invalid IRC channel. See hpaste.org for a full list of \
                  \valid IRC channels."


main :: IO ()
main = do
    options <- execParser $ info (helper <*> optParser) $
        mconcat [ fullDesc
                , header "hpasteit - command-line client for hpaste.org"
                ]

    paste <- case optFile options of
        "-"  -> BS.getContents
        file -> BS.readFile file

    res <- withManager $ \man -> try $ do
        rq <- parseUrl "http://hpaste.org/new"
        let rq' = urlEncodedBody
                [ ("title"   , BS.pack $ optTitle    options)
                , ("author"  , BS.pack $ optAuthor   options)
                , ("language", BS.pack $ optLanguage options)
                , ("channel" , BS.pack $ optChannel  options)
                , ("paste"   , paste                        )
                , ("email"   , ""                           )
                ] rq

        -- In order to grab the URL of the paste we don't follow redirects.
        httpLbs (rq' { redirectCount = 0 }) man

    case res of
        -- An exception is thrown if the server issues a redirect.
        Left (StatusCodeException (Status 302 _) hdrs)
          | Just loc <- lookup "Location" hdrs -> do

            putStrLn ("http://hpaste.org" ++ BS.unpack loc)
            exitSuccess

        Left exc -> do

            hPutStrLn stderr "Encountered unexpected HttpException:\n"
            hPutStrLn stderr (show exc)
            exitFailure

        Right resp -> do

            hPutStrLn stderr "Encountered unexpected response:\n"
            hPutStrLn stderr (show resp)
            exitFailure