{-# LANGUAGE OverloadedStrings #-}
module Text.AccentuateUs
( -- $doc
Lang
, Locale
, AUSResponse(..)
, LangsStatus(..)
, langs
, accentuate
, feedback
) where
import Control.Monad ( liftM )
import Data.Maybe ( fromMaybe )
import Data.Text.Encoding ( decodeUtf8, encodeUtf8 )
import Text.JSON ( JSON(..), JSValue(..), Result(Ok, Error), decode
, encode, toJSObject, valFromObj
)
import Network.HTTP ( Header(Header), HeaderName(..), Request(Request)
, RequestMethod(POST), getResponseBody, simpleHTTP
, catchIO
)
import Network.URI ( URI(URI), URIAuth(URIAuth) )
import qualified Data.ByteString.Char8 as C8
import qualified Data.Text as T
type Lang = C8.ByteString -- ^ An ISO-639 code.
type Locale = C8.ByteString -- ^ An ISO-639 code.
-- | Get langs and their localized names. E.g.,
--
-- > getEnglishName langs = fromMaybe "Not Found" $ "en" `lookup` langs
-- >
-- > TIO.putStrLn =<< liftM (either decodeUtf8 (getEnglishName . languages))
-- > (langs (Just "ga") 0)
--
-- The above example will get the localized name for English (ISO-639: en) for
-- localized into Irish (ISO-639: ga).
langs :: Maybe Locale -> Int -> IO (Either C8.ByteString AUSResponse)
langs l v = catchIO (liftM eitherDecode call) (\_ -> err)
where
call = post [PCall "langs", PLocale (mbString l), PVersion v]
err = return . Left $ "Network error. Unable to retrieve languages."
-- | For a given language, and optionally a locale, accentuates text. This
-- function is that which does the heavy lifting, restoring diacritics
-- (special characters) to otherwise plain text. E.g.,
--
-- > TIO.putStrLn =<< liftM (either decodeUtf8 text)
-- > (accentuate "vie" (Just "en") "My tu bo ke hoach la chan ten lua")
--
-- The above example accentuates the input text ("My tu...") in Vietnamese
-- with an English localization of error responses.
accentuate :: Lang -> Maybe Locale -> T.Text
-> IO (Either C8.ByteString AUSResponse)
accentuate la lo t = catchIO (liftM eitherDecode call) (\_ -> err)
where
call = post [PCall "lift", PLang la, PLocale (mbString lo), PText t]
err = return . Left $ C8.append
"Network error. Unable to accentuate text for language " la
-- | Submits corrected text as feedback to Accentuate.us. It is helpful for all
-- users if developers make good use of this function as it helps improve the
-- Accentuate.us language models by retraining them.
--
-- > feedback "ht" (Just "en")
-- > "Bon, la fè sa apre demen pito, lè la wè mwen andèy."
--
-- This example submits the *correct* input text (all diacritics in their
-- proper places) to the Accentuate.us servers to be queued for language model
-- retraining.
feedback :: Lang -> Maybe Locale -> T.Text
-> IO (Either C8.ByteString AUSResponse)
feedback la lo t = catchIO (liftM eitherDecode call) (\_ -> err)
where
call = post [PCall "feedback", PLang la, PLocale (mbString lo), PText t]
err = return . Left $ "Network error. Unable to submit feedback."
-- | Encapsulates various properties of an Accentuate.us API call
data Param
= PCall C8.ByteString
| PCode Integer
| PText T.Text
| PLang Lang
| PLocale Locale
| PVersion Int
deriving (Show)
-- | Represents responses for the three Accentuate.us calls.
data AUSResponse
= Langs { status :: LangsStatus
, version :: Int
, languages :: [(Lang, T.Text)] -- ^ [(ISO-639, Localized Language)]
}
| Lift { text :: T.Text }
| Feedback
deriving Show
-- | Represents languages response status
data LangsStatus = OutOfDate -- ^ Given version number < server's
| UpToDate -- ^ Given version number == server's
| OverDate -- ^ Given version number > server's
deriving (Show, Eq)
instance JSON AUSResponse where
readJSON (JSObject rsp) = do
call <- valFromObj "call" rsp
code <- valFromObj "code" rsp
case call of
"charlifter.langs" -> do
code' <- mbCode (codeToStatus code)
vers <- valFromObj "version" rsp
pairs <- pairs' code'
return Langs { status = code'
, version = read vers
, languages = pairs
}
where pairs' UpToDate = return []
pairs' _ = liftM (map splitPair . C8.lines) txt
txt = valFromObj "text" rsp
"charlifter.lift" ->
case code::Int of
200 -> liftM (Lift . decodeUtf8) (valFromObj "text" rsp)
400 -> fail'
_ -> failCode
"charlifter.feedback" ->
case code::Int of
100 -> return Feedback
400 -> fail'
_ -> failCode
c -> fail ("Unknown Accentuate.us call " ++ c)
where fail' = valFromObj "text" rsp >>= \e -> fail e
failCode = fail "Unknown Accentuate.us response code"
mbCode (Just c) = return c
mbCode Nothing = failCode
readJSON _ = undefined
showJSON = undefined
-- | Converts integer response code into data type LangsStatus
codeToStatus :: Int -> Maybe LangsStatus
codeToStatus c = case c of
100 -> Just OutOfDate
200 -> Just UpToDate
400 -> Just OverDate
_ -> Nothing
-- | Splits a string pair (separated by :) into a tuple, removing separator
splitPair :: C8.ByteString -> (C8.ByteString, T.Text)
splitPair s = removeSep $ C8.break (== ':') s
where removeSep (a, b) = (a, decodeUtf8 . C8.tail $ b)
-- | Sends response to server
post :: [Param] -> IO C8.ByteString
post ps = (simpleHTTP . prepRequest $ ps) >>= \r -> getResponseBody r
-- | Create request
prepRequest :: [Param] -> Request C8.ByteString
prepRequest params = Request (url lang) POST (headers body) body
where ps = toQuery params
body = C8.pack . encode . toJSObject $ ps
lang = mbString ("lang" `lookup` ps)
-- | Map parameters to call-appropriate tuples
toQuery :: [Param] -> [(String, C8.ByteString)]
toQuery = map toQuery' where
toQuery' p = case p of
PCall c -> ("call", "charlifter." `C8.append` c)
PCode c -> ("code", C8.pack . show $ c)
PText t -> ("text", encodeUtf8 t)
PLang l -> ("lang", l)
PLocale l -> ("locale", l)
PVersion v -> ("version", C8.pack . show $ v)
-- | Common response parsing
eitherDecode :: (JSON a) => C8.ByteString -> Either C8.ByteString a
eitherDecode = resultToEither' . decode . C8.unpack
where resultToEither' (Ok a) = Right a
resultToEither' (Error s) = Left . C8.pack $ s
-- | Conversion from optional parameter to (empty) string.
mbString :: Maybe C8.ByteString -> C8.ByteString
mbString = fromMaybe ""
-- | Generate appropriate headers
headers :: C8.ByteString -> [Header]
headers s =
[ Header HdrContentType "application/json; charset=utf-8"
, Header HdrUserAgent "Accentuate.us/0.9 haskell"
, Header HdrContentLength (show . length . C8.unpack $ s)
]
-- | Generate language-specific URL
url :: Lang -> URI
url lang = URI "http:" uriAuth "/" "" ""
where uriAuth = Just (URIAuth "" host ":8080")
base = "api.accentuate.us"
host = (if lang' /= "" then lang' ++ "." else lang') ++ base
lang' = C8.unpack lang
-- $doc
--
-- This package implements the Accentuate.us () API as it
-- is described at .
--
-- The documentation's examples assume the following conditions:
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import Text.AccentuateUs
-- > import Control.Monad (liftM)
-- > import Data.Either (either)
-- > import Data.Maybe (fromMaybe)
-- > import Data.Text.Encoding (decodeUtf8)
-- > import qualified Data.Text.IO as TIO