packages feed

themoviedb 0.1.0.1 → 1.0.0.0

raw patch · 49 files changed

+1403/−767 lines, 49 filesdep +binarydep +eitherdep +http-clientdep −HTTPdep −HUnitdep ~aesondep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: binary, either, http-client, http-client-tls, http-types, mtl, network-uri, tasty, tasty-hunit, text-binary, themoviedb, transformers

Dependencies removed: HTTP, HUnit

Dependency ranges changed: aeson, base, bytestring, network, text, time, unix

API changes (from Hackage documentation)

- Network.API.TheMovieDB: NetworkError :: String -> Error
- Network.API.TheMovieDB: ParseError :: String -> Error
- Network.API.TheMovieDB: apiKey :: Context -> Key
- Network.API.TheMovieDB: configErr :: Context -> IO (Either Error Configuration)
- Network.API.TheMovieDB: data Context
- Network.API.TheMovieDB: fetch :: Context -> MovieID -> IO Movie
- Network.API.TheMovieDB: fetchErr :: Context -> MovieID -> IO (Either Error Movie)
- Network.API.TheMovieDB: mkContext :: Key -> Context
- Network.API.TheMovieDB: search :: Context -> SearchQuery -> IO [Movie]
- Network.API.TheMovieDB: searchErr :: Context -> SearchQuery -> IO (Either Error [Movie])
- Network.API.TheMovieDB: type GenreID = Int
- Network.API.TheMovieDB: type MovieID = Int
- Network.API.TheMovieDB: type SearchQuery = String
- Network.API.TheMovieDB.Util: loadContext :: IO (Maybe Context)
- Network.API.TheMovieDB.Util: loadKey :: IO (Maybe Key)
+ Network.API.TheMovieDB: Episode :: ItemID -> Int -> Text -> Text -> Int -> Maybe Day -> Text -> Episode
+ Network.API.TheMovieDB: HttpExceptionError :: HttpException -> Error
+ Network.API.TheMovieDB: InvalidKeyError :: Error
+ Network.API.TheMovieDB: ResponseParseError :: String -> (Maybe ByteString) -> Error
+ Network.API.TheMovieDB: Season :: ItemID -> Int -> Maybe Day -> Int -> Text -> [Episode] -> Season
+ Network.API.TheMovieDB: ServiceError :: String -> Error
+ Network.API.TheMovieDB: TV :: ItemID -> Text -> Text -> [Genre] -> Double -> Text -> Maybe Day -> Maybe Day -> Int -> Int -> [Season] -> TV
+ Network.API.TheMovieDB: data Episode
+ Network.API.TheMovieDB: data Season
+ Network.API.TheMovieDB: data TV
+ Network.API.TheMovieDB: data TheMovieDB a
+ Network.API.TheMovieDB: episodeAirDate :: Episode -> Maybe Day
+ Network.API.TheMovieDB: episodeID :: Episode -> ItemID
+ Network.API.TheMovieDB: episodeName :: Episode -> Text
+ Network.API.TheMovieDB: episodeNumber :: Episode -> Int
+ Network.API.TheMovieDB: episodeOverview :: Episode -> Text
+ Network.API.TheMovieDB: episodeSeasonNumber :: Episode -> Int
+ Network.API.TheMovieDB: episodeStillPath :: Episode -> Text
+ Network.API.TheMovieDB: episodeStillURLs :: Configuration -> Episode -> [Text]
+ Network.API.TheMovieDB: fetchFullTVSeries :: ItemID -> TheMovieDB TV
+ Network.API.TheMovieDB: fetchMovie :: ItemID -> TheMovieDB Movie
+ Network.API.TheMovieDB: fetchTV :: ItemID -> TheMovieDB TV
+ Network.API.TheMovieDB: fetchTVSeason :: ItemID -> Int -> TheMovieDB Season
+ Network.API.TheMovieDB: runTheMovieDB :: Key -> TheMovieDB a -> IO (Either Error a)
+ Network.API.TheMovieDB: runTheMovieDBWithManager :: Manager -> Key -> TheMovieDB a -> IO (Either Error a)
+ Network.API.TheMovieDB: searchMovies :: Text -> TheMovieDB [Movie]
+ Network.API.TheMovieDB: searchTV :: Text -> TheMovieDB [TV]
+ Network.API.TheMovieDB: seasonAirDate :: Season -> Maybe Day
+ Network.API.TheMovieDB: seasonEpisodeCount :: Season -> Int
+ Network.API.TheMovieDB: seasonEpisodes :: Season -> [Episode]
+ Network.API.TheMovieDB: seasonID :: Season -> ItemID
+ Network.API.TheMovieDB: seasonNumber :: Season -> Int
+ Network.API.TheMovieDB: seasonPosterPath :: Season -> Text
+ Network.API.TheMovieDB: seasonPosterURLs :: Configuration -> Season -> [Text]
+ Network.API.TheMovieDB: tvFirstAirDate :: TV -> Maybe Day
+ Network.API.TheMovieDB: tvGenres :: TV -> [Genre]
+ Network.API.TheMovieDB: tvID :: TV -> ItemID
+ Network.API.TheMovieDB: tvLastAirDate :: TV -> Maybe Day
+ Network.API.TheMovieDB: tvName :: TV -> Text
+ Network.API.TheMovieDB: tvNumberOfEpisodes :: TV -> Int
+ Network.API.TheMovieDB: tvNumberOfSeasons :: TV -> Int
+ Network.API.TheMovieDB: tvOverview :: TV -> Text
+ Network.API.TheMovieDB: tvPopularity :: TV -> Double
+ Network.API.TheMovieDB: tvPosterPath :: TV -> Text
+ Network.API.TheMovieDB: tvPosterURLs :: Configuration -> TV -> [Text]
+ Network.API.TheMovieDB: tvSeasons :: TV -> [Season]
+ Network.API.TheMovieDB: type ItemID = Int
+ Network.API.TheMovieDB.Actions: config :: TheMovieDB Configuration
+ Network.API.TheMovieDB.Actions: fetchFullTVSeries :: ItemID -> TheMovieDB TV
+ Network.API.TheMovieDB.Actions: fetchMovie :: ItemID -> TheMovieDB Movie
+ Network.API.TheMovieDB.Actions: fetchTV :: ItemID -> TheMovieDB TV
+ Network.API.TheMovieDB.Actions: fetchTVSeason :: ItemID -> Int -> TheMovieDB Season
+ Network.API.TheMovieDB.Actions: searchMovies :: Text -> TheMovieDB [Movie]
+ Network.API.TheMovieDB.Actions: searchTV :: Text -> TheMovieDB [TV]
+ Network.API.TheMovieDB.Internal.Configuration: Configuration :: Text -> Text -> [Text] -> Configuration
+ Network.API.TheMovieDB.Internal.Configuration: cfgImageBaseURL :: Configuration -> Text
+ Network.API.TheMovieDB.Internal.Configuration: cfgImageSecBaseURL :: Configuration -> Text
+ Network.API.TheMovieDB.Internal.Configuration: cfgPosterSizes :: Configuration -> [Text]
+ Network.API.TheMovieDB.Internal.Configuration: data Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Binary Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Constructor C1_0Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Datatype D1Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance FromJSON Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Generic Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Selector S1_0_0Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Selector S1_0_1Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance Selector S1_0_2Configuration
+ Network.API.TheMovieDB.Internal.Configuration: instance ToJSON Configuration
+ Network.API.TheMovieDB.Internal.Configuration: posterURLs :: Configuration -> Text -> [Text]
+ Network.API.TheMovieDB.Internal.Date: (.::) :: Object -> Text -> Parser (Maybe Day)
+ Network.API.TheMovieDB.Internal.Date: Date :: Maybe Day -> Date
+ Network.API.TheMovieDB.Internal.Date: day :: Date -> Maybe Day
+ Network.API.TheMovieDB.Internal.Date: instance Eq Date
+ Network.API.TheMovieDB.Internal.Date: instance FromJSON Date
+ Network.API.TheMovieDB.Internal.Date: instance Show Date
+ Network.API.TheMovieDB.Internal.Date: newtype Date
+ Network.API.TheMovieDB.Internal.Date: parseDay :: Object -> Text -> Parser (Maybe Day)
+ Network.API.TheMovieDB.Internal.HTTP: apiGET :: Manager -> Key -> Path -> QueryText -> IO (Either Error Body)
+ Network.API.TheMovieDB.Internal.SearchResults: SearchResults :: [a] -> SearchResults a
+ Network.API.TheMovieDB.Internal.SearchResults: instance Eq a => Eq (SearchResults a)
+ Network.API.TheMovieDB.Internal.SearchResults: instance FromJSON a => FromJSON (SearchResults a)
+ Network.API.TheMovieDB.Internal.SearchResults: instance Show a => Show (SearchResults a)
+ Network.API.TheMovieDB.Internal.SearchResults: newtype SearchResults a
+ Network.API.TheMovieDB.Internal.SearchResults: searchResults :: SearchResults a -> [a]
+ Network.API.TheMovieDB.Internal.TheMovieDB: data TheMovieDB a
+ Network.API.TheMovieDB.Internal.TheMovieDB: getAndParse :: FromJSON a => Path -> QueryText -> TheMovieDB a
+ Network.API.TheMovieDB.Internal.TheMovieDB: instance Applicative TheMovieDB
+ Network.API.TheMovieDB.Internal.TheMovieDB: instance Functor TheMovieDB
+ Network.API.TheMovieDB.Internal.TheMovieDB: instance Monad TheMovieDB
+ Network.API.TheMovieDB.Internal.TheMovieDB: instance MonadIO TheMovieDB
+ Network.API.TheMovieDB.Internal.TheMovieDB: runTheMovieDB :: Key -> TheMovieDB a -> IO (Either Error a)
+ Network.API.TheMovieDB.Internal.TheMovieDB: runTheMovieDBWithManager :: Manager -> Key -> TheMovieDB a -> IO (Either Error a)
+ Network.API.TheMovieDB.Internal.TheMovieDB: runTheMovieDBWithRequestFunction :: RequestFunction -> TheMovieDB a -> IO (Either Error a)
+ Network.API.TheMovieDB.Internal.TheMovieDB: tmdbError :: Error -> TheMovieDB a
+ Network.API.TheMovieDB.Internal.TheMovieDB: type RequestFunction = Path -> QueryText -> IO (Either Error Body)
+ Network.API.TheMovieDB.Internal.Types: HttpExceptionError :: HttpException -> Error
+ Network.API.TheMovieDB.Internal.Types: InvalidKeyError :: Error
+ Network.API.TheMovieDB.Internal.Types: ResponseParseError :: String -> (Maybe ByteString) -> Error
+ Network.API.TheMovieDB.Internal.Types: ServiceError :: String -> Error
+ Network.API.TheMovieDB.Internal.Types: data Error
+ Network.API.TheMovieDB.Internal.Types: instance Show Error
+ Network.API.TheMovieDB.Internal.Types: type Body = ByteString
+ Network.API.TheMovieDB.Internal.Types: type ItemID = Int
+ Network.API.TheMovieDB.Internal.Types: type Key = Text
+ Network.API.TheMovieDB.Internal.Types: type Path = String
+ Network.API.TheMovieDB.Types.Episode: Episode :: ItemID -> Int -> Text -> Text -> Int -> Maybe Day -> Text -> Episode
+ Network.API.TheMovieDB.Types.Episode: data Episode
+ Network.API.TheMovieDB.Types.Episode: episodeAirDate :: Episode -> Maybe Day
+ Network.API.TheMovieDB.Types.Episode: episodeID :: Episode -> ItemID
+ Network.API.TheMovieDB.Types.Episode: episodeName :: Episode -> Text
+ Network.API.TheMovieDB.Types.Episode: episodeNumber :: Episode -> Int
+ Network.API.TheMovieDB.Types.Episode: episodeOverview :: Episode -> Text
+ Network.API.TheMovieDB.Types.Episode: episodeSeasonNumber :: Episode -> Int
+ Network.API.TheMovieDB.Types.Episode: episodeStillPath :: Episode -> Text
+ Network.API.TheMovieDB.Types.Episode: episodeStillURLs :: Configuration -> Episode -> [Text]
+ Network.API.TheMovieDB.Types.Episode: instance Eq Episode
+ Network.API.TheMovieDB.Types.Episode: instance FromJSON Episode
+ Network.API.TheMovieDB.Types.Episode: instance Show Episode
+ Network.API.TheMovieDB.Types.Genre: Genre :: ItemID -> Text -> Genre
+ Network.API.TheMovieDB.Types.Genre: data Genre
+ Network.API.TheMovieDB.Types.Genre: genreID :: Genre -> ItemID
+ Network.API.TheMovieDB.Types.Genre: genreName :: Genre -> Text
+ Network.API.TheMovieDB.Types.Genre: instance Eq Genre
+ Network.API.TheMovieDB.Types.Genre: instance FromJSON Genre
+ Network.API.TheMovieDB.Types.Genre: instance Show Genre
+ Network.API.TheMovieDB.Types.Movie: Movie :: ItemID -> Text -> Text -> [Genre] -> Double -> Text -> Maybe Day -> Bool -> Text -> Int -> Movie
+ Network.API.TheMovieDB.Types.Movie: data Movie
+ Network.API.TheMovieDB.Types.Movie: instance Eq Movie
+ Network.API.TheMovieDB.Types.Movie: instance FromJSON Movie
+ Network.API.TheMovieDB.Types.Movie: instance Show Movie
+ Network.API.TheMovieDB.Types.Movie: movieAdult :: Movie -> Bool
+ Network.API.TheMovieDB.Types.Movie: movieGenres :: Movie -> [Genre]
+ Network.API.TheMovieDB.Types.Movie: movieID :: Movie -> ItemID
+ Network.API.TheMovieDB.Types.Movie: movieIMDB :: Movie -> Text
+ Network.API.TheMovieDB.Types.Movie: movieOverview :: Movie -> Text
+ Network.API.TheMovieDB.Types.Movie: moviePopularity :: Movie -> Double
+ Network.API.TheMovieDB.Types.Movie: moviePosterPath :: Movie -> Text
+ Network.API.TheMovieDB.Types.Movie: moviePosterURLs :: Configuration -> Movie -> [Text]
+ Network.API.TheMovieDB.Types.Movie: movieReleaseDate :: Movie -> Maybe Day
+ Network.API.TheMovieDB.Types.Movie: movieRunTime :: Movie -> Int
+ Network.API.TheMovieDB.Types.Movie: movieTitle :: Movie -> Text
+ Network.API.TheMovieDB.Types.Season: Season :: ItemID -> Int -> Maybe Day -> Int -> Text -> [Episode] -> Season
+ Network.API.TheMovieDB.Types.Season: data Season
+ Network.API.TheMovieDB.Types.Season: instance Eq Season
+ Network.API.TheMovieDB.Types.Season: instance FromJSON Season
+ Network.API.TheMovieDB.Types.Season: instance Show Season
+ Network.API.TheMovieDB.Types.Season: seasonAirDate :: Season -> Maybe Day
+ Network.API.TheMovieDB.Types.Season: seasonEpisodeCount :: Season -> Int
+ Network.API.TheMovieDB.Types.Season: seasonEpisodes :: Season -> [Episode]
+ Network.API.TheMovieDB.Types.Season: seasonID :: Season -> ItemID
+ Network.API.TheMovieDB.Types.Season: seasonNumber :: Season -> Int
+ Network.API.TheMovieDB.Types.Season: seasonPosterPath :: Season -> Text
+ Network.API.TheMovieDB.Types.Season: seasonPosterURLs :: Configuration -> Season -> [Text]
+ Network.API.TheMovieDB.Types.TV: TV :: ItemID -> Text -> Text -> [Genre] -> Double -> Text -> Maybe Day -> Maybe Day -> Int -> Int -> [Season] -> TV
+ Network.API.TheMovieDB.Types.TV: data TV
+ Network.API.TheMovieDB.Types.TV: instance Eq TV
+ Network.API.TheMovieDB.Types.TV: instance FromJSON TV
+ Network.API.TheMovieDB.Types.TV: instance Show TV
+ Network.API.TheMovieDB.Types.TV: tvFirstAirDate :: TV -> Maybe Day
+ Network.API.TheMovieDB.Types.TV: tvGenres :: TV -> [Genre]
+ Network.API.TheMovieDB.Types.TV: tvID :: TV -> ItemID
+ Network.API.TheMovieDB.Types.TV: tvLastAirDate :: TV -> Maybe Day
+ Network.API.TheMovieDB.Types.TV: tvName :: TV -> Text
+ Network.API.TheMovieDB.Types.TV: tvNumberOfEpisodes :: TV -> Int
+ Network.API.TheMovieDB.Types.TV: tvNumberOfSeasons :: TV -> Int
+ Network.API.TheMovieDB.Types.TV: tvOverview :: TV -> Text
+ Network.API.TheMovieDB.Types.TV: tvPopularity :: TV -> Double
+ Network.API.TheMovieDB.Types.TV: tvPosterPath :: TV -> Text
+ Network.API.TheMovieDB.Types.TV: tvPosterURLs :: Configuration -> TV -> [Text]
+ Network.API.TheMovieDB.Types.TV: tvSeasons :: TV -> [Season]
- Network.API.TheMovieDB: Genre :: GenreID -> String -> Genre
+ Network.API.TheMovieDB: Genre :: ItemID -> Text -> Genre
- Network.API.TheMovieDB: Movie :: MovieID -> String -> String -> [Genre] -> Double -> String -> Day -> Bool -> String -> Int -> Movie
+ Network.API.TheMovieDB: Movie :: ItemID -> Text -> Text -> [Genre] -> Double -> Text -> Maybe Day -> Bool -> Text -> Int -> Movie
- Network.API.TheMovieDB: config :: Context -> IO Configuration
+ Network.API.TheMovieDB: config :: TheMovieDB Configuration
- Network.API.TheMovieDB: genreID :: Genre -> GenreID
+ Network.API.TheMovieDB: genreID :: Genre -> ItemID
- Network.API.TheMovieDB: genreName :: Genre -> String
+ Network.API.TheMovieDB: genreName :: Genre -> Text
- Network.API.TheMovieDB: movieID :: Movie -> MovieID
+ Network.API.TheMovieDB: movieID :: Movie -> ItemID
- Network.API.TheMovieDB: movieIMDB :: Movie -> String
+ Network.API.TheMovieDB: movieIMDB :: Movie -> Text
- Network.API.TheMovieDB: movieOverview :: Movie -> String
+ Network.API.TheMovieDB: movieOverview :: Movie -> Text
- Network.API.TheMovieDB: moviePosterPath :: Movie -> String
+ Network.API.TheMovieDB: moviePosterPath :: Movie -> Text
- Network.API.TheMovieDB: moviePosterURLs :: Configuration -> Movie -> [String]
+ Network.API.TheMovieDB: moviePosterURLs :: Configuration -> Movie -> [Text]
- Network.API.TheMovieDB: movieReleaseDate :: Movie -> Day
+ Network.API.TheMovieDB: movieReleaseDate :: Movie -> Maybe Day
- Network.API.TheMovieDB: movieTitle :: Movie -> String
+ Network.API.TheMovieDB: movieTitle :: Movie -> Text
- Network.API.TheMovieDB: type Key = String
+ Network.API.TheMovieDB: type Key = Text

