packages feed

hackernews (empty) → 0.1.0.0

raw patch · 14 files changed

+526/−0 lines, 14 filesdep +HsOpenSSLdep +aesondep +attoparsecsetup-changed

Dependencies added: HsOpenSSL, aeson, attoparsec, base, bytestring, hackernews, hspec, http-streams, io-streams, text, time

Files

+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2014 David Johnson++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ hackernews.cabal view
@@ -0,0 +1,51 @@+name:                hackernews+version:             0.1.0.0+description:         API for news.ycombinator.com+license:             MIT+synopsis:            API for Hacker News+license-file:        LICENSE+author:              David Johnson+maintainer:          djohnson.m@gmail.com+category:            Web+build-type:          Simple+cabal-version:       >=1.10++library+  ghc-options:         -Wall+  exposed-modules:     Web.HackerNews+  other-modules:       Web.HackerNews.Types+                     , Web.HackerNews.Client+                     , Web.HackerNews.User+                     , Web.HackerNews.Poll+                     , Web.HackerNews.Person+                     , Web.HackerNews.Update+                     , Web.HackerNews.Comment+                     , Web.HackerNews.Story+                     , Web.HackerNews.Util+  hs-source-dirs:      src+  build-depends:       HsOpenSSL >= 0.10.5+                     , aeson+                     , attoparsec >= 0.12.1.2+                     , base >=4.7 && <4.8+                     , bytestring >= 0.10.4.0+                     , hspec >= 1.11.4+                     , http-streams+                     , io-streams >= 1.1.4.6+                     , text >= 1.2.0.0+                     , time >= 1.4.2+  default-language:    Haskell2010++Test-Suite tests+    type:                exitcode-stdio-1.0+    hs-source-dirs:      tests+    main-is:             Test.hs+    build-depends:       base+                       , hackernews+                       , hspec+                       , hspec >= 1.11.4+    default-language:    Haskell2010+    ghc-options:         -Wall++source-repository head+  type:     git+  location: https://github.com/dmjio/hackernews
+ src/Web/HackerNews.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews+       ( -- * API Calls+         getStory+       , getComment+       , getPoll+       , getPollOpt+       , getUser+       , getTopStories+       , getMaxItem+       , getUpdates+       -- * Types+       , Comment   (..)+       , CommentId (..)+       , Poll      (..)+       , PollId    (..)+       , PollOpt   (..)+       , PollOptId (..)+       , Story     (..)+       , StoryId   (..)+       , User      (..)+       , UserId    (..)+       , Update    (..)+       , MaxItem+       , TopStories+       ) where++import           Data.Monoid                ((<>))++import           Web.HackerNews.Types+import           Web.HackerNews.Util        (toText)+import           Web.HackerNews.Client      (getItem)++------------------------------------------------------------------------------+-- | Retrieve a `Story` by `StoryId`+getStory :: StoryId -> IO (Maybe Story)+getStory (StoryId storyid) = getItem $ "item/" <> toText storyid++------------------------------------------------------------------------------+-- | Retrieve a `Comment` by `CommentId`+getComment :: CommentId -> IO (Maybe Comment)+getComment (CommentId commentid) = getItem $ "item/" <> toText commentid++------------------------------------------------------------------------------+-- | Retrieve a `Poll` by `PollId`+getPoll :: PollId -> IO (Maybe Poll)+getPoll (PollId pollid) = getItem $ "item/" <> toText pollid++------------------------------------------------------------------------------+-- | Retrieve a `PollOpt` by `PollOptId`+getPollOpt :: PollOptId -> IO (Maybe PollOpt)+getPollOpt (PollOptId polloptid) = getItem $ "item/" <> toText polloptid++------------------------------------------------------------------------------+-- | Retrieve a `User` by `UserId`+getUser :: UserId -> IO (Maybe User)+getUser (UserId userid) = getItem $ "user/" <> userid++------------------------------------------------------------------------------+-- | Retrieve the Top Stories on Hacker News+getTopStories :: IO (Maybe TopStories)+getTopStories = getItem "topstories"++------------------------------------------------------------------------------+-- | Retrieve the largest ItemId+getMaxItem :: IO (Maybe MaxItem)+getMaxItem = getItem "maxitem"++------------------------------------------------------------------------------+-- | Retrieve the largest ItemId+getUpdates :: IO (Maybe Update)+getUpdates = getItem "updates"+
+ src/Web/HackerNews/Client.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Client ( getItem ) where++import           Data.Aeson                 hiding (Result)+import           Data.Aeson.Parser          (value)+import           Data.Attoparsec.ByteString (parseOnly)+import           Data.Either                (rights)++import           Data.Monoid                ((<>))+import           Data.Text                  (Text)+import qualified Data.Text.Encoding         as T+import           Network.Http.Client+import           OpenSSL                    (withOpenSSL)+import qualified System.IO.Streams          as Streams++------------------------------------------------------------------------------+-- | HackerNews API request method+getItem+  :: FromJSON a+  => Text      +  -> IO (Maybe a)+getItem url = withOpenSSL $ do+  ctx <- baselineContextSSL+  con <- openConnectionSSL ctx "hacker-news.firebaseio.com" 443+  req <- buildRequest $ do+    http GET $ "/v0/" <> T.encodeUtf8 url <> ".json"+    setAccept "application/json"+  sendRequest con req emptyBody+  bytes <- receiveResponse con $ const $ Streams.read+  closeConnection con+  return $ case bytes of+   Nothing -> Nothing+   Just bs -> do+     let xs = rights [parseOnly value bs, parseOnly json bs]+     case xs of+       []    -> Nothing+       x : _ ->+         case fromJSON x of+          Success a -> Just a+          _         -> Nothing+
+ src/Web/HackerNews/Comment.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Comment where++import           Control.Applicative ((<$>), (<*>))+import           Control.Monad       (MonadPlus (mzero))+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),+                                      (.:), (.:?))+import           Data.Text           (Text)+import           Data.Time           (UTCTime)++import           Web.HackerNews.Util (fromSeconds)++------------------------------------------------------------------------------+-- | Types+data Comment = Comment {+    commentBy     :: Text+  , commentId     :: CommentId+  , commentKids   :: Maybe [Int]+  , commentParent :: Int+  , commentText   :: Text+  , commentTime   :: UTCTime+  , commentType   :: Text+  } deriving Show++newtype CommentId+  = CommentId Int+  deriving (Show, Eq)++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON Comment where+  parseJSON (Object o) =+     Comment <$> o .: "by"+             <*> (CommentId <$> o .: "id")+             <*> o .:? "kids"+             <*> o .: "parent"+             <*> o .: "text"+             <*> (fromSeconds <$> o .: "time")+             <*> o .: "type"+  parseJSON _ = mzero+++
+ src/Web/HackerNews/Person.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Person where++import           Control.Applicative ((<*>), (<$>))+import           Control.Monad       (MonadPlus (mzero))+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),+                                      (.:), (.:?))+import           Data.Text           (Text)+import           Data.Time           (UTCTime)++import           Web.HackerNews.Util (fromSeconds)++------------------------------------------------------------------------------+-- | Types+data Person = Person {+    personBy    :: Text+  , personId    :: PersonId+  , personKids  :: Maybe [Int]+  , personScore :: Maybe Int+  , personTime  :: UTCTime+  , personTitle :: Maybe Text+  , personType  :: Text+  , personUrl   :: Maybe Text+  } deriving (Show, Eq)++newtype PersonId+  = PersonId Text+  deriving (Show, Eq)++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON Person where+   parseJSON (Object o) =+     Person <$> o .: "by"+            <*> (PersonId <$> o .: "id")+            <*> o .:? "kids"+            <*> o .:? "score"+            <*> (fromSeconds <$> o .: "time")+            <*> o .:? "title"+            <*> o .: "type"+            <*> o .:? "url"+   parseJSON _ = mzero 
+ src/Web/HackerNews/Poll.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Poll where++import           Control.Applicative ((<*>), (<$>))+import           Control.Monad       (MonadPlus (mzero))+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),+                                      (.:))+import           Data.Text           (Text)+import           Data.Time           (UTCTime)++import           Web.HackerNews.Util (fromSeconds)++------------------------------------------------------------------------------+-- | Types+data Poll = Poll {+    pollBy    :: Text+  , pollId    :: PollId+  , pollKids  :: [Int]+  , pollParts :: [Int]+  , pollScore :: Int+  , pollText  :: Text+  , pollTime  :: UTCTime+  , pollTitle :: Text+  , pollType  :: Text+  } deriving (Show, Eq)++data PollOpt = PollOpt {+    pollOptBy     :: Text+  , pollOptId     :: PollOptId+  , pollOptParent :: Int+  , pollOptScore  :: Int+  , pollOptText   :: Text+  , pollOptTime   :: UTCTime+  , pollOptType   :: Text+  } deriving (Show, Eq)++newtype PollOptId+  = PollOptId Int+  deriving (Show, Eq)++newtype PollId+  = PollId Int+  deriving (Show, Eq)++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON Poll where+  parseJSON (Object o) =+     Poll <$> o .: "by"+          <*> (PollId <$> o .: "id")+          <*> o .: "kids"+          <*> o .: "parts"+          <*> o .: "score"+          <*> o .: "text"+          <*> (fromSeconds <$> o .: "time")+          <*> o .: "title"+          <*> o .: "type"+  parseJSON _ = mzero++instance FromJSON PollOpt where+  parseJSON (Object o) =+     PollOpt <$> o .: "by"+             <*> (PollOptId <$> o .: "id")+             <*> o .: "parent"+             <*> o .: "score"+             <*> o .: "text"+             <*> (fromSeconds <$> o .: "time")+             <*> o .: "type"+  parseJSON _ = mzero
+ src/Web/HackerNews/Story.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Story where++import           Control.Applicative ((<$>), (<*>))+import           Control.Monad       (MonadPlus (mzero))+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),+                                      (.:))+import           Data.Text           (Text)+import           Data.Time           (UTCTime)++import           Web.HackerNews.Util (fromSeconds)++------------------------------------------------------------------------------+-- | Types+data Story = Story {+    storyBy    :: Text+  , storyId    :: Int+  , storyKids  :: [Int]+  , storyScore :: Int+  , storyTime  :: UTCTime+  , storyTitle :: Text+  , storyType  :: Text+  , storyUrl   :: Text+  } deriving Show++newtype StoryId+  = StoryId Int+  deriving (Show, Eq)++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON Story where+   parseJSON (Object o) =+     Story <$> o .: "by"+           <*> o .: "id"+           <*> o .: "kids"+           <*> o .: "score"+           <*> (fromSeconds <$> o .: "time")+           <*> o .: "title"+           <*> o .: "type"+           <*> o .: "url"+   parseJSON _ = mzero+
+ src/Web/HackerNews/Types.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Types+       ( module H+       , StoryId   (..)+       , CommentId (..)+       , PollId    (..)+       , PollOptId (..)+       , UserId    (..)+       , MaxItem+       , TopStories+       ) where++import           Web.HackerNews.Comment as H+import           Web.HackerNews.Poll    as H+import           Web.HackerNews.Story   as H+import           Web.HackerNews.User    as H+import           Web.HackerNews.Update  as H++type MaxItem    = Int+type TopStories = [Int]++++
+ src/Web/HackerNews/Update.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.Update where++import           Control.Applicative ((<$>), (<*>))+import           Control.Monad       (MonadPlus (mzero))+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),+                                      (.:))+import           Data.Text           (Text)++------------------------------------------------------------------------------+-- | Types+data Update = Update {+    updateItems    :: [Int]+  , updateProfiles :: [Text]+  } deriving (Show, Eq)++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON Update where+  parseJSON (Object o) =+     Update <$> o .: "items"+            <*> o .: "profiles"+  parseJSON _ = mzero+
+ src/Web/HackerNews/User.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE OverloadedStrings #-}+module Web.HackerNews.User where++import           Control.Applicative ((<*>), (<$>))+import           Control.Monad       (MonadPlus (mzero))+import           Data.Aeson          (FromJSON (parseJSON), Value (Object),+                                      (.:))+import           Data.Text           (Text)+import           Data.Time           (UTCTime)++import           Web.HackerNews.Util (fromSeconds)++------------------------------------------------------------------------------+-- | Types+data User = User {+    userAbout     :: Text+  , userCreated   :: UTCTime+  , userDelay     :: Int+  , userId        :: UserId+  , userKarma     :: Int+  , userSubmitted :: [Int]+  } deriving (Show)++newtype UserId+      = UserId Text+      deriving (Show, Eq)++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON User where+  parseJSON (Object o) =+     User <$> o .: "about"+          <*> (fromSeconds <$> o .: "created")+          <*> o .: "delay"+          <*> (UserId <$> o .: "id")+          <*> o .: "karma"+          <*> o .: "submitted"+  parseJSON _ = mzero+
+ src/Web/HackerNews/Util.hs view
@@ -0,0 +1,18 @@+module Web.HackerNews.Util where++import qualified Data.Text as T+import           Data.Text    (Text)+import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import           Data.Time (UTCTime)++------------------------------------------------------------------------------+-- | Convert `Integer` to `UTCTime`+fromSeconds :: Integer -> UTCTime+fromSeconds = posixSecondsToUTCTime . fromInteger++------------------------------------------------------------------------------+-- | Convert `Show` constrained a to `Text`+toText :: Show a => a -> Text+toText = T.pack . show++
+ tests/Test.hs view
@@ -0,0 +1,35 @@+module Main where++import           Data.Maybe     (isJust)+import           Test.Hspec     (it, runIO, hspec, describe)+import           Web.HackerNews++main :: IO ()+main = hspec $ do+  describe "Hacker News API Tests" $ do+    story <- runIO $ getStory (StoryId 8863)+    it "Retrieves a Story" $ isJust story+    comment <- runIO $ getComment (CommentId 2921983)+    it "Retrieves a Comment" $ isJust comment+    user <- runIO $ getUser (UserId "dmjio")+    it "Retrieves a User" $ isJust user+    poll <- runIO $ getPoll (PollId 126809)+    it "Retrieves a Poll" $ isJust poll+    pollOpt <- runIO $ getPollOpt (PollOptId 160705)+    it "Retrieves a Pollopt" $ isJust pollOpt+    topStories <- runIO getTopStories+    it "Retrieves Top Stories" $ isJust topStories+    maxItem <- runIO getMaxItem+    it "Retrieves Max Item" $ isJust maxItem+    updates <- runIO getUpdates+    it "Retrieves Updates" $ isJust updates++++++++++