packages feed

pinboard 0.4 → 0.5

raw patch · 8 files changed

+313/−310 lines, 8 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Pinboard.Client.Internal: addErrMsg :: Text -> PinboardError -> PinboardError
- Pinboard.Client.Internal: checkStatusCode :: StatusCode -> Either PinboardError ()
- Pinboard.Client.Internal: connClose :: Either a Connection -> IO ()
- Pinboard.Client.Internal: connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)
- Pinboard.Client.Internal: connOpen :: IO (Either SomeException Connection)
- Pinboard.Client.Internal: connOpenRaw :: IO Connection
- Pinboard.Client.Internal: createParserErr :: Text -> PinboardError
- Pinboard.Client.Internal: httpStatusPinboardError :: PinboardErrorHTTPCode -> Either PinboardError a
- Pinboard.Client.Internal: parseJSONFromStream :: FromJSON a => InputStream ByteString -> IO (Either PinboardError a)
- Pinboard.Client.Internal: parseJSONResponseStream :: FromJSON a => Response -> InputStream ByteString -> IO (Response, Either PinboardError a)
- Pinboard.Client.Internal: pinboardJson :: FromJSON a => PinboardRequest -> Pinboard a
- Pinboard.Client.Internal: runPinboardJson :: FromJSON a => PinboardConfig -> Pinboard a -> IO (Either PinboardError a)
- Pinboard.Client.Internal: runPinboardSingleJson :: FromJSON a => PinboardConfig -> PinboardRequest -> IO (Either PinboardError a)
- Pinboard.Client.Internal: runPinboardSingleRaw :: PinboardConfig -> PinboardRequest -> (Response -> InputStream ByteString -> IO a) -> IO (Either PinboardError a)
- Pinboard.Client.Internal: runPinboardSingleRawBS :: PinboardConfig -> PinboardRequest -> IO (Either PinboardError ByteString)
- Pinboard.Client.Internal: sendPinboardRequest :: PinboardRequest -> PinboardConfig -> Connection -> (Response -> InputStream ByteString -> IO a) -> IO a
- Pinboard.Client.Internal: sendPinboardRequestBS :: PinboardRequest -> PinboardConfig -> Connection -> IO (Response, ByteString)
+ Pinboard.Client: addErrMsg :: Text -> PinboardError -> PinboardError
+ Pinboard.Client: checkStatusCode :: StatusCode -> Either PinboardError ()
+ Pinboard.Client: connClose :: Either a Connection -> IO ()
+ Pinboard.Client: connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)
+ Pinboard.Client: connOpen :: IO (Either SomeException Connection)
+ Pinboard.Client: connOpenRaw :: IO Connection
+ Pinboard.Client: createParserErr :: Text -> PinboardError
+ Pinboard.Client: httpStatusPinboardError :: PinboardErrorHTTPCode -> Either PinboardError a
+ Pinboard.Client: parseJSONFromStream :: FromJSON a => InputStream ByteString -> IO (Either PinboardError a)
+ Pinboard.Client: parseJSONResponseStream :: FromJSON a => Response -> InputStream ByteString -> IO (Response, Either PinboardError a)
+ Pinboard.Client: pinboardJson :: FromJSON a => PinboardRequest -> Pinboard a
+ Pinboard.Client: runPinboardSingleJson :: FromJSON a => PinboardConfig -> PinboardRequest -> IO (Either PinboardError a)
+ Pinboard.Client: runPinboardSingleRaw :: PinboardConfig -> PinboardRequest -> (Response -> InputStream ByteString -> IO a) -> IO (Either PinboardError a)
+ Pinboard.Client: runPinboardSingleRawBS :: PinboardConfig -> PinboardRequest -> IO (Either PinboardError ByteString)
+ Pinboard.Client: sendPinboardRequest :: PinboardRequest -> PinboardConfig -> Connection -> (Response -> InputStream ByteString -> IO a) -> IO a
+ Pinboard.Client: sendPinboardRequestBS :: PinboardRequest -> PinboardConfig -> Connection -> IO (Response, ByteString)
+ Pinboard.Client.Types: FormatJson :: ResultFormatType
+ Pinboard.Client.Types: FormatXml :: ResultFormatType
+ Pinboard.Client.Types: data ResultFormatType
+ Pinboard.Client.Types: instance Eq ResultFormatType
+ Pinboard.Client.Types: instance Show ResultFormatType
+ Pinboard.Client.Util: ensureResultFormatType :: ResultFormatType -> PinboardRequest -> PinboardRequest
- Pinboard.ApiRequest: addPostRequest :: Url -> Description -> Maybe Extended -> Maybe [Tag] -> Maybe DateTime -> Maybe Replace -> Maybe Shared -> Maybe ToRead -> PinboardRequest
+ Pinboard.ApiRequest: addPostRequest :: ResultFormatType -> Url -> Description -> Maybe Extended -> Maybe [Tag] -> Maybe DateTime -> Maybe Replace -> Maybe Shared -> Maybe ToRead -> PinboardRequest
- Pinboard.ApiRequest: deletePostRequest :: Url -> PinboardRequest
+ Pinboard.ApiRequest: deletePostRequest :: ResultFormatType -> Url -> PinboardRequest
- Pinboard.ApiRequest: deleteTagRequest :: Tag -> PinboardRequest
+ Pinboard.ApiRequest: deleteTagRequest :: ResultFormatType -> Tag -> PinboardRequest
- Pinboard.ApiRequest: getNoteListRequest :: PinboardRequest
+ Pinboard.ApiRequest: getNoteListRequest :: ResultFormatType -> PinboardRequest
- Pinboard.ApiRequest: getNoteRequest :: NoteId -> PinboardRequest
+ Pinboard.ApiRequest: getNoteRequest :: ResultFormatType -> NoteId -> PinboardRequest
- Pinboard.ApiRequest: getPostsAllRequest :: Maybe [Tag] -> Maybe StartOffset -> Maybe NumResults -> Maybe FromDateTime -> Maybe ToDateTime -> Maybe Meta -> PinboardRequest
+ Pinboard.ApiRequest: getPostsAllRequest :: ResultFormatType -> Maybe [Tag] -> Maybe StartOffset -> Maybe NumResults -> Maybe FromDateTime -> Maybe ToDateTime -> Maybe Meta -> PinboardRequest
- Pinboard.ApiRequest: getPostsDatesRequest :: Maybe [Tag] -> PinboardRequest
+ Pinboard.ApiRequest: getPostsDatesRequest :: ResultFormatType -> Maybe [Tag] -> PinboardRequest
- Pinboard.ApiRequest: getPostsForDateRequest :: Maybe [Tag] -> Maybe Date -> Maybe Url -> PinboardRequest
+ Pinboard.ApiRequest: getPostsForDateRequest :: ResultFormatType -> Maybe [Tag] -> Maybe Date -> Maybe Url -> PinboardRequest
- Pinboard.ApiRequest: getPostsMRUTimeRequest :: PinboardRequest
+ Pinboard.ApiRequest: getPostsMRUTimeRequest :: ResultFormatType -> PinboardRequest
- Pinboard.ApiRequest: getPostsRecentRequest :: Maybe [Tag] -> Maybe Count -> PinboardRequest
+ Pinboard.ApiRequest: getPostsRecentRequest :: ResultFormatType -> Maybe [Tag] -> Maybe Count -> PinboardRequest
- Pinboard.ApiRequest: getSuggestedTagsRequest :: Url -> PinboardRequest
+ Pinboard.ApiRequest: getSuggestedTagsRequest :: ResultFormatType -> Url -> PinboardRequest
- Pinboard.ApiRequest: getTagsRequest :: PinboardRequest
+ Pinboard.ApiRequest: getTagsRequest :: ResultFormatType -> PinboardRequest
- Pinboard.ApiRequest: getUserApiTokenRequest :: PinboardRequest
+ Pinboard.ApiRequest: getUserApiTokenRequest :: ResultFormatType -> PinboardRequest
- Pinboard.ApiRequest: getUserSecretRssKeyRequest :: PinboardRequest
+ Pinboard.ApiRequest: getUserSecretRssKeyRequest :: ResultFormatType -> PinboardRequest
- Pinboard.ApiRequest: renameTagRequest :: Old -> New -> PinboardRequest
+ Pinboard.ApiRequest: renameTagRequest :: ResultFormatType -> Old -> New -> PinboardRequest
- Pinboard.Client.Types: Format :: Text -> Param
+ Pinboard.Client.Types: Format :: ResultFormatType -> Param