Files

CHANGES view
@@ -1,7 +1,20 @@-2012-12-17  Peter Jones  <pjones@pmade.com>+-*- org -*-+#+title: Version History+#+startup: showall -	* Network/API/TheMovieDB/Util.hs: Use catch from Control.Exception-	since it doesn't seem to be in the GHC 7.6.1 Prelude.+* 1.0.0.0 (April 5, 2015) -	* themoviedb.cabal: Widen dependencies to compile with GHC 7.6.1.-	Release version 0.1.0.1.+  - Major rewrite of the interface+  - Removed utility functions for loading API keys+  - Added types and functions for TV series information+  - Fixed bug: https://github.com/pjones/themoviedb/issues/1+  - Fixed bug: https://github.com/pjones/themoviedb/issues/2++* 0.1.0.1 (December 17, 2012)++  - Small internal changes to compile with GHC 7.6.1.+  - Also compiles with GHC 7.4.2 and HP 2012.4.0.0.++* 0.1.0.0 (December 14, 2012)++  - Initial release.
LICENSE view
@@ -1,6 +1,6 @@ # Copyright and Authors -    Copyright (C) 2012 Peter Jones <pjones@pmade.com>+    Copyright (C) 2012-2015 Peter Jones <pjones@devalot.com>  # License (MIT) 
− Network/API/TheMovieDB.hs
@@ -1,73 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}--{- |--This library provides some data types and functions for fetching movie-metadata from <http://TheMovieDB.com>.  To use this library start by-requesting an API key from <http://docs.themoviedb.apiary.io>.--A typical workflow while using this library is:--  1. Place an API 'Key' inside a 'Context' using 'mkContext' or one of-     the utility functions in "Network.API.TheMovieDB.Util".--  2. Retrieve API 'Configuration' information using either the-     'config' function or the 'configErr' function.--  3. Search TheMovieDB using either the 'search' function or the-     'searchErr' function.--  4. Since the search functions don't return a full 'Movie' record-     follow them up with the 'fetch' function or the 'fetchErr'-     function.--This library also includes an example executable in the @example@-directory.--}-module Network.API.TheMovieDB (-  -- * Types-  -- ** TheMovieDB Metadata-  Configuration(),-  Movie(..),-  Genre(..),--  -- ** Context and Errors-  Context(),-  Error(..),--  -- ** Type Synonyms-  Key,-  MovieID,-  GenreID,-  SearchQuery,--  -- * API Functions-  -- ** Functions That Fail-  config,-  fetch,-  search,--  -- ** Functions That Return Errors-  configErr,-  fetchErr,-  searchErr,--  -- ** Utility functions-  mkContext,-  apiKey,-  moviePosterURLs-  ) where--import Network.API.TheMovieDB.Types-import Network.API.TheMovieDB.Config-import Network.API.TheMovieDB.Fetch-import Network.API.TheMovieDB.Search
− Network/API/TheMovieDB/Config.hs
@@ -1,23 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Config (configErr, config) where-import Network.API.TheMovieDB.Generic-import Network.API.TheMovieDB.Types---- | Fetch the API configuration information such as base URLs for--- movie posters.  Results in either an 'Error' or a 'Configuration'.-configErr :: Context -> IO (Either Error Configuration)-configErr ctx = getAndParse ctx "configuration" []---- | Fetch the API configuration information or fail.  For a function--- that returns an error instead of failing see 'configErr'.-config :: Context -> IO Configuration-config ctx = getOrFail $ configErr ctx
− Network/API/TheMovieDB/Fetch.hs
@@ -1,24 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Fetch (fetchErr, fetch) where-import Network.API.TheMovieDB.Generic-import Network.API.TheMovieDB.Types---- | Fetch the metadata for the movie with the given ID.  Returns--- either an 'Error' or a 'Movie'.-fetchErr :: Context -> MovieID -> IO (Either Error Movie)-fetchErr ctx mid = getAndParse ctx ("movie/" ++ show mid) []---- | Fetch the metadata for the movie with the given ID and fail if--- any errors are encountered along the way.  For a function that--- returns an error instead of failing see 'fetchErr'.-fetch :: Context -> MovieID -> IO Movie-fetch ctx mid = getOrFail $ fetchErr ctx mid
− Network/API/TheMovieDB/Generic.hs
@@ -1,27 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Generic (getAndParse, getOrFail) where-import Network.API.TheMovieDB.Types.Context-import Network.API.TheMovieDB.Types.API-import Data.Aeson---- Helper function to fetch and decode JSON.-getAndParse :: FromJSON a => Context -> Path -> Params -> IO (Either Error a)-getAndParse ctx path params =-  do httpResult <- ioFunc ctx (apiKey ctx) path params-     return $ case httpResult of-       Left  err  -> Left err-       Right body -> maybe (parseErr body) Right $ decode body-  where parseErr j = Left $ ParseError ("failed to parse JSON: " ++ show j)---- Helper function to fail or return the right part of an either.-getOrFail :: FromJSON a => IO (Either Error a) -> IO a-getOrFail toCheck = toCheck >>= either (fail . show) return
− Network/API/TheMovieDB/HTTP.hs
@@ -1,40 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.HTTP-       ( apiGET-       ) where--import Network.API.TheMovieDB.Types.API-import qualified Network.HTTP as H-import Network.URI---- The base URL for the version of the API we're using.-apiBaseURL :: String-apiBaseURL = "http://api.themoviedb.org/3/"---- Build a Network.HTTP.Request that can be used to access the API.-mkAPIRequest :: Key -> Path -> Params -> H.Request Body-mkAPIRequest key path params =-  case parseURI url of-    Nothing  -> error ("generated an invalid URI: " ++ url)-    Just uri -> H.insertHeaders headers $ request uri-  where query   = H.urlEncodeVars $ params ++ [("api_key", key)]-        url     = apiBaseURL ++ path ++ "?" ++ query-        headers = [H.Header H.HdrAccept "application/json"]-        request = H.mkRequest H.GET---- Build a URL and do an HTTP GET to TheMovieDB.-apiGET :: Key -> Path -> Params -> IO Response-apiGET key path params =-  do result <- H.simpleHTTP $ mkAPIRequest key path params-     return $ case result of-       Left err -> Left  $ NetworkError (show err)-       Right r  -> Right $ H.rspBody r
− Network/API/TheMovieDB/Search.hs
@@ -1,49 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Search (searchErr, search) where-import Control.Applicative-import Control.Monad (liftM)-import Data.Aeson-import Network.API.TheMovieDB.Generic-import Network.API.TheMovieDB.Types---- Internal wrapper to parse a list of movies from JSON.-newtype SearchResults = SearchResults-  {searchResults :: [Movie]} deriving (Eq, Show)--instance FromJSON SearchResults where-  parseJSON (Object v) = SearchResults <$> v .: "results"-  parseJSON _          = empty---- Internal function to translate search results to a list of movies.-fetchSearchResults :: Context -> SearchQuery -> IO (Either Error SearchResults)-fetchSearchResults ctx query = getAndParse ctx "search/movie" [("query", query)]---- | Search TheMovieDB using the given query string and return either--- an 'Error' if something went wrong or a list of matching 'Movie's.------ The movies returned will not have all their fields completely--- filled out, to get a complete record you'll need to follow this--- call up with a call to 'fetchErr' or 'fetch'.-searchErr :: Context -> SearchQuery -> IO (Either Error [Movie])-searchErr ctx query = liftM (fmap searchResults) $ fetchSearchResults ctx query---- | Search TheMovieDB using the given query string and return a list--- of movies.  This function fails if there are any errors.  For a--- function that returns an error instead of failing see 'searchErr'.------ The movies returned will not have all their fields completely--- filled out, to get a complete record you'll need to follow this--- call up with a call to 'fetchErr' or 'fetch'.-search :: Context -> SearchQuery -> IO [Movie]-search ctx query = getOrFail $ searchErr ctx query
− Network/API/TheMovieDB/Types.hs
@@ -1,31 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types-       ( Key-       , SearchQuery-       , Context(..)-       , Error(..)-       , ReleaseDate(..)-       , Configuration(..)-       , GenreID-       , Genre(..)-       , MovieID-       , Movie(..)-       , moviePosterURLs-       , mkContext-       ) where--import Network.API.TheMovieDB.Types.API-import Network.API.TheMovieDB.Types.Configuration-import Network.API.TheMovieDB.Types.Context-import Network.API.TheMovieDB.Types.Genre-import Network.API.TheMovieDB.Types.Movie-import Network.API.TheMovieDB.Types.ReleaseDate
− Network/API/TheMovieDB/Types/API.hs
@@ -1,41 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types.API-       ( Key-       , SearchQuery-       , Body-       , Response-       , Path-       , Params-       , IOFunc-       , Error(..)-       ) where--import qualified Data.ByteString.Lazy as B---- | Type for the API key issued by TheMovieDB.-type Key = String---- | A search query for TheMovieDB API.-type SearchQuery = String---- | Possible errors returned by the API.-data Error-  = NetworkError String -- ^ Network or HTTP error.-  | ParseError   String -- ^ Invalid or error response from the API.-  deriving (Eq, Show)---- Internal types.           -type Path     = String-type Params   = [(String, String)]-type Body     = B.ByteString-type Response = Either Error Body-type IOFunc   = Key -> Path -> Params -> IO Response           
− Network/API/TheMovieDB/Types/Configuration.hs
@@ -1,42 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types.Configuration-       ( Configuration(..)-       ) where--import Control.Applicative-import Data.Aeson-import Data.Aeson.Types (typeMismatch)---- | TheMovieDB API tries to preserve bandwidth by omitting--- information (such as full URLs for poster images) from most of the--- API calls.  Therefore in order to construct a complete URL for a--- movie poster you'll need to use the 'config' or 'configErr'--- function to retrieve API configuration information.------ A helper function is provided ('moviePosterURLs') that constructs a--- list of all poster URLs given a 'Movie' and 'Configuration'.-data Configuration = Configuration-  { cfgImageBaseURL    :: String   -- ^ The base URL for images.-  , cfgImageSecBaseURL :: String   -- ^ Base URL for secure images.-  , cfgPosterSizes     :: [String] -- ^ List of possible image sizes.-  }--instance FromJSON Configuration where-  parseJSON (Object v) =-    Configuration <$> images  "base_url"-                  <*> images  "secure_base_url"-                  <*> imagesM "poster_sizes" []-    where images key      = (v .: "images") >>= (.:  key)-          imagesM key def = (v .: "images") >>= (\x -> x .:? key .!= def)-  parseJSON _ = empty
− Network/API/TheMovieDB/Types/Context.hs
@@ -1,25 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types.Context (Context(..), mkContext) where-import Network.API.TheMovieDB.Types.API-import Network.API.TheMovieDB.HTTP---- | Data that needs to be given to the API functions.  Use the--- 'mkContext' function to turn an API 'Key' into a 'Context'.-data Context = Context-  { apiKey :: Key    -- ^ Extract an API 'Key' from a 'Context'.-  , ioFunc :: IOFunc -- ^ The internal function that does the API IO.-  }---- | Turns an API 'Key' into a 'Context' so that you can call the API--- functions such as 'fetch' and 'search'.-mkContext :: Key -> Context-mkContext key = Context key apiGET
− Network/API/TheMovieDB/Types/Genre.hs
@@ -1,29 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types.Genre (GenreID, Genre(..)) where-import Control.Applicative-import Data.Aeson-import Data.Aeson.Types (typeMismatch)---- | Type for representing unique genre IDs.-type GenreID = Int---- | Metadata for a genre.-data Genre = Genre-  { genreID   :: GenreID -- ^ TheMovieDB unique ID.-  , genreName :: String  -- ^ The name of the genre.-  } deriving (Eq, Show)--instance FromJSON Genre where-  parseJSON (Object v) = Genre <$> v .: "id" <*> v .: "name"-  parseJSON v          = typeMismatch "Genre" v
− Network/API/TheMovieDB/Types/Movie.hs
@@ -1,65 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types.Movie-       (MovieID, Movie(..), moviePosterURLs) where--import Control.Applicative-import Control.Monad (liftM)-import Data.Aeson-import Data.Aeson.Types (typeMismatch)-import Data.Time (Day(..))-import Network.API.TheMovieDB.Types.Configuration (Configuration(..))-import Network.API.TheMovieDB.Types.Genre (Genre(..))-import Network.API.TheMovieDB.Types.ReleaseDate (ReleaseDate(..))---- | Type for representing unique movie IDs.-type MovieID = Int---- | Metadata for a movie.------   * The 'moviePosterPath' is an incomplete URL.  To construct a---     complete URL you'll need to use the 'Configuration' type.  You---     can also use 'moviePosterURLs'.-data Movie = Movie-  { movieID          :: MovieID -- ^ TheMovieDB unique ID.-  , movieTitle       :: String  -- ^ The name/title of the movie.-  , movieOverview    :: String  -- ^ Short plot summary.-  , movieGenres      :: [Genre] -- ^ List of 'Genre's.-  , moviePopularity  :: Double  -- ^ Popularity ranking.-  , moviePosterPath  :: String  -- ^ Incomplete URL for poster image.-                                -- See 'moviePosterURLs'.-  , movieReleaseDate :: Day     -- ^ Movie release date.-  , movieAdult       :: Bool    -- ^ TheMovieDB adult movie flag.-  , movieIMDB        :: String  -- ^ IMDB.com ID.-  , movieRunTime     :: Int     -- ^ Movie length in minutes.-  } deriving (Eq, Show)--instance FromJSON Movie where-  parseJSON (Object v) =-    Movie <$> v .:  "id"-          <*> v .:  "title"-          <*> v .:? "overview"    .!= ""-          <*> v .:? "genres"      .!= []-          <*> v .:? "popularity"  .!= 0.0-          <*> v .:? "poster_path" .!= ""-          <*> liftM releaseDate (v .: "release_date")-          <*> v .:? "adult"       .!= False-          <*> v .:? "imdb_id"     .!= ""-          <*> v .:? "runtime"     .!= 0-  parseJSON _ = empty---- | Return a list of URLs for all possible movie posters.-moviePosterURLs :: Configuration -> Movie -> [String]-moviePosterURLs c m = [base ++ size ++ poster | size <- cfgPosterSizes c]-  where base   = cfgImageBaseURL c-        poster = moviePosterPath m
− Network/API/TheMovieDB/Types/ReleaseDate.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}-module Network.API.TheMovieDB.Types.ReleaseDate (ReleaseDate(..)) where-import Data.Aeson-import Data.Aeson.Types (typeMismatch)-import Data.Text (unpack)-import Data.Time (parseTime, Day(..))-import System.Locale (defaultTimeLocale)---- Type wrapper for Day to parse a movie's release date.-newtype ReleaseDate = ReleaseDate-  {releaseDate :: Day} deriving (Eq, Show)---- Parse release dates in JSON.-instance FromJSON ReleaseDate where-  parseJSON (String t) =-    case parseTime defaultTimeLocale "%Y-%m-%d" (unpack t) of-      Just d -> return $ ReleaseDate d-      _      -> fail "could not parse release_date"-  parseJSON v = typeMismatch "ReleaseDate" v
− Network/API/TheMovieDB/Util.hs
@@ -1,84 +0,0 @@-{---This file is part of the Haskell package themoviedb. It is subject to-the license terms in the LICENSE file found in the top-level directory-of this distribution and at git://pmade.com/themoviedb/LICENSE. No-part of themoviedb package, including this file, may be copied,-modified, propagated, or distributed except according to the terms-contained in the LICENSE file.---}--{- |--This module provides some helper functions for loading an API 'Key' or-complete 'Context' from the user's environment or from the file-system.  Currently only POSIX systems are supported but patches are-welcome!---}-module Network.API.TheMovieDB.Util (loadKey, loadContext) where-import Prelude hiding (catch)-import Control.Exception (catch)-import Control.Monad (liftM, msum)-import Data.Char (isSpace)-import Network.API.TheMovieDB.HTTP-import Network.API.TheMovieDB.Types-import System.Environment (getEnv)-import System.IO.Error (isDoesNotExistError)-import System.Posix.Files-import System.Posix.User---- Fetch the value of an environment variable without an exception.-getEnvMaybe :: String -> IO (Maybe String)-getEnvMaybe n = env `catch` err-  where env   = liftM Just $ getEnv n-        err e = if isDoesNotExistError e-                  then return Nothing-                  else ioError e---- Expand @~@ at the front of a file path.-expandFile :: FilePath -> IO FilePath-expandFile ('~':rest) = do userID <- getEffectiveUserID-                           entry  <- getUserEntryForID userID-                           return  $ homeDirectory entry ++ rest-expandFile dir        = return dir---- Return the contents of a file if it exists, otherwise Nothing.-readFileMaybe :: FilePath -> IO (Maybe String)-readFileMaybe n = do realName <- expandFile n-                     exists   <- fileExist realName-                     if exists-                       then liftM Just (readFirstLine realName)-                       else return Nothing-  where readFirstLine = liftM skipSpace . readFile-        skipSpace     = filter $ not . isSpace----- | Fetch an API 'Key' from the first of:------     * @TMDB_KEY@ environment variable------     * @XDG_CONFIG_HOME/tmdbkey@ file (where XDG_CONFIG_HOME is---       an environment variable)------     * @~\/.config\/tmdbkey@ file------     * @~/.tmdbkey@ file------ If the key can't be loaded from any of those places the result will--- be 'Nothing'.-loadKey :: IO (Maybe Key)-loadKey = liftM msum . sequence $ [env, xdgConfig, config, home]-  where env       = getEnvMaybe "TMDB_KEY"-        config    = readFileMaybe "~/.config/tmdbkey"-        home      = readFileMaybe "~/.tmdbkey"-        xdgConfig = do dir <- getEnvMaybe "XDG_CONFIG_HOME"-                       case dir of-                         Nothing -> return Nothing-                         Just d  -> readFileMaybe (d ++ "/tmdbkey")---- | Uses 'loadKey' to fetch an API 'Key' and wrap it into a default--- 'Context' using 'mkContext'.-loadContext :: IO (Maybe Context)-loadContext = liftM (fmap mkContext) loadKey
README.md view
@@ -5,7 +5,7 @@ request an API key from TheMovieDB.  Follow the directions on the [API][] page. -[TheMovieDB]: http://themoviedb.com+[TheMovieDB]: http://themoviedb.org [API]: http://docs.themoviedb.apiary.io  # Documentation@@ -20,9 +20,3 @@ Surprising, I know.  [example]: https://github.com/pjones/themoviedb/blob/master/example/Main.hs--# Warning--This library currently uses HTTP and not *HTTPS* while sending your-API key to TheMovieDB.  In order to fix this we'll need to use-something other than `Network.HTTP`.  Patches welcome!
TODO.org view
@@ -1,5 +1,13 @@ #+title: TheMovieDB To-do List -* Features-** TODO Switch to HTTPS/SSL/TLS+* Milestone: Release 1.0.0.0+** DONE Switch to HTTPS/SSL/TLS (via http-conduit)+   CLOSED: [2015-03-20 Fri 18:02]    - This requires using something other than Network.HTTP.+** DONE Remove key utility functions+   CLOSED: [2015-03-20 Fri 18:02]+** DONE Ensure parsing errors are handled properly+   CLOSED: [2015-03-20 Fri 18:02]+** TODO Prune unneeded dependencies in the cabal file+** TODO Clean up documentation+** TODO Clean up tests
− Test/TheMovieDB.hs
@@ -1,67 +0,0 @@--- http://www.haskell.org/haskellwiki/HUnit_1.0_User%27s_Guide-import Control.Monad (liftM, when)-import qualified Data.ByteString.Lazy.Char8 as B-import Network.API.TheMovieDB-import Network.API.TheMovieDB.Types.API-import Network.API.TheMovieDB.Types.Context-import System.Exit (exitFailure)-import Data.List (isPrefixOf)-import Data.Time (fromGregorian)--import Test.HUnit-  (Test(..), Counts(..),-   runTestTT, assertFailure,-   assertEqual, assertBool)--loadGoodMovieFile :: Key -> Path -> Params -> IO Response-loadGoodMovieFile _ _ _ =-  liftM Right $ fmap B.pack (readFile "Test/movie-good.json")--loadBadMovieFile :: Key -> Path -> Params -> IO Response-loadBadMovieFile _ _ _ =-  liftM Right $ fmap B.pack (readFile "Test/movie-bad.json")--fakeNetworkError :: Key -> Path -> Params -> IO Response-fakeNetworkError _ _ _ = return $ Left $ NetworkError "fake outage"--goodMovieFieldsTest = TestCase $ do-  result <- fetchErr (Context "" loadGoodMovieFile) 0-  case result of-    Left    err -> assertFailure $ show err-    Right movie -> do-      assertEqual "movieID" 105 (movieID movie)-      assertEqual "movieTitle" "Back to the Future" (movieTitle movie)-      assertBool "movieOverview" $-        "Eighties teenager" `isPrefixOf` movieOverview movie-      assertEqual "movieGenres" 4 (length $ movieGenres movie)-      assertEqual "moviePopularity" 80329.688 $ moviePopularity movie-      assertEqual "moviePosterPath" "/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg" $-        moviePosterPath movie-      assertEqual "movieReleaseDate" (fromGregorian 1985 7 3) $-        movieReleaseDate movie-      assertBool "movieAdult" $ not (movieAdult movie)-      assertEqual "movieIMDB" "tt0088763" $ movieIMDB movie-      assertEqual "movieRunTime" 116 $ movieRunTime movie--badMovieJSONTest = TestCase $ do-  result <- fetchErr (Context "" loadBadMovieFile) 0-  case result of-    Left (ParseError e) -> return ()-    _                   -> assertFailure "JSON should have been bad"--shouldHaveNetworkErrorTest = TestCase $ do-  result <- fetchErr (Context "" fakeNetworkError) 0-  case result of-    Left (NetworkError e) -> return ()-    _                     -> assertFailure "should have network error"---- Why can't this be automatic?-unitTests = TestList-  [ TestLabel "All movie fields are parsed" goodMovieFieldsTest-  , TestLabel "Failure with bad JSON" badMovieJSONTest-  , TestLabel "Propagate network failure" shouldHaveNetworkErrorTest-  ]--main = do counts <- runTestTT unitTests-          let bad = errors counts + failures counts-          when (bad > 0) exitFailure
− Test/config-good.json
@@ -1,1 +0,0 @@-{"images":{"base_url":"http://cf2.imgobject.com/t/p/","secure_base_url":"https://d1zm4mmpsrckwj.cloudfront.net/t/p/","poster_sizes":["w92","w154","w185","w342","w500","original"],"backdrop_sizes":["w300","w780","w1280","original"],"profile_sizes":["w45","w185","h632","original"],"logo_sizes":["w45","w92","w154","w185","w300","w500","original"]},"change_keys":["adult","also_known_as","alternative_titles","biography","birthday","budget","cast","character_names","crew","deathday","general","genres","homepage","images","imdb_id","name","original_title","overview","plot_keywords","production_companies","production_countries","releases","revenue","runtime","spoken_languages","status","tagline","title","trailers","translations"]}
− Test/movie-bad.json
@@ -1,1 +0,0 @@-{"adult":1,"backdrop_path":"/x4N74cycZvKu5k3KDERJay4ajR3.jpg","belongs_to_collection":{"id":264,"name":"Back to the Future Collection","poster_path":"/qdVjh8PWGWqbssVqdwZIgr7Ckts.jpg","backdrop_path":"/c9C9Pg2QctyjZHRmS0P8rZg1OTA.jpg"},"budget":19000000,"genres":[{"id":12,"name":"Adventure"},{"id":35,"name":"Comedy"},{"id":878,"name":"Science Fiction"},{"id":10751,"name":"Family"}],"homepage":"http://www.bttfmovie.com/","id":105,"imdb_id":"tt0088763","original_title":"Back to the Future","overview":"Eighties teenager Marty McFly is accidentally sent back in time to 1955, inadvertently disrupting his parents' first meeting and attracting his mother's romantic interest. Marty must repair the damage to history by rekindling his parents' romance and - with the help of his eccentric inventor friend Doc Brown - returning to 1985.","popularity":80329.688,"poster_path":"/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg","production_companies":[{"name":"Universal Pictures","id":33},{"name":"Amblin Entertainment","id":56}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1985-07-03","revenue":381109762,"runtime":116,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"status":"Released","tagline":"He's the only kid ever to get into trouble before he was born.","title":"Back to the Future","vote_average":8.9,"vote_count":123}
− Test/movie-good.json
@@ -1,1 +0,0 @@-{"adult":false,"backdrop_path":"/x4N74cycZvKu5k3KDERJay4ajR3.jpg","belongs_to_collection":{"id":264,"name":"Back to the Future Collection","poster_path":"/qdVjh8PWGWqbssVqdwZIgr7Ckts.jpg","backdrop_path":"/c9C9Pg2QctyjZHRmS0P8rZg1OTA.jpg"},"budget":19000000,"genres":[{"id":12,"name":"Adventure"},{"id":35,"name":"Comedy"},{"id":878,"name":"Science Fiction"},{"id":10751,"name":"Family"}],"homepage":"http://www.bttfmovie.com/","id":105,"imdb_id":"tt0088763","original_title":"Back to the Future","overview":"Eighties teenager Marty McFly is accidentally sent back in time to 1955, inadvertently disrupting his parents' first meeting and attracting his mother's romantic interest. Marty must repair the damage to history by rekindling his parents' romance and - with the help of his eccentric inventor friend Doc Brown - returning to 1985.","popularity":80329.688,"poster_path":"/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg","production_companies":[{"name":"Universal Pictures","id":33},{"name":"Amblin Entertainment","id":56}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1985-07-03","revenue":381109762,"runtime":116,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"status":"Released","tagline":"He's the only kid ever to get into trouble before he was born.","title":"Back to the Future","vote_average":8.9,"vote_count":123}
− Test/search-good.json
@@ -1,1 +0,0 @@-{"page":1,"results":[{"adult":false,"backdrop_path":"/x4N74cycZvKu5k3KDERJay4ajR3.jpg","id":105,"original_title":"Back to the Future","release_date":"1985-07-03","poster_path":"/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg","popularity":80329.688,"title":"Back to the Future","vote_average":8.9,"vote_count":123},{"adult":false,"backdrop_path":"/snLFUFzWUZCdYkAbhZlaGJxiY3I.jpg","id":165,"original_title":"Back to the Future Part II","release_date":"1989-11-20","poster_path":"/k5dzvCQkXU2CAhLtlj9BHE7xmyK.jpg","popularity":11893.083,"title":"Back to the Future Part II","vote_average":8.4,"vote_count":56},{"adult":false,"backdrop_path":"/pP2a4MBQbIeZMbErBWMjxTT5Npb.jpg","id":196,"original_title":"Back to the Future Part III","release_date":"1990-05-25","poster_path":"/6DmgPTZYaug7QNDjOhUDWyjOQDl.jpg","popularity":11975.265,"title":"Back to the Future Part III","vote_average":8.4,"vote_count":59},{"adult":false,"backdrop_path":"/9gSVZIxWvFoEo03WylTHDSzpUvf.jpg","id":20803,"original_title":"Иван Васильевич меняет профессию","release_date":"1973-01-01","poster_path":"/f1y86qb0GAcUn0Vc7TQzglePxj8.jpg","popularity":0.576,"title":"Ivan Vasilievich: Back to the Future","vote_average":8.8,"vote_count":3},{"adult":false,"backdrop_path":"/4ag7bLf1edaEKuxKDQ5WB820PJw.jpg","id":12889,"original_title":"Futurama: The Beast with a Billion Backs","release_date":"2008-06-24","poster_path":"/8d1ljps74inNnsSP36iRjFpMbT2.jpg","popularity":3.424,"title":"Futurama: The Beast with a Billion Backs","vote_average":7.6,"vote_count":4},{"adult":false,"backdrop_path":null,"id":48499,"original_title":"Back to the Future Part 2 Behind-the-Scenes Special Presentation","release_date":"1989-01-01","poster_path":null,"popularity":0.011,"title":"Back to the Future Part 2 Behind-the-Scenes Special Presentation","vote_average":0.0,"vote_count":0},{"adult":false,"backdrop_path":null,"id":48501,"original_title":"Back to the Future: Making the Trilogy","release_date":"2002-01-01","poster_path":null,"popularity":0.01,"title":"Back to the Future: Making the Trilogy","vote_average":0.0,"vote_count":0},{"adult":false,"backdrop_path":null,"id":113268,"original_title":"The Secrets of the Back to the Future Trilogy","release_date":"1990-06-12","poster_path":"/sXxrpdsuyDDam1Jk2OSKTTEubXR.jpg","popularity":0.006,"title":"The Secrets of the Back to the Future Trilogy","vote_average":0.0,"vote_count":0}],"total_pages":1,"total_results":8}
example/Main.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE OverloadedStrings #-}+ {-  This file is part of the Haskell package themoviedb. It is subject to@@ -8,59 +10,145 @@ contained in the LICENSE file.  -}-module Main where-import Control.Monad (when)-import Data.List (intercalate)-import Data.Maybe (isNothing, fromJust)-import Data.Time (toGregorian, formatTime)++--------------------------------------------------------------------------------+module Main (main) where++--------------------------------------------------------------------------------+import Control.Monad.IO.Class (liftIO)+import Data.Maybe (listToMaybe)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time (formatTime) import Network.API.TheMovieDB-import Network.API.TheMovieDB.Util (loadContext) import System.Environment (getArgs)-import System.Exit (exitFailure)+import System.Exit (exitFailure, exitSuccess) import System.Locale (defaultTimeLocale) import Text.Printf (printf) --- Simple banner style printing of a 'Movie'+--------------------------------------------------------------------------------+-- | Simple banner style printing of a 'Movie'. printMovieHeader :: Movie -> IO () printMovieHeader m =-  printf "%8d: %s (%s)\n" (movieID m) (movieTitle m) year-  where year = formatTime defaultTimeLocale "%Y" $ movieReleaseDate m+  printf "%8d: %s (%s)\n" (movieID m) (T.unpack $ movieTitle m) year+  where year = case movieReleaseDate m of+                 Just d  -> formatTime defaultTimeLocale "%Y" d+                 Nothing -> "----" --- Print more detailed information for a 'Movie'.+--------------------------------------------------------------------------------+-- | Simple banner style printing of a 'TV'.+printTVHeader :: TV -> IO ()+printTVHeader t =+  printf "%8d: %s (%s)\n" (tvID t) (T.unpack $ tvName t) year+  where year = case tvFirstAirDate t of+                 Just d  -> formatTime defaultTimeLocale "%Y" d+                 Nothing -> "----"++--------------------------------------------------------------------------------+-- | Print more detailed information for a 'Movie'. printMovieDetails :: Movie -> IO () printMovieDetails m =   do putStrLn $ "Popularity: " ++ show (moviePopularity m)      putStrLn $ strJoin $ map genreName (movieGenres m)      putStrLn "-- "-     putStrLn $ movieOverview m-  where strJoin = intercalate ", "+     putStrLn $ T.unpack (movieOverview m)+  where strJoin = T.unpack . T.intercalate ", " --- Well, of course, it's main!-main :: IO ()-main = do args <- getArgs-          contextM <- loadContext-          when (isNothing contextM) $ error "failed to load API key"-          let context = fromJust contextM-          cfg <- config context-          case args of-            ["key"]           -> putStrLn $ apiKey context+--------------------------------------------------------------------------------+-- | Print information about a season.+printSeason :: Season -> IO ()+printSeason s =+  do putStrLn $ "Season " ++ show (seasonNumber s) ++ " " ++ year+     mapM_ (putStrLn . episode) $ seasonEpisodes s -            ["search", query] -> do movies <- search context query-                                    mapM_ printMovieHeader movies+  where+    episode e = "\tE" ++ show (episodeNumber e) ++ " "+                ++ T.unpack (episodeName e) -            ["fetch", mID]    -> do movie <- fetch context (read mID)-                                    printMovieHeader movie-                                    mapM_ putStrLn $ moviePosterURLs cfg movie-                                    printMovieDetails movie+    year = case seasonAirDate s of+             Just d  -> formatTime defaultTimeLocale "%Y" d+             Nothing -> "----" -            _                 -> do putStrLn usage-                                    exitFailure+--------------------------------------------------------------------------------+-- | Search for movies with a query string.+searchAndListMovies :: Text -> TheMovieDB ()+searchAndListMovies query = do+  movies <- searchMovies query+  liftIO $ mapM_ printMovieHeader movies -  where usage = "Usage: tmdb {search QUERY|fetch ID|key}\n\n" +++--------------------------------------------------------------------------------+-- | Find a specific movie given its ID.+fetchAndPrintMovie :: ItemID -> TheMovieDB ()+fetchAndPrintMovie mid = do+  cfg <- config+  movie <- fetchMovie mid++  liftIO $ do+    printMovieHeader movie+    mapM_ (putStrLn . T.unpack) (moviePosterURLs cfg movie)+    printMovieDetails movie++--------------------------------------------------------------------------------+-- | Search for TV series using a query string.+searchAndListTV :: Text -> TheMovieDB ()+searchAndListTV query = do+  tvs <- searchTV query+  liftIO $ mapM_ printTVHeader tvs++--------------------------------------------------------------------------------+-- | Find a specific TV series given its ID.+fetchAndPrintTV :: ItemID -> TheMovieDB ()+fetchAndPrintTV tid = do+  tv <- fetchTV tid+  liftIO $ printTVHeader tv+  liftIO $ mapM_ printSeason (tvSeasons tv)++--------------------------------------------------------------------------------+fetchAndPrintSeason :: ItemID -> Int -> TheMovieDB ()+fetchAndPrintSeason t s = do+  season <- fetchTVSeason t s+  liftIO $ printSeason season++--------------------------------------------------------------------------------+fetchAndPrintFullTV :: ItemID -> TheMovieDB ()+fetchAndPrintFullTV t = do+  tv <- fetchFullTVSeries t+  liftIO $ printTVHeader tv+  liftIO $ mapM_ printSeason (tvSeasons tv)++--------------------------------------------------------------------------------+-- | Low budget command line parsing and dispatch.+main :: IO ()+main = do+  args <- getArgs+  let key = maybe T.empty T.pack (listToMaybe args)++  result <- runTheMovieDB key $+    case args of+      [_, "search", query]  -> searchAndListMovies (T.pack query)+      [_, "fetch", mid]     -> fetchAndPrintMovie (read mid)+      [_, "tsearch", query] -> searchAndListTV (T.pack query)+      [_, "tfetch", tid]    -> fetchAndPrintTV (read tid)+      [_, "season", t, s]   -> fetchAndPrintSeason (read t) (read s)+      [_, "tfull", t]       -> fetchAndPrintFullTV (read t)+      _                     -> liftIO (putStrLn usage >> exitFailure)++  case result of+    Left err -> print err >> exitFailure+    Right _  -> exitSuccess++  where usage = "Usage: tmdb key {search|fetch|tsearch|tfetch|season|tfull}\n\n" ++                 "Description:\n" ++                 "       search: search TheMovieDB\n" ++-                "       fetch:  fetch info about one movie\n" ++-                "       key:    output your API key\n\n" +++                "        fetch: fetch info about one movie\n" +++                "      tsearch: search for TV series\n" +++                "       tfetch: fetch TV series by ID\n" +++                "       season: fetch one season by TV ID and season num\n" +++                "        tfull: pull everything about TV series by ID\n" ++                 "Examples:\n" ++-                "       tmdb search \"back to the future\"\n" ++-                "       tmdb fetch 105"+                "       tmdb KEY search \"back to the future\"\n" +++                "       tmdb KEY fetch 105\n" +++                "       tmdb KEY tsearch Firefly\n" +++                "       tmdb KEY tfetch 1437\n" +++                "       tmdb KEY season 1437 1\n" +++                "       tmdb KEY tfull 1437\n"
+ src/Network/API/TheMovieDB.hs view
@@ -0,0 +1,78 @@+{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++{- |++This library provides some data types and functions for fetching movie+metadata from <http://TheMovieDB.org>.  To use this library start by+requesting an API key from <http://docs.themoviedb.apiary.io>.++Example:++@+import Network.API.TheMovieDB++main :: IO ()+main = do+  -- The API key assigned to you (as a 'Text' value).+  let key = "your API key"++  -- The 'fetch' function will get a 'Movie' record based on its ID.+  result <- runTheMovieDB key (fetchMovie 9340)++  -- Do something with the result (or error).+  putStrLn (show result)+@++This library also includes an example executable in the @example@+directory.+-}+module Network.API.TheMovieDB+  ( -- * Types+    Movie (..)+  , TV (..)+  , Season (..)+  , Episode (..)+  , Genre (..)+  , Error (..)+  , ItemID+  , Key++    -- * API Functions+  , TheMovieDB+  , runTheMovieDB+  , runTheMovieDBWithManager+  , searchMovies+  , fetchMovie+  , searchTV+  , fetchTV+  , fetchTVSeason+  , fetchFullTVSeries++    -- * Utility Types and Functions+  , Configuration+  , config+  , moviePosterURLs+  , tvPosterURLs+  , seasonPosterURLs+  , episodeStillURLs+  ) where++--------------------------------------------------------------------------------+import Network.API.TheMovieDB.Actions+import Network.API.TheMovieDB.Internal.Configuration+import Network.API.TheMovieDB.Internal.TheMovieDB+import Network.API.TheMovieDB.Internal.Types+import Network.API.TheMovieDB.Types.Episode+import Network.API.TheMovieDB.Types.Genre+import Network.API.TheMovieDB.Types.Movie+import Network.API.TheMovieDB.Types.Season+import Network.API.TheMovieDB.Types.TV
+ src/Network/API/TheMovieDB/Actions.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Actions+       ( searchMovies+       , fetchMovie+       , searchTV+       , fetchTV+       , fetchTVSeason+       , fetchFullTVSeries+       , config+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Text (Text)+import Network.API.TheMovieDB.Internal.Configuration+import Network.API.TheMovieDB.Internal.SearchResults+import Network.API.TheMovieDB.Internal.TheMovieDB+import Network.API.TheMovieDB.Internal.Types+import Network.API.TheMovieDB.Types.Movie+import Network.API.TheMovieDB.Types.Season+import Network.API.TheMovieDB.Types.TV++--------------------------------------------------------------------------------+-- | Search TheMovieDB using the given query string.+--+-- The movies returned will not have all their fields completely+-- filled out, to get a complete record you'll need to follow this+-- call up with a call to 'fetchMovie'.+searchMovies :: Text -> TheMovieDB [Movie]+searchMovies query = searchResults <$> search+  where search = getAndParse "search/movie" [("query", Just query)]++--------------------------------------------------------------------------------+-- | Fetch the metadata for the 'Movie' with the given ID.+fetchMovie :: ItemID -- ^ TheMovieDB ID for the movie.+           -> TheMovieDB Movie+fetchMovie mid = getAndParse ("movie/" ++ show mid) []++--------------------------------------------------------------------------------+-- | Search TheMovieDB for matching 'TV' series.+--+-- The 'TV' values returned from this function will be partial+-- records.  The only fields that will be available are 'tvID',+-- 'tvName', 'tvPosterPath', 'tvPopularity', and possibly+-- 'tvFirstAirDate'.+--+-- To get full 'TV' records you need to follow this function with a+-- call to 'fetchTV' using the desired 'tvID' value.+searchTV :: Text -> TheMovieDB [TV]+searchTV query = searchResults <$> search+  where search = getAndParse "search/tv" [("query", Just query)]++--------------------------------------------------------------------------------+-- | Fetch metadata for a 'TV' series given its TheMovieDB ID.  The+-- metadata for 'Season's listed in the TV series will not have+-- complete 'Episode' information.+--+-- After calling this function you should call 'fetchTVSeason' to fill+-- in the 'Episode' metadata, or just begin with 'fetchFullTVSeries'.+fetchTV :: ItemID -- ^ TheMovieDB ID for the TV series.+        -> TheMovieDB TV+fetchTV i = getAndParse ("tv/" ++ show i) []++--------------------------------------------------------------------------------+-- | Fetch metadata for a 'Season', including all 'Episode's.+fetchTVSeason :: ItemID         -- ^ TheMovieDB ID for the TV series.+              -> Int            -- ^ Season number (not season ID).+              -> TheMovieDB Season+fetchTVSeason i n = getAndParse ("tv/" ++ show i ++ "/season/" ++ show n) []++--------------------------------------------------------------------------------+-- | Fetch full metadata for a 'TV' series, including all seasons and+-- episodes.+--+-- This function will make multiple HTTP requests to TheMovieDB API.+fetchFullTVSeries :: ItemID -- ^ TheMovieDB ID for the TV series.+                  -> TheMovieDB TV+fetchFullTVSeries i = do+  tv      <- fetchTV i+  seasons <- mapM (fetchTVSeason i . seasonNumber) (tvSeasons tv)+  return tv {tvSeasons = seasons}++--------------------------------------------------------------------------------+-- | Fetch the API configuration information such as base URLs for+-- movie posters.  The resulting configuration value should be cached+-- and only requested every few days.+config :: TheMovieDB Configuration+config = getAndParse "configuration" []
+ src/Network/API/TheMovieDB/Internal/Configuration.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE OverloadedStrings,  DeriveGeneric #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+-- | Internal configuration information for TheMovieDB API.+module Network.API.TheMovieDB.Internal.Configuration+       ( Configuration (..)+       , posterURLs+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Binary+import Data.Monoid+import Data.Text (Text)+import Data.Text.Binary ()+import GHC.Generics (Generic)++--------------------------------------------------------------------------------+-- | TheMovieDB API tries to preserve bandwidth by omitting+-- information (such as full URLs for poster images) from most of the+-- API calls.  Therefore in order to construct a complete URL for a+-- movie poster you'll need to use the 'config' function to retrieve+-- API configuration information.+--+-- A helper function is provided ('moviePosterURLs') that constructs a+-- list of all poster URLs given a 'Movie' and 'Configuration'.+--+-- According to the API documentation for TheMovieDB, you should cache+-- the 'Configuration' value and only request it every few days.+-- Therefore, it is an instance of the 'Binary' class so it can be+-- serialized to and from a cache file on disk.+--+-- Alternatively, the 'FromJSON' and 'ToJSON' instances can be used to+-- cache the 'Configuration' value.+data Configuration = Configuration+  { cfgImageBaseURL    :: Text   -- ^ The base URL for images.+  , cfgImageSecBaseURL :: Text   -- ^ Base URL for secure images.+  , cfgPosterSizes     :: [Text] -- ^ List of possible image sizes.+  } deriving (Generic)++--------------------------------------------------------------------------------+instance Binary Configuration++--------------------------------------------------------------------------------+instance FromJSON Configuration where+  parseJSON (Object v) =+    Configuration <$> images  "base_url"+                  <*> images  "secure_base_url"+                  <*> imagesM "poster_sizes" []+    where images key      = (v .: "images") >>= (.:  key)+          imagesM key def = (v .: "images") >>= (\x -> x .:? key .!= def)+  parseJSON _ = empty++--------------------------------------------------------------------------------+instance ToJSON Configuration where+  toJSON c = object [ "images" .= object+    [ "base_url"        .= cfgImageBaseURL c+    , "secure_base_url" .= cfgImageSecBaseURL c+    , "poster_sizes"    .= cfgPosterSizes c+    ]]+++--------------------------------------------------------------------------------+-- | Return a list of URLs for all possible posters posters.+posterURLs :: Configuration -> Text -> [Text]+posterURLs c p = [cfgImageBaseURL c <> size <> p | size <- cfgPosterSizes c]
+ src/Network/API/TheMovieDB/Internal/Date.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+-- | Utility type for working with release dates.+module Network.API.TheMovieDB.Internal.Date+       ( Date (..)+       , parseDay+       , (.::)+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Aeson.Types (Parser, typeMismatch)+import Data.Text (Text)+import qualified Data.Text as T+import Data.Time (parseTime, Day(..))+import System.Locale (defaultTimeLocale)++--------------------------------------------------------------------------------+-- | A simple type wrapper around 'Day' in order to parse a movie's+-- release date, which may be null or empty.+newtype Date = Date {day :: Maybe Day} deriving (Eq, Show)++--------------------------------------------------------------------------------+-- | Aeson helper function to parse dates in TheMovieDB API.+parseDay :: Object -> Text -> Parser (Maybe Day)+parseDay v key =+  do m <- date+     return $ case m of+       Nothing -> Nothing+       Just d  -> day d+  where+    date :: Parser (Maybe Date)+    date = v .:? key <|> pure Nothing++--------------------------------------------------------------------------------+(.::) :: Object -> Text -> Parser (Maybe Day)+(.::) = parseDay++--------------------------------------------------------------------------------+-- | Parse release dates in JSON.+instance FromJSON Date where+  parseJSON (Null) = return (Date Nothing)+  parseJSON (String t)+    | T.null t  = return (Date Nothing)+    | otherwise = case parseTime defaultTimeLocale "%Y-%m-%d" (T.unpack t) of+                    Just d -> return $ Date (Just d)+                    _      -> fail "could not parse TheMovieDB date field"+  parseJSON v = typeMismatch "Date" v
+ src/Network/API/TheMovieDB/Internal/HTTP.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE  OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+-- | Simple interface for fetching JSON files from the API via HTTP.+module Network.API.TheMovieDB.Internal.HTTP+       ( apiGET+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Control.Exception+import Network.API.TheMovieDB.Internal.Types+import qualified Network.HTTP.Client as HC+import Network.HTTP.Types++--------------------------------------------------------------------------------+-- | The base URL for the version of the API we're using.+apiBaseURL :: String+apiBaseURL = "https://api.themoviedb.org/3/"++--------------------------------------------------------------------------------+-- | Build a HTTP request that can be used to access the API.+mkAPIRequest :: Key -> Path -> QueryText -> IO HC.Request+mkAPIRequest key path params = do+  req <- HC.parseUrl (apiBaseURL ++ path)++  return $ req { HC.queryString    = query+               , HC.requestHeaders = headers+               }++  where+    query     = renderQuery False . queryTextToQuery $ allParams+    allParams = params ++ [("api_key", Just key)]+    headers   = [("Accept", "application/json")]++--------------------------------------------------------------------------------+-- | Build a URL and do an HTTP GET to TheMovieDB.+apiGET :: HC.Manager -> Key -> Path -> QueryText -> IO (Either Error Body)+apiGET manager key path params = do+  request  <- mkAPIRequest key path params+  response <- catch (Right <$> HC.httpLbs request manager) httpError++  return $ case response of+    Left  e -> Left e+    Right r+      | statusIsSuccessful (HC.responseStatus r) -> Right (HC.responseBody r)+      | otherwise -> Left (ServiceError . show $ HC.responseStatus r)++  where+    httpError :: HC.HttpException -> IO (Either Error (HC.Response Body))+    httpError e = return $ case e of+      HC.StatusCodeException (Status 401 _) _ _ -> Left InvalidKeyError+      _                                         -> Left (HttpExceptionError e)
+ src/Network/API/TheMovieDB/Internal/SearchResults.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+-- | Utility type for processing movie search results.+module Network.API.TheMovieDB.Internal.SearchResults+       ( SearchResults (..)+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson++--------------------------------------------------------------------------------+-- | Internal wrapper to parse a list of results from JSON.+newtype SearchResults a = SearchResults {searchResults :: [a]}+                          deriving (Eq, Show)++--------------------------------------------------------------------------------+instance (FromJSON a) => FromJSON (SearchResults a) where+  parseJSON (Object v) = SearchResults <$> v .: "results"+  parseJSON _          = empty
+ src/Network/API/TheMovieDB/Internal/TheMovieDB.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Internal.TheMovieDB+       ( TheMovieDB+       , RequestFunction+       , getAndParse+       , tmdbError+       , runTheMovieDB+       , runTheMovieDBWithManager+       , runTheMovieDBWithRequestFunction+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Control.Monad.Reader+import Control.Monad.Trans.Either+import Data.Aeson+import Network.API.TheMovieDB.Internal.HTTP+import Network.API.TheMovieDB.Internal.Types+import Network.HTTP.Client (Manager, withManager)+import Network.HTTP.Client.TLS (tlsManagerSettings)+import Network.HTTP.Types++--------------------------------------------------------------------------------+-- | The type for functions that make requests to the API (or pretend+-- to make a request for testing purposes).+type RequestFunction = (Path -> QueryText -> IO (Either Error Body))++--------------------------------------------------------------------------------+-- | Result type for operations involving TheMovieDB API.+newtype TheMovieDB a =+  TheMovieDB {unTMDB :: ReaderT RequestFunction (EitherT Error IO) a}+  deriving (Functor, Applicative, Monad, MonadIO)++--------------------------------------------------------------------------------+-- | Helper function for making a request using the request function+-- stashed away in the reader monad.+runRequest :: Path -> QueryText -> TheMovieDB Body+runRequest path params = TheMovieDB $ do+  func   <- ask+  result <- liftIO (func path params)+  lift (hoistEither result)++--------------------------------------------------------------------------------+-- | Helper function to preform an HTTP GET and decode the JSON result.+getAndParse :: FromJSON a => Path -> QueryText -> TheMovieDB a+getAndParse path params = do+  body <- runRequest path params++  case eitherDecode body of+    Left  e -> tmdbError $ ResponseParseError ("bad JSON: " ++ e) (Just body)+    Right a -> return a++--------------------------------------------------------------------------------+-- | Create a 'TheMovieDB' value representing an error.+tmdbError :: Error -> TheMovieDB a+tmdbError = TheMovieDB . lift . left++--------------------------------------------------------------------------------+-- | Execute requests for TheMovieDB with the given API key and produce+-- either an error or a result.+--+-- This version creates a temporary 'Manager' using+-- 'tlsManagerSettings'.  If you want to use an existing 'Manager' you+-- should use 'runTheMovieDBWithManager' instead.+runTheMovieDB+  :: Key                        -- ^ The API key to include in all requests.+  -> TheMovieDB a               -- ^ The API calls to make.+  -> IO (Either Error a)        -- ^ Response or error.+runTheMovieDB k t =+  withManager tlsManagerSettings (\m -> runTheMovieDBWithManager m k t)++--------------------------------------------------------------------------------+-- | Execute requests for TheMovieDB with the given API key and produce+-- either an error or a result.+--+-- This version allows you to provide a 'Manager' value which should+-- have been created to allow TLS requests (e.g., with 'tlsManagerSettings').+runTheMovieDBWithManager+  :: Manager                    -- ^ The 'Manager' to use.+  -> Key                        -- ^ The API key to include in all requests.+  -> TheMovieDB a               -- ^ The API calls to make.+  -> IO (Either Error a)        -- ^ Response or error.+runTheMovieDBWithManager m k = runTheMovieDBWithRequestFunction (apiGET m k)++--------------------------------------------------------------------------------+-- | Low-level interface for executing a 'TheMovieDB' using the given+-- request function.+runTheMovieDBWithRequestFunction+  :: RequestFunction            -- ^ The request function to use.+  -> TheMovieDB a               -- ^ The API calls to make.+  -> IO (Either Error a)        -- ^ Response.+runTheMovieDBWithRequestFunction f t = runEitherT $ runReaderT (unTMDB t) f
+ src/Network/API/TheMovieDB/Internal/Types.hs view
@@ -0,0 +1,64 @@+{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+-- | Simple types and synonyms, mostly to make the type signatures+-- easier to read.+module Network.API.TheMovieDB.Internal.Types+       ( ItemID+       , Key+       , Body+       , Path+       , Error (..)+       ) where++--------------------------------------------------------------------------------+import Data.ByteString.Lazy (ByteString)+import Data.Text (Text)+import Network.HTTP.Client (HttpException)++--------------------------------------------------------------------------------+-- | Type to represent IDs used by the API.+type ItemID = Int++--------------------------------------------------------------------------------+-- | Type for the API key issued by TheMovieDB.+type Key = Text++--------------------------------------------------------------------------------+-- | URL path.+type Path = String+            +--------------------------------------------------------------------------------            +-- | HTTP body.            +type Body = ByteString++--------------------------------------------------------------------------------+-- | Possible errors returned by the API.+data Error = InvalidKeyError+             -- ^ Missing or invalid API key.  Make sure you are using+             -- a valid API key issued by+             -- <https://www.themoviedb.org/faq/api>.++           | HttpExceptionError HttpException+             -- ^ An exception relating to HTTP was thrown while+             -- interacting with the API.+             +           | ServiceError String+             -- ^ The HTTP interaction with the API service did not+             -- result in a successful response.  Information about+             -- the failure is encoded in the String.+             +           | ResponseParseError String (Maybe ByteString)+             -- ^ Invalid or error response from the API.+             +           deriving (Show)+
+ src/Network/API/TheMovieDB/Types/Episode.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Types.Episode+       ( Episode (..)+       , episodeStillURLs+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Aeson.Types (typeMismatch)+import Data.Text (Text)+import Data.Time (Day (..))+import Network.API.TheMovieDB.Internal.Configuration+import Network.API.TheMovieDB.Internal.Date+import Network.API.TheMovieDB.Internal.Types++--------------------------------------------------------------------------------+-- | Metadata for a TV Episode.+--+--   * The 'episodeStillPath' field is an incomplete URL.  To+--     construct a complete URL you'll need to use the 'Configuration'+--     type and the 'episodeStillURLs' helper function.+data Episode = Episode+  { episodeID :: ItemID+    -- ^ TheMovieDB unique ID.++  , episodeNumber :: Int+    -- ^ Episode sequence number.++  , episodeName :: Text+    -- ^ Episode name.++  , episodeOverview :: Text+    -- ^ Short description of the episode.++  , episodeSeasonNumber :: Int+    -- ^ The season this episode belongs to.++  , episodeAirDate :: Maybe Day+    -- ^ Episode air date, if it ever aired.++  , episodeStillPath :: Text+    -- ^ Incomplete URL to a still image from the episode.  See the+    -- 'episodeStillURLs' function for more information.++  } deriving (Eq, Show)++--------------------------------------------------------------------------------+instance FromJSON Episode where+  parseJSON (Object v) =+    Episode <$> v .:  "id"+            <*> v .: "episode_number"+            <*> v .: "name"+            <*> v .: "overview"+            <*> v .:  "season_number" .!= 0+            <*> v .:: "air_date"+            <*> v .:  "still_path"    .!= ""+  parseJSON v = typeMismatch "Episode" v++--------------------------------------------------------------------------------+-- | Return a list of URLs for all possible episode still images.+episodeStillURLs :: Configuration -> Episode -> [Text]+episodeStillURLs c e = posterURLs c (episodeStillPath e)
+ src/Network/API/TheMovieDB/Types/Genre.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Types.Genre+       ( Genre(..)+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Aeson.Types (typeMismatch)+import Data.Text (Text)+import Network.API.TheMovieDB.Internal.Types++--------------------------------------------------------------------------------+-- | Metadata for a genre.+data Genre = Genre+  { genreID   :: ItemID -- ^ TheMovieDB unique ID.+  , genreName :: Text   -- ^ The name of the genre.+  } deriving (Eq, Show)++--------------------------------------------------------------------------------+instance FromJSON Genre where+  parseJSON (Object v) = Genre <$> v .: "id" <*> v .: "name"+  parseJSON v          = typeMismatch "Genre" v
+ src/Network/API/TheMovieDB/Types/Movie.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Types.Movie+       ( Movie(..)+       , moviePosterURLs+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Aeson.Types (typeMismatch)+import Data.Text (Text)+import Data.Time (Day (..))+import Network.API.TheMovieDB.Internal.Configuration+import Network.API.TheMovieDB.Internal.Date+import Network.API.TheMovieDB.Internal.Types+import Network.API.TheMovieDB.Types.Genre++--------------------------------------------------------------------------------+-- | Metadata for a movie.+--+--   * The 'moviePosterPath' field is an incomplete URL.  To construct+--     a complete URL you'll need to use the 'Configuration' type and+--     the 'moviePosterURLs' helper function.+data Movie = Movie+  { movieID :: ItemID+    -- ^ TheMovieDB unique ID.++  , movieTitle :: Text+    -- ^ The name/title of the movie.++  , movieOverview :: Text+    -- ^ Short plot summary.++  , movieGenres :: [Genre]+    -- ^ List of 'Genre's.++  , moviePopularity :: Double+    -- ^ Popularity ranking.++  , moviePosterPath :: Text+    -- ^ Incomplete URL for poster image.  See 'moviePosterURLs'.++  , movieReleaseDate :: Maybe Day+    -- ^ Movie release date.  (Movie may not have been released yet.)++  , movieAdult :: Bool+    -- ^ TheMovieDB adult movie flag.++  , movieIMDB :: Text+    -- ^ IMDB.com ID.++  , movieRunTime :: Int+    -- ^ Movie length in minutes.++  } deriving (Eq, Show)++--------------------------------------------------------------------------------+instance FromJSON Movie where+  parseJSON (Object v) =+    Movie <$> v .:  "id"+          <*> v .:  "title"+          <*> v .:? "overview"    .!= ""+          <*> v .:? "genres"      .!= []+          <*> v .:? "popularity"  .!= 0.0+          <*> v .:? "poster_path" .!= ""+          <*> v .:: "release_date"+          <*> v .:? "adult"       .!= False+          <*> v .:? "imdb_id"     .!= ""+          <*> v .:? "runtime"     .!= 0+  parseJSON v = typeMismatch "Movie" v++--------------------------------------------------------------------------------+-- | Return a list of URLs for all possible movie posters.+moviePosterURLs :: Configuration -> Movie -> [Text]+moviePosterURLs c m = posterURLs c (moviePosterPath m)
+ src/Network/API/TheMovieDB/Types/Season.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Types.Season+       ( Season (..)+       , seasonPosterURLs+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Aeson.Types (typeMismatch)+import Data.Text (Text)+import Data.Time (Day (..))+import Network.API.TheMovieDB.Internal.Configuration+import Network.API.TheMovieDB.Internal.Date+import Network.API.TheMovieDB.Internal.Types+import Network.API.TheMovieDB.Types.Episode++--------------------------------------------------------------------------------+-- | Metadata for a TV Season.+--+--   * The 'seasonPosterPath' field is an incomplete URL.  To+--     construct a complete URL you'll need to use the 'Configuration'+--     type and the 'seasonPosterURLs' helper function.+data Season = Season+  { seasonID :: ItemID+    -- ^ TheMovieDB unique ID.++  , seasonNumber :: Int+    -- ^ Season sequence number.  Remember that season 0 is sometimes+    -- used to hold unreleased/unaired episodes.++  , seasonAirDate :: Maybe Day+    -- ^ The date this season began to air, if ever.++  , seasonEpisodeCount :: Int+    -- ^ Number of episodes in this season.++  , seasonPosterPath :: Text+    -- ^ Incomplete URL for poster image.  See 'seasonPosterURLs'.++  , seasonEpisodes :: [Episode]+    -- ^ List of 'Episode's.++  } deriving (Eq, Show)++--------------------------------------------------------------------------------+instance FromJSON Season where+  parseJSON (Object v) =+    Season <$> v .:  "id"+           <*> v .:  "season_number" .!= 0+           <*> v .:: "air_date"+           <*> v .:? "episode_count" .!= 0+           <*> v .:  "poster_path"   .!= ""+           <*> v .:? "episodes"      .!= []+  parseJSON v = typeMismatch "Season" v++--------------------------------------------------------------------------------+-- | Return a list of URLs for all possible season posters.+seasonPosterURLs :: Configuration -> Season -> [Text]+seasonPosterURLs c s = posterURLs c (seasonPosterPath s)
+ src/Network/API/TheMovieDB/Types/TV.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE OverloadedStrings #-}++{-++This file is part of the Haskell package themoviedb. It is subject to+the license terms in the LICENSE file found in the top-level directory+of this distribution and at git://pmade.com/themoviedb/LICENSE. No+part of themoviedb package, including this file, may be copied,+modified, propagated, or distributed except according to the terms+contained in the LICENSE file.++-}++--------------------------------------------------------------------------------+module Network.API.TheMovieDB.Types.TV+       ( TV(..)+       , tvPosterURLs+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import Data.Aeson+import Data.Aeson.Types (typeMismatch)+import Data.Text (Text)+import Data.Time (Day (..))+import Network.API.TheMovieDB.Internal.Configuration+import Network.API.TheMovieDB.Internal.Date+import Network.API.TheMovieDB.Internal.Types+import Network.API.TheMovieDB.Types.Genre+import Network.API.TheMovieDB.Types.Season++--------------------------------------------------------------------------------+-- | Metadata for a TV series.+--+--   * The 'tvPosterPath' field is an incomplete URL.  To construct a+--     complete URL you'll need to use the 'Configuration' type and the+--    'tvPosterURLs' helper function.+data TV = TV+  { tvID :: ItemID+    -- ^ TheMovieDB unique ID.++  , tvName :: Text+    -- ^ The name of the TV series.++  , tvOverview :: Text+    -- ^ Short description of the TV series.++  , tvGenres :: [Genre]+    -- ^ List of 'Genre's.++  , tvPopularity :: Double+    -- ^ Popularity ranking.++  , tvPosterPath :: Text+    -- ^ Incomplete URL for poster image.  See 'tvPosterURLs'.++  , tvFirstAirDate :: Maybe Day+    -- ^ Air date for first episode.++  , tvLastAirDate :: Maybe Day+    -- ^ Air date for last episode.++  , tvNumberOfSeasons :: Int+    -- ^ Number of seasons for the TV series.++  , tvNumberOfEpisodes :: Int+    -- ^ Total number of episodes for all seasons.++  , tvSeasons :: [Season]+    -- ^ Information about each season.+    --+    -- The number of elements in this list may not match+    -- 'tvNumberOfSeasons'.  Information about special episodes and+    -- unreleased episodes are usually kept in a 'Season' listed as+    -- season 0.  Therefore, the first element in this list might not+    -- be season 1.++  } deriving (Eq, Show)++--------------------------------------------------------------------------------+instance FromJSON TV where+  parseJSON (Object v) =+    TV <$> v .:  "id"+       <*> v .:  "name"+       <*> v .:? "overview"           .!= ""+       <*> v .:? "genres"             .!= []+       <*> v .:? "popularity"         .!= 0.0+       <*> v .:? "poster_path"        .!= ""+       <*> v .:: "first_air_date"+       <*> v .:: "last_air_date"+       <*> v .:? "number_of_seasons"  .!= 0+       <*> v .:? "number_of_episodes" .!= 0+       <*> v .:? "seasons"            .!= []+  parseJSON v = typeMismatch "TV" v++--------------------------------------------------------------------------------+-- | Return a list of URLs for all possible TV posters.+tvPosterURLs :: Configuration -> TV -> [Text]+tvPosterURLs c m = posterURLs c (tvPosterPath m)
+ test/Main.hs view
@@ -0,0 +1,15 @@+--------------------------------------------------------------------------------+module Main (main) where++--------------------------------------------------------------------------------+import qualified MovieTest as M+import qualified TVTest as T+import Test.Tasty++--------------------------------------------------------------------------------+tests :: TestTree+tests = testGroup "Tests" [ M.tests, T.tests ]++--------------------------------------------------------------------------------+main :: IO ()+main = defaultMain tests
+ test/MovieTest.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+module MovieTest (tests) where++--------------------------------------------------------------------------------+import Test.Tasty+import Test.Tasty.HUnit+import TestHelper++--------------------------------------------------------------------------------+import qualified Data.Text as T+import Network.API.TheMovieDB+import Network.API.TheMovieDB.Internal.TheMovieDB++--------------------------------------------------------------------------------+testSearchMovie :: Assertion+testSearchMovie = do+  movies <- fakeTMDB "test/search-good.json" (searchMovies T.empty)+  assertEqual "length" 8 (length movies)++--------------------------------------------------------------------------------+testMoviePosterURLs :: Assertion+testMoviePosterURLs = do+  cfg   <- fakeTMDB "test/config-good.json" config+  movie <- fakeTMDB "test/movie-good.json" (fetchMovie 0)+  let urls = moviePosterURLs cfg movie+      poster = "http://cf2.imgobject.com/t/p/w92/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg"+  assertEqual "length" 6 (length urls)+  assertEqual "url" poster (head urls)++--------------------------------------------------------------------------------+goodMovieFieldsTest :: Assertion+goodMovieFieldsTest = do+  movie <- fakeTMDB "test/movie-good.json" (fetchMovie 0)+  assertEqual "movieID" 105 (movieID movie)+  assertEqual "movieTitle" "Back to the Future" (movieTitle movie)+  assertBool "movieOverview" $ "Eighties teenager" `T.isPrefixOf` movieOverview movie+  assertEqual "movieGenres" 4 (length $ movieGenres movie)+  assertEqual "moviePopularity" 80329.688 $ moviePopularity movie+  assertEqual "moviePosterPath" "/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg" $ moviePosterPath movie+  assertEqual "movieReleaseDate" (mkDay 1985 7 3) (movieReleaseDate movie)+  assertBool "movieAdult" $ not (movieAdult movie)+  assertEqual "movieIMDB" "tt0088763" $ movieIMDB movie+  assertEqual "movieRunTime" 116 $ movieRunTime movie++--------------------------------------------------------------------------------+badMovieJSONTest :: Assertion+badMovieJSONTest = do+  result <- runTheMovieDBWithRequestFunction+            (fileRequest "test/movie-bad.json") (fetchMovie 0)++  case result of+    Left (ResponseParseError _ _) -> return ()+    _                             -> assertFailure "JSON should have been bad"++--------------------------------------------------------------------------------+shouldHaveNetworkErrorTest :: Assertion+shouldHaveNetworkErrorTest = do+  result <- runTheMovieDBWithRequestFunction fakeNetworkError (fetchMovie 0)+  case result of+    Left (ServiceError _) -> return ()+    _                     -> assertFailure "should have network error"++--------------------------------------------------------------------------------+tests :: TestTree+tests = testGroup "Movies"+  [ testCase "All movie fields are parsed" goodMovieFieldsTest+  , testCase "Failure with bad JSON" badMovieJSONTest+  , testCase "Propagate network failure" shouldHaveNetworkErrorTest+  , testCase "Searching movies" testSearchMovie+  , testCase "Movie poster URLs" testMoviePosterURLs+  ]
+ test/TVTest.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+module TVTest (tests) where++--------------------------------------------------------------------------------+import Test.Tasty+import Test.Tasty.HUnit+import TestHelper++--------------------------------------------------------------------------------+import qualified Data.Text as T+import Network.API.TheMovieDB++--------------------------------------------------------------------------------+testTVBasicFields :: TV -> Assertion+testTVBasicFields tv = do+  assertEqual "id"     1437                               (tvID   tv)+  assertEqual "name"   "Firefly"                          (tvName tv)+  assertEqual "poster" "/mWNadwBZIx8NyEw4smGftYtHHrE.jpg" (tvPosterPath tv)+  assertEqual "aired"  (mkDay 2002 12 20)                 (tvFirstAirDate tv)++--------------------------------------------------------------------------------+testSearchTV :: Assertion+testSearchTV = do+  tvs <- fakeTMDB "test/search-tv-good.json" (searchTV T.empty)+  assertEqual "length" 1 (length tvs)+  mapM_ testTVBasicFields tvs++--------------------------------------------------------------------------------+testFetchTV :: Assertion+testFetchTV = do+  tv <- fakeTMDB "test/tv-good.json" (fetchTV 0)+  testTVBasicFields tv+  assertEqual "genres"     2  (length $ tvGenres tv)+  assertEqual "seasons"    1  (tvNumberOfSeasons tv)+  assertEqual "episodes"   13 (tvNumberOfEpisodes tv)+  assertEqual "season len" 2  (length $ tvSeasons tv)++--------------------------------------------------------------------------------+testFetchSeason :: Assertion+testFetchSeason = do+  season <- fakeTMDB "test/season-good.json" (fetchTVSeason 0 0)+  assertEqual "length" 14 (length $ seasonEpisodes season)++--------------------------------------------------------------------------------+testSeasonPoster :: Assertion+testSeasonPoster = do+  cfg    <- fakeTMDB "test/config-good.json" config+  season <- fakeTMDB "test/season-good.json" (fetchTVSeason 0 0)++  let expect = "http://cf2.imgobject.com/t/p/w92/2dxsbVMoxsYH0Pta2mbFjF7mhHr.jpg"+      urls   = seasonPosterURLs cfg season++  assertEqual "length" 6 (length urls)+  assertEqual "poster" expect (head urls)++--------------------------------------------------------------------------------+testTVPoster :: Assertion+testTVPoster = do+  cfg <- fakeTMDB "test/config-good.json" config+  tv  <- fakeTMDB "test/tv-good.json" (fetchTV 0)++  let expect = "http://cf2.imgobject.com/t/p/w92/mWNadwBZIx8NyEw4smGftYtHHrE.jpg"+      urls   = tvPosterURLs cfg tv++  assertEqual "length" 6 (length urls)+  assertEqual "poster" expect (head urls)++--------------------------------------------------------------------------------+tests :: TestTree+tests = testGroup "TV"+  [ testCase "Search fields" testSearchTV+  , testCase "Fetch fields"  testFetchTV+  , testCase "Season fields" testFetchSeason+  , testCase "Season Poster" testSeasonPoster+  , testCase "TV Poster"     testTVPoster+  ]
+ test/TestHelper.hs view
@@ -0,0 +1,36 @@+--------------------------------------------------------------------------------+module TestHelper+       ( fakeTMDB+       , fileRequest+       , fakeNetworkError+       , mkDay+       ) where++--------------------------------------------------------------------------------+import Control.Applicative+import qualified Data.ByteString.Lazy as B+import Data.Time (Day (..), fromGregorian)+import Network.API.TheMovieDB.Internal.TheMovieDB+import Network.API.TheMovieDB.Internal.Types+import Test.Tasty.HUnit++--------------------------------------------------------------------------------+fakeTMDB :: FilePath -> TheMovieDB a -> IO a+fakeTMDB path m = do+  result <- runTheMovieDBWithRequestFunction (fileRequest path) m++  case result of+    Left  e -> assertFailure (show e) >> fail (show e)+    Right a -> return a++--------------------------------------------------------------------------------+fileRequest :: FilePath -> RequestFunction+fileRequest path _ _ = Right <$> B.readFile path++--------------------------------------------------------------------------------+fakeNetworkError :: RequestFunction+fakeNetworkError _ _ = return $ Left $ ServiceError "fake outage"++--------------------------------------------------------------------------------+mkDay :: Integer -> Int -> Int -> Maybe Day+mkDay y m d = Just (fromGregorian y m d)
+ test/config-good.json view
@@ -0,0 +1,1 @@+{"images":{"base_url":"http://cf2.imgobject.com/t/p/","secure_base_url":"https://d1zm4mmpsrckwj.cloudfront.net/t/p/","poster_sizes":["w92","w154","w185","w342","w500","original"],"backdrop_sizes":["w300","w780","w1280","original"],"profile_sizes":["w45","w185","h632","original"],"logo_sizes":["w45","w92","w154","w185","w300","w500","original"]},"change_keys":["adult","also_known_as","alternative_titles","biography","birthday","budget","cast","character_names","crew","deathday","general","genres","homepage","images","imdb_id","name","original_title","overview","plot_keywords","production_companies","production_countries","releases","revenue","runtime","spoken_languages","status","tagline","title","trailers","translations"]}
+ test/movie-bad.json view
@@ -0,0 +1,1 @@+{"adult":1,"backdrop_path":"/x4N74cycZvKu5k3KDERJay4ajR3.jpg","belongs_to_collection":{"id":264,"name":"Back to the Future Collection","poster_path":"/qdVjh8PWGWqbssVqdwZIgr7Ckts.jpg","backdrop_path":"/c9C9Pg2QctyjZHRmS0P8rZg1OTA.jpg"},"budget":19000000,"genres":[{"id":12,"name":"Adventure"},{"id":35,"name":"Comedy"},{"id":878,"name":"Science Fiction"},{"id":10751,"name":"Family"}],"homepage":"http://www.bttfmovie.com/","id":105,"imdb_id":"tt0088763","original_title":"Back to the Future","overview":"Eighties teenager Marty McFly is accidentally sent back in time to 1955, inadvertently disrupting his parents' first meeting and attracting his mother's romantic interest. Marty must repair the damage to history by rekindling his parents' romance and - with the help of his eccentric inventor friend Doc Brown - returning to 1985.","popularity":80329.688,"poster_path":"/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg","production_companies":[{"name":"Universal Pictures","id":33},{"name":"Amblin Entertainment","id":56}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1985-07-03","revenue":381109762,"runtime":116,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"status":"Released","tagline":"He's the only kid ever to get into trouble before he was born.","title":"Back to the Future","vote_average":8.9,"vote_count":123}
+ test/movie-good.json view
@@ -0,0 +1,1 @@+{"adult":false,"backdrop_path":"/x4N74cycZvKu5k3KDERJay4ajR3.jpg","belongs_to_collection":{"id":264,"name":"Back to the Future Collection","poster_path":"/qdVjh8PWGWqbssVqdwZIgr7Ckts.jpg","backdrop_path":"/c9C9Pg2QctyjZHRmS0P8rZg1OTA.jpg"},"budget":19000000,"genres":[{"id":12,"name":"Adventure"},{"id":35,"name":"Comedy"},{"id":878,"name":"Science Fiction"},{"id":10751,"name":"Family"}],"homepage":"http://www.bttfmovie.com/","id":105,"imdb_id":"tt0088763","original_title":"Back to the Future","overview":"Eighties teenager Marty McFly is accidentally sent back in time to 1955, inadvertently disrupting his parents' first meeting and attracting his mother's romantic interest. Marty must repair the damage to history by rekindling his parents' romance and - with the help of his eccentric inventor friend Doc Brown - returning to 1985.","popularity":80329.688,"poster_path":"/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg","production_companies":[{"name":"Universal Pictures","id":33},{"name":"Amblin Entertainment","id":56}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1985-07-03","revenue":381109762,"runtime":116,"spoken_languages":[{"iso_639_1":"en","name":"English"}],"status":"Released","tagline":"He's the only kid ever to get into trouble before he was born.","title":"Back to the Future","vote_average":8.9,"vote_count":123}
+ test/search-good.json view
@@ -0,0 +1,1 @@+{"page":1,"results":[{"adult":false,"backdrop_path":"/x4N74cycZvKu5k3KDERJay4ajR3.jpg","id":105,"original_title":"Back to the Future","release_date":"1985-07-03","poster_path":"/pTpxQB1N0waaSc3OSn0e9oc8kx9.jpg","popularity":80329.688,"title":"Back to the Future","vote_average":8.9,"vote_count":123},{"adult":false,"backdrop_path":"/snLFUFzWUZCdYkAbhZlaGJxiY3I.jpg","id":165,"original_title":"Back to the Future Part II","release_date":"1989-11-20","poster_path":"/k5dzvCQkXU2CAhLtlj9BHE7xmyK.jpg","popularity":11893.083,"title":"Back to the Future Part II","vote_average":8.4,"vote_count":56},{"adult":false,"backdrop_path":"/pP2a4MBQbIeZMbErBWMjxTT5Npb.jpg","id":196,"original_title":"Back to the Future Part III","release_date":"1990-05-25","poster_path":"/6DmgPTZYaug7QNDjOhUDWyjOQDl.jpg","popularity":11975.265,"title":"Back to the Future Part III","vote_average":8.4,"vote_count":59},{"adult":false,"backdrop_path":"/9gSVZIxWvFoEo03WylTHDSzpUvf.jpg","id":20803,"original_title":"Иван Васильевич меняет профессию","release_date":"","poster_path":"/f1y86qb0GAcUn0Vc7TQzglePxj8.jpg","popularity":0.576,"title":"Ivan Vasilievich: Back to the Future","vote_average":8.8,"vote_count":3},{"adult":false,"backdrop_path":"/4ag7bLf1edaEKuxKDQ5WB820PJw.jpg","id":12889,"original_title":"Futurama: The Beast with a Billion Backs","release_date":"2008-06-24","poster_path":"/8d1ljps74inNnsSP36iRjFpMbT2.jpg","popularity":3.424,"title":"Futurama: The Beast with a Billion Backs","vote_average":7.6,"vote_count":4},{"adult":false,"backdrop_path":null,"id":48499,"original_title":"Back to the Future Part 2 Behind-the-Scenes Special Presentation","release_date":"1989-01-01","poster_path":null,"popularity":0.011,"title":"Back to the Future Part 2 Behind-the-Scenes Special Presentation","vote_average":0.0,"vote_count":0},{"adult":false,"backdrop_path":null,"id":48501,"original_title":"Back to the Future: Making the Trilogy","release_date":"2002-01-01","poster_path":null,"popularity":0.01,"title":"Back to the Future: Making the Trilogy","vote_average":0.0,"vote_count":0},{"adult":false,"backdrop_path":null,"id":113268,"original_title":"The Secrets of the Back to the Future Trilogy","release_date":"1990-06-12","poster_path":"/sXxrpdsuyDDam1Jk2OSKTTEubXR.jpg","popularity":0.006,"title":"The Secrets of the Back to the Future Trilogy","vote_average":0.0,"vote_count":0}],"total_pages":1,"total_results":8}
+ test/search-tv-good.json view
@@ -0,0 +1,1 @@+{"page":1,"results":[{"backdrop_path":"/qlJB8bkK1JXAQ0m02OYS1ArS6DZ.jpg","id":1437,"original_name":"Firefly","first_air_date":"2002-12-20","origin_country":["US"],"poster_path":"/mWNadwBZIx8NyEw4smGftYtHHrE.jpg","popularity":2.61700779681977,"name":"Firefly","vote_average":8.9,"vote_count":55}],"total_pages":1,"total_results":2}
+ test/season-good.json view
@@ -0,0 +1,1 @@+{"air_date":"2002-12-20","episodes":[{"air_date":"2002-12-20","crew":[],"episode_number":1,"guest_stars":[],"name":"Serenity","overview":"Malcolm Reynolds is a veteran and the captain of Serenity. He and his crew are smuggling goods, but they need to pick up some passengers for extra money. However, not all the passengers are what they seem.","id":1048593,"production_code":null,"season_number":1,"still_path":"/rzfjCUiYc2c2V3sWs2Fsy94Qs2D.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-09-20","crew":[],"episode_number":2,"guest_stars":[],"name":"The Train Job","overview":"The crew of Serenity take on a train heist commissioned by a crime lord. They steal the goods, only to find it is medicine that's desperately needed by the town.","id":1048594,"production_code":null,"season_number":1,"still_path":"/4TUlW4Wlare35S94f22su3k0oso.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-09-27","crew":[],"episode_number":3,"guest_stars":[],"name":"Bushwhacked","overview":"Serenity is pulled in by a Tohoku-class cruiser while investigating a spaceship that was attacked by Reavers. Simon and River must hide to prevent capture, while something is wrong with the lone survivor of the attacked spaceship.","id":1048595,"production_code":null,"season_number":1,"still_path":"/q7ThwoiakscSHdPOkcL9wCkppQd.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-11-01","crew":[],"episode_number":4,"guest_stars":[],"name":"Shindig","overview":"Inara attends a formal society dance, only to find Malcolm there as well, attempting to set up a smuggling job. Mal comes to blows with Inara's conceited date and finds himself facing a duel with a renowned swordsman, and only one night to learn how to fence.","id":1048596,"production_code":null,"season_number":1,"still_path":"/6gpWRWOCXZZ2n8sAvAQprYRYmbk.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-11-08","crew":[],"episode_number":5,"guest_stars":[],"name":"Safe","overview":"Mal must choose which crew members to save when one is gravely wounded and two others are kidnapped. Simon finds an uneasy haven in a remote village, but River's uncanny perceptions jeopardize the Tams' temporary safety.","id":1048597,"production_code":null,"season_number":1,"still_path":"/uEm8v5cTmVlXoLPlyUCGFqqCNnL.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-10-04","crew":[],"episode_number":6,"guest_stars":[],"name":"Our Mrs. Reynolds","overview":"As an unexpected reward for an unpaid job, Mal finds himself married to a naïve, subservient young woman named Saffron. The crew are amused at his discomfort and Book lectures him on propriety, but things aren't as smoothly straightforward as they thought them to be.","id":1048598,"production_code":null,"season_number":1,"still_path":"/eDPbpIl8jXXwy4HYPGvUwYejm2Z.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-10-18","crew":[],"episode_number":7,"guest_stars":[],"name":"Jaynestown","overview":"Returning to a planet where he ran into some serious trouble years ago, Jayne discovers that he's become a local folk legend. Mal decides to use this entertaining distraction to complete a job, but some unfinished business may derail his plans.","id":1048599,"production_code":null,"season_number":1,"still_path":"/4a69VCTUjkTcGmJZUFWYagnyvu2.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-10-25","crew":[],"episode_number":8,"guest_stars":[],"name":"Out of Gas","overview":"After Serenity suffers a catastrophe that leaves her crew with only hours of oxygen, flashbacks show how Mal and Zoë acquired Serenity and assembled their motley band.","id":1048600,"production_code":null,"season_number":1,"still_path":"/4dNFxJa2MaPTkrojhtyn7TC3Zei.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-11-15","crew":[],"episode_number":9,"guest_stars":[],"name":"Ariel","overview":"Hard up for cash, Serenity takes on a job from Simon: help him get a thorough diagnostic of River in return for the opportunity to loot the vast medical stores of an Alliance hospital on central world Ariel. But River's pursuers are hot on their trail, and they receive some unexpected inside help.","id":1048601,"production_code":null,"season_number":1,"still_path":"/bubMd7bU5f3JQr0GL1BcyUsFlow.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-12-06","crew":[],"episode_number":10,"guest_stars":[],"name":"War Stories","overview":"Angered at Zoë's unshakable war connection to Mal, Wash demands a shot at a field assignment. Unfortunately, crime lord Niska chooses this moment to exact a brutal vengeance for Mal's failure to complete an earlier job.","id":1048602,"production_code":null,"season_number":1,"still_path":"/WFe1wBmXMUM9JqeZ72IbBiHZBt.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2003-06-28","crew":[],"episode_number":11,"guest_stars":[],"name":"Trash","overview":"Saffron returns to plague Serenity with a scheme to steal a rare antique weapon from a wealthy landowner. Unfortunately for Mal, she neglects to mention just how she came across the information needed to break into the landowner's home.","id":1048603,"production_code":null,"season_number":1,"still_path":"/5K1vfXVxCHvgNKbKaOiZKGsP3Xh.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2003-07-15","crew":[],"episode_number":12,"guest_stars":[],"name":"The Message","overview":"A former Independence soldier who had served with Mal and Zoë returns in a dramatic manner, with a vicious Alliance officer chasing after him for some unusual smuggled goods.","id":1048604,"production_code":null,"season_number":1,"still_path":"/hmaj11blnsllg9nx1fID7qb9lKC.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2003-09-19","crew":[],"episode_number":13,"guest_stars":[],"name":"Heart of Gold","overview":"A Companion-trained friend of Inara's who runs a brothel calls for help from Serenity when a local bigwig reveals his intentions to take \"his\" baby from the girl he impregnated.","id":1048605,"production_code":null,"season_number":1,"still_path":"/ut43sdIEL5Y6VtAppHxqiYIpPRr.jpg","vote_average":0.0,"vote_count":0},{"air_date":"2002-12-13","crew":[],"episode_number":14,"guest_stars":[],"name":"Objects in Space","overview":"Serenity encounters a ruthlessly professional bounty hunter, Jubal Early, who will stop at nothing to retrieve River. But River, feeling unwelcome on the ship, takes a novel approach to escaping from the long arm of the Alliance.","id":1048606,"production_code":null,"season_number":1,"still_path":"/wHmE9jsb4RZs1Kk00dTFU4cNqNl.jpg","vote_average":0.0,"vote_count":0}],"name":"Season 1","overview":"Captain Malcolm 'Mal' Reynolds is a former galactic war veteran who is the captain of the transport ship \"Serenity\". Mal and his crew, ensign Zoe Alleyne Washburne; Zoe's husband, pilot Hoban 'Wash' Washburne; muscular mercenary Jayne Cobb; young mechanic Kaylee Frye; former Alliance medical officer Simon Tam; his disturbed teenage sister River (both on the run from the interplanetary government \"The Alliance\"); the beautiful courtesan Inara Serra; and preacher Shepherd Book do any jobs, legal or illegal, they can find as the Serenity crew travels across the outskirts of outer space.","id":3846,"poster_path":"/2dxsbVMoxsYH0Pta2mbFjF7mhHr.jpg","season_number":1}
+ test/tv-good.json view
@@ -0,0 +1,1 @@+{"backdrop_path":"/qlJB8bkK1JXAQ0m02OYS1ArS6DZ.jpg","created_by":[{"id":12891,"name":"Joss Whedon","profile_path":"/n2x16sVDgj46PabFgJ0jvgjqxWl.jpg"}],"episode_run_time":[45,60,42,44],"first_air_date":"2002-12-20","genres":[{"id":18,"name":"Drama"},{"id":10765,"name":"Sci-Fi & Fantasy"}],"homepage":"","id":1437,"in_production":false,"languages":["en"],"last_air_date":"2002-12-13","name":"Firefly","networks":[{"id":19,"name":"Fox Broadcasting Company"}],"number_of_episodes":13,"number_of_seasons":1,"origin_country":["US"],"original_language":"en","original_name":"Firefly","overview":"Firefly is an American space western drama television series created by writer and director Joss Whedon, under his Mutant Enemy Productions label. The series is set in the year 2517, after the arrival of humans in a new star system, and follows the adventures of the renegade crew of Serenity, a \"Firefly-class\" spaceship. The ensemble cast portrays the nine characters who live on Serenity. Whedon pitched the show as \"nine people looking into the blackness of space and seeing nine different things\".\n\nThe show explores the lives of a group of people who fought on the losing side of a civil war and others who now make a living on the outskirts of society, as part of the pioneer culture that exists on the fringes of their star system. In this future, the only two surviving superpowers, the United States and China, fused to form the central federal government, called the Alliance, resulting in the fusion of the two cultures. According to Whedon's vision, \"nothing will change in the future: technology will advance, but we will still have the same political, moral, and ethical problems as today\".","popularity":2.61700779681977,"poster_path":"/mWNadwBZIx8NyEw4smGftYtHHrE.jpg","production_companies":[],"seasons":[{"air_date":"2005-09-30","episode_count":4,"id":3847,"poster_path":"/oMy3GEeWT14LRjTTFWwlc4uQld1.jpg","season_number":0},{"air_date":"2002-12-20","episode_count":14,"id":3846,"poster_path":"/2dxsbVMoxsYH0Pta2mbFjF7mhHr.jpg","season_number":1}],"status":"Ended","type":"Scripted","vote_average":8.9,"vote_count":55}
themoviedb.cabal view
@@ -1,90 +1,113 @@--- http://www.haskell.org/cabal/users-guide/developing-packages.html+-------------------------------------------------------------------------------- name:          themoviedb-version:       0.1.0.1+version:       1.0.0.0 synopsis:      Haskell API bindings for http://themoviedb.org homepage:      http://github.com/pjones/themoviedb bug-reports:   http://github.com/pjones/themoviedb/issues license:       MIT license-file:  LICENSE-author:        Peter Jones <pjones@pmade.com>-maintainer:    Peter Jones <pjones@pmade.com>-copyright:     Copyright: (c) 2012 Peter Jones <pjones@pmade.com>+author:        Peter Jones <pjones@devalot.com>+maintainer:    Peter Jones <pjones@devalot.com>+copyright:     Copyright: (c) 2012-2015 Peter Jones category:      Network, API stability:     experimental-tested-with:   GHC == 7.0.4, GHC == 7.6.1+tested-with:   GHC == 7.8.4 build-type:    Simple-cabal-version: >=1.8+cabal-version: >=1.10 description:   This library provides functions for retrieving metadata                from the <http://TheMovieDB.org> API.  Documentation                can be found in the "Network.API.TheMovieDB" module. +-------------------------------------------------------------------------------- extra-source-files:   CHANGES   README.md-  LICENSE   TODO.org-  Test/config-good.json-  Test/movie-bad.json-  Test/movie-good.json-  Test/search-good.json-  Test/TheMovieDB.hs+  test/*.json +--------------------------------------------------------------------------------+source-repository head+  type: git+  location: git://github.com/pjones/themoviedb.git++--------------------------------------------------------------------------------+flag maintainer+  description: Enable settings for the package maintainer.+  manual: True+  default: False++-------------------------------------------------------------------------------- library-  exposed-modules: Network.API.TheMovieDB,-                   Network.API.TheMovieDB.Util-  other-modules:   Network.API.TheMovieDB.Config,-                   Network.API.TheMovieDB.Fetch,-                   Network.API.TheMovieDB.Search,-                   Network.API.TheMovieDB.Generic,-                   Network.API.TheMovieDB.HTTP,-                   Network.API.TheMovieDB.Types,-                   Network.API.TheMovieDB.Types.API,-                   Network.API.TheMovieDB.Types.Configuration,-                   Network.API.TheMovieDB.Types.Context,-                   Network.API.TheMovieDB.Types.Genre,-                   Network.API.TheMovieDB.Types.Movie,-                   Network.API.TheMovieDB.Types.ReleaseDate+  exposed-modules:+    Network.API.TheMovieDB+    Network.API.TheMovieDB.Actions+    Network.API.TheMovieDB.Internal.Configuration+    Network.API.TheMovieDB.Internal.Date+    Network.API.TheMovieDB.Internal.HTTP+    Network.API.TheMovieDB.Internal.SearchResults+    Network.API.TheMovieDB.Internal.TheMovieDB+    Network.API.TheMovieDB.Internal.Types+    Network.API.TheMovieDB.Types.Episode+    Network.API.TheMovieDB.Types.Genre+    Network.API.TheMovieDB.Types.Movie+    Network.API.TheMovieDB.Types.Season+    Network.API.TheMovieDB.Types.TV -  build-depends:-    base <5.0.0.0,-    old-locale ==1.0.*,-    time >=1.2.0.0 && <1.5.0.0,-    HTTP ==4000.2.*,-    network >=2.3.0.0 && <2.5.0.0,-    bytestring >=0.9.0 && <0.10.1.0,-    text ==0.11.*,-    aeson ==0.6.*,-    unix >=2.4.0.0 && <2.7.0.0+  default-language: Haskell2010+  hs-source-dirs: src+  ghc-options: -Wall -fwarn-incomplete-uni-patterns+  ghc-prof-options: -prof -auto-all +  if flag(maintainer)+    ghc-options: -Werror++  build-depends: aeson         >= 0.6   && < 0.9+               , base          >= 4.6   && < 5.0+               , binary        >= 0.7   && < 0.8+               , bytestring    >= 0.9   && < 0.11+               , either        >= 4.3   && < 4.4+               , http-client   >= 0.4   && < 0.5+               , http-client-tls >= 0.2.2 && < 0.3+               , http-types    >= 0.8   && < 0.9+               , mtl           >= 2.1   && < 2.2+               , network       >= 2.3   && < 2.7+               , network-uri   >= 2.6   && < 2.7+               , old-locale    >= 1.0   && < 1.1+               , text          >= 0.11  && < 1.3+               , text-binary   >= 0.1   && < 0.2+               , time          >= 1.2   && < 1.6+               , transformers  >= 0.3   && < 0.5+               , unix          >= 2.5   && < 2.8++-------------------------------------------------------------------------------- executable tmdb-  main-is: example/Main.hs+  default-language: Haskell2010+  hs-source-dirs: example+  main-is: Main.hs -  build-depends:-    base,-    unix,-    old-locale,-    time,-    text,-    aeson,-    network,-    HTTP,-    bytestring+  ghc-options: -Wall -fwarn-incomplete-uni-patterns +  if flag(maintainer)+    ghc-options: -Werror++  build-depends: themoviedb, base, unix, old-locale, time,+                 text, aeson, network, bytestring, transformers+++-------------------------------------------------------------------------------- test-suite test   type: exitcode-stdio-1.0-  main-is: Test/TheMovieDB.hs-  build-depends:-    base,-    unix,-    old-locale,-    time,-    text,-    aeson,-    network,-    HTTP,-    bytestring,-    HUnit+  default-language: Haskell2010+  hs-source-dirs: test+  main-is: Main.hs+  other-modules: TestHelper MovieTest TVTest -source-repository head-  type: git-  location: git://github.com/pjones/themoviedb.git+  ghc-options: -Wall -fwarn-incomplete-uni-patterns++  if flag(maintainer)+    ghc-options: -Werror++  build-depends: tasty       >= 0.10 && < 0.11+               , tasty-hunit >= 0.9  && < 0.10+               , themoviedb, base, unix, old-locale, time+               , text, aeson, network, bytestring, transformers