openweathermap 0.1.0 → 0.2.0
raw patch · 12 files changed
+345/−144 lines, 12 filesdep +http-api-datadep +servant-client-coredep +timePVP ok
version bump matches the API change (PVP)
Dependencies added: http-api-data, servant-client-core, time
API changes (from Hackage documentation)
- Web.OpenWeatherMap.API: weatherByCoord :: Maybe Double -> Maybe Double -> Maybe String -> ClientM CurrentWeather
- Web.OpenWeatherMap.API: weatherByName :: Maybe String -> Maybe String -> ClientM CurrentWeather
- Web.OpenWeatherMap.Client: Coord :: Double -> Double -> Location
- Web.OpenWeatherMap.Client: Name :: String -> Location
- Web.OpenWeatherMap.Client: data Location
+ Web.OpenWeatherMap.API: currentWeather :: String -> Location -> ClientM CurrentWeather
+ Web.OpenWeatherMap.API: forecastWeather :: String -> Location -> ClientM ForecastWeather
+ Web.OpenWeatherMap.Client: getForecast :: String -> Location -> IO (Either ClientError ForecastWeather)
+ Web.OpenWeatherMap.Types.City: City :: String -> Maybe String -> Coord -> Int -> Int -> Int -> City
+ Web.OpenWeatherMap.Types.City: [coord] :: City -> Coord
+ Web.OpenWeatherMap.Types.City: [country] :: City -> Maybe String
+ Web.OpenWeatherMap.Types.City: [name] :: City -> String
+ Web.OpenWeatherMap.Types.City: [sunrise] :: City -> Int
+ Web.OpenWeatherMap.Types.City: [sunset] :: City -> Int
+ Web.OpenWeatherMap.Types.City: [timezone] :: City -> Int
+ Web.OpenWeatherMap.Types.City: data City
+ Web.OpenWeatherMap.Types.City: instance Data.Aeson.Types.FromJSON.FromJSON Web.OpenWeatherMap.Types.City.City
+ Web.OpenWeatherMap.Types.City: instance GHC.Generics.Generic Web.OpenWeatherMap.Types.City.City
+ Web.OpenWeatherMap.Types.City: instance GHC.Show.Show Web.OpenWeatherMap.Types.City.City
+ Web.OpenWeatherMap.Types.Forecast: Forecast :: Int -> Clouds -> Main -> [Weather] -> Wind -> Forecast
+ Web.OpenWeatherMap.Types.Forecast: [clouds] :: Forecast -> Clouds
+ Web.OpenWeatherMap.Types.Forecast: [dt] :: Forecast -> Int
+ Web.OpenWeatherMap.Types.Forecast: [main] :: Forecast -> Main
+ Web.OpenWeatherMap.Types.Forecast: [weather] :: Forecast -> [Weather]
+ Web.OpenWeatherMap.Types.Forecast: [wind] :: Forecast -> Wind
+ Web.OpenWeatherMap.Types.Forecast: data Forecast
+ Web.OpenWeatherMap.Types.Forecast: instance Data.Aeson.Types.FromJSON.FromJSON Web.OpenWeatherMap.Types.Forecast.Forecast
+ Web.OpenWeatherMap.Types.Forecast: instance GHC.Generics.Generic Web.OpenWeatherMap.Types.Forecast.Forecast
+ Web.OpenWeatherMap.Types.Forecast: instance GHC.Show.Show Web.OpenWeatherMap.Types.Forecast.Forecast
+ Web.OpenWeatherMap.Types.ForecastWeather: ForecastWeather :: [Forecast] -> City -> ForecastWeather
+ Web.OpenWeatherMap.Types.ForecastWeather: [city] :: ForecastWeather -> City
+ Web.OpenWeatherMap.Types.ForecastWeather: [list] :: ForecastWeather -> [Forecast]
+ Web.OpenWeatherMap.Types.ForecastWeather: data ForecastWeather
+ Web.OpenWeatherMap.Types.ForecastWeather: instance Data.Aeson.Types.FromJSON.FromJSON Web.OpenWeatherMap.Types.ForecastWeather.ForecastWeather
+ Web.OpenWeatherMap.Types.ForecastWeather: instance GHC.Generics.Generic Web.OpenWeatherMap.Types.ForecastWeather.ForecastWeather
+ Web.OpenWeatherMap.Types.ForecastWeather: instance GHC.Show.Show Web.OpenWeatherMap.Types.ForecastWeather.ForecastWeather
+ Web.OpenWeatherMap.Types.Location: Coord :: Double -> Double -> Location
+ Web.OpenWeatherMap.Types.Location: Name :: String -> Location
+ Web.OpenWeatherMap.Types.Location: data Location
+ Web.OpenWeatherMap.Types.Location: instance Servant.Client.Core.HasClient.HasClient m api => Servant.Client.Core.HasClient.HasClient m (Web.OpenWeatherMap.Types.Location.Location Servant.API.Sub.:> api)
- Web.OpenWeatherMap.Types.Coord: Coord :: Double -> Double -> Coord
+ Web.OpenWeatherMap.Types.Coord: Coord :: Maybe Double -> Maybe Double -> Coord
- Web.OpenWeatherMap.Types.Coord: [lat] :: Coord -> Double
+ Web.OpenWeatherMap.Types.Coord: [lat] :: Coord -> Maybe Double
- Web.OpenWeatherMap.Types.Coord: [lon] :: Coord -> Double
+ Web.OpenWeatherMap.Types.Coord: [lon] :: Coord -> Maybe Double
Files
- ChangeLog.md +7/−0
- README.md +32/−4
- cmd/Main.hs +44/−102
- cmd/Print.hs +125/−0
- lib/Web/OpenWeatherMap/API.hs +15/−19
- lib/Web/OpenWeatherMap/Client.hs +13/−16
- lib/Web/OpenWeatherMap/Types/City.hs +21/−0
- lib/Web/OpenWeatherMap/Types/Coord.hs +2/−2
- lib/Web/OpenWeatherMap/Types/Forecast.hs +23/−0
- lib/Web/OpenWeatherMap/Types/ForecastWeather.hs +20/−0
- lib/Web/OpenWeatherMap/Types/Location.hs +34/−0
- openweathermap.cabal +9/−1
ChangeLog.md view
@@ -1,3 +1,10 @@+0.2.0+=====++ * (* BREAKING *) Made Location a part of the API to+ reduce code duplication.++ 0.1.0 =====
README.md view
@@ -22,16 +22,19 @@ ----- ```-Usage: openweathermap ([-K|--api-key-file APIKEYFILE] | [-k|--api-key APIKEY])- ((-c|--city CITY) | --lat NUM --lon NUM) [-d|--debug]+Usage: openweathermap [(-K|--api-key-file APIKEYFILE) | (-k|--api-key APIKEY)]+ ((-q|--query STRING) | --lat NUM --lon NUM)+ [(-n|--current) | (-f|--forecast)] [-d|--debug] Available options: -K,--api-key-file APIKEYFILE Read API key from this file -k,--api-key APIKEY API key- -c,--city CITY City name+ -q,--query STRING City name, e. g. Santiago or Santiago,CU --lat NUM Latitude in decimal degrees --lon NUM Longitude in decimal degrees+ -n,--current current weather (default)+ -f,--forecast forecast weather -d,--debug Enable debug -h,--help Show this help text @@ -46,11 +49,36 @@ -------- ```-$ openweathermap -c norilsk+$ openweathermap -q norilsk Norilsk,RU (69.35°, 88.2°): Clouds, H 100 %, P 753 mmHg, T +4 °C, ↓ 1 m/s $ openweathermap --lat 55.7522200 --lon 37.6155600 Moscow,RU (55.75°, 37.62°): Clear, H 45 %, P 762 mmHg, T +18..+21 °C, → 4 m/s++$ openweathermap -q kaliningrad -f+Kaliningrad,RU (54.7065°, 20.511°)+2020-04-19 17:00:00 +0200: Clear, H 66 %, P 767 mmHg, T +7 °C, ↓ 7 m/s+2020-04-19 20:00:00 +0200: Clear, H 79 %, P 768 mmHg, T +4 °C, ↓ 5 m/s+2020-04-19 23:00:00 +0200: Clear, H 84 %, P 769 mmHg, T +3 °C, ↓ 4 m/s+2020-04-20 02:00:00 +0200: Clear, H 85 %, P 770 mmHg, T +3 °C, ↓ 3 m/s+2020-04-20 05:00:00 +0200: Clear, H 84 %, P 770 mmHg, T +2 °C, ↓ 3 m/s+2020-04-20 08:00:00 +0200: Clear, H 76 %, P 770 mmHg, T +5 °C, ↓ 4 m/s+2020-04-20 11:00:00 +0200: Clear, H 65 %, P 771 mmHg, T +8 °C, ↓ 4 m/s+2020-04-20 14:00:00 +0200: Clear, H 62 %, P 771 mmHg, T +9 °C, ↓ 4 m/s+...++$ openweathermap --lat -12.0432 --lon -77.0282 -f+Lima,PE (-12.0432°, -77.0282°)+2020-04-19 10:00:00 -0500: Clear, H 70 %, P 764 mmHg, T +22..+24 °C, ↗ 3 m/s+2020-04-19 13:00:00 -0500: Clear, H 64 %, P 762 mmHg, T +23..+25 °C, ↗ 4 m/s+2020-04-19 16:00:00 -0500: Clouds, H 66 %, P 761 mmHg, T +23..+24 °C, ↑ 4 m/s+2020-04-19 19:00:00 -0500: Clouds, H 72 %, P 763 mmHg, T +22 °C, ↑ 4 m/s+2020-04-19 22:00:00 -0500: Clouds, H 72 %, P 764 mmHg, T +22 °C, ↑ 3 m/s+2020-04-20 01:00:00 -0500: Clouds, H 76 %, P 763 mmHg, T +21 °C, ↑ 2 m/s+2020-04-20 04:00:00 -0500: Clear, H 80 %, P 763 mmHg, T +20 °C, ↑ 3 m/s+2020-04-20 07:00:00 -0500: Clouds, H 79 %, P 764 mmHg, T +20 °C, ↑ 3 m/s+2020-04-20 10:00:00 -0500: Clouds, H 72 %, P 764 mmHg, T +21 °C, ↗ 3 m/s+... ```
cmd/Main.hs view
@@ -5,7 +5,6 @@ ) where import Control.Monad (when)-import Data.List (intercalate) import Data.Semigroup ((<>)) import Data.Version (showVersion) import System.Exit (die)@@ -17,6 +16,7 @@ , (<|>) , auto , execParser+ , flag' , fullDesc , header , help@@ -33,26 +33,25 @@ import System.Directory (createDirectoryIfMissing) import System.Environment.XDG.BaseDir (getUserConfigDir, getUserConfigFile) -import Paths_openweathermap (version) -- from cabal import qualified Web.OpenWeatherMap.Client as Client-import qualified Web.OpenWeatherMap.Types.Coord as Coord-import qualified Web.OpenWeatherMap.Types.CurrentWeather as CurrentWeather-import qualified Web.OpenWeatherMap.Types.Main as Main-import qualified Web.OpenWeatherMap.Types.Sys as Sys-import qualified Web.OpenWeatherMap.Types.Weather as Weather-import qualified Web.OpenWeatherMap.Types.Wind as Wind+import Web.OpenWeatherMap.Types.Location (Location(..)) +import Paths_openweathermap (version) -- from cabal+import Print (printCurrectWeather, printForecastWeather)+ appName :: String appName = "openweathermap" -parseLocation :: Parser Client.Location+parseLocation :: Parser Location parseLocation = byName <|> byCoord where byName =- Client.Name <$>- strOption (long "city" <> short 'c' <> metavar "CITY" <> help "City name")+ Name <$>+ strOption+ (long "query" <> short 'q' <> metavar "STRING" <>+ help "City name, e. g. Santiago or Santiago,CU") byCoord =- Client.Coord <$>+ Coord <$> option auto (long "lat" <> metavar "NUM" <> help "Latitude in decimal degrees") <*>@@ -77,15 +76,29 @@ strOption (long "api-key" <> short 'k' <> metavar "APIKEY" <> help "API key") -data Config = Config- { apikey :: Maybe ApiKey- , location :: Client.Location- , debug :: Bool- }+data Weather+ = Current+ | Forecast +parseWeather :: Parser Weather+parseWeather =+ flag'+ Current+ (long "current" <> short 'n' <> help "current weather (default)") <|>+ flag' Forecast (long "forecast" <> short 'f' <> help "forecast weather") <|>+ pure Current++data Config =+ Config+ { apikey :: Maybe ApiKey+ , location :: Location+ , weather :: Weather+ , debug :: Bool+ }+ parseConfig :: Parser Config parseConfig =- Config <$> optional parseApiKey <*> parseLocation <*>+ Config <$> optional parseApiKey <*> parseLocation <*> parseWeather <*> switch (long "debug" <> short 'd' <> help "Enable debug") getApiKey :: Maybe ApiKey -> IO String@@ -95,93 +108,22 @@ createDirectoryIfMissing True =<< getUserConfigDir appName getUserConfigFile appName "key" >>= getApiKey . Just . ApiKeyFile -showLocation :: CurrentWeather.CurrentWeather -> String-showLocation w = city ++ maybe "" ("," ++) country ++ " " ++ coords- where- name = CurrentWeather.name w- coord = CurrentWeather.coord w- country = Sys.country . CurrentWeather.sys $ w- city =- if name /= ""- then name- else "<unknown>"- coords =- "(" ++ show (Coord.lat coord) ++ "°, " ++ show (Coord.lon coord) ++ "°)"--showWeather :: [Weather.Weather] -> String-showWeather w = intercalate "," $ Weather.main <$> w--showHumidity :: Main.Main -> String-showHumidity m = "H " ++ show hm ++ " %"- where- hm :: Int- hm = round . Main.humidity $ m---- https://en.wikipedia.org/wiki/Millimeter_of_mercury-showPressure :: Main.Main -> String-showPressure m = "P " ++ show p ++ " mmHg"- where- hPa2mmHg hpa = hpa * 0.750061561303- p :: Int- p = round . hPa2mmHg . Main.pressure $ m---- https://stackoverflow.com/q/7490660/933161-showWind :: Wind.Wind -> String-showWind w = dir ++ " " ++ show speed ++ " m/s"- where- speed :: Int- speed = round . Wind.speed $ w- deg = Wind.deg w- -- [ "N", "NE", "E", "SE", "S", "SW", "W", "NW" ]- dirs = ["↓", "↙", "←", "↖", "↑", "↗", "→", "↘"]- l = length dirs- sector = round $ (deg * fromIntegral l) / 360.0- dir = dirs !! (sector `rem` l)--showTemp :: Main.Main -> String-showTemp m = "T " ++ temp ++ " °C"- where- k2c k = k - 273.15 -- Kelvin to Celsius- tmax :: Int- tmin :: Int- tmax = round . k2c . Main.temp_max $ m- tmin = round . k2c . Main.temp_min $ m- show' t =- if t > 0- then "+" ++ show t- else show t- temp =- if tmax /= tmin- then show' tmin ++ ".." ++ show' tmax- else show' tmin--printWeather :: CurrentWeather.CurrentWeather -> IO ()-printWeather w = putStrLn out- where- weather = showWeather $ CurrentWeather.weather w- place = showLocation w- mainw = CurrentWeather.main w- wind = CurrentWeather.wind w- out =- place ++- ": " ++- intercalate- ", "- [ weather- , showHumidity mainw- , showPressure mainw- , showTemp mainw- , showWind wind- ]- run :: Config -> IO () run cfg = do appid <- getApiKey . apikey $ cfg- Client.getWeather appid (location cfg) >>= \case- Left err -> die $ show err- Right weather -> do- when (debug cfg) $ hPrint stderr weather- printWeather weather+ case weather cfg of+ Current ->+ Client.getWeather appid (location cfg) >>= \case+ Left err -> die $ show err+ Right cw -> do+ when (debug cfg) $ hPrint stderr cw+ printCurrectWeather cw+ Forecast ->+ Client.getForecast appid (location cfg) >>= \case+ Left err -> die $ show err+ Right fw -> do+ when (debug cfg) $ hPrint stderr fw+ printForecastWeather fw main :: IO () main = run =<< execParser opts
+ cmd/Print.hs view
@@ -0,0 +1,125 @@+module Print+ ( printCurrectWeather+ , printForecastWeather+ ) where++import Data.List (intercalate)+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)+import Data.Time.LocalTime (TimeZone, minutesToTimeZone, utcToZonedTime)++import qualified Web.OpenWeatherMap.Types.City as City+import qualified Web.OpenWeatherMap.Types.Coord as Coord+import qualified Web.OpenWeatherMap.Types.CurrentWeather as CW+import qualified Web.OpenWeatherMap.Types.Forecast as FC+import qualified Web.OpenWeatherMap.Types.ForecastWeather as FW+import qualified Web.OpenWeatherMap.Types.Main as Main+import qualified Web.OpenWeatherMap.Types.Sys as Sys+import qualified Web.OpenWeatherMap.Types.Weather as Weather+import qualified Web.OpenWeatherMap.Types.Wind as Wind++printCurrectWeather :: CW.CurrentWeather -> IO ()+printCurrectWeather cw =+ putStrLn+ (place +++ ": " +++ intercalate+ ", "+ [ w+ , showHumidity mainw+ , showPressure mainw+ , showTemp mainw+ , showWind wind+ ])+ where+ w = showWeather $ CW.weather cw+ place = showLocation (CW.name cw) (Sys.country . CW.sys $ cw) (CW.coord cw)+ mainw = CW.main cw+ wind = CW.wind cw++printForecastWeather :: FW.ForecastWeather -> IO ()+printForecastWeather fw = do+ let c = FW.city fw+ tz = minutesToTimeZone (City.timezone c `div` 60)+ place = showLocation (City.name c) (City.country c) (City.coord c)+ putStrLn place+ mapM_ putStrLn (showForecast tz <$> FW.list fw)++showForecast :: TimeZone -> FC.Forecast -> String+showForecast tz fc =+ localtime +++ ": " +++ intercalate+ ", "+ [ showWeather (FC.weather fc)+ , showHumidity mainw+ , showPressure mainw+ , showTemp mainw+ , showWind (FC.wind fc)+ ]+ where+ localtime =+ show . utcToZonedTime tz . posixSecondsToUTCTime . fromIntegral $ FC.dt fc+ mainw = FC.main fc++showLocation :: String -> Maybe String -> Coord.Coord -> String+showLocation name country coord =+ name' ++ maybe "" ("," ++) country ++ " " ++ coords+ where+ coords = showCoord coord+ name' =+ if name /= ""+ then name+ else "<unknown>"++showCoord :: Coord.Coord -> String+showCoord coord =+ "(" +++ maybe "?" show (Coord.lat coord) +++ "°, " ++ maybe "?" show (Coord.lon coord) ++ "°)"++showWeather :: [Weather.Weather] -> String+showWeather w = intercalate "," $ Weather.main <$> w++showHumidity :: Main.Main -> String+showHumidity m = "H " ++ show hm ++ " %"+ where+ hm :: Int+ hm = round . Main.humidity $ m++-- https://en.wikipedia.org/wiki/Millimeter_of_mercury+showPressure :: Main.Main -> String+showPressure m = "P " ++ show p ++ " mmHg"+ where+ hPa2mmHg hpa = hpa * 0.750061561303+ p :: Int+ p = round . hPa2mmHg . Main.pressure $ m++-- https://stackoverflow.com/q/7490660/933161+showWind :: Wind.Wind -> String+showWind w = dir ++ " " ++ show speed ++ " m/s"+ where+ speed :: Int+ speed = round . Wind.speed $ w+ deg = Wind.deg w+ -- [ "N", "NE", "E", "SE", "S", "SW", "W", "NW" ]+ dirs = ["↓", "↙", "←", "↖", "↑", "↗", "→", "↘"]+ l = length dirs+ sector = round $ (deg * fromIntegral l) / 360.0+ dir = dirs !! (sector `rem` l)++showTemp :: Main.Main -> String+showTemp m = "T " ++ temp ++ " °C"+ where+ k2c k = k - 273.15 -- Kelvin to Celsius+ tmax :: Int+ tmin :: Int+ tmax = round . k2c . Main.temp_max $ m+ tmin = round . k2c . Main.temp_min $ m+ show' t =+ if t > 0+ then "+" ++ show t+ else show t+ temp =+ if tmax /= tmin+ then show' tmin ++ ".." ++ show' tmax+ else show' tmin
lib/Web/OpenWeatherMap/API.hs view
@@ -6,33 +6,29 @@ {-# LANGUAGE TypeOperators #-} module Web.OpenWeatherMap.API- ( weatherByName- , weatherByCoord+ ( currentWeather+ , forecastWeather ) where import Data.Proxy (Proxy(..)) -import Servant.API ((:<|>)(..), (:>), Get, JSON, QueryParam)+import Servant.API ((:<|>)(..), (:>), Get, JSON, QueryParam', Required, Strict) import Servant.Client (ClientM, client) import Web.OpenWeatherMap.Types.CurrentWeather (CurrentWeather)+import Web.OpenWeatherMap.Types.ForecastWeather (ForecastWeather)+import Web.OpenWeatherMap.Types.Location (Location) -type GetCurrentWeather = AppId :> Get '[ JSON] CurrentWeather+type QueryParam = QueryParam' '[ Required, Strict] -type AppId = QueryParam "appid" String+type Current+ = "weather" :> QueryParam "appid" String :> Location :> Get '[ JSON] CurrentWeather -type API- = "weather" :> QueryParam "q" String :> GetCurrentWeather :<|> "weather" :> QueryParam "lat" Double :> QueryParam "lon" Double :> GetCurrentWeather+type Forecast+ = "forecast" :> QueryParam "appid" String :> Location :> Get '[ JSON] ForecastWeather --- | Request current weather in the city.-weatherByName ::- Maybe String -- ^ City name, e. g. \"Moscow\" or \"Moscow,ru\".- -> Maybe String -- ^ API key.- -> ClientM CurrentWeather--- | Request current weather at the geographic coordinates (in decimal degrees).-weatherByCoord ::- Maybe Double -- ^ Latitude, e. g. 55.7522200 for Moscow.- -> Maybe Double -- ^ Longitude, e. g. 37.6155600 for Moscow.- -> Maybe String -- ^ API key.- -> ClientM CurrentWeather-weatherByName :<|> weatherByCoord = client (Proxy :: Proxy API)+type API = Current :<|> Forecast++forecastWeather :: String -> Location -> ClientM ForecastWeather+currentWeather :: String -> Location -> ClientM CurrentWeather+(currentWeather :<|> forecastWeather) = client (Proxy :: Proxy API)
lib/Web/OpenWeatherMap/Client.hs view
@@ -2,8 +2,8 @@ High-level client functions perfoming requests to OpenWeatherMap API. -} module Web.OpenWeatherMap.Client- ( Location(..)- , getWeather+ ( getWeather+ , getForecast ) where import Network.HTTP.Client (defaultManagerSettings, newManager)@@ -11,7 +11,6 @@ ( BaseUrl(BaseUrl) , ClientEnv , ClientError- , ClientM , Scheme(Http) , mkClientEnv , runClientM@@ -19,12 +18,8 @@ import qualified Web.OpenWeatherMap.API as API import Web.OpenWeatherMap.Types.CurrentWeather (CurrentWeather)---- | Various way to specify location.-data Location- = Name String -- ^ City name.- | Coord Double- Double -- ^ Geographic coordinates: latitude and longitude.+import Web.OpenWeatherMap.Types.ForecastWeather (ForecastWeather)+import Web.OpenWeatherMap.Types.Location (Location) -- | Make a request to OpenWeatherMap API -- and return current weather in given location.@@ -32,14 +27,16 @@ String -- ^ API key. -> Location -> IO (Either ClientError CurrentWeather)-getWeather appid loc = defaultEnv >>= runClientM (api loc appid)+getWeather appid loc = defaultEnv >>= runClientM (API.currentWeather appid loc) -api ::- Location- -> String -- ^ API key.- -> ClientM CurrentWeather-api (Name city) = API.weatherByName (Just city) . Just-api (Coord lat lon) = API.weatherByCoord (Just lat) (Just lon) . Just+-- | Make a request to OpenWeatherMap API+-- and return forecast weather in given location.+getForecast ::+ String -- ^ API key.+ -> Location+ -> IO (Either ClientError ForecastWeather)+getForecast appid loc =+ defaultEnv >>= runClientM (API.forecastWeather appid loc) defaultEnv :: IO ClientEnv defaultEnv = do
+ lib/Web/OpenWeatherMap/Types/City.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}++module Web.OpenWeatherMap.Types.City+ ( City(..)+ ) where++import GHC.Generics (Generic)++import Data.Aeson (FromJSON)++import Web.OpenWeatherMap.Types.Coord (Coord)++data City = City+ { name :: String+ , country :: Maybe String+ , coord :: Coord+ , timezone :: Int+ , sunset :: Int+ , sunrise :: Int+ } deriving (Show, Generic, FromJSON)
lib/Web/OpenWeatherMap/Types/Coord.hs view
@@ -10,6 +10,6 @@ import Data.Aeson (FromJSON) data Coord = Coord- { lon :: Double- , lat :: Double+ { lon :: Maybe Double+ , lat :: Maybe Double } deriving (Show, Generic, FromJSON)
+ lib/Web/OpenWeatherMap/Types/Forecast.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}++module Web.OpenWeatherMap.Types.Forecast+ ( Forecast(..)+ ) where++import GHC.Generics (Generic)++import Data.Aeson (FromJSON)++import Web.OpenWeatherMap.Types.Clouds (Clouds)+import Web.OpenWeatherMap.Types.Main (Main)+import Web.OpenWeatherMap.Types.Weather (Weather)+import Web.OpenWeatherMap.Types.Wind (Wind)++data Forecast = Forecast+ { dt :: Int+ , clouds :: Clouds+ , main :: Main+ , weather :: [Weather]+ , wind :: Wind+ } deriving (Show, Generic, FromJSON)
+ lib/Web/OpenWeatherMap/Types/ForecastWeather.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}++module Web.OpenWeatherMap.Types.ForecastWeather+ ( ForecastWeather(..)+ ) where++import GHC.Generics (Generic)++import Data.Aeson (FromJSON)++import Web.OpenWeatherMap.Types.City (City)+import Web.OpenWeatherMap.Types.Forecast (Forecast)++-- | Response to requests for forecast weather.+-- Refer to <https://openweathermap.org/forecast5>.+data ForecastWeather = ForecastWeather+ { list :: [Forecast]+ , city :: City+ } deriving (Show, Generic, FromJSON)
+ lib/Web/OpenWeatherMap/Types/Location.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++module Web.OpenWeatherMap.Types.Location+ ( Location(..)+ ) where++import Data.Proxy (Proxy(..))++import Servant.API ((:>))+import Servant.Client (Client, HasClient, clientWithRoute, hoistClientMonad)+import Servant.Client.Core.Request (appendToQueryString)+import Web.HttpApiData (toQueryParam)++-- | Various way to specify location.+data Location+ = Name String -- ^ City name.+ | Coord Double Double -- ^ Geographic coordinates: latitude and longitude.++instance HasClient m api => HasClient m (Location :> api) where+ type Client m (Location :> api) = Location -> Client m api+ clientWithRoute pm Proxy req loc =+ clientWithRoute pm (Proxy :: Proxy api) (addParams loc req)+ where+ addParams (Name q) = appendToQueryString "q" (Just $ toQueryParam q)+ addParams (Coord lat lon) =+ appendToQueryString "lat" (Just $ toQueryParam lat) .+ appendToQueryString "lon" (Just $ toQueryParam lon)+ hoistClientMonad pm _ f cl =+ \a -> hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
openweathermap.cabal view
@@ -1,5 +1,5 @@ name: openweathermap-version: 0.1.0+version: 0.2.0 synopsis: Access data at OpenWeatherMap description: Client library and command-line utility to access OpenWeatherMap https://openweathermap.org@@ -28,15 +28,21 @@ build-depends: base >= 4.9 && < 5 , aeson+ , http-api-data , http-client , servant , servant-client >= 0.16+ , servant-client-core exposed-modules: Web.OpenWeatherMap.API Web.OpenWeatherMap.Client+ Web.OpenWeatherMap.Types.City Web.OpenWeatherMap.Types.Clouds Web.OpenWeatherMap.Types.Coord Web.OpenWeatherMap.Types.CurrentWeather+ Web.OpenWeatherMap.Types.Forecast+ Web.OpenWeatherMap.Types.ForecastWeather+ Web.OpenWeatherMap.Types.Location Web.OpenWeatherMap.Types.Main Web.OpenWeatherMap.Types.Sys Web.OpenWeatherMap.Types.Weather@@ -47,12 +53,14 @@ ghc-options: -Wall -static hs-source-dirs: cmd main-is: Main.hs+ other-modules: Print if flag(cmd) build-depends: base >= 4.9 && < 5 , directory , openweathermap , optparse-applicative >= 0.13.0.0+ , time , xdg-basedir else buildable: False