openweathermap 0.0.0 → 0.1.0
raw patch · 14 files changed
+154/−144 lines, 14 filesdep ~servant-clientsetup-changedPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: servant-client
API changes (from Hackage documentation)
- Web.OpenWeatherMap.Types.Sys: [message] :: Sys -> Double
- Web.OpenWeatherMap.Client: getWeather :: String -> Location -> IO (Either ServantError CurrentWeather)
+ Web.OpenWeatherMap.Client: getWeather :: String -> Location -> IO (Either ClientError CurrentWeather)
- Web.OpenWeatherMap.Types.Sys: Sys :: Double -> Maybe String -> Int -> Int -> Sys
+ Web.OpenWeatherMap.Types.Sys: Sys :: Maybe String -> Int -> Int -> Sys
Files
- ChangeLog.md +9/−0
- README.md +2/−2
- Setup.hs +0/−2
- cmd/Main.hs +75/−55
- lib/Web/OpenWeatherMap/API.hs +15/−22
- lib/Web/OpenWeatherMap/Client.hs +26/−23
- lib/Web/OpenWeatherMap/Types/Clouds.hs +3/−5
- lib/Web/OpenWeatherMap/Types/Coord.hs +3/−5
- lib/Web/OpenWeatherMap/Types/CurrentWeather.hs +4/−5
- lib/Web/OpenWeatherMap/Types/Main.hs +4/−5
- lib/Web/OpenWeatherMap/Types/Sys.hs +4/−7
- lib/Web/OpenWeatherMap/Types/Weather.hs +3/−5
- lib/Web/OpenWeatherMap/Types/Wind.hs +3/−5
- openweathermap.cabal +3/−3
ChangeLog.md view
@@ -1,3 +1,12 @@+0.1.0+=====++ * Removed the `message` field from the `Sys` type.+ It was some internal parameter and might be absent.++ * Require servant-client >= 0.16 (for `ClientError` instead of `ServantError`).++ 0.0.0 =====
README.md view
@@ -7,7 +7,7 @@ ============ OpenWeatherMap is written in Haskell with [GHC](http://www.haskell.org/ghc/).-All required Haskell libraries are listed in [openweathermap.cabal](ldapply.cabal).+All required Haskell libraries are listed in [openweathermap.cabal](openweathermap.cabal). Use [cabal-install](http://www.haskell.org/haskellwiki/Cabal-Install) to fetch and build all pre-requisites automatically. @@ -36,7 +36,7 @@ -h,--help Show this help text ```-By default, `openweathermap` read the API key+By default, `openweathermap` reads the API key from the `$XDG_CONFIG_HOME/openweathermap/key` file, where [`$XDG_CONFIG_HOME`](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) is typically `~/.config` on Linux systems.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
cmd/Main.hs view
@@ -1,8 +1,8 @@ {-# LANGUAGE LambdaCase #-} -module Main (- main-) where+module Main+ ( main+ ) where import Control.Monad (when) import Data.List (intercalate)@@ -11,9 +11,24 @@ import System.Exit (die) import System.IO (IOMode(ReadMode), hGetLine, hPrint, stderr, withFile) -import Options.Applicative (- (<**>), (<|>), Parser, auto, execParser, fullDesc, header, help, helper,- info, long, metavar, option, optional, short, strOption, switch+import Options.Applicative+ ( Parser+ , (<**>)+ , (<|>)+ , auto+ , execParser+ , fullDesc+ , header+ , help+ , helper+ , info+ , long+ , metavar+ , option+ , optional+ , short+ , strOption+ , switch ) import System.Directory (createDirectoryIfMissing) import System.Environment.XDG.BaseDir (getUserConfigDir, getUserConfigFile)@@ -33,22 +48,17 @@ parseLocation :: Parser Client.Location parseLocation = byName <|> byCoord where- byName = Client.Name <$> strOption- ( long "city"- <> short 'c'- <> metavar "CITY"- <> help "City name" )-- byCoord = Client.Coord- <$> option auto- ( long "lat"- <> metavar "NUM"- <> help "Latitude in decimal degrees" )- <*> option auto- ( long "lon"- <> metavar "NUM"- <> help "Longitude in decimal degrees" )-+ byName =+ Client.Name <$>+ strOption (long "city" <> short 'c' <> metavar "CITY" <> help "City name")+ byCoord =+ Client.Coord <$>+ option+ auto+ (long "lat" <> metavar "NUM" <> help "Latitude in decimal degrees") <*>+ option+ auto+ (long "lon" <> metavar "NUM" <> help "Longitude in decimal degrees") data ApiKey = ApiKeyFile FilePath@@ -57,18 +67,15 @@ parseApiKey :: Parser ApiKey parseApiKey = fromFile <|> inCmdLine where- fromFile = ApiKeyFile <$> strOption- ( long "api-key-file"- <> short 'K'- <> metavar "APIKEYFILE"- <> help "Read API key from this file" )-- inCmdLine = ApiKey <$> strOption- ( long "api-key"- <> short 'k'- <> metavar "APIKEY"- <> help "API key" )-+ fromFile =+ ApiKeyFile <$>+ strOption+ (long "api-key-file" <> short 'K' <> metavar "APIKEYFILE" <>+ help "Read API key from this file")+ inCmdLine =+ ApiKey <$>+ strOption+ (long "api-key" <> short 'k' <> metavar "APIKEY" <> help "API key") data Config = Config { apikey :: Maybe ApiKey@@ -77,10 +84,9 @@ } parseConfig :: Parser Config-parseConfig = Config- <$> optional parseApiKey- <*> parseLocation- <*> switch (long "debug" <> short 'd' <> help "Enable debug")+parseConfig =+ Config <$> optional parseApiKey <*> parseLocation <*>+ switch (long "debug" <> short 'd' <> help "Enable debug") getApiKey :: Maybe ApiKey -> IO String getApiKey (Just (ApiKey key)) = return key@@ -89,16 +95,18 @@ 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) ++ "°)"+ 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@@ -125,7 +133,7 @@ speed = round . Wind.speed $ w deg = Wind.deg w -- [ "N", "NE", "E", "SE", "S", "SW", "W", "NW" ]- dirs = [ "↓", "↙", "←", "↖", "↑", "↗", "→", "↘" ]+ dirs = ["↓", "↙", "←", "↖", "↑", "↗", "→", "↘"] l = length dirs sector = round $ (deg * fromIntegral l) / 360.0 dir = dirs !! (sector `rem` l)@@ -138,10 +146,14 @@ 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+ 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@@ -150,15 +162,23 @@ place = showLocation w mainw = CurrentWeather.main w wind = CurrentWeather.wind w- out = place ++ ": " ++ intercalate ", "- [ weather, showHumidity mainw, showPressure mainw, showTemp mainw,- showWind wind ]+ 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+ Left err -> die $ show err Right weather -> do when (debug cfg) $ hPrint stderr weather printWeather weather@@ -167,7 +187,7 @@ main = run =<< execParser opts where opts = info (parseConfig <**> helper) (fullDesc <> header desc)- desc = "openweathermap "- ++ showVersion version- ++ " - command-line client for https://openweathermap.org/api"-+ desc =+ "openweathermap " +++ showVersion version +++ " - command-line client for https://openweathermap.org/api"
lib/Web/OpenWeatherMap/API.hs view
@@ -2,44 +2,37 @@ Direct API functions. For API key (a.k.a appid) refer to <http://openweathermap.org/appid>. -}- {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} -module Web.OpenWeatherMap.API (- weatherByName,- weatherByCoord-) where+module Web.OpenWeatherMap.API+ ( weatherByName+ , weatherByCoord+ ) where import Data.Proxy (Proxy(..)) -import Servant.API ((:>), (:<|>)(..), JSON, Get, QueryParam)-import Servant.Client (client)-import Servant.Common.Req (ClientM)+import Servant.API ((:<|>)(..), (:>), Get, JSON, QueryParam)+import Servant.Client (ClientM, client) import Web.OpenWeatherMap.Types.CurrentWeather (CurrentWeather) +type GetCurrentWeather = AppId :> Get '[ JSON] CurrentWeather -type GetCurrentWeather = AppId :> Get '[JSON] CurrentWeather type AppId = QueryParam "appid" String type API- = "weather" :> QueryParam "q" String :> GetCurrentWeather- :<|> "weather" :> QueryParam "lat" Double :> QueryParam "lon" Double- :> GetCurrentWeather+ = "weather" :> QueryParam "q" String :> GetCurrentWeather :<|> "weather" :> QueryParam "lat" Double :> QueryParam "lon" Double :> GetCurrentWeather -- | Request current weather in the city.-weatherByName- :: Maybe String -- ^ City name, e. g. \"Moscow\" or \"Moscow,ru\".- -> Maybe String -- ^ API key.+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.+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)-
lib/Web/OpenWeatherMap/Client.hs view
@@ -1,38 +1,42 @@ {-| High-level client functions perfoming requests to OpenWeatherMap API. -}-module Web.OpenWeatherMap.Client (- Location(..),- getWeather-) where+module Web.OpenWeatherMap.Client+ ( Location(..)+ , getWeather+ ) where -import Network.HTTP.Client (newManager, defaultManagerSettings)-import Servant.Client (ClientEnv(..), runClientM, ServantError)-import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..))-import Servant.Common.Req (ClientM)+import Network.HTTP.Client (defaultManagerSettings, newManager)+import Servant.Client+ ( BaseUrl(BaseUrl)+ , ClientEnv+ , ClientError+ , ClientM+ , Scheme(Http)+ , mkClientEnv+ , runClientM+ ) -import Web.OpenWeatherMap.Types.CurrentWeather (CurrentWeather) 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.-+ = Name String -- ^ City name.+ | Coord Double+ Double -- ^ Geographic coordinates: latitude and longitude. -- | Make a request to OpenWeatherMap API -- and return current weather in given location.-getWeather- :: String -- ^ API key.+getWeather ::+ String -- ^ API key. -> Location- -> IO (Either ServantError CurrentWeather)-getWeather appid loc =- defaultEnv >>= runClientM (api loc appid)+ -> IO (Either ClientError CurrentWeather)+getWeather appid loc = defaultEnv >>= runClientM (api loc appid) -api- :: Location- -> String -- ^ API key.+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@@ -40,10 +44,9 @@ defaultEnv :: IO ClientEnv defaultEnv = do manager <- newManager defaultManagerSettings- return $ ClientEnv manager baseUrl+ return $ mkClientEnv manager baseUrl -- XXX openweathermap.org does not support HTTPS, -- XXX appid is passed in clear text. Oops. baseUrl :: BaseUrl baseUrl = BaseUrl Http "api.openweathermap.org" 80 "/data/2.5"-
lib/Web/OpenWeatherMap/Types/Clouds.hs view
@@ -1,16 +1,14 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.Clouds (- Clouds(..)-) where+module Web.OpenWeatherMap.Types.Clouds+ ( Clouds(..)+ ) where import GHC.Generics (Generic) import Data.Aeson (FromJSON)- data Clouds = Clouds { all :: Double } deriving (Show, Generic, FromJSON)-
lib/Web/OpenWeatherMap/Types/Coord.hs view
@@ -1,17 +1,15 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.Coord (- Coord(..)-) where+module Web.OpenWeatherMap.Types.Coord+ ( Coord(..)+ ) where import GHC.Generics (Generic) import Data.Aeson (FromJSON) - data Coord = Coord { lon :: Double , lat :: Double } deriving (Show, Generic, FromJSON)-
lib/Web/OpenWeatherMap/Types/CurrentWeather.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.CurrentWeather (- CurrentWeather(..)-) where+module Web.OpenWeatherMap.Types.CurrentWeather+ ( CurrentWeather(..)+ ) where -import Prelude hiding (id) import GHC.Generics (Generic)+import Prelude hiding (id) import Data.Aeson (FromJSON) @@ -32,4 +32,3 @@ , name :: String , cod :: Int } deriving (Show, Generic, FromJSON)-
lib/Web/OpenWeatherMap/Types/Main.hs view
@@ -1,16 +1,16 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.Main (- Main(..)-) where+module Web.OpenWeatherMap.Types.Main+ ( Main(..)+ ) where import GHC.Generics (Generic) import Data.Aeson (FromJSON)- {-# ANN module "HLint: ignore Use camelCase" #-}+ data Main = Main { temp :: Double , pressure :: Double@@ -20,4 +20,3 @@ , sea_level :: Maybe Double , grnd_level :: Maybe Double } deriving (Show, Generic, FromJSON)-
lib/Web/OpenWeatherMap/Types/Sys.hs view
@@ -1,19 +1,16 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.Sys (- Sys(..)-) where+module Web.OpenWeatherMap.Types.Sys+ ( Sys(..)+ ) where import GHC.Generics (Generic) import Data.Aeson (FromJSON)- data Sys = Sys- { message :: Double- , country :: Maybe String+ { country :: Maybe String , sunrise :: Int , sunset :: Int } deriving (Show, Generic, FromJSON)-
lib/Web/OpenWeatherMap/Types/Weather.hs view
@@ -1,19 +1,17 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.Weather (- Weather(..)-) where+module Web.OpenWeatherMap.Types.Weather+ ( Weather(..)+ ) where import GHC.Generics (Generic) import Data.Aeson (FromJSON) - data Weather = Weather { id :: Int , main :: String , description :: String , icon :: String } deriving (Show, Generic, FromJSON)-
lib/Web/OpenWeatherMap/Types/Wind.hs view
@@ -1,17 +1,15 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -module Web.OpenWeatherMap.Types.Wind (- Wind(..)-) where+module Web.OpenWeatherMap.Types.Wind+ ( Wind(..)+ ) where import GHC.Generics (Generic) import Data.Aeson (FromJSON)- data Wind = Wind { speed :: Double , deg :: Double } deriving (Show, Generic, FromJSON)-
openweathermap.cabal view
@@ -1,5 +1,5 @@ name: openweathermap-version: 0.0.0+version: 0.1.0 synopsis: Access data at OpenWeatherMap description: Client library and command-line utility to access OpenWeatherMap https://openweathermap.org@@ -11,7 +11,7 @@ category: Web build-type: Simple extra-source-files: README.md ChangeLog.md-cabal-version: >= 1.20+cabal-version: 1.20 source-repository head type: git@@ -30,7 +30,7 @@ , aeson , http-client , servant- , servant-client >= 0.9+ , servant-client >= 0.16 exposed-modules: Web.OpenWeatherMap.API Web.OpenWeatherMap.Client