Files

pinboard.cabal view
@@ -1,5 +1,5 @@ name:                pinboard-version:             0.4+version:             0.5 synopsis:            Access to the Pinboard API license:             MIT license-file:        LICENSE@@ -58,7 +58,6 @@                        Pinboard.ApiRequest                        Pinboard.ApiTypes                         Pinboard.Client-                       Pinboard.Client.Internal                        Pinboard.Client.Error                        Pinboard.Client.Types                        Pinboard.Client.Util
src/Pinboard/Api.hs view
@@ -33,11 +33,12 @@       getNote,     ) where -import Pinboard.Client.Internal (pinboardJson)+import Pinboard.Client          (pinboardJson) import Pinboard.Client.Types    (Pinboard) import Control.Applicative      ((<$>)) import Data.Text                (Text) import Data.Time                (UTCTime)+import Pinboard.Client.Types    (ResultFormatType (..)) import Pinboard.ApiTypes         import Pinboard.ApiRequest                                             @@ -48,7 +49,7 @@   :: Maybe [Tag] -- ^ filter by up to three tags   -> Maybe Count -- ^ number of results to return. Default is 15, max is 100     -> Pinboard Posts-getPostsRecent tags count = pinboardJson $ getPostsRecentRequest tags count+getPostsRecent tags count = pinboardJson $ getPostsRecentRequest FormatJson tags count  -- | posts/all : Returns all bookmarks in the user's account. getPostsAll@@ -59,7 +60,7 @@   -> Maybe ToDateTime -- ^ return only bookmarks created before this time   -> Maybe Meta -- ^ include a change detection signature for each bookmark   -> Pinboard [Post]-getPostsAll tags start results fromdt todt meta = pinboardJson $ getPostsAllRequest tags start results fromdt todt meta +getPostsAll tags start results fromdt todt meta = pinboardJson $ getPostsAllRequest FormatJson tags start results fromdt todt meta   -- | posts/get : Returns one or more posts on a single day matching the arguments.  -- If no date or url is given, date of most recent bookmark will be used.@@ -68,18 +69,18 @@   -> Maybe Date -- ^ return results bookmarked on this day   -> Maybe Url -- ^ return bookmark for this URL   -> Pinboard Posts-getPostsForDate tags date url = pinboardJson $ getPostsForDateRequest tags date url+getPostsForDate tags date url = pinboardJson $ getPostsForDateRequest FormatJson tags date url  -- | posts/dates : Returns a list of dates with the number of posts at each date. getPostsDates   :: Maybe [Tag] -- ^ filter by up to three tags   -> Pinboard PostDates-getPostsDates tags = pinboardJson $ getPostsDatesRequest tags+getPostsDates tags = pinboardJson $ getPostsDatesRequest FormatJson tags   -- | posts/update : Returns the most recent time a bookmark was added, updated or deleted. getPostsMRUTime :: Pinboard UTCTime-getPostsMRUTime = fromUpdateTime <$> pinboardJson getPostsMRUTimeRequest+getPostsMRUTime = fromUpdateTime <$> pinboardJson (getPostsMRUTimeRequest FormatJson)  -- | posts/suggest : Returns a list of popular tags and recommended tags for a given URL.  -- Popular tags are tags used site-wide for the url; @@ -87,13 +88,13 @@ getSuggestedTags   :: Url   -> Pinboard [Suggested]-getSuggestedTags url = pinboardJson $ getSuggestedTagsRequest url+getSuggestedTags url = pinboardJson $ getSuggestedTagsRequest FormatJson url  -- | posts/delete : Delete an existing bookmark. deletePost    :: Url   -> Pinboard ()-deletePost url = fromDoneResult <$> (pinboardJson $ deletePostRequest url)+deletePost url = fromDoneResult <$> (pinboardJson $ deletePostRequest FormatJson url)  -- | posts/add : Add a bookmark addPost@@ -106,7 +107,7 @@   -> Maybe Shared   -- ^ Make bookmark public. Default is "yes" unless user has enabled the "save all bookmarks as private" user setting, in which case default is "no"   -> Maybe ToRead   -- ^ Marks the bookmark as unread. Default is "no"   -> Pinboard ()-addPost url descr ext tags ctime repl shared toread = fromDoneResult <$> (pinboardJson $ addPostRequest url descr ext tags ctime repl shared toread)+addPost url descr ext tags ctime repl shared toread = fromDoneResult <$> (pinboardJson $ addPostRequest FormatJson url descr ext tags ctime repl shared toread)  -- TAGS ---------------------------------------------------------------------- @@ -115,14 +116,14 @@ -- times they were used. getTags    :: Pinboard TagMap-getTags = fromJsonTagMap <$> (pinboardJson $ getTagsRequest)+getTags = fromJsonTagMap <$> (pinboardJson $ getTagsRequest FormatJson )   -- | tags/delete : Delete an existing tag. deleteTag    :: Tag    -> Pinboard ()-deleteTag tag = fromDoneResult <$> (pinboardJson $ deleteTagRequest tag)+deleteTag tag = fromDoneResult <$> (pinboardJson $ deleteTagRequest FormatJson tag)   -- | tags/rename : Rename an tag, or fold it in to an existing tag@@ -130,7 +131,7 @@   :: Old -- ^ note: match is not case sensitive   -> New -- ^ if empty, nothing will happen   -> Pinboard ()-renameTag old new = fromDoneResult <$> (pinboardJson $ renameTagRequest old new)+renameTag old new = fromDoneResult <$> (pinboardJson $ renameTagRequest FormatJson old new)   -- USER ----------------------------------------------------------------------@@ -138,12 +139,12 @@ -- | user/secret : Returns the user's secret RSS key (for viewing private feeds) getUserSecretRssKey    :: Pinboard Text-getUserSecretRssKey = fromTextResult <$> (pinboardJson $ getUserSecretRssKeyRequest)+getUserSecretRssKey = fromTextResult <$> (pinboardJson $ getUserSecretRssKeyRequest FormatJson )  -- | user/api_token : Returns the user's API token (for making API calls without a password) getUserApiToken    :: Pinboard Text-getUserApiToken = fromTextResult <$> (pinboardJson $ getUserApiTokenRequest)+getUserApiToken = fromTextResult <$> (pinboardJson $ getUserApiTokenRequest FormatJson )   -- NOTES ---------------------------------------------------------------------@@ -151,11 +152,11 @@ -- | notes/list : Returns a list of the user's notes (note text detail is not included) getNoteList    :: Pinboard NoteList-getNoteList = pinboardJson $ getNoteListRequest+getNoteList = pinboardJson $ getNoteListRequest FormatJson   -- | notes/id : Returns an individual user note. The hash property is a 20 character long sha1 hash of the note text. getNote    :: NoteId   -> Pinboard Note-getNote noteid = pinboardJson $ getNoteRequest noteid+getNote noteid = pinboardJson $ getNoteRequest FormatJson noteid 
src/Pinboard/ApiRequest.hs view
@@ -9,7 +9,7 @@ -- Portability : POSIX -- -- These request builders allow you to build request params which can --- sent via Pinboard.Client.Internal, in the case you need more control+-- sent via Pinboard.Client, in the case you need more control -- for how the response should be processed over what Pinboard.Api provides.  module Pinboard.ApiRequest@@ -42,34 +42,40 @@ import Data.Text                (unwords) import Data.Maybe               (catMaybes) import Pinboard.ApiTypes        +import Pinboard.Client.Types    (ResultFormatType (..))+                                              -- POSTS ---------------------------------------------------------------------  -- | posts/recent : Returns a list of the user's most recent posts, filtered by tag. getPostsRecentRequest-  :: Maybe [Tag] -- ^ filter by up to three tags+  :: ResultFormatType+  -> Maybe [Tag] -- ^ filter by up to three tags   -> Maybe Count -- ^ number of results to return. Default is 15, max is 100     -> PinboardRequest-getPostsRecentRequest tags count = PinboardRequest path params+getPostsRecentRequest fmt tags count = PinboardRequest path params   where      path = "posts/recent" -    params = catMaybes [ Tag . unwords <$> tags+    params = catMaybes [ Just (Format fmt)+                       , Tag . unwords <$> tags                        , Count <$> count ]  -- | posts/all : Returns all bookmarks in the user's account. getPostsAllRequest-  :: Maybe [Tag] -- ^ filter by up to three tags+  :: ResultFormatType+  -> Maybe [Tag] -- ^ filter by up to three tags   -> Maybe StartOffset -- ^ offset value (default is 0)   -> Maybe NumResults -- ^ number of results to return. Default is all   -> Maybe FromDateTime -- ^ return only bookmarks created after this time   -> Maybe ToDateTime -- ^ return only bookmarks created before this time   -> Maybe Meta -- ^ include a change detection signature for each bookmark   -> PinboardRequest-getPostsAllRequest tags start results fromdt todt meta = +getPostsAllRequest fmt tags start results fromdt todt meta =    PinboardRequest path params   where      path = "posts/all" -    params = catMaybes [ Tag . unwords <$> tags+    params = catMaybes [ Just (Format fmt) +                       , Tag . unwords <$> tags                        , Start <$> start                        , Results <$> results                        , FromDateTime <$> fromdt@@ -80,60 +86,68 @@ -- | posts/get : Returns one or more posts on a single day matching the arguments.  -- If no date or url is given, date of most recent bookmark will be used. getPostsForDateRequest-  :: Maybe [Tag] -- ^ filter by up to three tags+  :: ResultFormatType+  -> Maybe [Tag] -- ^ filter by up to three tags   -> Maybe Date -- ^ return results bookmarked on this day   -> Maybe Url -- ^ return bookmark for this URL   -> PinboardRequest-getPostsForDateRequest tags date url = PinboardRequest path params+getPostsForDateRequest fmt tags date url = PinboardRequest path params   where      path = "posts/get" -    params = catMaybes [ Tag . unwords <$> tags+    params = catMaybes [ Just (Format fmt)  +                       , Tag . unwords <$> tags                        , Date <$> date                        , Url <$> url ]   -- | posts/dates : Returns a list of dates with the number of posts at each date. getPostsDatesRequest-  :: Maybe [Tag] -- ^ filter by up to three tags+  :: ResultFormatType+  -> Maybe [Tag] -- ^ filter by up to three tags   -> PinboardRequest-getPostsDatesRequest tags = PinboardRequest path params+getPostsDatesRequest fmt tags = PinboardRequest path params   where      path = "posts/dates" -    params = catMaybes [ Tag . unwords <$> tags ]+    params = catMaybes [ Just (Format fmt)  +                       , Tag . unwords <$> tags ]   -- | posts/update : Returns the most recent time a bookmark was added, updated or deleted. getPostsMRUTimeRequest -  :: PinboardRequest-getPostsMRUTimeRequest = PinboardRequest path params+  :: ResultFormatType+  -> PinboardRequest+getPostsMRUTimeRequest fmt = PinboardRequest path params   where      path = "posts/update" -    params = []+    params = [Format fmt]  -- | posts/suggest : Returns a list of popular tags and recommended tags for a given URL.  -- Popular tags are tags used site-wide for the url;  -- Recommended tags are drawn from the user's own tags. getSuggestedTagsRequest-  :: Url+  :: ResultFormatType+  -> Url   -> PinboardRequest-getSuggestedTagsRequest url = PinboardRequest path params+getSuggestedTagsRequest fmt url = PinboardRequest path params   where      path = "posts/suggest" -    params = [ Url url ]+    params = [ Format fmt, Url url ]  -- | posts/delete : Delete an existing bookmark. deletePostRequest -  :: Url+  :: ResultFormatType+  -> Url   -> PinboardRequest-deletePostRequest url = PinboardRequest path params+deletePostRequest fmt url = PinboardRequest path params   where      path = "posts/delete" -    params = [Url url]+    params = [Format fmt, Url url]   -- | posts/add : Add a bookmark addPostRequest-  :: Url            -- ^ the URL of the item+  :: ResultFormatType+  -> Url            -- ^ the URL of the item   -> Description    -- ^ Title of the item. This field is unfortunately named 'description' for backwards compatibility with the delicious API   -> Maybe Extended -- ^ Description of the item. Called 'extended' for backwards compatibility with delicious API   -> Maybe [Tag]    -- ^ List of up to 100 tags@@ -142,11 +156,12 @@   -> Maybe Shared   -- ^ Make bookmark public. Default is "yes" unless user has enabled the "save all bookmarks as private" user setting, in which case default is "no"   -> Maybe ToRead   -- ^ Marks the bookmark as unread. Default is "no"   -> PinboardRequest-addPostRequest url descr ext tags ctime repl shared toread = +addPostRequest fmt url descr ext tags ctime repl shared toread =    PinboardRequest path params   where      path = "posts/add" -    params = catMaybes [ Just $ Url url+    params = catMaybes [ Just (Format fmt)  +                       , Just $ Url url                        , Just $ Description descr                        , Extended <$> ext                        , Tags . unwords <$> tags@@ -161,69 +176,76 @@ -- | tags/get : Returns a full list of the user's tags along with the number of  -- times they were used. getTagsRequest-  :: PinboardRequest-getTagsRequest = PinboardRequest path params+  :: ResultFormatType+  -> PinboardRequest+getTagsRequest fmt = PinboardRequest path params   where      path = "tags/get" -    params = []+    params = [Format fmt]   -- | tags/delete : Delete an existing tag. deleteTagRequest -  :: Tag +  :: ResultFormatType+  -> Tag    -> PinboardRequest-deleteTagRequest tag = PinboardRequest path params+deleteTagRequest fmt tag = PinboardRequest path params   where      path = "tags/delete" -    params = [Tag tag]+    params = [Format fmt, Tag tag]   -- | tags/rename : Rename an tag, or fold it in to an existing tag renameTagRequest -  :: Old -- ^ note: match is not case sensitive+  :: ResultFormatType+  -> Old -- ^ note: match is not case sensitive   -> New -- ^ if empty, nothing will happen   -> PinboardRequest-renameTagRequest old new = PinboardRequest path params+renameTagRequest fmt old new = PinboardRequest path params   where      path = "tags/rename" -    params = [Old old, New new]+    params = [Format fmt, Old old, New new]   -- USER ----------------------------------------------------------------------  -- | user/secret : Returns the user's secret RSS key (for viewing private feeds) getUserSecretRssKeyRequest -  :: PinboardRequest-getUserSecretRssKeyRequest = PinboardRequest path params+  :: ResultFormatType+  -> PinboardRequest+getUserSecretRssKeyRequest fmt = PinboardRequest path params   where      path = "user/secret" -    params = []+    params = [Format fmt]  -- | user/api_token : Returns the user's API token (for making API calls without a password) getUserApiTokenRequest -  :: PinboardRequest-getUserApiTokenRequest = PinboardRequest path params+  :: ResultFormatType+  -> PinboardRequest+getUserApiTokenRequest fmt = PinboardRequest path params   where      path = "user/api_token" -    params = []+    params = [Format fmt]   -- NOTES ---------------------------------------------------------------------  -- | notes/list : Returns a list of the user's notes (note text detail is not included) getNoteListRequest -  :: PinboardRequest-getNoteListRequest = PinboardRequest path params+  :: ResultFormatType+  -> PinboardRequest+getNoteListRequest fmt = PinboardRequest path params   where      path = "notes/list" -    params = []+    params = [Format fmt]  -- | notes/id : Returns an individual user note. The hash property is a 20 character long sha1 hash of the note text. getNoteRequest -  :: NoteId+  :: ResultFormatType+  -> NoteId   -> PinboardRequest-getNoteRequest noteid = PinboardRequest path params+getNoteRequest fmt noteid = PinboardRequest path params   where      path = "notes" </> noteid-    params = []+    params = [Format fmt] 
src/Pinboard/ApiTypes.hs view
@@ -23,7 +23,6 @@ import System.Locale       (defaultTimeLocale) import qualified Data.HashMap.Strict as HM - -- * Posts  data Posts = Posts {
src/Pinboard/Client.hs view
@@ -1,41 +1,230 @@--- |+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE TupleSections #-}++------------------------------------------------------------------------------+-- |  -- Module      : Pinboard.Client--- Copyright   : (c) Jon Schoning+-- Copyright   : (c) Jon Schoning, 2015 -- Maintainer  : jonschoning@gmail.com -- Stability   : experimental -- Portability : POSIX------ @--- import Pinboard--- --- main :: IO ()--- main = do---   let config = fromApiToken "api token"---   result <- runPinboardJson config $ getPostsRecent Nothing Nothing---   case result of---     Right details -> print details---     Left pinboardError -> print pinboardError--- @+------------------------------------------------------------------------------+ module Pinboard.Client     ( -      -- * Client-      -- | Executes the methods defined in Pinboard.Api-      runPinboardJson-      -- | Create a default PinboardConfig using the supplied apiToken-    , fromApiToken+      -- * Config+      fromApiToken       -- | The PinboardConfig provides authentication via apiToken     , PinboardConfig       (..)-      -- * Client Dependencies+      -- * Monadic, Json+    , runPinboardJson+    , pinboardJson+      -- * Single+    , runPinboardSingleRaw+    , runPinboardSingleRawBS+    , runPinboardSingleJson+      -- * Sending+    , sendPinboardRequest+    , sendPinboardRequestBS+      -- * Connections+    , connOpenRaw+    , connOpen+    , connClose+    , connFail+     -- * JSON Streams+    ,parseJSONResponseStream+    ,parseJSONFromStream+     -- * Status Codes+    ,checkStatusCode+     -- * Error Helpers+    ,addErrMsg+    ,createParserErr+    ,httpStatusPinboardError+     -- * Client Dependencies     , module Pinboard.Client.Error     , module Pinboard.Client.Types     , module Pinboard.Client.Util     ) where -import Pinboard.Client.Internal++import Control.Applicative        ((<$>))+import Control.Exception          (catch, SomeException, try, bracket)+import Control.Monad.IO.Class     (MonadIO (liftIO))+import Control.Monad.Reader       (ask, runReaderT)+import Control.Monad.Trans.Either (runEitherT, hoistEither)+import Data.ByteString.Char8      (pack)+import Data.Monoid                ((<>))+import Data.Aeson                 (parseJSON, json', FromJSON)+import Data.Aeson.Types           (parseEither)+import Network.Http.Client        (Request, Connection, Method (GET), baselineContextSSL, +                                   buildRequest, closeConnection, concatHandler, concatHandler', +                                   getStatusCode, http, openConnectionSSL, receiveResponse, sendRequest,+                                   setHeader, emptyBody, Response, StatusCode)+import Network                    (withSocketsDo)+import Network.HTTP.Types         (urlEncode)+import OpenSSL                    (withOpenSSL)+import System.IO.Streams          (InputStream)+import System.IO.Streams.Attoparsec (parseFromStream)+ import Pinboard.Client.Types import Pinboard.Client.Error import Pinboard.Client.Util-import Data.ByteString.Char8(pack) +import qualified Data.ByteString             as S+import qualified Data.Text                   as T+import qualified Data.Text.Encoding          as T+++-- | Create a default PinboardConfig using the supplied apiToken fromApiToken :: String -> PinboardConfig fromApiToken token = PinboardConfig { debug = False, apiToken = pack token }++--------------------------------------------------------------------------------+-- | Execute computations from Pinboard.Api w/ json deserialization+runPinboardJson+    :: FromJSON a+    => PinboardConfig+    -> Pinboard a+    -> IO (Either PinboardError a)+runPinboardJson config requests = withOpenSSL $+  bracket connOpen connClose (either (connFail ConnectionFailure) go)+  where go conn = runReaderT (runEitherT requests) (config, conn) +                  `catch` connFail UnknownErrorType++-- | Create a Pinboard value from a PinboardRequest w/ json deserialization+pinboardJson :: FromJSON a => PinboardRequest -> Pinboard a+pinboardJson req = do +  (config, conn)  <- ask+  (_, result) <- liftIO $ sendPinboardRequest (ensureResultFormatType FormatJson req) config conn parseJSONResponseStream+  hoistEither result+++--------------------------------------------------------------------------------++runPinboardSingleRaw+    :: PinboardConfig       +    -> PinboardRequest+    -> (Response -> InputStream S.ByteString -> IO a)+    -> IO (Either PinboardError a)+runPinboardSingleRaw config req handler = withOpenSSL $ +  bracket connOpen connClose (either (connFail ConnectionFailure) go)+    where go conn = (Right <$> sendPinboardRequest req config conn handler)+                    `catch` connFail UnknownErrorType ++runPinboardSingleRawBS+    :: PinboardConfig       +    -> PinboardRequest+    -> IO (Either PinboardError S.ByteString)+runPinboardSingleRawBS config req = runPinboardSingleRaw config req concatHandler'++runPinboardSingleJson+    :: FromJSON a+    => PinboardConfig       +    -> PinboardRequest+    -> IO (Either PinboardError a)+runPinboardSingleJson config = runPinboardJson config . pinboardJson+++--------------------------------------------------------------------------------++sendPinboardRequest+      :: PinboardRequest +      -> PinboardConfig +      -> Connection +      -> (Response -> InputStream S.ByteString -> IO a)+      -> IO a+sendPinboardRequest PinboardRequest{..} PinboardConfig{..} conn handler = do+   let url = S.concat [ T.encodeUtf8 requestPath +                      , "?" +                      , paramsToByteString $ ("auth_token", urlEncode False apiToken) : encodeParams requestParams ]+   req <- buildReq url+   sendRequest conn req emptyBody+   receiveResponse conn handler+++sendPinboardRequestBS +  :: PinboardRequest +  -> PinboardConfig +  -> Connection +  -> IO (Response, S.ByteString) +sendPinboardRequestBS request config conn = sendPinboardRequest request config conn handler+  where handler response responseInputStream = do resultBS <- concatHandler response responseInputStream+                                                  return (response, resultBS)++--------------------------------------------------------------------------------++buildReq ::  S.ByteString -> IO Request+buildReq url = buildRequest $ do+  http GET ("/v1/" <> url)+  setHeader "Connection" "Keep-Alive"  +  setHeader "User-Agent" "pinboard.hs/0.4"  ++--------------------------------------------------------------------------------++parseJSONResponseStream +    :: FromJSON a +    => Response +    -> InputStream S.ByteString+    -> IO (Response, Either PinboardError a)+parseJSONResponseStream response stream = +  (response,) <$> either (return . Left . addErrMsg (toText response)) +                         (const $ parseJSONFromStream stream) +                         (checkStatusCode $ getStatusCode response)+++parseJSONFromStream +    :: FromJSON a +    => InputStream S.ByteString +    -> IO (Either PinboardError a)+parseJSONFromStream s = do +  r <- parseFromStream (parseEither parseJSON <$> json') s+  return $ either (Left . createParserErr . toText)  Right r+  `catch` connFail ParseFailure++--------------------------------------------------------------------------------++checkStatusCode :: StatusCode -> Either PinboardError ()+checkStatusCode = \case+  200 -> Right ()+  400 -> httpStatusPinboardError BadRequest+  401 -> httpStatusPinboardError UnAuthorized+  402 -> httpStatusPinboardError RequestFailed+  403 -> httpStatusPinboardError Forbidden+  404 -> httpStatusPinboardError NotFound+  429 -> httpStatusPinboardError TooManyRequests+  c | c >= 500 -> httpStatusPinboardError PinboardServerError+  _   -> httpStatusPinboardError UnknownHTTPCode++--------------------------------------------------------------------------------++httpStatusPinboardError :: PinboardErrorHTTPCode -> Either PinboardError a+httpStatusPinboardError err = Left $ defaultPinboardError +  { errorType = HttpStatusFailure+  , errorHTTP = Just err }++addErrMsg :: T.Text -> PinboardError -> PinboardError+addErrMsg msg err = err {errorMsg = msg}++createParserErr :: T.Text -> PinboardError+createParserErr msg = PinboardError ParseFailure msg Nothing Nothing Nothing ++--------------------------------------------------------------------------------+++connOpenRaw :: IO Connection+connOpenRaw = withSocketsDo $ do+  ctx <- baselineContextSSL+  openConnectionSSL ctx "api.pinboard.in" 443++connOpen :: IO (Either SomeException Connection)+connOpen = try connOpenRaw++connClose :: Either a Connection -> IO ()+connClose = either (const $ return ()) closeConnection++connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)+connFail e msg = return $ Left $ PinboardError e (toText msg) Nothing Nothing Nothing++
− src/Pinboard/Client/Internal.hs
@@ -1,220 +0,0 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}-{-# LANGUAGE TupleSections #-}----------------------------------------------------------------------------------- | --- Module      : Pinboard.Client.Internal--- Copyright   : (c) Jon Schoning, 2015--- Maintainer  : jonschoning@gmail.com--- Stability   : experimental--- Portability : POSIX---------------------------------------------------------------------------------module Pinboard.Client.Internal-    ( -      -- * Monadic-      pinboardJson-    , runPinboardJson-      -- * Single-    , runPinboardSingleRaw-    , runPinboardSingleRawBS-    , runPinboardSingleJson-      -- * Sending-    , sendPinboardRequest-    , sendPinboardRequestBS-      -- * Connections-    , connOpenRaw-    , connOpen-    , connClose-    , connFail-     -- * JSON Streams-    ,parseJSONResponseStream-    ,parseJSONFromStream-     -- * Status Codes-    ,checkStatusCode-     -- * Error Helpers-    ,addErrMsg-    ,createParserErr-    ,httpStatusPinboardError-    ) where---import Control.Applicative        ((<$>))-import Control.Exception          (catch, SomeException, try, bracket)-import Control.Monad.IO.Class     (MonadIO (liftIO))-import Control.Monad.Reader       (ask, runReaderT)-import Control.Monad.Trans.Either (runEitherT, hoistEither)-import Data.Monoid                ((<>))-import Data.Aeson                 (parseJSON, json', FromJSON)-import Data.Aeson.Types           (parseEither)-import Network.Http.Client        (Request, Connection, Method (GET), baselineContextSSL, -                                   buildRequest, closeConnection, concatHandler, concatHandler', -                                   getStatusCode, http, openConnectionSSL, receiveResponse, sendRequest,-                                   setHeader, emptyBody, Response, StatusCode)-import Network                    (withSocketsDo)-import Network.HTTP.Types         (urlEncode)-import OpenSSL                    (withOpenSSL)-import System.IO.Streams          (InputStream)-import System.IO.Streams.Attoparsec (parseFromStream)--import Pinboard.Client.Error      (PinboardError (..),-                                   PinboardErrorHTTPCode (..),-                                   PinboardErrorType (..),-                                   defaultPinboardError)-import Pinboard.Client.Types      (Pinboard,-                                   PinboardConfig (..),-                                   PinboardRequest (..),-                                   Param (..))-import Pinboard.Client.Util       (encodeParams, paramsToByteString, toText)--import qualified Data.ByteString             as S-import qualified Data.Text                   as T-import qualified Data.Text.Encoding          as T---pinboardJson :: FromJSON a => PinboardRequest -> Pinboard a-pinboardJson req = do -  let reqJson = req { requestParams = Format "json" : requestParams req } -  (config, conn)  <- ask-  (_, result) <- liftIO $ sendPinboardRequest reqJson config conn parseJSONResponseStream-  hoistEither result--runPinboardJson-    :: FromJSON a-    => PinboardConfig-    -> Pinboard a-    -> IO (Either PinboardError a)-runPinboardJson config requests = withOpenSSL $-  bracket connOpen connClose (either (connFail ConnectionFailure) go)-  where go conn = runReaderT (runEitherT requests) (config, conn) -                  `catch` connFail UnknownErrorType------------------------------------------------------------------------------------runPinboardSingleRaw-    :: PinboardConfig       -    -> PinboardRequest-    -> (Response -> InputStream S.ByteString -> IO a)-    -> IO (Either PinboardError a)-runPinboardSingleRaw config req handler = withOpenSSL $ -  bracket connOpen connClose (either (connFail ConnectionFailure) go)-    where go conn = (Right <$> sendPinboardRequest req config conn handler)-                    `catch` connFail UnknownErrorType --runPinboardSingleRawBS-    :: PinboardConfig       -    -> PinboardRequest-    -> IO (Either PinboardError S.ByteString)-runPinboardSingleRawBS config req = runPinboardSingleRaw config req concatHandler'--runPinboardSingleJson-    :: FromJSON a-    => PinboardConfig       -    -> PinboardRequest-    -> IO (Either PinboardError a)-runPinboardSingleJson config = runPinboardJson config . pinboardJson-------------------------------------------------------------------------------------sendPinboardRequest-      :: PinboardRequest -      -> PinboardConfig -      -> Connection -      -> (Response -> InputStream S.ByteString -> IO a)-      -> IO a-sendPinboardRequest PinboardRequest{..} PinboardConfig{..} conn handler = do-   let url = S.concat [ T.encodeUtf8 requestPath -                      , "?" -                      , paramsToByteString $ ("auth_token", urlEncode False apiToken) : encodeParams requestParams ]-   req <- buildReq url-   sendRequest conn req emptyBody-   receiveResponse conn handler---sendPinboardRequestBS -  :: PinboardRequest -  -> PinboardConfig -  -> Connection -  -> IO (Response, S.ByteString) -sendPinboardRequestBS request config conn = sendPinboardRequest request config conn handler-  where handler response responseInputStream = do resultBS <- concatHandler response responseInputStream-                                                  return (response, resultBS)------------------------------------------------------------------------------------buildReq ::  S.ByteString -> IO Request-buildReq url = buildRequest $ do-  http GET ("/v1/" <> url)-  setHeader "Connection" "Keep-Alive"  -  setHeader "User-Agent" "pinboard.hs/0.4"  ------------------------------------------------------------------------------------parseJSONResponseStream -    :: FromJSON a -    => Response -    -> InputStream S.ByteString-    -> IO (Response, Either PinboardError a)-parseJSONResponseStream response stream = -  (response,) <$> either (return . Left . addErrMsg (toText response)) -                         (const $ parseJSONFromStream stream) -                         (checkStatusCode $ getStatusCode response)---parseJSONFromStream -    :: FromJSON a -    => InputStream S.ByteString -    -> IO (Either PinboardError a)-parseJSONFromStream s = do -  r <- parseFromStream (parseEither parseJSON <$> json') s-  return $ either (Left . createParserErr . toText)  Right r-  `catch` connFail ParseFailure------------------------------------------------------------------------------------checkStatusCode :: StatusCode -> Either PinboardError ()-checkStatusCode = \case-  200 -> Right ()-  400 -> httpStatusPinboardError BadRequest-  401 -> httpStatusPinboardError UnAuthorized-  402 -> httpStatusPinboardError RequestFailed-  403 -> httpStatusPinboardError Forbidden-  404 -> httpStatusPinboardError NotFound-  429 -> httpStatusPinboardError TooManyRequests-  c | c >= 500 -> httpStatusPinboardError PinboardServerError-  _   -> httpStatusPinboardError UnknownHTTPCode------------------------------------------------------------------------------------httpStatusPinboardError :: PinboardErrorHTTPCode -> Either PinboardError a-httpStatusPinboardError err = Left $ defaultPinboardError -  { errorType = HttpStatusFailure-  , errorHTTP = Just err }--addErrMsg :: T.Text -> PinboardError -> PinboardError-addErrMsg msg err = err {errorMsg = msg}--createParserErr :: T.Text -> PinboardError-createParserErr msg = PinboardError ParseFailure msg Nothing Nothing Nothing -------------------------------------------------------------------------------------connOpenRaw :: IO Connection-connOpenRaw = withSocketsDo $ do-  ctx <- baselineContextSSL-  openConnectionSSL ctx "api.pinboard.in" 443--connOpen :: IO (Either SomeException Connection)-connOpen = try connOpenRaw--connClose :: Either a Connection -> IO ()-connClose = either (const $ return ()) closeConnection--connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)-connFail e msg = return $ Left $ PinboardError e (toText msg) Nothing Nothing Nothing--
src/Pinboard/Client/Types.hs view
@@ -10,6 +10,7 @@   ( Pinboard   , PinboardRequest (..)   , PinboardConfig  (..)+  , ResultFormatType (..)   , Param (..)   , ParamsBS   ) where@@ -46,7 +47,10 @@  ------------------------------------------------------------------------------ -data Param = Format Text+data ResultFormatType = FormatJson | FormatXml+      deriving (Show, Eq)++data Param = Format ResultFormatType            | Tag Text            | Tags Text            | Old Text
src/Pinboard/Client/Util.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-} -- | -- Module      : Pinboard.Client.Util -- Copyright   : (c) Jon Schoning, 2015@@ -15,6 +16,7 @@     , paramToName     , paramToText     , encodeParams+    , ensureResultFormatType     ) where  import           Data.Monoid           (Monoid, mconcat, mempty, (<>))@@ -22,7 +24,7 @@ import           Data.Text             (Text) import qualified Data.Text             as T import qualified Data.Text.Encoding    as T-import           Pinboard.Client.Types (PinboardConfig (..), Param (..), ParamsBS)+import           Pinboard.Client.Types (PinboardRequest (..), PinboardConfig (..), ResultFormatType (..), Param (..), ParamsBS) import Network.HTTP.Types(urlEncode)  ------------------------------------------------------------------------------@@ -65,13 +67,20 @@   return ( T.encodeUtf8 k          , (urlEncode True . T.encodeUtf8 ) v          )+ensureResultFormatType :: ResultFormatType -> PinboardRequest -> PinboardRequest+ensureResultFormatType fmt req = +  if hasFormat then req else req { requestParams = Format fmt : params }+  where params = requestParams req+        hasFormat = Format fmt `elem` params + paramToText :: Param -> (Text, Text) paramToText (Tag a)      = ("tag", a) paramToText (Tags a)     = ("tags", a) paramToText (Old a)      = ("old", a) paramToText (New a)      = ("new", a)-paramToText (Format a)   = ("format", a)+paramToText (Format FormatJson) = ("format", "json")+paramToText (Format FormatXml)  = ("format", "xml") paramToText (Count a)    = ("count", toText a) paramToText (Start a)    = ("start", toText a) paramToText (Results a)  = ("results", toText a)